Здравствуйте все!
Есть небольшой вопрос по паттерну Composite
Казалось бы тривиальная задача, но что-то туплю.
Допустим есть два абстрактных класса один простой, второй сам является наследником первого и еще содержит в себе массив дочерних. Как например сложная геометрическая фигура сама является фигурой, но состоит из нескольких простых. Для примера выбрал именно фигуры.
Собственно реальные классы конкретных фигур наследуются от базовых абстраткных. Один соотвтст. конкретная простая фигура (унаследован от базового простого), другой — конкретная составная фигура (унаследован от базового составного).
И хотелось бы во втором классе, который унаследован от составной фигуры, обращаться к элементам массива дочерних фигур как к типу класса унаследованной простой фигуры, а не базовой простой фигуры.
Немного путанно звучит, наверное понятнее будут по коду.
// абстрактрные классы фигурpublic abstract class ShapeBase
{
// размеры, ширина высотаpublic virtual int Width { get; set; }
public virtual int Height { get; set; }
// простой конструкторpublic ShapeBase(){Width = 10;Height = 20;}
}
public abstract class ShapeCompositeBase: ShapeBase
{
// размер композитной фигуры, сумма соотв.размеров дочернихpublic override int Width { get { return Shapes.Sum(new Func<ShapeBase, int>(SumW)); } }
public override int Height { get{ return Shapes.Sum(new Func<ShapeBase, int>(SumH)); } }
private int SumW(ShapeBase sb) {return sb.Width;}
private int SumH(ShapeBase sb) {return sb.Height;}
//дочерние фигурыpublic virtual List<ShapeBase> Shapes { get; set; }
public ShapeCompositeBase()
{
Shapes = new List<ShapeBase>();
}
}
// производные класы
// представляет конкретную простую фигуруpublic class SimpleShape : ShapeBase { public int SpecProperty { get; set; } }//свойство производного класса
// представляет конкретную сложную фигуру состоящую из конкретных простыхpublic class CompositeShape : ShapeCompositeBase{}
public static class MyTester
{
public static void Test()
{
SimpleShape ss = new SimpleShape();
CompositeShape cs = new CompositeShape();
cs.Shapes.Add(ss);
cs.Shapes.Add(ss);
// вот тут хотелось бы иметь коллекцию типа List<SimpleShape> а не List<ShapeBase>
// чтобы не разыменовывать, а обращаться напрямую ведь cs.Shapes лежат объекты SimpleShape
// а не ShapeBase. Хотя конечно я нигде это не указывал, потмоу как если попытаться в CompositeShape
// добавить public override List<SimpleShape> Shapes { get; set; } - будет ошибка:
// "CompositeShape.Shapes': type must be 'System.Collections.Generic.List<ShapeBase>' to match overridden member 'ShapeCompositeBase.Shapes'"
((SimpleShape) cs.Shapes[0]).SpecProperty = 1;
// вместе с тем нужно сохранить возможность передавать объекты коллекции на обработку как ShapeBase
ShowSizes(cs.Shapes[0]);
ShowSizes(cs);
/*
* соответственно вопрос
* как можно в производном классе сложной фигуры, хранить коллекцию конкретных простых фигур,
* пока нашел только способ с промежуточным классом-коллекцией, которая инкапсулирует преобразование
* из ShapeBase в SimpleShape, а само свойство в CompositeShape скрывается следующим образом:
* SimpleShapeCollection shapes = new SimpleShapeCollection(base.Shapes);
* public new SimpleShapeCollection Shapes { get{ return shapes; } }
* Но это похоже на какие-то костыли, наверняка есть способ поизящнее аткое организоать..
*/
}
public static void ShowSizes(ShapeBase sb)
{
//some code to show Width,Height
}
}
Здравствуйте, SomewhereSomehow, Вы писали:
SS>И хотелось бы во втором классе, который унаследован от составной фигуры, обращаться к элементам массива дочерних фигур как к типу класса унаследованной простой фигуры, а не базовой простой фигуры.
Загадочный вопрос
SS>Немного путанно звучит, наверное понятнее будут по коду.
SS>
SS> // вот тут хотелось бы иметь коллекцию типа List<SimpleShape> а не List<ShapeBase>
SS> // чтобы не разыменовывать, а обращаться напрямую ведь cs.Shapes лежат объекты SimpleShape
SS> // а не ShapeBase. Хотя конечно я нигде это не указывал, потмоу как если попытаться в CompositeShape
SS> // добавить public override List<SimpleShape> Shapes { get; set; } - будет ошибка:
SS> // "CompositeShape.Shapes': type must be 'System.Collections.Generic.List<ShapeBase>' to match overridden member 'ShapeCompositeBase.Shapes'"
SS> ((SimpleShape) cs.Shapes[0]).SpecProperty = 1;
SS> // вместе с тем нужно сохранить возможность передавать объекты коллекции на обработку как ShapeBase
SS> ShowSizes(cs.Shapes[0]);
SS> ShowSizes(cs);
SS> /*
SS> * соответственно вопрос
SS> * как можно в производном классе сложной фигуры, хранить коллекцию конкретных простых фигур,
SS> * пока нашел только способ с промежуточным классом-коллекцией, которая инкапсулирует преобразование
SS> * из ShapeBase в SimpleShape, а само свойство в CompositeShape скрывается следующим образом:
я бы делал примерно так - сам композит не стоит перегружать лишними методами, пропертями.
[c#]
public static class Helper
{
public static IList<TShape> Shapes(this ShapeBase shapeBase)
where TShape : ShapeBase
{
return Shapes.CastTo<TShape>().ToList();
}
}
Здравствуйте, SomewhereSomehow, Вы писали:
SS>Собственно реальные классы конкретных фигур наследуются от базовых абстраткных. Один соотвтст. конкретная простая фигура (унаследован от базового простого), другой — конкретная составная фигура (унаследован от базового составного). SS>И хотелось бы во втором классе, который унаследован от составной фигуры, обращаться к элементам массива дочерних фигур как к типу класса унаследованной простой фигуры, а не базовой простой фигуры.
ИМХО, разделение фигур на простые и составные, а также — базовые простые и базовые составные — лишнее. Это и порождает проблему. Сделайте так, чтобы любая фигура — даже если она простая — могла бы быть и составной. Поместите список "детей" в самый базовый класс.
Собственно, об этом я писал в статье Освобождение узников оператора if. Там разбирается похожий пример с окнами. С одной стороны, окно может иметь дочерние окна, с другой стороны — не у всех окон они есть (например, у кнопок). Вывод, который сделан в статье: "Если кажется, что это не так (например, Вас удивляет, зачем "кнопке" иметь дочерние окна), скажите себе: "Лучше иметь 1 избыточную переменную и компактную программу, чем громоздкую программу без избыточных переменных". Функция избыточной переменной у кнопки — "устранять лишнюю логику". Очень полезная "кнопочная избыточность"."
Здравствуйте, Ikemefula, Вы писали: I>я бы делал примерно так — сам композит не стоит перегружать лишними методами, пропертями. I>
I>public static class Helper
I>{
I> public static IList<TShape> Shapes(this ShapeBase shapeBase)
I> where TShape : ShapeBase
I> {
I> return Shapes.CastTo<TShape>().ToList();
I> }
I>}
I>
Спасибо.
Вас не затруднит чуть подробнее описать суть того, что вы предлагаете?
А то приведенный фрагмент кода как-то не очень понятен (как мне так и компилятору).
И что значит "не перегружать лишними методами и свойствами"...
Там вроде вообще нет методов, только два свойства, которые для составных фигур реализуют несколько иную логику вычисления длины и ширины по сравнению с простыми фигурами.
Как иначе определить такое поведение если не тут?
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>Здравствуйте, SomewhereSomehow, Вы писали:
SS>>Собственно реальные классы конкретных фигур наследуются от базовых абстраткных. Один соотвтст. конкретная простая фигура (унаследован от базового простого), другой — конкретная составная фигура (унаследован от базового составного). SS>>И хотелось бы во втором классе, который унаследован от составной фигуры, обращаться к элементам массива дочерних фигур как к типу класса унаследованной простой фигуры, а не базовой простой фигуры.
КЛ>ИМХО, разделение фигур на простые и составные, а также — базовые простые и базовые составные — лишнее. Это и порождает проблему. Сделайте так, чтобы любая фигура — даже если она простая — могла бы быть и составной. Поместите список "детей" в самый базовый класс.
КЛ>Собственно, об этом я писал в статье Освобождение узников оператора if. Там разбирается похожий пример с окнами. С одной стороны, окно может иметь дочерние окна, с другой стороны — не у всех окон они есть (например, у кнопок). Вывод, который сделан в статье: "Если кажется, что это не так (например, Вас удивляет, зачем "кнопке" иметь дочерние окна), скажите себе: "Лучше иметь 1 избыточную переменную и компактную программу, чем громоздкую программу без избыточных переменных". Функция избыточной переменной у кнопки — "устранять лишнюю логику". Очень полезная "кнопочная избыточность"."
КЛ>В Вашем случае рекомендую поступить точно так же.
Не поверите, так сначала и было.
Я сделал один класс, Назовем его БазоваяФигура в который поместил свойства: Высота,Ширина,Дочерние объекты.
От него унаследовал два класса: Фигура1, Фигура2 (назовем пок атак, чтобы не путаться с составными и простыми т.к. теперь все составные).
Далее мне необходимо в классе Фигура2 держать список объектов Фигура1. Вместо этого у меня есть массив объектов БазоваяФигура.
Проблема все равно остается.
Либо я не до конца понимаю вашу мысль.
Здравствуйте, SomewhereSomehow, Вы писали:
SS>Здравствуйте, Ikemefula, Вы писали: I>>я бы делал примерно так — сам композит не стоит перегружать лишними методами, пропертями. I>>
I>>public static class Helper
I>>{
I>> public static IList<TShape> Shapes(this ShapeBase shapeBase)
I>> where TShape : ShapeBase
I>> {
I>> return Shapes.CastTo<TShape>().ToList();
I>> }
I>>}
I>>
SS>Спасибо. SS>Вас не затруднит чуть подробнее описать суть того, что вы предлагаете?
Я предлагаю сделать экстеншт метод, который будет приводить чилдов к нужному типу.
SS>И что значит "не перегружать лишними методами и свойствами"... SS>Там вроде вообще нет методов, только два свойства, которые для составных фигур реализуют несколько иную логику вычисления длины и ширины по сравнению с простыми фигурами.
Еще поясню свою мысль, зачем это надо.
По моему мнению, совсем хорошо бы это выглядело если бы:
Были классы
SimpleShape, CompositeShape, AnyOtherShape.
Интерфейс:
ISizeCalculatable.
Все классы имплементят этот интерфейс в соответствии со своей логикой.
Ну и соответвенно в метод обработки передается ShowSizes(ISizeCalculatable sb)
Этот метод хорош еще тем, что по сути, кроме базовго набора свойств и того, что формы могут включать в себя другие формы — их ничего не объединяет, что согласуется со смыслом интерфейсов.
Ои могут иметь абсолютно разные наборы свойств, кроме тах базовых что я вынес в абстрактный класс.
Но совершенно четко известно, что набор базовых свойств будет расширяться, и в случае с интерфейсом, добавление одного свойства в интерфейс — повлечет за собой необходимость дописывать реализацию это свойствя во все классы реализующие этот интерфейс.
Так что хотелось бы все-таки понять, как обойтись абстрактными классами (или генериками, только хз как).
Здравствуйте, Ikemefula, Вы писали:
I>Здравствуйте, SomewhereSomehow, Вы писали:
SS>>Здравствуйте, Ikemefula, Вы писали: I>>>я бы делал примерно так — сам композит не стоит перегружать лишними методами, пропертями. I>>>
I>>>public static class Helper
I>>>{
I>>> public static IList<TShape> Shapes(this ShapeBase shapeBase)
I>>> where TShape : ShapeBase
I>>> {
I>>> return Shapes.CastTo<TShape>().ToList();
I>>> }
I>>>}
I>>>
SS>>Спасибо. SS>>Вас не затруднит чуть подробнее описать суть того, что вы предлагаете?
I>Я предлагаю сделать экстеншт метод, который будет приводить чилдов к нужному типу.
Ну я так понял необходимо будет явно вызывать этот метод? Если так, то я как раз от этого хотел уйти.
SS>>И что значит "не перегружать лишними методами и свойствами"... SS>>Там вроде вообще нет методов, только два свойства, которые для составных фигур реализуют несколько иную логику вычисления длины и ширины по сравнению с простыми фигурами.
I>А список чилдов ?
А куда в таком случае помещать список чайлдов как не в композит?
Здравствуйте, SomewhereSomehow, Вы писали:
SS>Не поверите, так сначала и было. SS>Я сделал один класс, Назовем его БазоваяФигура в который поместил свойства: Высота,Ширина,Дочерние объекты.
Зачем в базовом классе Вам понадобились высота и ширина? (Я предлагал только добавить туда список дочерних объектов.)
SS>От него унаследовал два класса: Фигура1, Фигура2 (назовем пок атак, чтобы не путаться с составными и простыми т.к. теперь все составные).
Чем Фигура1 отличалась от Фигура2? И чем они обе отличались от БазоваяФигура?
SS>Далее мне необходимо в классе Фигура2 держать список объектов Фигура1. Вместо этого у меня есть массив объектов БазоваяФигура.
Почему Фигура2 должна знать о том, какие дочерние фигуры в ней содержатся? Почему Фигуре2 принципиально, чтобы в ней находились Фигуры1?
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>Здравствуйте, SomewhereSomehow, Вы писали:
SS>>Не поверите, так сначала и было. SS>>Я сделал один класс, Назовем его БазоваяФигура в который поместил свойства: Высота,Ширина,Дочерние объекты. КЛ>Зачем в базовом классе Вам понадобились высота и ширина? (Я предлагал только добавить туда список дочерних объектов.)
Если высота и ширина являются общими для всех фигур, то почему следует делать в каждом производном классе те же самые свойства что можно определить в базовом?
SS>>От него унаследовал два класса: Фигура1, Фигура2 (назовем пок атак, чтобы не путаться с составными и простыми т.к. теперь все составные). КЛ>Чем Фигура1 отличалась от Фигура2? И чем они обе отличались от БазоваяФигура?
Отличия я тут не приводил, ибо сути вопроса не меняют, но они достаточные, для того чтобы разносить их в разные классы.
Суть не в том чем одна фигура отличается от другой, это пример, я его выбрал т.к. он помогает более ясно представить паттерн композит (по крайней мере для меня).
Если смущает слово фигра, можете обозначить КлассА, КлассБ.
Примем необходимость разных классов, как условие задачи.
SS>>Далее мне необходимо в классе Фигура2 держать список объектов Фигура1. Вместо этого у меня есть массив объектов БазоваяФигура. КЛ>Почему Фигура2 должна знать о том, какие дочерние фигуры в ней содержатся? Почему Фигуре2 принципиально, чтобы в ней находились Фигуры1?
Потому что фигура2 должна использовать свойства и методы фигуры1, при этом нельзя выносить эти методы в базовый класс, т.к. далеко не все (а точнее вообще никто) из наследников базового класса не будет иметь методов и свойств специфичных для фигуры1.
Здравствуйте, SomewhereSomehow, Вы писали:
SS>Суть не в том чем одна фигура отличается от другой, это пример, я его выбрал т.к. он помогает более ясно представить паттерн композит (по крайней мере для меня). SS>Если смущает слово фигра, можете обозначить КлассА, КлассБ.
Хочу уточнить , композит представляет такой же интерфейс как и базовый класс. Т.е. для пользователя композита он не должен никак отличаться от базового , в этом то и его суть.
Здравствуйте, SomewhereSomehow, Вы писали:
SS>Все классы имплементят этот интерфейс в соответствии со своей логикой. SS>Ну и соответвенно в метод обработки передается ShowSizes(ISizeCalculatable sb)
SS>Этот метод хорош еще тем, что по сути, кроме базовго набора свойств и того, что формы могут включать в себя другие формы — их ничего не объединяет, что согласуется со смыслом интерфейсов. SS>Ои могут иметь абсолютно разные наборы свойств, кроме тах базовых что я вынес в абстрактный класс. SS>Но совершенно четко известно, что набор базовых свойств будет расширяться, и в случае с интерфейсом, добавление одного свойства в интерфейс — повлечет за собой необходимость дописывать реализацию это свойствя во все классы реализующие этот интерфейс. SS>Так что хотелось бы все-таки понять, как обойтись абстрактными классами (или генериками, только хз как).
Здравствуйте, SomewhereSomehow, Вы писали:
SS>Если высота и ширина являются общими для всех фигур, то почему следует делать в каждом производном классе те же самые свойства что можно определить в базовом?
Ок.
SS>Отличия я тут не приводил, ибо сути вопроса не меняют, но они достаточные, для того чтобы разносить их в разные классы. SS>Суть не в том чем одна фигура отличается от другой, это пример, я его выбрал т.к. он помогает более ясно представить паттерн композит (по крайней мере для меня).
Как верно заметил minorlogic паттерн Компоновщик как раз-таки предполагает, что у всех классов одинаковый интерфейс. В список дочерних элементов можно добавить объект любого класса иерархии, и от этого ничего не поменяется. Если же данное условие не выполняется, то надо смотреть на конкретную задачу, т.к. либо решение (использовать Компоновщик) неправильное, либо его не так нужно применять к конкретной задаче.
КЛ>>Почему Фигура2 должна знать о том, какие дочерние фигуры в ней содержатся? Почему Фигуре2 принципиально, чтобы в ней находились Фигуры1? SS>Потому что фигура2 должна использовать свойства и методы фигуры1, при этом нельзя выносить эти методы в базовый класс, т.к. далеко не все (а точнее вообще никто) из наследников базового класса не будет иметь методов и свойств специфичных для фигуры1.
Всё это очень странно. Если Вы не можете распространить методы фигуры1 на всю иерархию, то это означает, что фигура1 не вписывается в паттерн Компоновщик. И тут возможны два варианта:
(1) либо всё-таки "вписать" фигуру1 в иерархию, распространив её специфичные методы на все классы иерархии;
(2) либо вынести различия в отдельные структуры данных, никак не связанные с иерархией классов компоновщика.
В любом случае, надо смотреть на конкретную задачу.
SS>А смысл который вы хотели донести? Т.е. сделать абстрактный класс составной фигуры генерик типом, чтобы...что?
чтобы он был контейнером для типов фигур которые нужны.
потом к тебя будет класс
class МногоКвадратиков : ShapeCompositeBase<Квадратик> {
Спасибо за советы!
Да, видимо компоновщик в чистом виде не подходит...
Сейчас читаю и вникаю в Visitor, по наводке BluntBlind, возможно это то что нужно, но пока до конца не разобрался.
Здравствуйте, cvetkov, Вы писали:
C>Здравствуйте, SomewhereSomehow, Вы писали:
SS>>А смысл который вы хотели донести? Т.е. сделать абстрактный класс составной фигуры генерик типом, чтобы...что? C>чтобы он был контейнером для типов фигур которые нужны.
C>потом к тебя будет класс
C>class МногоКвадратиков : ShapeCompositeBase<Квадратик> {
C>void обработатьКвадратики(){ C> Квадратик квадратик = Shapesх[1]; C> квадратик.обработать(); C>}
C>}
SS>>КАк это будет выглядеть? C>ну както так. просто я думал дальше и так понятно будет
Спасибо за разъяснения.
В генериках я слабоват, попробую поразбираться в них в разрезе вашей идеи..