Home > front end >  How to organise abstract class hierarchy in C#
How to organise abstract class hierarchy in C#

Time:09-04

I have a situation in C# when I need multiple inheritance in order to not double code in implementation class, but that's forbidden in C#.

What should I do?

I have the following interfaces:

public interface ICar
{
   void Move();
   void GetService();
}

public interface ISuperCar : ICar
{
   void MoveFast();
   void HaveARace();
}

And abstract classes:

public abstract class Car : ICar
{
   public void Move()
   {
      Console.WriteLine("Car moves");
   }

   public abstract void GetService();
}

public abstract class SuperCar : Car, ISuperCar
{
   public void MoveFast()
   {
      Console.WriteLine("SuperCar moves fastly");
   }

   public abstract void HaveARace();
}

And their implementations:

public class ToyotaCar : Car
{
   public override void GetService()
   {
      Console.WriteLine("Getting ToyotaService");
   }
}

public class ToyotaSuperCar : SuperCar
{
   public override void GetService()
   {
      Console.WriteLine("Getting ToyotaService");
   }

   public override void HaveARace()
   {
      Console.WriteLine("Have a race in Toyota competition");
   }
}

And the question is how not to double method GetService(). Logically it should by:

public class ToyotaSuperCar : SuperCar, ToyotaCar
{
   public override void HaveARace()
   {
      Console.WriteLine("Have a race in Toyota competition");
   }
}

But multiple inheritance is not available in C#

CodePudding user response:

You could use composition instead of inheritance. Make your SuperCar class implement ICar instead of inheriting from Car and pass an ICar object in the constructor to which you delegate the methods:

public abstract class SuperCar : ICar, ISuperCar
{
    private readonly ICar car;

    public SuperCar(ICar car)
    {
        this.car = car;
    }

    public void Move() => car.Move();

    public void GetService() => car.GetService();

    public void MoveFast()
    {
        Console.WriteLine("SuperCar moves fastly");
    }

   public abstract void HaveARace();
}

Your ToyotaSuperCar class would then look like so:

public class ToyotaSuperCar : SuperCar
{
    public ToyotaSuperCar(ToyotaCar car) : base(car) { }

    public override void HaveARace()
    {
       Console.WriteLine("Have a race in Toyota competition");
    }
}

CodePudding user response:

You need to do something like this:

public class SuperCarPart : Car, ISuperCar
{
    public override void GetService() { throw new NotImplementedException(); }
    public void HaveARace() { throw new NotImplementedException(); }
    public void MoveFast() { throw new NotImplementedException(); }
}

public class ToyotaSuperCar : ToyotaCar, ISuperCar
{
    private SuperCarPart _superCarPart = new SuperCarPart();
    public void HaveARace()
    {
        _superCarPart.HaveARace();
    }

    public void MoveFast()
    {
        _superCarPart.MoveFast();
    }
}

CodePudding user response:

A possibility would be to declare service as its own interface

public interface ICarService
{
    void CarryOutService();
}

The car interfaces then look like this:

public interface ICar
{
    ICarService Service { get; }
    void Move();
}

public interface ISuperCar : ICar
{
    void MoveFast();
    void HaveARace();
}

The abstract classes need a constructor having a ICarService parameter.

public abstract class Car : ICar
{
    public Car(ICarService service) => Service = service;

    public ICarService Service { get; }

    public void Move()
    {
        Console.WriteLine("Car moves");
    }
}

public abstract class SuperCar : Car, ISuperCar
{
    public SuperCar(ICarService service)
        : base(service)
    {
    }

    public void MoveFast()
    {
        Console.WriteLine("SuperCar moves fast");
    }

    public abstract void HaveARace();
}

A possible implementation of a service:

public class ToyotaService : ICarService
{
    public void CarryOutService()
    {
        Console.WriteLine("Carrying out ToyotaService");
    }
}

In the concrete implementations of the cars you have the choice to either pass the service in the constructors or to instantiate them directly. Since a Toyota will always have a ToyotaService you can instantiate them directly. For more flexibility use the constructor approach.

public class ToyotaCar : Car
{
    public ToyotaCar()
        : base(new ToyotaService())
    {
    }
}

public class ToyotaSuperCar : SuperCar
{
    public ToyotaSuperCar()
        : base(new ToyotaService())
    {
    }

    public override void HaveARace()
    {
        Console.WriteLine("Have a race in Toyota competition");
    }
}

Usage:

var car = new ToyotaSuperCar();
car.Service.CarryOutService();

We followed the Composition over inheritance principle here. It has the additional advantage that it becomes easier to extend the service interface in future, since this does not impact the implementation of the cars.

  • Related