Как правильно спроектировать параллельную структуру наследования в C #? - PullRequest
5 голосов
/ 18 мая 2011

У меня есть 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, но невозможно обновить свойство подкласса, если свойство базового класса обновлено.

Какие есть другие варианты и какие из них наиболее чистые (и почему)?

Примечание. Приведенный выше код значительно упрощен для иллюстрации проблемы.У реальных классов есть дополнительные свойства и методы, но это подмножество - суть проблемы, с которой я столкнулся.

Ответы [ 2 ]

2 голосов
/ 18 мая 2011

Я согласен с Яуром, что дженерики могут помочь. Что касается ваших возможностей и поддержания простоты модели - возможно, это зависит от специфики, например, от обязанностей ваших 4 классов.

Допустим, вы имеете дело с отношениями родителей и детей различных транспортных средств и деталей транспортных средств.

Сценарий 1: Унаследованные отношения вводят ортогональные возможности.

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

Конечно, автомобили должны знать о 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 [] или создать свойство MyParts, которое делегирует MyChildren.

В этом примере я думаю, что модель довольно проста из-за того, что метафору родитель / потомок довольно легко обмануть. Кроме того, если вы добавляете Truck-TruckParts (или Resident-Line, 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;}
}

В этом случае Truck & Car do делятся большим кодом. Но это начинает сталкиваться с проблемами подписи, которые нелегко решить даже с помощью дженериков. Здесь я хотел бы сделать базовый класс более универсальным (Vehicle-VehiclePart). Или рассмотрите возможность рефакторинга этого второго сценария в первый сценарий. Или используйте коллекцию для управления родителями и детьми и наследование для консолидации кода Car-Truck.

Во всяком случае, я не совсем уверен, что любой сценарий соответствует вашему случаю. По крайней мере, некоторые факторы основаны на том, как вы (и как вы можете) организовать свои отношения.

0 голосов
/ 18 мая 2011

Дженерики могут помочь вам хотя бы частично ... что-то вроде:

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

}
...