Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 741
cdeszaq Avatar asked Nov 04 '22 21:11

cdeszaq


1 Answers

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.

like image 191
Matt Avatar answered Nov 09 '22 09:11

Matt