По некоторым причинам я не могу перенести GetSummary в IItem чтобы эту батарею заменить полиморфизмом. Будем считать, что я не имею доступа к коду сборки, в которой лежит IItem.
Как мне поступить? Не могу придумать.
Кроме функции GetSummary есть ещё одна, реализованная по такому же принципу.
--
---
Вы получили это сообщение, поскольку подписаны на группу "dotnetconf".
Чтобы отменить подписку на эту группу и больше не получать от нее сообщения, отправьте письмо на электронный адрес dotnetconf+...@googlegroups.com.
Чтобы настроить другие параметры, перейдите по ссылке https://groups.google.com/d/optout.
То что вы написали в первом способе не будет работать:
* Класс SummaryA, реализующий интерфейс с методом GetSummary(IItem item) не может переопределить тип параметра в GetSummary(ItemA item)
* Метод SummaryFactory.Instance() должен возвращать интерфейс с методом GetSummary, а не IItem
--
--
Кстати, можно же использовать возможности DLR
и написать такой код
GetSummary(IItem item)
{
GetSummry((dynamic)item);
}
Хочу тут ответить на мелкие вопросы пользователям @korchakmv, @denis kodua, @alewmt, а потом отдельным номером обсудить интересную тему, предложенную alewmt
@korchakmv: "Я так понимаю, что ручного кастинга в данном случае не избежать. [...] Единственное что можно сделать, так это выбрать подходящее место, чтобы спрятать это все дело."
- Да, на данный момент я поступил именно так.
Есть интерфейс декоратора
public interface IItemFrontendDecorator
{
string GetSummary();
IItemFrontendDecorator Init(IItem item);
}
Есть базовый типизированный класс, имплементирующий этот интерфейс. Он реализует метод Init, в котором спрятан "ручной кастинг".
public abstract class BaseFrontendDecorator<T> : IItemFrontendDecorator where T : IItem
{
protected T Item { get; private set; }
public abstract string GetSummary();
public IItemFrontendDecorator Init(IItem item)
{
Item = (T)item;
return this;
}
}
Кроме того есть конкретные реализации для всех типов Item и маленькая фабрика декораторов.
Клиентский код выглядит так:
var deco = DecoFactory.CreateDeco(item);
if (deco != null)
{
Summary = deco.GetSummary();
}
@denis kodua: "а если для каждого класса Item создать наследника и расширить его?"
- Проблема в том, что весь клиентский код работает с типом IItem. То есть сложность не в том, куда положить код GetSummary, а в том как перейти от типа IItem, который не знает никакого GetSummary к типу, который знает.
@alewmt: "перенести проверку типа в метод расширения", "использовать возможности DLR"
- Так как клиентский код должен плясать от IItem не сработает extension. А из-за того что для построения саммари нужны различные свойства конкретных items ничего не выйдет с dynamic.
Дело в том что нет никакого бонуса в том чтобы прятать проверку
типа в вашем случае. Вы не получите правильный ООП, потому что
нарушение инкапсуляции никуда не денется. У вас есть доменный
объект который должен сам отвечать на вопрос о саммари, сам - это
ключевое слово. Инкапсуляция это НЕ сокрытие полей класса с
выставлением наружу пропертей 1 в 1 с полями, как ошибочно
полагает огромное количество разработчиков. Инкапсуляция это
компоновка данных и обрабатывающих их методов в одну сущность.
Поэтому когда вы лезете в объект чтобы получить саммари вы
получаете нарушение инкапсуляции. Вот именно в этом проблема кода.
Еще раз повторю мысль. Необязательно писать в строжайшем
соответствии с ООП, а иногда даже противопоказано.
ООП, как бы пародоксально это не звучало, совсем не "наследование инкапсуляция и полиморфизм" это философская концепция, а 3 указанных категории это только инструменты реализации. Поэтому используя набор паттернов, чтобы в конце цепочки получить полиморфный вызов вы не получите ООП, вы получите орден адепта культа карго. Делать правильный ООП надо уметь.
Какой вариант из этих у вас не заработал?
с динамиком:
GetSummary(IItem item)
{
GetSummry((dynamic)item);
}
с экстеншеном:
{
if (item is ItemA) GetSummry((ItemA)item);
if (item is ItemB) GetSummry((ItemB)item);
}
item.GetSummary() это и есть бизнес логика.
Никакой не заработал.
В реализации конкретных GetSummary необходимо обращаться к разным свойствам. Duck typing не работает.
ItemA
PropertyA;
Property1;
ItemB
PropertyB;
Property2;
Всё что было общего у них, уже вынесено в интерфейс. Остался разнобой, к которому как раз и обращается GetSummary
Вариант с экстеншн метод не работает потому что на какой тип я буду вешать экстеншн? Клиент знает только интерфейс IItem. Для конкретной реализации GetSummary мне нужен конкретный тип ItemA, ItemB и так далее.
Но это всё уже не важно. Давайте лучше обсудим нарушение инкапсуляции.
Рассмотрим такой пример:
Есть набор уже извеснтых нам айтемов IEnumerable<IItem> который пользователь видит в гриде.
Данное конкретное приложение отображает их в гриде строкой, которую подготавливает GetSummary
Как эта строка должна выглядеть -- зависит от конкретного приложения. Эта логика должна оставаться на уровне приложения.
Как подружить в таком случае инкапсуляцию и распределение ответственности по слоям?
--
--- Вы получили это сообщение, поскольку подписаны на группу dotnetconf.
Чтобы отменить подписку на эту группу и больше не получать от нее сообщения, отправьте письмо на электронный адрес dotnetconf+...@googlegroups.com.
Настройки подписки и доставки писем: https://groups.google.com/d/optout.
--
--- Вы получили это сообщение, поскольку подписаны на группу dotnetconf.
Чтобы отменить подписку на эту группу и больше не получать от нее сообщения, отправьте письмо на электронный адрес dotnetconf+...@googlegroups.com.
Функциональные требования это бизнес логика, даже если это разные
фронтэнды к общему бэкенду. Сама формулировка "данные зависят от
приложения" запутывает. Уровень приложения это технический аспект
- как организовать взаимодействие пользователя с моделью.