Копиклонирование (клоникопирование?)
От: Артем1 Россия  
Дата: 01.02.11 21:11
Оценка:
Здравствуйте.

Посоветуйте, как бы вы решали такую проблему.

Есть сущность (с наследниками)

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 криво спроектирован тоже принимаются, но с примером, как надо было спроектировать
Re: Копиклонирование (клоникопирование?)
От: dfbag7 Россия  
Дата: 04.02.11 11:18
Оценка:
Здравствуйте, Артем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 криво спроектирован тоже принимаются, но с примером, как надо было спроектировать
Re[2]: Копиклонирование (клоникопирование?)
От: Артем1 Россия  
Дата: 04.02.11 21:25
Оценка:
Здравствуйте, dfbag7, Вы писали:

D>Здравствуйте, Артем1, Вы писали:


А>>Посоветуйте, как бы вы решали такую проблему.


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

D>Тогда производные классы могут ничего не знать друг о друге.

Ну да, у меня тоже такое CopyTo получилось. Только виртуальное с перегрузкой в наследниках, где они кастят передаваемый объект к себе и если смогли прикастить, копируют свои данные. Ну и вызов базового метода. Только плохо получается, что все реализуют интерфейс IClonable<Конкретный класс> и перегружают метод интерфейса ICopyTo<СамыйБазовыйИзИерархии> .

D>Но у меня есть ощущение, что рано или поздно вам все равно придется преобразовывать CostInfo в OtherCostInfo и обратно, и этот путь — неправильный. Особенно печально все станет, если родственников CostInfo много, и наследники добавляют в базовый класс различные данные.


Эта печаль уже присутствует. Пока решается постобработкой, так как внешний по отношению к копированию функционал.

D>[skipped]

D>Поскольку деталей про SomeCostHolder нет, то и посоветовать что-либо трудно.

Другой SomeCostHolder — это имелся в виду другой инстанс того же класса. Там верхнеуровневый контейнер другой.

D>Как вариант: можно выделить код, из-за которого "другой SomeCostHolder" не может обрабатывать CostInfo, в отдельный класс...[skipped]


Да, так и сделано. Только вот сама структура DM кривая получилась, поэтому такие вопросы и поднимаются. Думаю о замене наследования композицией, но пока не додумал еще

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