在关联来自不同基类的派生类时避免双向依赖

Ped*_*ini 4 c# polymorphism dependencies

我正在研究一个模型,可以用一堆不同的车辆做一些事情。每个 Vehicle 都应该做一些事情,但每种 Vehicle 类型做不同的事情。所以我使用 .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()
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这是我的程序的核心功能,它按预期工作。现在我应该根据每辆车所做的事情输出报告。每种类型的车辆都应该输出不同类型的报告,所以我为它做了一个类似的解决方案:

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()
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这也工作正常,但问题是 Car 和 Motorcycle 现在依赖于 CarReport 和 MotorcycleReport。由于这是我的程序的非核心功能,并且报告结构在未来版本中可能会发生很大变化,因此我希望以报告依赖于车辆但车辆不依赖于报告的方式来实现它。

我尝试了一个外部重载方法,它获取车辆并输出正确的报告或将抽象报告(或接口 IReport)传递给车辆“GetReport”方法但是因为我的 RunModel 方法不知道它正在处理什么类型的车辆,我找不到将其映射到正确报告类型的方法。

有没有办法避免这种双向依赖?

Fun*_*unk 6

保持核心域尽可能简单是正确的。它应该只需要处理自己的复杂性,尽可能少地受到外界的干扰和依赖。

首先想到的是,即使继承可能对Vehicle层次结构有意义。问题是,这些报告有意义吗?你会单独使用抽象基Report类吗?仅具有共同属性的一种。

如果确实如此

您可以使用经理来接管创建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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以像这样使用它。

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果没有

您可以将工作划分为两个单独的流程。

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

区别

第一个方法返回一个基类列表。第二种方法在对象上存储不同类型的列表。一旦你有不同的类型,你不能再在没有首先向上转换的情况下在类型化集合中返回它们。

最后的想法

报告结构在未来版本中可能会有很大变化

您可以ReportTypeVehicle. 想象一下未来需要为肌肉车和家用车创建不同的报告。与其深入研究继承,您还可以仅根据枚举值生成不同的报告。

  • 您当然可以在这里实现一些 GoF 行为模式,但我选择保持简单。最好不要过度设计解决方案,添加设计模式也会增加复杂性。我将其留给实施者来决定是否值得。 (2认同)