C# 在关联来自不同基类的派生类时避免双向依赖
我正在研究一个模型,它可以用一堆不同的车辆来做一些事情。 每辆车都应该做些什么,但每种车型都做不同的事情。 因此,我使用.NET Framework以这种方式实现了它:C# 在关联来自不同基类的派生类时避免双向依赖,c#,dependencies,polymorphism,C#,Dependencies,Polymorphism,我正在研究一个模型,它可以用一堆不同的车辆来做一些事情。 每辆车都应该做些什么,但每种车型都做不同的事情。 因此,我使用.NET Framework以这种方式实现了它: 抽象类车辆 { 抽象void DoStuff() } 汽车类别:汽车 { 重写void DoStuff() { //在这里做些汽车用品 } } 摩托车类别:汽车 { 重写void DoStuff() { //在这里做些摩托车的事 } } 类模型 { 运行模式(车辆[]车辆收集) { foreach(车辆当前车辆中的车辆采集) {
抽象类车辆
{
抽象void DoStuff()
}
汽车类别:汽车
{
重写void DoStuff()
{
//在这里做些汽车用品
}
}
摩托车类别:汽车
{
重写void DoStuff()
{
//在这里做些摩托车的事
}
}
类模型
{
运行模式(车辆[]车辆收集)
{
foreach(车辆当前车辆中的车辆采集)
{
currentVehicle.DoStuff()
}
}
}
这是我的程序的核心功能,它按预期工作。
现在,我应该根据每辆车所做的工作输出报告。每种类型的车辆都应该输出不同类型的报告,因此我为其制定了类似的解决方案:
抽象类车辆
{
抽象的void DoStuff();
抽象报表GetReport();
}
汽车类别:汽车
{
覆盖报表GetReport()
{
返回新的CarReport(本);
}
}
摩托车类别:汽车
{
覆盖报表GetReport()
{
返回新摩托车报告(本);
}
}
抽象课堂报告
{
int Foo{get;set;}
报告(车辆)
{
Foo=_vehicle.CommonProperty;
}
}
课堂报告:报告
{
字符串栏{get;set;}
CarReport(Car\u Car):基础(Car)
{
酒吧=_car.CarPropoerty;
}
}
摩托车类报告:报告
{
boolbaz{get;set;}
摩托车报告(摩托车循环):基本(摩托车循环)
{
Baz=_cycle.motorkeproperty;
}
}
类模型
{
运行模式(车辆[]车辆收集)
{
foreach(车辆当前车辆中的车辆采集)
{
currentVehicle.DoStuff()
currentVehicle.GetReport()
}
}
}
这也很好,但问题是汽车和摩托车现在依赖于CarReport和MotorcycleReport。由于这对我的程序来说是非核心功能,并且报告结构在未来版本中可能会发生很大变化,因此我希望以报告依赖于车辆的方式来实现它,但车辆不依赖于报告
我尝试了一种外部重载方法,它可以获取车辆并输出正确的报告
或者将抽象报告(或接口IReport)传递给Vehicle“GetReport”方法
但由于我的RunModel方法不知道它处理的是哪种类型的车辆,因此我无法找到将其映射到正确报告类型的方法
有没有办法避免这种双向依赖关系?依赖关系注入可能会有所帮助。.Net Core中的内置依赖项注入没有提供在IReport的两个不同实现之间切换的选项,但是您可以将ICarReport的实现注入到类Car中,将IMotorcycleReport的实现注入到类Motorcycle中。然后,如果实现发生了更改,您可以交换掉它们,而不必更改依赖它们的类 还有其他IoC容器,如Lightinject,允许您注入称为命名依赖项的不同IReport实现。你可能想搜索类似的东西 另外,我不确定您使用的是.Net核心还是.Net框架。NETCore内置了依赖项注入,但您需要为.NETFramework安装一个Nuget包,如Lightinject或Ninject 编辑: 听起来您正在寻找一种设计模式来实现控制反转(IoC)。在这种情况下,正如不同的答案所指出的,您可以使用工厂模式、服务定位器模式或依赖注入模式 如果您的项目很旧或已经非常大,依赖注入可能不适用于您。在这种情况下,它可能是你的下一个项目要研究的东西。在本例中,工厂模式可能正是您想要的。这取决于很多细节,我们现在还不知道
此外,对于不同的模式会有不同的意见,但通常有许多模式可用于解决特定的设计问题。您可以使用泛型在报表和相应的工具之间创建链接。基于此信息,您可以创建基于车辆的报告 车辆和报告如下所示:
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)
{
}
}
如果需要优化性能,我们可以缓存字典:
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;
}
}
让核心域尽可能简单是正确的。它应该只需要处理自己的复杂性,尽量少受到外界的干扰和依赖 首先想到的是,即使继承对于
Vehicle
层次结构可能有意义。问题是,这对报告有意义吗?您是否会单独使用抽象基Report
类?只有公共属性的一个
如果有
您可以使用经理来接管创建报告的职责
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());
}
}
}
公共类报表管理器
{
公共报告GetReport(T车辆),其中T:车辆
{
开关(车辆)
{
箱车:
归还新的CarReport(car);
案例摩托车:
归还新摩托车报告(摩托车);
违约:
抛出新的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;
}
}
公共类模型
{
私有只读ReportManager _ReportManager;
公共模型(ReportManager ReportManager)
{
_reportManager=reportManager;
}
公共列表运行模式(车辆[]辆)
{
var报告=新列表();
foreach(车辆中的var车辆)
{
车辆.DoStuff();
报告。添加(_reportMana)
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();
}
}
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;
}
}
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;
}
}