Search code examples
c#design-patterns

How to enforce specific implementation of an interface, depending on implementation of containing interface?


Supposing I have the following interfaces:

public interface IVehicle 
{
    ITrackInfo TrackInfo { get; set; }
    void ShowInfo();
}

public interface ITrackInfo
{
   int Length { get; set; }
}

Now I have these classes implementing ITrackInfo

public class RailInfo : ITrackInfo
{
   int Length { get; set;}
   int SleeperSpacing { get; set; }
}

public class CanalInfo : ITrackInfo 
{
   int Length  { get; set; }
   decimal AverageDepth { get; set; }
}

public class RoadInfo : ITrackInfo
{ 
   int Length { get; set; }
   Color Colour { get; set; }
}

But with the classes that implement IVehicle, I want them to have a specific implementation of ITrackInfo.

public class Train : IVehicle 
{
   RailInfo TrackInfo { get; set; }
   public void ShowInfo() {
      Console.WriteLine("Railway is {0}km long". TrackInfo.Length.ToString());
      Console.WriteLine("Distance between sleepers is {0}cm", TrackInfo.SleeperSpacing);
   }

}

public class Boat : IVehicle 
{
   CanalInfo TrackInfo { get; set; }
   public void ShowInfo() {
      Console.WriteLine("Canal is {0}km long". TrackInfo.Length.ToString());
      Console.WriteLine("The average depth is {0}m", TrackInfo.AverageDepth.ToString("F2"));
   }
}

public class Bus : IVehicle
{
   RoadInfo TrackInfo { get; set; }
   public void ShowInfo() {
      Console.WriteLine("Road is {0}km long". TrackInfo.Length.ToString());
      Console.WriteLine("The road is coloured {0}", TrackInfo.Colour.ToString());
   }
}

Obviously I can't write it as shown above - this produces a compile error in the IVehicle implementations as the TrackInfo property must be specified as ITrackInfo.

Is there a way of forcing specific implementations of ITrackInfo for specific implemenations of IVehicle?

I'm hoping for something a bit more elegant than just putting a check in ShowInfo():

var info = TrackInfo as RailInfo;
if (info == null)
{ 
    throw new Exception("Incorrect track info");
}

Solution

  • Is there a way of forcing specific implementations of ITrackInfo for specific implemenations of IVehicle?

    Yes, use generics like this:

    public interface IVehicle<TI> where TI: ITrackInfo 
    {
        TI TrackInfo { get; set; }
        void ShowInfo();
    }
    

    Here you are saying that TrackInfo's type will be a concrete implementation of ITrackInfo.

    With this in your hand, the IVehicle implementations can be fixed:

    public class Train: IVehicle<RailInfo>
    {
       public RailInfo TrackInfo { get; set; }
       public void ShowInfo() 
       {
          Console.WriteLine("Railway is {0}km long", TrackInfo.Length.ToString());
          Console.WriteLine("Distance between sleepers is {0}cm", TrackInfo.SleeperSpacing);
       }
    }
    
    public class Boat: IVehicle<CanalInfo> 
    {
       public CanalInfo TrackInfo { get; set; }
       public void ShowInfo() 
       {
          Console.WriteLine("Canal is {0}km long", TrackInfo.Length.ToString());
          Console.WriteLine("The average depth is {0}m", TrackInfo.AverageDepth.ToString("F2"));
       }
    }
    
    public class Bus: IVehicle<RoadInfo>
    {
       public RoadInfo TrackInfo { get; set; }
       public void ShowInfo() 
       {
          Console.WriteLine("Road is {0}km long", TrackInfo.Length.ToString());
          Console.WriteLine("The road is coloured {0}", TrackInfo.Colour.ToString());
       }
    }
    

    Dotnet Fiddle link: https://dotnetfiddle.net/GAb9k9