What is the best practice for creating a system of platforms that does not use Unity’s tag system?

What is the best practice for creating a system of platforms that does not use Unity’s tag system?

Right now I'm finalizing my character raycast controller, but have come to the realization that Unity's tag system, which I'm using as a way for my raycasts to detect different types of platforms, is limited and may cause issues further into development when I start adding more platform types, or use the controller for AI (do I want my AI to be affected the same way the player is affected by platforms?).
So far I've got a bunch of different platforms, like passable platforms, icy blocks and jump boosts. These all work great, and change the character's default movement parameters, but I rely on things like this in order for changes to be made:
if (hit.collider.tag == "platformPassable")

Is there a better way to do this? I am not the most experienced programmer, but my initial solution was to create a Platform class with a TypeOfPlatform enum that will allow me to make sub classes that implement changes based on this type, but I'm worried that having to access a component for each raycast on a platform on which the character or AI is standing will lead to horrible performance issues. Would I have to reference the base class in the character controller, or would I access the character controller in the base class?
I'm just not very comfortable with inheritances like this and would appreciate some guidance. 
Update: here's a segment of my code (there are many other likes this) that hopefully helps illustrate what I believe will become problematic as I continue development:
private void MoveVertically(ref Vector3 deltaMovement)
{
    float directionY = Mathf.Sign(deltaMovement.y);
    float rayLength = Mathf.Abs(deltaMovement.y) + skinWidth;

    for (int i = 0; i < verticalRayCount; i++)
    {
        Vector2 rayOrigin = (directionY == -1) ? raycastOrigins.bottomLeft : raycastOrigins.topLeft;
        rayOrigin += Vector2.right * (verticalRaySpacing * i + deltaMovement.x);

        RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.up * directionY, rayLength, collisionMask);

        Debug.DrawRay(rayOrigin, Vector2.up * directionY * rayLength, Color.red);

        if (hit)
        {
            if (hit.collider.tag == "platformPassable" || hit.collider.tag == "icePassable")
            {
                State.IsCollidingWithPassablePlatform = true;

                if (directionY == 1)
                {
                    State.IsCollidingAbove = false;
                    continue;
                }

                if (CanJumpDown)
                {
                    State.IsCollidingBelow = false;
                    continue;
                }
            }

            if (hit.collider.tag == "iceNormal" || hit.collider.tag == "icePassable")
            {
                Parameters.accelerationTimeGrounded = 1.0f;
                Parameters.accelerationTimeAirborne = 3.0f;
            }

            else if (overrideParameters == null)
            {
                Parameters.accelerationTimeGrounded = defaultParameters.accelerationTimeGrounded;
                Parameters.accelerationTimeAirborne = defaultParameters.accelerationTimeAirborne;
            }

            deltaMovement.y = (hit.distance - skinWidth) * directionY;
            rayLength = hit.distance;

            if (State.IsClimbingSlope)
            {
                deltaMovement.x = deltaMovement.y / Mathf.Tan(State.SlopeAngle * Mathf.Deg2Rad) * Mathf.Sign(deltaMovement.x);
            }

            State.IsCollidingBelow = directionY == -1;
            State.IsCollidingAbove = directionY == 1;
            CanJumpDown = false;
        }
    }

As you can see there are a lot of platform types that are somewhat similar. Plus, since this script will be placed on any character, such as the player or the enemy, both of these entities will be bound to the same movement parameters. What if I didn't want an enemy to slide around on an ice block? I'm not sure there's a way to prevent this in my code in its current structure.

Solutions/Answers:

Answer 1:

Depending on the level of complexity required you could go one of two routes:

If you have a set of GameObjects that represent the different block types and all you need are a set of properties to pass into your player controller, then you could create a simple class that exposes those properties that you can add to the GameObject and create some prefabs for each type:

public class Platform : MonoBehaviour {
  public float AccelerationTimeGrounded;
  public float AccelerationTimeAirborne;
  public bool IsPassable;
}

The you can just add a "platform" tag to your platforms and grab the Platform component:

if (hit) {
  if (hit.collider.CompareTag("platform")) {
    var platform = hit.collider.gameObject.GetComponent<Platform>();

    // grab the properties as needed:
    Parameters.accelerationTimeGrounded = platform.AccelerationTimeGrounded;
    Parameters.accelerationTimeAirborne = platform.AccelerationTimeAirborne;
    Parameters.isPassable = platform.IsPassable;
  }
}

If however you need more complex requirements (for example you need to calculate some value in different ways depending on the type of platform - for example you want to multiply a value if it's an Ice platform, but only add a value if it's a Rock platform you can use an Interface within the scripts and then have the different types of platform implement them to pass the appropriate values to your player controller:

// Interface for all platform classes to inherit from.
interface IPlatform {
  float AccelerationTimeGrounded { get; }
  float AccelerationTimeAirborne { get; }
  bool IsPassable { get; }

  float FinalAcceleration (float current);
}

Then you can implement some concrete classes:

public class IcePlatform : IPlatform, MonoBehaviour {
   // Expose the properties to the editor with a reasonable default
   [SerializeField]
   private float _accelerationTimeGrounded = 1.0f;
   [SerializeField]
   private float _accelerationTimeAirborne = 3.0f;

   public float AccelerationTimeGrounded { get { return _accelerationTimeGrounded; } }
   public float AccelerationTimeAirborne { get { return _accelerationTimeAirborne; } }
   public bool IsPassable { get { return false; } }

   public bool FinalAcceleration (float current) {
     return current * AccelerationTimeGrounded;
   }
}

// Technically this could inherit from IcePlatform and just override
// the IsPassable property.
public class PassableIcePlatform : IPlatform, MonoBehaviour {
   // Expose the properties to the editor
   [SerializeField]
   private float _accelerationTimeGrounded = 1.0f;
   [SerializeField]
   private float _accelerationTimeAirborne = 3.0f;

   public float AccelerationTimeGrounded { get { return _accelerationTimeGrounded; } }
   public float AccelerationTimeAirborne { get { return _accelerationTimeAirborne; } }
   public bool IsPassable { get { return true; } }

   public bool FinalAcceleration (float current) {
     return current * AccelerationTimeGrounded;
   }
}

public class RockPlatform : IPlatform, MonoBehaviour {
   // Expose the properties to the editor with a reasonable default
   [SerializeField]
   private float _accelerationTimeGrounded = 0.5f;
   [SerializeField]
   private float _accelerationTimeAirborne = 1.0f;

   public float AccelerationTimeGrounded { get { return _accelerationTimeGrounded; } }
   public float AccelerationTimeAirborne { get { return _accelerationTimeAirborne; } }
   public bool IsPassable { get { return false; } }

   public bool FinalAcceleration (float current) {
     return current + AccelerationTimeGrounded;
   }
}

You can then use the Interface in your hit check without having to care about what type of IPlatform it is:

if (hit) {
  if (hit.collider.CompareTag("platform")) {
    // Get the component that implements IPlatform
    var platform = hit.collider.gameObject.GetComponent<IPlatform>();

    // grab the properties as needed:
    Parameters.accelerationTimeGrounded = platform.AccelerationTimeGrounded;
    Parameters.accelerationTimeAirborne = platform.AccelerationTimeAirborne;
    Parameters.isPassable = platform.IsPassable;

    // Call the method:
    float final = platform.FinalAcceleration(someCurrentValue);
  }
}

References