Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding two-way dependency when correlating derived classes from different base classes

I'm working on a model that do some stuff with a bunch of different Vehicles. Every Vehicle is supposed to do stuff, but each Vehicle type does a different stuff. So I implemented it this way, using .NET Framework:

abstract class Vehicle
{
   abstract void DoStuff()
}

class Car : Vehicle
{
   override void DoStuff()
   {
       //Do some Car stuff here
   }
}

class Motorcycle : Vehicle
{
   override void DoStuff()
   {
       //Do some Motorcycle stuff here
   }
}
class Model
{
  RunModel(Vehicle[] vehicleCollection)
  {
    foreach(Vehicle currentVehicle in vehicleCollection)
    {
      currentVehicle.DoStuff()
    }
  }
}

This is the core funcionallity of my program and it's working as expected. Now I'm supposed to output reports based on the stuff each Vehicle has done. Each type of Vehicle is supposed to ouput a different kind of Report, so I made a similar solution for it:

abstract class Vehicle
{
   abstract void DoStuff();
   abstract Report GetReport();
}

class Car : Vehicle
{
   override Report GetReport()
   {
       return new CarReport(this);
   }
}

class Motorcycle : Vehicle
{
   override Report GetReport()
   {
       return new MotorcycleReport(this);
   }
}

abstract class Report
{
   int Foo {get; set;}

   Report (Vehicle _vehicle)
   {
       Foo = _vehicle.CommonProperty;
   }
      
}

class CarReport : Report
{
   string Bar {get; set;}
   CarReport(Car _car) : base(_car)
   {
       Bar = _car.CarPropoerty;
   }
}

class MotorcycleReport : Report
{
   bool Baz {get; set;}
   MotorcycleReport(Motorcycle _cycle) : base(_cycle)
   {
       Baz= _cycle.MotorcyclePropoerty;
   }
}
class Model
{
  RunModel(Vehicle[] vehicleCollection)
  {
    foreach(Vehicle currentVehicle in vehicleCollection)
    {
      currentVehicle.DoStuff()
      currentVehicle.GetReport()
    }
  }
}

This is working fine too, but the problem is that Car and Motorcycle now depends on CarReport and MotorcycleReport. Since this is non-core functionallity to my program and the Report structure may change a lot in future versions, I'd like to implement it in a way that the Reports depends on the Vehicles, but the Vehicles do not depend on the Reports.

I've tried an external overloaded method that gets a Vehicle and outputs the proper Report Or passing an abstract Report (or interface IReport) to the Vehicle "GetReport" method But since my RunModel method doesn't know what type of Vehicle it is dealing with, I can't find a way to map it to the right Report type.

Is there a way to avoid this two-way dependency?

like image 201
Pedro Ludovico Bozzini Avatar asked Dec 23 '22 17:12

Pedro Ludovico Bozzini


2 Answers

You're right about keeping the core domain as simple as possible. It should only have to deal with its own complexity, with as little interference and dependencies from the outside.

First thing that comes to mind is that even though inheritance may make sense for the Vehicle hierarchy. Question is, does it make sense for the reports? Will you ever use the abstract base Report class by itself? The one with only the common properties.

If it does

You can use a manager to take over the responsibility of creating Reports.

public class ReportManager
{
    public Report GetReport<T>(T vehicle) where T : Vehicle
    {
        switch (vehicle)
        {
            case Car car:
                return new CarReport(car);

            case Motorcycle motorcycle:
                return new MotorcycleReport(motorcycle);

            default:
                throw new NotImplementedException(vehicle.ToString());
        }
    }
}

You can use it like this.

public class Model
{
    private readonly ReportManager _reportManager;

    public Model(ReportManager reportManager)
    {
        _reportManager = reportManager;
    }

    public List<Report> RunModel(Vehicle[] vehicles)
    {
        var reports = new List<Report>();

        foreach (var vehicle in vehicles)
        {
            vehicle.DoStuff();
            reports.Add(_reportManager.GetReport(vehicle));
        }

        return reports;
    }
}

If it doesn't

You can divide the work over two separate flows.

public class Model
{
    public List<CarReport> CarReports { get; private set; }
    public List<MotorcycleReport> MotorcycleReports { get; private set; }

    public void RunModel(Vehicle[] vehicles)
    {
        // 1. Do stuff
        foreach (var vehicle in vehicles)
        {
            vehicle.DoStuff();
        }
        // 2. Get reports
        CarReports = vehicles.OfType<Car>().Select(car => new CarReport(car)).ToList();
        MotorcycleReports = vehicles.OfType<Motorcycle>().Select(motorcycle => new MotorcycleReport(motorcycle)).ToList();
    }
}

The difference

The first method returns a list of base class. The second method stores lists of different types on the object. Once you have different types, you can no longer return them in a typed collection without upcasting first.

Final thoughts

Report structure may change a lot in future versions

You could implement an enum ReportType on Vehicle. Imagine a future request to create different reports for muscle cars and family cars. Instead of diving deeper into inheritance, you could then generate different reports based solely upon the enum value.

like image 80
Funk Avatar answered Jan 22 '23 06:01

Funk


Dependency injection might help. Built in dependency injection in .Net Core doesn’t give the option to switch between two different implementations of IReport but you can inject an implementation of ICarReport into Class Car and an implementation of IMotorcycleReport into Class Motorcycle. Then, you can swap out the implementations if they change without changing the Classes that depend on them.

There are other IoC containers, like Lightinject, that do allow you to inject different implementations of IReport called named dependencies. You may want to search for something like that.

Also, I’m not sure if you’re using .Net Core or .Net Framework. .Net Core has built in dependency injection but you’ll need to install a Nuget package like Lightinject or Ninject for the .Net Framework.

Edit:

It sounds like you are looking for a design pattern to achieve Inversion of Control (IoC). In that case, as the different answers point out, you can use the Factory Pattern, Service Locator Pattern or Dependency Injection Pattern.

Dependency Injection may not work for you if your project is old or already very large. In that case it may be something to look into for your next project. The Factory Pattern might be exactly what you're looking for in this case. It all depends on a lot of details we don't know at this point.

Also, there will be varying opinions on the different patterns but there are often many patterns that can be used to solve a particular design problem.

like image 25
Dustin C Avatar answered Jan 22 '23 05:01

Dustin C