2014-09-22 2 views
-1

У меня есть следующий шаблон реального мира, где базовый класс BCA содержит общедоступное свойство B базового класса типа BCB. Оба BCA и BCB имеют два производных класса каждый - DCA1 и DCA2, DCB1 и DCB2 соответственно. В каждом из DCA1 и DCA2 собственность в реальном мире фактически относится к типу DCB1 и DCB2 соответственно.Базовый класс A содержит свойство базового класса B, но производный класс A1 имеет производный класс B1

Как это наиболее эффективно транслируется в код - дизайн ООП? Должен ли я держать B в BCA, или я должен использовать его в каждом из DCA1 и DCA2? Должен ли я использовать дженерики? Что-то не так с этим дизайном? Бывает, что я уже пару раз встречался в этом проекте, и почему-то это не кажется правильным; но мне интересно, ошибочна ли моя концепция.

EDIT: Я даю более конкретный пример, чтобы сделать его более ясным. Извините, но мне не разрешено размещать изображения, чтобы показать их на диаграмме классов, но так оно и есть:

A Pet имеет свойство DailyMeal типа PetFood. Свойство Cat's DailyMeal имеет тип CatFood, а Dog's DailyMeal имеет тип DogFood.

Я надеюсь, что это сделает рисунок более четким.

EDIT2: вопрос повторно выражен как: Как вы модель такого реального сценария в котором у вас есть автомобиль с двигателем, ElectricCar с ElectricEngine и DieselCar с DieselEngine и т.д., в термины ООП и, в частности, в C#, так что вы можете использовать полиморфизм:

Car myCar = myElectricCar;

myCar.Engine = myElectricEngine; 

и т.д., и в то же время гарантируя, что каждый тип автомобиля будет иметь правильный тип двигателя. Цель состоит в том, чтобы смоделировать этот реальный сценарий с точки зрения классов, интерфейсов и/или дженериков или любого другого, что требуется, и воспользоваться всеми преимуществами ООП.

+2

Если эти свойства доступны для записи, это нарушает принцип замещения Лискова. В любом случае, нет хорошего решения с классами, но вы можете использовать ковариантные интерфейсы. –

+5

Все ваши акронимы действительно запутывают. Есть ли вероятность, что вы можете показать пример кода с более простыми именами? – StriplingWarrior

+0

@StriplingWarrior Автомобиль имеет двигатель. ElectricCar имеет ElectricEngine. –

ответ

0

Давайте будем использовать следующий пример: все автомобили имеют двигатели, но электромобили имеют электрические двигатели.

Вы не можете создать устройство с классами, но вы можете с ковариант- интерфейсами:

interface ICar<out TEngine> 
{ 
    TEngine Engine { get; } 
} 

class Engine { } 

class Car : ICar<Engine> 
{ 
    private readonly Engine engine; 

    public Car(Engine engine) 
    { 
     this.engine = engine; 
    } 

    public Engine Engine { get { return this.engine; } } 
} 

class ElectricEngine : Engine { } 

class ElectricCar : ICar<ElectricEngine> 
{ 
    private readonly ElectricEngine engine; 

    public Car(ElectricEngine engine) 
    { 
     this.engine = engine; 
    } 

    public ElectricEngine Engine { get { return this.engine; } } 
} 

Обратите внимание, однако, что ElectricCar не наследуется от автомобиля. Тем не менее, ICar<ElectricEngine> отнесено к ICar<Engine>, так это работает:

ICar<Engine> myCar = new ElectricCar(new ElectricEngine()); 

Конечно, вы можете также определить, не универсальный интерфейс ИКАР, который содержит все свойства и методы, которые не имеют ничего общего с двигателем.

Обратите внимание, что ICar<out TEngine> является ковариантным. Это не может быть контравариантным.Это означает, что свойство Engine должно быть доступно только для чтения.

UPDATE

Вы спросили, почему собственность должна быть только для чтения, и будет ли что-то по своей сути неправильно с ним быть доступен для записи. Хорошо, да. Рассмотрим этот сценарий:

ICar<Engine> myCar = new ElectricCar(); 

// assume ICar<Engine> has a writabel property Engine of type Engine 

myCar.Engine = new DieselEngine(); 

Записываемое свойство типа Engine должно было бы разрешить этот последний оператор. Но во время выполнения это не может работать, конечно, потому что myCar - это ElectricCar, и те, кто не принимают DieselEngines.

Предлагаю вам прочитать о Liskov Substitution Principle.

Было бы хорошо для ElectricCar иметь записываемый Engine свойство типа ElectricEngine, его сеттер просто не может быть частью интерфейса ICar<TEngine>. Из-за этого установщик свойства не может быть полиморфным.

+0

Очень интересно! Можем ли мы работать с ограничением чтения или есть что-то по своей сути неправильно с этим? Проблема теперь повторно выражается как: «У нас есть два набора иерархий (автомобили и двигатели), которые объединены в цепочку иерархии (ElectriCars-ElectricEngines, DieselCars-DieselEngines и т. Д.) Нам нужно установить эту муфту, быть в состоянии использовать полиморфизм и иметь возможность изменять связанное свойство (Двигатель автомобиля) во время жизни объектов ». Можно ли это сделать (в C#)? – GDS

+0

@GDS Я обновил свой ответ. –

+0

Был ли быстрый взгляд на Принцип замены Лискова. Выглядит довольно активно и будет выглядеть более внимательно. Однако в вашем решении я полностью понимаю, почему невозможно иметь записываемое свойство. Вопрос заключался в том, можно ли каким-либо образом добиться этого в C# (т. Е. Такого типа связывания иерархии, полиморфизма и возможности записи). Из Принципа замещения Лискова у меня возникает ощущение, что это может быть теоретически невозможно. Это правильно? – GDS

0

Использование дженериков. Сделать BCA родовое в его типе собственности, и ограничить его к быть получены из BCB:

class BCA<T> where T: BCB 
{ 
    public T prop {get; set;} 
} 
+0

Проблема в том, что мы не объединяем два набора иерархий вместе (см. Комментарий к ответу Криса Вандермоттена). С простым использованием дженериков мы можем получить DieselCar с ElectricEngine. – GDS

Смежные вопросы