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



我正在开发一个模型,它可以用一堆不同的车辆做一些事情。每辆车都应该做一些事情,但每种车都做不同的事情。所以我用这种方式实现它,使用.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()
}
}
}

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

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()
}
}
}

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

我尝试了一种外部过载方法,它可以获取车辆并输出正确的报告或者将抽象报告(或接口IReport)传递给车辆";GetReport"方法但由于我的RunModel方法不知道它处理的是哪种类型的车辆,我找不到将其映射到正确的Report类型的方法。

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

保持核心域尽可能简单是正确的。它应该只需要处理自己的复杂性,几乎不需要来自外部的干扰和依赖。

首先想到的是,即使继承可能对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());
}
}
}

你可以这样使用它。

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;
}
}

如果没有

你可以把工作分成两个单独的流程。

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();
}
}

差异

第一个方法返回基类的列表。第二种方法在对象上存储不同类型的列表。一旦您有了不同的类型,如果不先进行upcasting,就不能再在类型化集合中返回它们。

最后的想法

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

您可以在Vehicle上实现枚举ReportType。想象一下,未来有人要求为肌肉车和家用车创建不同的报告。您可以仅根据枚举值生成不同的报告,而不是深入研究继承。

依赖项注入可能会有所帮助。.Net Core中的内置依赖项注入不提供在IReport的两个不同实现之间切换的选项,但您可以将ICarReport的实现注入Class Car,将IMotorcycleReport的实现插入Class Motorcycle。然后,如果实现发生更改,则可以交换它们,而不更改依赖它们的类。

还有其他IoC容器,如Lightinject,允许您注入称为命名依赖项的IReport的不同实现。你可能想搜索类似的东西。

此外,我不确定您使用的是.Net Core还是.Net Framework。Net Core已经内置了依赖项注入,但您需要为.Net Framework安装像Lightinject或Ninject这样的Nuget包。

编辑:

听起来您正在寻找一种实现控制反转(IoC)的设计模式。在这种情况下,正如不同的答案所指出的,您可以使用工厂模式服务定位器模式赖注入模式

如果您的项目是旧的或已经非常大,依赖注入可能不适用于您。在这种情况下,这可能是你下一个项目需要考虑的问题。在这种情况下,工厂模式可能正是您想要的。这完全取决于我们目前不知道的许多细节。

此外,对于不同的模式会有不同的意见,但通常有许多模式可以用来解决特定的设计问题。

为了避免双向依赖,车辆应该依赖于抽象。以及报告。正如智者所说:;高级模块不应依赖于低级模块。两者都应该取决于基于抽象"这是我的建议,它是为了尽可能少地干扰你的示例程序。Model.RunModel方法仍然不知道它正在处理什么类型的车辆,这没关系。CarReportGenerator(和MotorcycleReportGenerator)中的GenerateReport方法是对具体报告执行具体操作的正确方法。报告结构的未来变化应在那里解决。

让我们切入代码:

abstract class Vehicle
{
public int CommonProperty { get; set; }
public abstract void DoStuff();
public abstract Report GetReport();
}
class Car : Vehicle
{
public string CarProperty { get; set; }
public override void DoStuff()
{
//Do some Car stuff here
Console.WriteLine("Doing Car stuff here.");
}
public override Report GetReport()
{
// Injecting dependency
CarReportGenerator crpt = new CarReportGenerator(this);

return crpt.GenerateReport();
}
}
class Motorcycle : Vehicle
{
public bool MotorcycleProperty { get; set; }
public override void DoStuff()
{
//Do some Motorcycle stuff here
Console.WriteLine("Doing Motorcycle stuff here.");
}
public override Report GetReport()
{
// Injecting dependency
MotorcycleReportGenerator mrpt = new MotorcycleReportGenerator(this);
return mrpt.GenerateReport();
}
}
abstract class Report
{
public int Foo { get; set; }
}
class CarReport : Report
{
public string Bar { get; set; }
}
class MotorcycleReport : Report
{
public bool Baz { get; set; }
}
class Model
{
internal void RunModel(Vehicle[] vehicleCollection)
{
foreach (Vehicle currentVehicle in vehicleCollection)
{
currentVehicle.DoStuff();
//currentVehicle.GetReport();
Report rpt = currentVehicle.GetReport();
Console.WriteLine(rpt.Foo);
}
}
}
interface IReportGenerator
{
Report GenerateReport();
}
class CarReportGenerator : IReportGenerator
{
private Car _car;

public CarReportGenerator(Vehicle car)
{
_car = (Car)car;
}
public Report GenerateReport()
{
CarReport crpt = new CarReport();
// acces to commom Property from Vehicle
crpt.Foo = _car.CommonProperty;
// acces to concrete Car Property from Car
crpt.Bar = _car.CarProperty;
// go on with report, print, email, whatever needed
return crpt;
}
}
class MotorcycleReportGenerator : IReportGenerator
{
private Motorcycle _motorc;
public MotorcycleReportGenerator(Vehicle motorc)
{
_motorc = (Motorcycle)motorc;
}
public Report GenerateReport()
{
MotorcycleReport mrpt = new MotorcycleReport();
// acces to commom Property from Vehicle
mrpt.Foo = _motorc.CommonProperty;
// acces to concrete Motorcycle Property from Motorcycle
mrpt.Baz = _motorc.MotorcycleProperty;
// go on with report, print, email, whatever needed
return mrpt;
}
}

车辆和报告之间的依赖关系消失了。如果以后要添加一种新的车辆和报告,则可以在不更改当前工作内容的情况下进行添加。

// A whole new vehicle, report and report generator.
class Quad : Vehicle
{
public double QuadProperty { get; set; }
public override void DoStuff()
{
//Do some Quad stuff here
Console.WriteLine("Doing Quad stuff here.");
}
public override Report GetReport()
{
// Injecting dependency
QuadReportGenerator crpt = new QuadReportGenerator(this);
return crpt.GenerateReport();
}
}
class QuadReport : Report
{
public double Doe { get; set; }
}
class QuadReportGenerator : IReportGenerator
{
private Quad _quad;
public QuadReportGenerator(Vehicle quad)
{
_quad = (Quad)quad;
}
public Report GenerateReport()
{
QuadReport crpt = new QuadReport();
// acces to commom Property from Vehicle
crpt.Foo = _quad.CommonProperty;
// acces to concrete Quad Property from Quad
crpt.Doe = _quad.QuadProperty;
// go on with report, print, email, whatever needed
return crpt;
}
}

但是,Vehicles和ReportGenerator之间存在新的依赖关系。它可以通过创建一个用于车辆IVehicle的接口和另一个用于报告IReport的接口来解决,因此车辆和ReportGenerators依赖于它。您可以更进一步,创建一个新的接口IVehicleReport,包含方法Report GetReport()。通过这种方式,车辆和报告的当前和未来关注点被分开。

您可以使用Factory类删除依赖项。这是一个示例代码。

class Program
{
static void Main(string[] args)
{
// Set your Report Builder Factory (Concrete / Dynamic)
ConcretReportBuilderFactory concreteReportBuilderFactory = new ConcretReportBuilderFactory();
DynamicReportBuilderFactory dynamicReportBuilderFactory = new DynamicReportBuilderFactory();
Vehicle[] vehicleCollection = new Vehicle[]
{
new Car(concreteReportBuilderFactory),
new Motorcycle(dynamicReportBuilderFactory)
};
RunModel(vehicleCollection);
Console.ReadKey();
}
static void RunModel(Vehicle[] vehicleCollection)
{
foreach (Vehicle currentVehicle in vehicleCollection)
{
currentVehicle.DoStuff();
var vehicleReport = currentVehicle.GetReport();
}
}
}
public abstract class Vehicle
{
protected readonly ReportBuilderFactory reportBuilderFactory;
// I'm using Constructor Injection, but you can use Property or Method injection 
// if you want to free your constructor remaining parameter less.
public Vehicle(ReportBuilderFactory reportBuilderFactory)
{
this.reportBuilderFactory = reportBuilderFactory;
}
public abstract void DoStuff();
public abstract Report GetReport();
public string CommonProperty { get; set; }
}
public class Car : Vehicle
{
public Car(ReportBuilderFactory reportBuilderFactory) : base(reportBuilderFactory)
{
}
public override void DoStuff()
{
//Do some Car stuff here
}
public override Report GetReport()
{
return this.reportBuilderFactory.GetReport(this);
}
}
public class Motorcycle : Vehicle
{
public Motorcycle(ReportBuilderFactory reportBuilderFactory) : base(reportBuilderFactory)
{
}
public override void DoStuff()
{
//Do some Motorcycle stuff here
}
public override Report GetReport()
{
var report = this.reportBuilderFactory.GetReport(this);
return report;
}
}
public abstract class Report
{
public Report(Vehicle vehicle)
{
Foo = vehicle.CommonProperty;
}
public string Foo { get; set; }
public virtual void ShowReport()
{
Console.WriteLine("This is Base Report");
}
}
[ReportFor("Car")] // (Pass class name as argument) .For the implementation of DynamicReportBuilderFactory.
public class CarReport : Report
{
string Bar { get; set; }
public CarReport(Car _car) : base(_car)
{
Bar = _car.CommonProperty;
}
public override void ShowReport()
{
Console.WriteLine("This is Car Report.");
}
}
[ReportFor("Motorcycle")] // (Pass class name as argument) .For the implementation of DynamicReportBuilderFactory
public class MotorcycleReport : Report
{
public MotorcycleReport(Vehicle vehicle) : base(vehicle)
{
}
public override void ShowReport()
{
Console.WriteLine("This is Motor Cycle Report.");
}
}    
[AttributeUsage(AttributeTargets.Class)]
public class ReportFor : Attribute
{
public string ReportSource { get; private set; }
public ReportFor(string ReportSource)
{
this.ReportSource = ReportSource;
}
}
public abstract class ReportBuilderFactory
{
public abstract Report GetReport(Vehicle vehicle);
}
// Static Implementation . this is tightly coupled with Sub Classes of Report class.
public sealed class ConcretReportBuilderFactory : ReportBuilderFactory
{
public override Report GetReport(Vehicle vehicle)
{
switch (vehicle)
{
case Car car:
return new CarReport(car);
case Motorcycle motorcycle:
return new MotorcycleReport(motorcycle);
default:
throw new NotImplementedException(vehicle.ToString());
}
}
}
// Dynamic Implementation . this is loosely coupled with Sub Classes of Report class.
public sealed class DynamicReportBuilderFactory : ReportBuilderFactory
{
private readonly Dictionary<string, Type> _availableReports;
public DynamicReportBuilderFactory()
{
_availableReports = GetAvailableReportTypes();
}
static Dictionary<string, Type> GetAvailableReportTypes()
{
var reports = Assembly.GetExecutingAssembly()
.GetTypes().Where(t => typeof(Report).IsAssignableFrom(t)
&& t.IsAbstract == false
&& t.IsInterface == false
// Geting classes which have "ReportFor" attribute
&& t.GetCustomAttribute<ReportFor>() != null 
);
// You can raise warning or log Report derived classes which dosn't have "ReportFor" attribute
// We can split ReportSource property contains "," and register same type for it . Like "CarReport, BikeReport"
return reports.ToDictionary(x => x.GetCustomAttribute<ReportFor>()?.ReportSource);
}
public override Report GetReport(Vehicle vehicle)
{
var reportType = _availableReports[vehicle.GetType().Name];
return Activator.CreateInstance(reportType,vehicle) as Report;
}
}

您可以使用泛型在报表与其对应的工具之间创建链接。根据这些信息,您可以创建基于车辆的报告。

车辆和报告如下:

public abstract class Vehicle
{
}
public class Car : Vehicle
{
}
public class Motorcycle : Vehicle
{
}
public abstract class Report
{
}
public abstract class Report<T> : Report where T:Vehicle
{
int Foo { get; set; }
public Report(T _vehicle)
{
}
}
public class CarReport : Report<Car>
{
string Bar { get; set; }
public CarReport(Car _car) : base(_car)
{
}
}
public class MotorcycleReport : Report<Motorcycle>
{
bool Baz { get; set; }
public MotorcycleReport(Motorcycle _cycle) : base(_cycle)
{
}
}

1-我们可以根据当前的Vehicle:使用反射来生成Report对象

public abstract class Vehicle
{
public Report GetReport()
{
var genericReportType = typeof(Report<>).MakeGenericType(this.GetType());
var reportType = Assembly.GetExecutingAssembly().GetTypes().Where(x => genericReportType.IsAssignableFrom(x)).Single();
return Activator.CreateInstance(reportType, this) as Report;
}
}

如果我们需要优化性能,我们可以缓存一个字典:

public abstract class Vehicle
{
private static Dictionary<Type, Type> vehicleToReport;
static Vehicle()
{
var reports = Assembly.GetExecutingAssembly().GetTypes().Where(x => typeof(Report).IsAssignableFrom(x) && x.IsAbstract == false);
vehicleToReport = reports.ToDictionary(x => x.BaseType.GetGenericArguments().Single(), x => x);
}
public Report GetReport()
{
var reportType = vehicleToReport[this.GetType()];
return Activator.CreateInstance(reportType, this) as Report;
}
}

我希望以报表依赖于车辆,但车辆不依赖于报告。

2-如果要从Vehicle中完全删除Report依赖项。您可以创建一个工厂类,并将GetReport方法从Vehicle类移动到此工厂方法。

您可以实现工厂方法(我们通常称之为工厂设计模式)。有两个选项可以实现这种工厂方法:

a) 由于报告和车辆的通用实现,使用如上所述的反射可以动态发现新车的新报告。

b) 只需硬编码下面的映射vehicleToReport即可将Vehicle映射到Report:

public class ReportFactory
{
private static Dictionary<Type, Type> vehicleToReport;
static ReportFactory()
{
//Build the mappings dynamically using reflection or just hardcode it.
var reports = Assembly.GetExecutingAssembly().GetTypes().Where(x => typeof(Report).IsAssignableFrom(x) && x.IsAbstract == false);
vehicleToReport = reports.ToDictionary(x => x.BaseType.GetGenericArguments().Single(), x => x);
}
public Report GetReport(Vehicle vehicle)
{
var reportType = vehicleToReport[vehicle.GetType()];
return Activator.CreateInstance(reportType, vehicle) as Report;
}
}

最新更新