开发者

How can I cleanly design a parallel inheritance structure in C#?

I have 2 sets of 2 classes where each pair has a super/sub-class relationship, and the开发者_运维技巧 orthogonal pair has a dependency relationship. What I am trying to determine is what to do with the constructors and/or bodies of the properties to keep the model as simple as possible with minimal data duplication.

Here's the structure in code:

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
}

I have considered overriding the base properties in the sub-classes, but that doesn't fit very cleanly because the properties in the sub-classes don't have the same signature and so I would have to add properties.

I have also considered setting the base property in the sub-class constructor and set methods, but there is no way for the sub-class property to be updated if the base-class's property is updated.

What other options are there, and which is the cleanest (and why)?

Note: The above code is greatly simplified to illustrate the problem. There are additional properties and methods on the real classes, but this subset is the essence of the trouble I'm having.


I agree with Yaur that generics may help. As far as your options and keeping the model simple as possible - this probably depends on the specifics like the responsibilities of your 4 classes.

Let's say you're dealing with parent/child relationships of various vehicles & vehicle parts.

Scenario 1: The inherited relationship brings in orthogonal capability.

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

Of course, Cars should specifically know about CarPart, not ItemChild. So you fall back on generics here.

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

You can call subclass.MyChildren[] just fine, or make a MyParts property which delegates to MyChildren.

In this example, I think the model is pretty simple due to the fact that the parent/child metaphor is pretty easy to grok. Plus, if you add Truck-TruckParts (or Household-Resident, Shape-Line, etc.) you're not really increasing the complexity.

An alternative here would be to move the parent/child "responsibility" to a collection object (possibly custom), like so:

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

The downside here is that, while clean, Truck and Car might not share a lot of code (if that's what you were wanting).

Scenario 2: The inherited relationship is about specializing to a parallel item.

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

In this case, Truck and Car do share more code. But this starts running into signature problems that aren't easily solved even with generics. Here, I'd consider making the base class more generic (Vehicle-VehiclePart). Or consider refactoring this second scenario into the first scenario. Or use the collection for parent/child management and the inheritance stictly for Car-Truck code consolidation.

At any rate, I'm not really sure that either scenario matches your case. At least some factor are based on how you have (and how you can) arrange your relationships.


Generics may be able to help you with at least part of this... something like:

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)
    {

    }
}

making it work where you need to subclass in both directions can get tricky (and messy) quickly, but will look something like:

// 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>
{

}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜