Здравствуйте.
Посоветуйте, как бы вы решали такую проблему.
Есть сущность (с наследниками)
public interface IClonable<T> where T:...
{
T Clone();
}
public class CostInfo: ..., IClonable<CostInfo>
{
...
protected virtual CostInfo newInstance()
{
return new CostInfo(...);
}
public CostInfo Clone()
{
var ci = newInstance();
//set props
return ci;
}
}
public class OtherCostInfo: ..., IClonable<OtherCostInfo>
{
...
protected override OtherCostInfo newInstance()
{
return new OtherCostInfo(...);
}
public new OtherCostInfo Clone()
{
var ci = (OtherCostInfo)base.Clone();
//set OtherCostInfo specific props
return ci;
}
}
Так уж вышло, что эти сущности хранятся в другой сущности. Причем коллекция может содержать в некоторых случаях CostInfo, в других OtherCostInfo (либо либо, вместе не хранятся).
public class SomeCostHolder
{
public IList<CostInfo> Costs { get { ... } }
}
Потребовался функционал для копирования SomeCostHolder. Среди прочего кода появился хелпер:
//псевдокод
public void CopyCollection<T>(IList<T> source, IList<T> target, ...) where T: IClonable<T>
{
source.ForEach(si => target.Add(si.Clone()));
}
И тут выяснилось, что бывают ситуации, когда при копировании надо конвертировать CostInfo в OtherCostInfo, т.к. другой SomeCostHolder, в который копируются данные, не ожидает у себя в коллекции CostInfo. Проблема у меня тут в том, что метод Clone специально добавлен в DomainModel, что-бы быть поближе к копируемым данным и не забывать его обновлять при изменении модели. А теперь ситуация вынуждает превратить копирование (клонирование) в конвертирование. Для задачи достаточно скопировать только то, что может быть скопировано (от общих предков). Специальная логика конвертации не требуется. При этом не хочется вводит связность в модель (разные наследники знают друг о друге) и не хочется тащить все это вовне в отдельный класс (будем забывать обновлять его). Вводить m:m конвертеров по кол-ву возможных сочетаний как то тоже гм.
Свое решение приводить не буду (пока), хочется посмотреть на возможные подходы.
зы: а то три года времени не было толком на форуме посидеть, а тут как почитал, сколько копий сломано в Anemic vs Rich, так сразу сомнения появляются в квалификации
ззы: высказывания по типу SomeCostHolder криво спроектирован тоже принимаются, но с примером, как надо было спроектировать
Здравствуйте, Артем1, Вы писали:
А>Посоветуйте, как бы вы решали такую проблему.
Если можно копировать только общие данные для всех предков, то и включать эту функциональность имхо нужно в базовый класс.
Тогда производные классы могут ничего не знать друг о друге.
Т.е. если CostInfo и OtherCostInfo имеют общего предка CostInfoParent, то в этом классе сделать метод вроде
public void CopyTo(CostInfoParent other)
{
// ...
}
Но у меня есть ощущение, что рано или поздно вам все равно придется преобразовывать CostInfo в OtherCostInfo и обратно, и этот путь — неправильный. Особенно печально все станет, если родственников CostInfo много, и наследники добавляют в базовый класс различные данные.
Поэтому я бы в первую очередь занялся тем, что изменил бы "другой SomeCostHolder" так, чтобы он принимал любые типы, которые ему могут прийти.
Поскольку деталей про SomeCostHolder нет, то и посоветовать что-либо трудно.
Как вариант: можно выделить код, из-за которого "другой SomeCostHolder" не может обрабатывать CostInfo, в отдельный класс. Затем также в отдельный класс с тем же интерфейсом оформить код, аналогично работающий с OtherCostInfo. Далее применить паттерн dependency injection (или strategy — кому как нравится). Весьма вероятно, что в процессе этих изменений вам придется несколько изменить и классы CostInfo с родичами, т.к., похоже, в иерархии родственников CostInfo есть проблемы (либо лишние классы, либо каких-то классов не хватает).
[skipped]
А>И тут выяснилось, что бывают ситуации, когда при копировании надо конвертировать CostInfo в OtherCostInfo, т.к. другой SomeCostHolder, в который копируются данные, не ожидает у себя в коллекции CostInfo. Проблема у меня тут в том, что метод Clone специально добавлен в DomainModel, что-бы быть поближе к копируемым данным и не забывать его обновлять при изменении модели. А теперь ситуация вынуждает превратить копирование (клонирование) в конвертирование. Для задачи достаточно скопировать только то, что может быть скопировано (от общих предков). Специальная логика конвертации не требуется. При этом не хочется вводит связность в модель (разные наследники знают друг о друге) и не хочется тащить все это вовне в отдельный класс (будем забывать обновлять его). Вводить m:m конвертеров по кол-ву возможных сочетаний как то тоже гм.
А>Свое решение приводить не буду (пока), хочется посмотреть на возможные подходы.
А>зы: а то три года времени не было толком на форуме посидеть, а тут как почитал, сколько копий сломано в Anemic vs Rich, так сразу сомнения появляются в квалификации
А>ззы: высказывания по типу SomeCostHolder криво спроектирован тоже принимаются, но с примером, как надо было спроектировать
Здравствуйте, dfbag7, Вы писали:
D>Здравствуйте, Артем1, Вы писали:
А>>Посоветуйте, как бы вы решали такую проблему.
D>Если можно копировать только общие данные для всех предков, то и включать эту функциональность имхо нужно в базовый класс.
D>Тогда производные классы могут ничего не знать друг о друге.
Ну да, у меня тоже такое CopyTo получилось. Только виртуальное с перегрузкой в наследниках, где они кастят передаваемый объект к себе и если смогли прикастить, копируют свои данные. Ну и вызов базового метода. Только плохо получается, что все реализуют интерфейс IClonable<Конкретный класс> и перегружают метод интерфейса ICopyTo<СамыйБазовыйИзИерархии>
.
D>Но у меня есть ощущение, что рано или поздно вам все равно придется преобразовывать CostInfo в OtherCostInfo и обратно, и этот путь — неправильный. Особенно печально все станет, если родственников CostInfo много, и наследники добавляют в базовый класс различные данные.
Эта печаль уже присутствует. Пока решается постобработкой, так как внешний по отношению к копированию функционал.
D>[skipped]
D>Поскольку деталей про SomeCostHolder нет, то и посоветовать что-либо трудно.
Другой SomeCostHolder — это имелся в виду другой инстанс того же класса. Там верхнеуровневый контейнер другой.
D>Как вариант: можно выделить код, из-за которого "другой SomeCostHolder" не может обрабатывать CostInfo, в отдельный класс...[skipped]
Да, так и сделано. Только вот сама структура DM кривая получилась, поэтому такие вопросы и поднимаются. Думаю о замене наследования композицией, но пока не додумал еще
Раз у по крайне мере двух человек мысли по решению совпали, значит у меня получилось не совсем безнадежно, спасибо