Существует также еще один, возможно, даже более предпочтительный способ достижения полиморфизма: Композиция .
Но из предоставленных вами вариантов я бы предпочел первый. Но сделайте ваши базовые классы как можно более общими (так как это ваш базовый класс, и вы не хотите сужать использование, потому что ваши базовые классы слишком специфичны):
Первый пример:
abstract class Item
{
int Id;
abstract Decimal GetPrice();
abstract IItemDetail GetDetail();
}
Так как в вашем первом примере выборка данных из базы данных зависит от типа, было бы лучше перенести специфические для типа операции в сам тип (следовательно, каждый производный тип должен реализовывать свое специфическое поведение. Это исключает тип чеки). И из-за этого делать эти методы статичными в базовом классе не имеет особого смысла (в отличие от того, где выборка данных всегда работает на основе общего типа). Но это дизайнерское решение.
Ваш второй пример представляет те неприятные проверки типов, которые упоминались ранее. Они делают ваш код трудным для расширения и трудным для понимания (например, читаемость). Это связано с тем, что, как было сказано ранее, вы переместили специфические для типа операции (которые нельзя обобщить, например, шаблоны) в тип (может быть базовым типом), который не обладает всей информацией, необходимой для выполнения операции. Этот тип ничего не знает об объекте. Ни типа, ни состояния. Перед выполнением операций этот тип должен запросить у цели всю информацию: какой тип объекта? В каком состоянии это? Вот почему мы применяем принцип Tell-Don't-Ask и наследование, чтобы избавиться от этого.
Третий пример больше похож на статический вспомогательный класс. Методы могут быть статическими (и когда они поддерживаются универсальными или шаблонными), так как этот вспомогательный тип предназначен для работы с любым типом (или общим базовым типом). Но опять же, если операции зависят от типа (как в вашем примере), тогда это приведет к переключению типов и, возможно, к проверке состояния, что является большим шагом по сравнению с написанием расширяемого / поддерживаемого кода.
Ваш первый вариант из предоставленных вами - самый чистый. Это позволяет полиморфизм. И это не нарушает Закон Деметры или Открыто-Закрыто . Добавление нового типа, например «Кино», выполняется путем реализации нового «Элемента», что очень важно с точки зрения расширяемости.
Но есть больше возможностей для достижения того, что вы пытаетесь сделать. Я сказал Состав раньше. Но как насчет инкапсуляции? Мы могли бы использовать Encapsulation , чтобы извлечь изменяющуюся часть. У нас может быть один тип «Item», который используется для всех видов элементов и извлекать поведение, специфичное для типа (единственное поведение, в котором реализации нашего подтипа будут отличаться, например, доступ к БД в вашем примере) в новый тип:
class Item
{
// ctor
Item(IItemPlayer typeSpecificPlayer)
{
this.TypeSpecificPlayer = typeSpecificPlayer;
}
public void Play()
{
this.TypeSpecificPlayer.Play(this);
}
public int Id;
private IItemPlayer TypeSpecificPlayer;
}
interface IItemPlayer
{
void Play(Item itemToPlay);
}
class RecordIItemPlayer implements IItemPlayer
{
void Play(Item item) { print("Put the needle to the groove"); }
}
class MovieIItemPlayer implements IItemPlayer
{
void Play(Item item) { print("Play the video"); }
}
Конструктор принудительно вводит различные компоненты для Композиции типа "Предмет". В этом примере использовались Composition и Encapsulation .
Вы бы использовали это как:
var record = new Item(new RecordPlayer());
var movie = new Item(new MoviePlayer());
recordItem.TypeSpecificPlayer.Play();
movieItem.TypeSpecificPlayer.Play();
Без использования Композиция «IItemPlayer» становится ассоциированным внешним типом:
interface IItemPlayer
{
void Play(Item item);
}
class RecordIItemPlayer implements IItemPlayer
{
void Play(Item item) { print("Put the needle to the groove"); }
}
class MovieIItemPlayer implements IItemPlayer
{
void Play(Item item) { print("Play the video"); }
}
class Item
{
int Id;
Decimal Price;
IItemDetail Details;
}
и используйте его как:
var record = new Item();
var recordItemPlayer = new RecordItemPlayer();
var movie = new Item();
var movieItemPlayer = new MovieItemPlayer();
recordItemPlayer.Play(record);
movieItemPlayer.Play(movie);
Если нужны дополнительные варианты, например, способы воспроизведения определенного типа медиа, мы бы просто добавили новую реализацию «IItemPlayer».