Есть довольно много подобных вопросов там 1 ,2 ,3 ,4 , но в этом вопросе, похоже, не совсем так, и решения не кажутся оптимальными.
Это общий вопрос ООП, предполагая, что полиморфизм, дженерики и миксины доступны. Фактический язык, который нужно использовать - это OOP Javascript (Typescript), но это та же проблема в Java или C ++.
У меня есть параллельные иерархии классов, которые иногда используют одно и то же поведение (интерфейс и реализацию), но иногда каждая имеет свое «защищенное» поведение. Проиллюстрировано так:
Это только для иллюстрации ; это не фактическая диаграмма классов. Чтобы прочитать это:
- Все, что находится в общей иерархии (в центре), является общим для обеих иерархий Canvas (слева) и SVG (справа). Под долей я имею в виду как интерфейс, так и реализацию.
- Что-либо только в левом или правом столбцах означает поведение (методы и члены), специфичное для этой иерархии. Например:
- И левая, и правая иерархии используют абсолютно одинаковые механизмы проверки, показанные как один метод (
Viewee.validate()
) в общей иерархии. - Только у иерархии холста есть метод
paint()
. Этот метод вызывает метод рисования для всех дочерних элементов. - Иерархия SVG должна переопределить
addChild()
методComposite
, но это не относится к иерархии холста.
- И левая, и правая иерархии используют абсолютно одинаковые механизмы проверки, показанные как один метод (
- Конструкции из двух боковых иерархий не могут быть смешаны. Фабрика гарантирует это.
Решение I - Дразнить Помимо Наследования
Наследование Фаулера, кроме Наследования, похоже, здесь не работает, потому что между двумя параллелями есть некоторое расхождение.
Решение II - Mixins
Это единственное, о чем я сейчас могу думать. Две иерархии разрабатываются отдельно, но на каждом уровне классы смешиваются в общий класс, который не является частью иерархии классов. Опуская structural
вилку, будет выглядеть так:
Обратите внимание, что каждый столбец будет в своем собственном пространстве имен, поэтому имена классов не будут конфликтовать.
Вопрос
Кто-нибудь может увидеть недостатки этого подхода? Кто-нибудь может придумать лучшее решение?
добавление
Вот пример кода, как это будет использоваться. Пространство имен svg
может быть заменено на canvas
:
var iView = document.getElementById( 'view' ),
iKandinsky = new svg.Kandinsky(),
iEpigone = new svg.Epigone(),
iTonyBlair = new svg.TonyBlair( iView, iKandinsky ),
iLayer = new svg.Layer(),
iZoomer = new svg.Zoomer(),
iFace = new svg.Rectangle( new Rect( 20, 20, 100, 60) ),
iEyeL = new svg.Rectangle( new Rect( 20, 20, 20, 20) ),
iEyeR = new svg.Rectangle( new Rect( 60, 20, 20, 20) );
iKandinsky.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iEpigone.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iFace.addChildren( iEyeL, iEyeR );
iZoomer.setZoom( new Point( 2, 2 ) );
iZoomer.addChild( iFace );
iLayer.addChild( iZoomer );
iTonyBlair.setContent( iLayer );
По сути, во время выполнения клиенты составляют иерархию экземпляров подклассов Viewee; вот так:
Скажем, все эти зрители взяты из иерархии холста, они отображаются путем обхода иерархии, вызывающей paint()
каждого зрителя. Если они из иерархии svg, пользователи знают, как добавить себя в DOM, но paint()
обхода нет.
Ответы:
Второй подход лучше разделяет интерфейсы, следуя принципу разделения интерфейсов.
Однако я бы добавил интерфейс Paintable.
Я также изменил бы некоторые имена. Не нужно создавать путаницу:
источник
На самом деле это совсем не так. Typescript имеет существенное преимущество перед Java, а именно структурную типизацию. Вы можете сделать что-то похожее в C ++ с шаблонами, напечатанными по типу утки, но это требует гораздо больших усилий.
По сути, определяйте свои классы, но не беспокойтесь о расширении или определении каких-либо интерфейсов. Затем просто определите необходимый интерфейс и примите его в качестве параметра. Затем объекты могут соответствовать этому интерфейсу - им не нужно знать, чтобы расширить его заранее. Каждая функция может объявить точно и только те биты, о которых они насмехаются, и компилятор даст вам пропуск, если последний тип встретит его, даже если классы на самом деле не расширяют эти интерфейсы явно.
Это освобождает вас от необходимости фактически определять иерархию интерфейса и определять, какие классы должны расширять какие интерфейсы.
Просто определите каждый класс и забудьте об интерфейсах - структурная типизация позаботится об этом.
Например:
Это абсолютно законный Typescript. Обратите внимание, что функции-потребители не знают и не дают ни малейшего представления о базовых классах или интерфейсах, используемых в определении классов.
Неважно, связаны ли классы по наследству. Не имеет значения, расширили ли они ваш интерфейс. Просто определите интерфейс как параметр, и все готово - все классы, которые ему соответствуют, принимаются.
источник
validate()
метод (реализация которого идентична для обоих); тогда только svg.Viewee необходимо переопределить addChild () , тогда как только canvas. Viewee нуждается в paint () (который вызывает paint () для всех дочерних элементов - которые являются членами базового класса Composite ). Так что я не могу визуализировать это с помощью структурной типизации.SVGViewee.addChild()
, ноCanvasViewee
также нужна точно такая же функция. Так что, кажется, имеет смысл, что оба присущи из Composite?Краткая информация
Решение 3: Шаблон проектирования программного обеспечения "Параллельная классовая иерархия" - ваш друг.
Длинный расширенный ответ
Ваш дизайн НАЧИНАЕТСЯ ПРАВО. Он может быть оптимизирован, некоторые классы или члены могут быть удалены, но идея «параллельной иерархии», которую вы применяете для решения проблемы, ПРАВИЛЬНА.
Имейте дело с той же концепцией несколько раз, обычно в иерархиях управления.
Через некоторое время я закончил делать то же решение, что и другие разработчики, что иногда называют шаблоном проектирования «Параллельная иерархия» или шаблоном проектирования «Двойная иерархия».
(1) Вы когда-нибудь разделяли один класс на единую иерархию классов?
(2) Вы когда-нибудь разделяли один класс на несколько классов без иерархии?
Если вы применили эти предыдущие решения отдельно, то это способ решить некоторые проблемы.
Но что, если мы объединим эти два решения одновременно?
Объедините их, и вы получите этот «шаблон дизайна».
Реализация
Теперь давайте применим шаблон проектирования программного обеспечения «Параллельная классовая иерархия» в вашем случае.
В настоящее время у вас есть 2 или более независимых иерархий классов, которые очень похожи, имеют сходные ассоциации или намеки, имеют сходные свойства или методы.
Вы хотите избежать дублирования кода или членов («непротиворечивость»), но вы не можете объединить эти классы непосредственно в один из-за различий между ними.
Итак, ваши иерархии очень похожи на этот рисунок, но, тем не менее, их более одного:
В этом, еще не сертифицированном, шаблон проектирования, несколько одинаковых иерархий, объединены в одну иерархию, и каждый общий или общий класс расширяется подклассами.
Обратите внимание, что это решение является сложным, поскольку вы уже имеете дело с несколькими иерархиями, поэтому это сложный сценарий.
1 Корневой класс
В каждой иерархии есть общий «корневой» класс.
В вашем случае для каждой иерархии существует независимый «составной» класс, который может иметь некоторые схожие свойства и несколько похожих методов.
Некоторые из этих участников могут быть объединены, некоторые из этих участников не могут быть объединены.
Итак, что может сделать разработчик, так это создать базовый корневой класс и создать подкласс для эквивалентного случая для каждой иерархии.
На рисунке 2 вы видите диаграмму только для этого класса, в которой каждый класс хранит свое пространство имен.
Члены, опущены, к настоящему времени.
Как вы можете заметить, каждый «составной» класс больше не находится в отдельной иерархии, а объединен в одну общую или общую иерархию.
Затем давайте добавим элементы, которые одинаковы, можно переместить в суперкласс, а элементы, которые отличаются, в каждый базовый класс.
И, как вы уже знаете, «виртуальные» или «перегруженные» методы определены в базовом классе, но заменены в подклассах. Как на рисунке 3.
Обратите внимание, что, возможно, есть некоторые классы без членов, и вы можете испытать искушение удалить эти классы, DONT. Они называются «полые классы», «перечислительные классы» и другие имена.
2 Подкласса
Вернемся к первой диаграмме. Каждый «составной» класс имел подкласс «Viewee» в каждой иерархии.
Процесс повторяется для каждого класса. Обратите внимание, что на рисунке 4 класс «Common :: Viewee» происходит от «Common :: Composite», но для простоты класс «Common :: Composite» на диаграмме опущен.
Вы заметите, что «Canvas :: Viewee» и «SVG :: Viewee», НЕ ДОЛЖНЫ происходить из своего соответствующего «Composite», а вместо этого из общего «Common :: Viewee».
Вы можете добавить участников, сейчас.
3 Повторите процесс
Процесс будет продолжен, для каждого класса «Canvas :: Visual» не будет происходить из «Canvas :: Viewee», а Buit из «Commons :: Visual», «Canvas :: Structural» не будет происходить из «Canvas :: Viewee» ", Buit от" Commons :: Structural "и так далее.
4 Трехмерная иерархическая диаграмма
Вы закончите получать вид трехмерной диаграммы с несколькими слоями, верхний слой, имеет «общую» иерархию, а нижние слои имеют каждую дополнительную иерархию.
Ваши исходные независимые иерархии классов, где что-то похожее на это (рисунок 6):
Обратите внимание, что некоторые классы опущены, и вся иерархия «Canvas» опущена, для упрощения.
Конечная интегрированная иерархия классов может выглядеть примерно так:
Обратите внимание, что некоторые классы опущены, и целые классы «Canvas» опущены, для упрощения, но будут аналогичны классам «SVG».
«Общие» классы могут быть представлены как один слой трехмерной диаграммы, классы «SVG» на другом уровне и классы «Canvas» на третьем уровне.
Убедитесь, что каждый слой относится к первому, в котором каждый класс имеет родительский класс «общей» иерархии.
Реализация кода может потребовать использования либо наследования интерфейса, наследования классов, либо «mixins», в зависимости от того, что поддерживает ваш язык программирования.
Резюме
Как и в любом программном решении, не торопитесь с оптимизацией, оптимизация очень важна, но плохая оптимизация может стать большей проблемой, чем исходная.
Я не рекомендую применять ни «Решение 1», ни «Решение 2».
На «Решение 1» не распространяется, поскольку наследование требуется в каждом конкретном случае.
«Решение 2», «Mixins» могут быть применены, но, после проектирования классов и иерархий.
Mixins, являются альтернативой для наследования на основе интерфейса или множественного наследования на основе классов.
Мое предлагаемое решение 3 иногда называют шаблоном проектирования «Параллельная иерархия» или шаблоном проектирования «Двойная иерархия».
Многие разработчики / дизайнеры не согласятся с этим и считают, что этого не должно быть. Но я использовал miself и другие разработчики в качестве общего решения проблем, например, вашего вопроса.
Еще одна недостающая вещь. В ваших предыдущих решениях основная проблема заключалась не в том, чтобы использовать «миксины» или «интерфейсы», а в том, чтобы сначала уточнить модель ваших классов, а затем использовать существующую функцию языка программирования.
источник
canvas.viewee
и всем его потомкам нужен метод, называемыйpaint()
. Ни классам,common
ниsvg
классам это не нужно. Но в вашем решении, иерархия вcommon
неcanvas
или ,svg
как в моем решении 2. Так как именно вpaint()
конце концов во всех подклассах ,canvas.viewee
если нет наследства есть?paint()
должен быть перемещен или объявлен в "canvas :: viewee". Общая идея шаблона остается, но некоторые элементы могут быть перемещены или изменены.canvas::viewee
?В статье под названием « Шаблоны проектирования для работы с иерархиями двойного наследования в C ++» дядя Боб представляет решение под названием « Лестница в небеса» . Заявлено намерение:
И схема при условии:
Хотя в решении 2 нет виртуального наследования, оно в значительной степени согласуется с шаблоном « Лестница в небеса» . Таким образом, решение 2 представляется разумным для этой проблемы.
источник