【问题标题】:How can I cleanly design a parallel inheritance structure in C#?如何在 C# 中干净地设计并行继承结构?
【发布时间】:2011-08-28 02:22:31
【问题描述】:

我有 2 组 2 个类,其中每对具有超类/子类关系,正交对具有依赖关系。我要确定的是如何处理构造函数和/或属性的主体,以使模型尽可能简单,数据重复最少。

代码结构如下:

public class Base1 {
    public List<Base2> MyBase2Things { get; set; }
    // Do things with Base2 objects
}

public class Sub1 : Base1 {
    public List<Sub2> MySub2Things { get; set; }
    // Do things with Sub2 objects and also with Base2 objects
}

public class Base2 {
    public Base1 MyBase1 { get; set; }
    // Do things with the Base1 object
}

public class Sub2 : Base2 {
    public Sub1 MySub1 { get; set; }
    // Do things with the Sub1 object
}

我考虑过覆盖子类中的基本属性,但这不太适合,因为子类中的属性没有相同的签名,所以我必须添加属性。

我也考虑过在子类构造函数和set方法中设置基属性,但是如果基类的属性更新了,子类属性就没有办法更新了。

还有哪些其他选项,哪个最干净(以及为什么)?

注意:上面的代码为了说明问题做了很大的简化。真正的类有额外的属性和方法,但这个子集是我遇到的问题的本质。

【问题讨论】:

  • 我认为简单的答案是你不能。在 .Net 4.0 中,协方差对您有所帮助,但是……希望我错了。 stackoverflow.com/questions/245607/…
  • @Jodrell - 好的,所以如果由于 CLR 和安全问题施加的限制,我不能像我希望的那样干净利落,那么我能做的下一个最好的事情是最大限度地减少数据重复并确保数据完整性,同时仍遵循良好的设计最佳实践?
  • 了解您要完成的工作会有所帮助。 Liskov 替换原则说任何适用于 Base1 的方法都应该适用于 Sub1,并且由于您有不同的列表,因此这是不正确的。无论如何,我会避免公开你的名单。你可以通过显式的 Add/Remove/Get 方法来解决它吗?
  • @NerdFury - Sub1 需要处理特定于 Sub2 的事情,但 Base1 只需要处理 Base2 的事情。由于 Sub1 和 Sub2 是子类,它们从父类继承列表,因此不会违反 Liskov 替换。我同意公开列表有点臭,但这不是主要问题。但是,我认为 Add/Remove/Get 方法可能是正确的方法。
  • “并行继承结构”在当时看起来总是很好,但他们迟早会咬我!

标签: c# inheritance subclass


【解决方案1】:

我同意 Yaur 的观点,即泛型可能会有所帮助。就您的选择而言并尽可能保持模型简单 - 这可能取决于您的 4 个类的职责等细节。

假设您正在处理各种车辆和车辆零件的父/子关系。

场景一:继承关系带来正交能力。

public class ItemParent {  // formerly Base1
    public List<ItemChild> MyChildren {get; set;}
}

public class ItemChild {  // formerly Base2
    public ItemParent MyParent {get; set;}
}

public class Car : ItemParent {  // formerly Sub1
    public List<CarPart> MyParts {get; set;}
}

public class CarPart : ItemChild {  // formerly Sub2
    public Car ParentCar {get; set;}
}

当然,Cars 应该特别了解 CarPart,而不是 ItemChild。所以你在这里求助于泛型。

public class ItemParent<T> where T : ItemChild {
    public List<T> MyChildren {get; set;}
}

public class ItemChild<T> where T : ItemParent {
    public T MyParent {get; set;}
}

public class Car : ItemParent<CarPart> {}
public class CarPart : ItemChild<Car> {}

public class Truck : ItemParent<TruckPart> {}
public class TruckPart : ItemChild<Truck> {}

您可以调用 subclass.MyChildren[] 就好了,或者创建一个委托给 MyChildren 的 MyParts 属性。

在这个例子中,我认为模型非常简单,因为父/子隐喻很容易理解。此外,如果您添加 Truck-TruckParts(或 Household-Resident、Shape-Line 等),您并没有真正增加复杂性。

这里的替代方法是将父/子“职责”移动到集合对象(可能是自定义),如下所示:

public class ParentChildCollection<TParent, TChild> {}

public class Car {
    private ParentChildCollection<Car, CarPart> PartHierarchy;
    public List<CarPart> MyParts {get { return PartHierarchy.GetMyChildren(this); } }
}

public class CarPart {
    private ParentChildCollection<Car, CarPart> PartHierarcy;
    public Car ParentCar {get { return PartHierarchy.GetMyParent(this); }}
}

这里的缺点是,虽然很干净,但 Truck 和 Car 可能不会共享很多代码(如果这是您想要的)。

场景 2:继承关系是专门针对平行项。

public class Car {  // formerly Base1
    public List<CarPart> MyParts {get; set;}
}

public class CarPart {  // formerly Base2
    public Car MyParent {get; set;}
}

public class Truck : Car {  // formerly Sub1
    public List<TruckPart> MyParts {get; set;}
}

public class TruckPart : CarPart {  // formerly Sub2
    public Truck MyParent {get; set;}
}

在这种情况下,卡车和汽车共享更多代码。但这开始遇到签名问题,即使使用泛型也不容易解决。在这里,我会考虑使基类更通用(Vehicle-VehiclePart)。或者考虑将第二种情况重构为第一种情况。或者使用集合进行父/子管理和继承 stictly 用于 Car-Truck 代码合并。

无论如何,我不确定这两种情况是否符合您的情况。至少有一些因素取决于您如何(以及如何)安排您的关系。

【讨论】:

    【解决方案2】:

    泛型可能至少可以帮助您解决其中的部分问题……例如:

    public class Base1<T>
        where T: Base2
    {
        public List<T> MyThings { get; set; }
    
        protected Base1(List<T> listOfThings)
        {
            this.MyThings = listOfThings;
        }
    }
    
    public class Sub1 : Base1<Sub2>
    {
        public Sub1(List<Sub2> listofThings):
            base(listofThings)
        {
    
        }
    }
    

    让它在你需要在两个方向子类化的地方工作会很快变得棘手(和混乱),但看起来像:

    // Base 1 hierachy
    abstract public class Base1
    {
        protected abstract Base2 GetBase2(int index); //we can't return the list directly
    }
    
    public class Base1<Base2Type> :Base1
        where Base2Type : Base2
    {
        public List<Base2Type> MyBase2s { get; set; }
    
        protected Base1(List<Base2Type> listOfThings)
        {
            this.MyBase2s = listOfThings;
        }
    
        protected override Base2  GetBase2(int index)
        {
            return MyBase2s[index];
        }
    
    }
    
    public class Sub1<MySub1Type,MySub2Type> : Base1<MySub2Type>
        where MySub1Type : Sub1<MySub1Type,MySub2Type>
        where MySub2Type : Sub2<MySub1Type, MySub2Type>
    {
        public Sub1(List<MySub2Type> listOfThings):
            base(listOfThings)
        {
            this.MyBase2s = listOfThings;
        }
    }
    
    public class Sub1 : Sub1<Sub1,Sub2>
    {
        public Sub1(List<Sub2> listofThings):
            base(listofThings)
        {
    
        }
    }
    
    
    // base 2 hirachy
    abstract public class Base2
    {
        protected abstract Base1 MyBase1 { get; }
    }
    
    public class Base2<Base1Type,Base2Type> : Base2
        where Base1Type: Base1<Base2Type>
        where Base2Type : Base2
    {
        public Base1Type myBase1;
    
        protected override Base1 MyBase1{ get {return myBase1;} }
    }
    
    public class Sub2<Sub1Type, Sub2Type> : Base2<Sub1Type,Sub2Type>
        where Sub1Type : Sub1<Sub1Type,Sub2Type>
        where Sub2Type : Sub2<Sub1Type,Sub2Type>
    {
    
    }
    
    public class Sub2 : Sub2<Sub1,Sub2>
    {
    
    }
    

    【讨论】:

      猜你喜欢
      • 2014-01-10
      • 1970-01-01
      • 2011-03-12
      • 2013-03-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-26
      • 2014-09-15
      相关资源
      最近更新 更多