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

C# 在关联来自不同基类的派生类时避免双向依赖,c#,dependencies,polymorphism,C#,Dependencies,Polymorphism,我正在研究一个模型,它可以用一堆不同的车辆来做一些事情。 每辆车都应该做些什么,但每种车型都做不同的事情。 因此,我使用.NET Framework以这种方式实现了它: 抽象类车辆 { 抽象void DoStuff() } 汽车类别:汽车 { 重写void DoStuff() { //在这里做些汽车用品 } } 摩托车类别:汽车 { 重写void DoStuff() { //在这里做些摩托车的事 } } 类模型 { 运行模式(车辆[]车辆收集) { foreach(车辆当前车辆中的车辆采集) {

我正在研究一个模型,它可以用一堆不同的车辆来做一些事情。 每辆车都应该做些什么,但每种车型都做不同的事情。 因此,我使用.NET Framework以这种方式实现了它:

抽象类车辆
{
抽象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;
        }
    }