Паралельні ієрархії - частково однакові, частково різні


12

Є дуже багато подібних питань 1 ,2 ,3 ,4 , але в цьому питанні це не так, і рішення не здаються оптимальними.

Це загальне питання про ООП, припускаючи, що наявні поліморфізм, генерики та міксини. Фактична мова, якою слід скористатись, є OOP Javascript (Typescript), але це та сама проблема в Java або C ++.

У мене є ієрархії паралельних класів, які іноді мають однакову поведінку (інтерфейс та реалізація), але іноді кожна має власну "захищену" поведінку. Ілюстровано так:

3 паралельні ієрархії класів, центральний стовпець показує загальні частини, лівий стовпець - ієрархію полотна, а правий стовпець - ієрархію SVG

Це лише для ілюстрації ; це не фактична діаграма класів. Щоб прочитати:

  • Все, що є у загальній ієрархії (у центрі), поділяється між ієрархіями Canvas (зліва) та SVG (справа). Під часткою я маю на увазі як інтерфейс, так і реалізацію.
  • Все, що знаходиться лише в лівій або правій колонці, означає поведінку (методи та члени), специфічну для цієї ієрархії. Наприклад:
    • І ліва, і права ієрархії використовують абсолютно однакові механізми перевірки, показані як єдиний метод ( Viewee.validate()) на загальній ієрархії.
    • Лише ієрархія полотна має метод paint(). Цей метод називає метод фарби для всіх дітей.
    • Ієрархія SVG потребує переосмислення addChild()методу Composite, але це не так з ієрархією полотна.
  • Конструкції з двох бічних ієрархій не можна змішувати. Завод забезпечує це.

Рішення I - наслідування в розрив

Навпаки, наслідування Фаулера, крім здачі, не справляється із цим завданням, оскільки між двома паралелями є певна розбіжність.

Розчин II - Міксини

Це єдиний, про який я зараз можу придумати. Дві ієрархії розвиваються окремо, але на кожному рівні класи поєднують загальний клас, який не є частиною ієрархії класів. Опустивши 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()обходу.



Може спробувати повнофункціональний шаблон дизайну декораторів (Erich Gamma et als, Шаблони дизайну)?
Зон

Що за переглядач? Що означає "паралель" як іменник (на відміну від прикметника)?
Тулен Кордова

У вас є багаторазове успадкування?
Тулен Кордова

Чи містять класи Canvas або SVG додаткові стану чи дані, які не є загальними? Як ви використовуєте заняття? Чи можете ви показати приклад коду, який показує, як можна використовувати ці hiearchies?
Ейфорія

Відповіді:


5

Другий підхід краще сегментує інтерфейси, дотримуючись принципу поділу інтерфейсу.

Однак я б додав інтерфейс Paintable.

Я також змінив би деякі назви. Не потрібно створювати плутанину:

// common

public interface IComposite {
    public void addChild(Composite e);
}

public interface IViewee extends IComposite{
    public void validate();
    public List<IBound> getAbsoluteBouns();
}

public interface IVisual {
    public List<IBound> getBounds();
}

public interface IRec {
}

public interface IPaintable {
    public void paint();
}

// canvas

public interface ICanvasViewee extends IViewee, IPaintable {
}

public interface ICanvasVisual extends IViewee, IVisual {
}

public interface ICanvasRect extends ICanvasVisual, IRec {
}


// SVG

public interface ISVGViewee extends IViewee {
    public void element();
}

public interface ISVGVisual extends IVisual, ISVGViewee {
}

public interface ISVGRect extends ISVGVisual, IRect {
}

Я думаю, що інтерфейси можуть допомогти у цьому випадку. Мені хотілося б дізнатися з причин, чому ваша відповідь була оскаржена.
umlcat

не потік, але експоненціальні інтерфейси ІМХО не є гарною схемою
dagnelies

@arnaud, що ти маєш на увазі під "експоненціальними інтерфейсами"?
Тулен Кордова

@ user61852 ... ну, скажімо, це багато інтерфейсів. "Експонентний" насправді був неправильним терміном, він більше схожий на "мультиплікативний". У тому сенсі, якби у вас було більше "граней" (композитних, візуальних, живописних ...) та більше "елементів" (полотно, svg ...), у вас вийшло б багато інтерфейсів.
dagnelies

@arnaud У вас є суть, але принаймні є гнучка конструкція заздалегідь, і кошмар спадщини ОП вирішиться, коли ви не відчуваєте себе змушеним продовжувати. Ви розширюєте деякий клас, якщо хочете і не змушені надуманою ієрархією.
Тулен Кордова

3

Це загальне питання про ООП, припускаючи, що наявні поліморфізм, генерики та міксини. Фактична мова, якою слід скористатись, є OOP Javascript (Typescript), але це та сама проблема в Java або C ++.

Це насправді зовсім не так. Typescript має істотну перевагу перед Java, а саме - структурне введення тексту. Ви можете зробити щось подібне в C ++ за допомогою шаблонів, набраних качками, але це набагато більше зусиль.

В основному, визначте свої класи, але не турбуйтеся розширювати чи визначати будь-які інтерфейси. Потім просто визначте потрібний інтерфейс і прийміть його як параметр. Потім об’єкти можуть відповідати цьому інтерфейсу - не потрібно знати їх заздалегідь. Кожна функція може точно оголошувати, і лише біти, про які вони поспішають, компілятор видасть вам пропуск, якщо кінцевий тип відповідає цьому, хоча класи насправді не розширюють ці інтерфейси явно.

Це звільняє вас від необхідності насправді визначити ієрархію інтерфейсу та визначити, які класи мають розширювати, які інтерфейси.

Просто визначте кожен клас і забудьте про інтерфейси - про це подбає структурний набір тексту.

Наприклад:

class SVGViewee {
    validate() { /* stuff */ }
    addChild(svg: SVG) { /* stuff */ }
}
class CanvasViewee {
    validate() { /* stuff */ }
    paint() { /* stuff */ }
}
interface SVG {
    addChild: { (svg: SVG): void };
}
f(viewee: { validate: { (): boolean }; }) {
    viewee.validate();
}
g(svg: SVG) {
    svg.addChild(svg);
}
h(canvas: { paint: { (): void }; }) {
    canvas.paint();
}
f(SVGViewee());
f(CanvasViewee());
g(SVGViewee());
h(CanvasViewee());

Це абсолютно законний Typescript. Зауважте, що споживачі функції не знають і не дають жодного лайно щодо базових класів або інтерфейсів, що використовуються у визначенні класів.

Не має значення, пов’язані чи ні класи за спадщиною. Не має значення, чи розширили ваш інтерфейс. Просто визначте інтерфейс як параметр, і ви готові - всі класи, які відповідають йому, приймаються.


Це звучить багатообіцяюче, але я не дуже розумію пропозицію (вибачте, можливо, занадто великі упередження OOP). Можливо, ви можете поділитися прикладом коду? Наприклад, і svg.Viewee, і canvas.Viewee потрібен validate()метод (реалізація якого однакова для обох); то тільки svg.Viewee потрібно переосмислити addChild () , тоді як тільки canvas.Viewee потребує paint () (який викликає paint () для всіх дітей - учасників базового класу Composite ). Тому я не можу реально уявити це за допомогою структурного набору тексту.
Іжакі

Ви розглядаєте цілу купу речей, які в цьому випадку просто зовсім не мають значення.
DeadMG

Тому я, мабуть, не отримав відповіді взагалі. Було б добре, якщо ви допрацюєте.
Іжакі

Я зробив правку. Суть полягає в тому, що базові класи абсолютно не мають значення і ніхто про них не переймається. Вони є лише детальною інформацією про реалізацію.
DeadMG

1
ГАРАЗД. Це починає мати сенс. А) Я знаю, що таке типи качок. Б) Я не впевнений, чому інтерфейси є настільки центральними в цій відповіді - розбиття класу заради обміну загальною поведінкою, ви зараз можете сміливо ігнорувати інтерфейси. В) Скажіть у своєму прикладі, що у вас є SVGViewee.addChild(), але CanvasVieweeтакож потрібна точно така ж особливість. Тож, здається, мені є сенс, що обидва притаманні композиту?
Іжакі

3

Швидкий огляд

Рішення 3: Шаблон дизайну програмного забезпечення «Паралельна ієрархія класів» - ваш друг.

Довгий розширений відповідь

Ваша конструкція НАЧАЛО ВПРАВО. Це може бути оптимізовано, деякі класи або члени можуть бути видалені, але ідея "паралельної ієрархії", яку ви застосовуєте для вирішення проблеми, ПРАВИЛЬНА.

Майте справу з тією ж концепцією кілька разів, як правило, в ієрархіях управління.

Через деякий час я ЗАКРИТИ РОЗВ'ЯЗУВАННЯ ІНШИХ РОЗВ’ЯЗУВАННЯ ІНШИХ РОЗВИТКІВ, які іноді називають "Шаблон дизайну" Паралельної Ієрархії "або" Дизайн подвійної Ієрархії ".

(1) Ви коли-небудь розділяли один клас на єдину ієрархію класів?

(2) Ви коли-небудь розділяли один клас на кілька класів без ієрархії?

Якщо ви застосовували ці попередні рішення окремо, то вони є способом вирішення деяких проблем.

Але що робити, якщо поєднати ці два рішення одночасно?

Поєднайте їх, і ви отримаєте цей «Дизайн-шаблон».

Впровадження

Тепер давайте застосуємо шаблон дизайну програмного забезпечення "Паралельна ієрархія класів" до вашого випадку.

На даний момент у вас є 2 або більше незалежних ієрархій класів, які дуже схожі, мають схожі асоціації або функціонування, мають схожі властивості або методи.

Ви хочете уникнути дублювання коду або членів ("узгодженість"), але ви не можете об'єднати ці класи безпосередньо в один, через відмінності між ними.

Отже, ваші ієрархії дуже схожі на цю цифру, але, проте, їх існує більше:

................................................
...............+----------------+...............
...............|     Common::   |...............
...............|    Composite   |...............
...............+----------------+...............
...............|      ...       |...............
...............+-------+--------+...............
.......................|........................
.......................^........................
....................../.\.......................
.....................+-+-+......................
.......................|........................
...............+-------+--------+...............
...............|     Common::   |...............
...............|     Viewee     |...............
...............+----------------+...............
...............|      ...       |...............
...............+-------+--------+...............
.......................|........................
.......................^........................
....................../.\.......................
.....................+-+-+......................
.......................|........................
..........+------------+------------+...........
..........|.........................|...........
..+-------+--------+........+-------+--------+..
..|     Common::   |........|     Common::   |..
..|     Visual     |........|   Structural   |..
..+----------------+........+----------------+..
..|      ...       |........|      ...       |..
..+----------------+........+----------------+..
................................................

Figure 1

У цьому, ще не сертифікованому, Шаблон дизайну, РІЗНІ ПІДПРИЄМНІ ІЄРАРХІЇ, ПІДГОТОВЛЕННЯ, В ЄДИНУ ІЄРАРХІЮ, і кожен спільний або загальний клас розширюється за допомогою підкласифікації.

Зауважте, що це рішення є складним, оскільки ви вже маєте справу з кількома ієрархіями, тому це складний сценарій.

1 Кореневий клас

У кожній ієрархії є спільний "кореневий" клас.

У вашому випадку існує незалежний клас "Композит" для кожної ієрархії, який може мати схожі властивості та деякі подібні методи.

Деякі з цих членів можуть бути об'єднані, деякі з цих членів не можуть бути об'єднані.

Отже, те, що може зробити розробник, - це створити базовий кореневий клас та підкласити еквівалентний випадок для кожної ієрархії.

На малюнку 2 ви можете побачити схему саме для цього класу, в якій кожен клас зберігає в ньому простір імен.

Члени, поки що опущені.

................................................
...............+-------+--------+...............
...............|     Common::   |...............
...............|    Composite   |...............
...............+----------------+...............
...............|      ...       |...............
...............+-------+--------+...............
.......................|........................
.......................^........................
....................../.\.......................
.....................+-+-+......................
.......................|........................
..........+------------+------------+...........
..........|.........................|...........
..+-------+--------+........+-------+--------+..
..|     Canvas::   |........|      SVG::     |..
..|    Composite   |........|    Composite   |..
..+----------------+........+----------------+..
..|      ...       |........|      ...       |..
..+----------------+........+----------------+..
................................................

Figure 2

Як ви можете зауважити, кожен клас "Композит" вже не є окремою ієрархією, а об'єднаний в єдину спільну або загальну ієрархію.

Потім додамо членів, ті, що є однаковими, можна перемістити до надкласу, а також ті, що відрізняються, до кожного базового класу.

І як ви вже знаєте, "віртуальні" або "перевантажені" методи визначаються в базовому класі, але замінюються в підкласах. Як і малюнок 3.

................................................
.............+--------------------+.............
.............|       Common::     |.............
.............|      Composite     |.............
.............+--------------------+.............
.............| [+] void AddChild()|.............
.............+---------+----------+.............
.......................|........................
.......................^........................
....................../.\.......................
.....................+-+-+......................
.......................|........................
..........+------------+------------+...........
..........|.........................|...........
..+-------+--------+........+-------+--------+..
..|     Canvas::   |........|      SVG::     |..
..|    Composite   |........|    Composite   |..
..+----------------+........+----------------+..
..|      ...       |........|      ...       |..
..+----------------+........+----------------+..
................................................

Figure 3

Зауважте, що, можливо, є кілька класів без членів, і, можливо, ви захопите їх видалити, DONT. Їх називають «порожнисті класи», «перелічувальні класи» та інші назви.

2 Підкласи

Повернемося до першої схеми. Кожен клас "Композит" мав підклас "Viewee" у кожній ієрархії.

Процес повторюється для кожного класу. Зауважте, що, ніж на малюнку 4, клас "Загальний :: Viewee" походить від "Загального :: Композиту", але, для простоти, клас "Загальний :: Композит" виключається з діаграми.

................................................
.............+--------------------+.............
.............|       Common::     |.............
.............|       Viewee       |.............
.............+--------------------+.............
.............|        ...         |.............
.............+---------+----------+.............
.......................|........................
.......................^........................
....................../.\.......................
.....................+-+-+......................
.......................|........................
..........+------------+------------+...........
..........|.........................|...........
..+-------+--------+........+-------+--------+..
..|     Canvas::   |........|      SVG::     |..
..|     Viewee     |........|     Viewee     |..
..+----------------+........+----------------+..
..|      ...       |........|      ...       |..
..+----------------+........+----------------+..
................................................

Figure 4

Ви зауважите, що "Canvas :: Viewee" та "SVG :: Viewee" НЕ ДІЛЬШІ спускаються із відповідного "Composite", а із загального "Common :: Viewee".

Ви можете додати членів зараз.

......................................................
.........+------------------------------+.............
.........|            Common::          |.............
.........|            Viewee            |.............
.........+------------------------------+.............
.........| [+] bool Validate()          |.............
.........| [+] Rect GetAbsoluteBounds() |.............
.........+-------------+----------------+.............
.......................|..............................
.......................^..............................
....................../.\.............................
.....................+-+-+............................
.......................|..............................
..........+------------+----------------+.............
..........|.............................|.............
..+-------+---------+........+----------+----------+..
..|      Canvas::   |........|         SVG::       |..
..|      Viewee     |........|        Viewee       |..
..+-----------------+........+---------------------+..
..|                 |........| [+] Viewee Element  |..
..+-----------------+........+---------------------+..
..| [+] void Paint()|........| [+] void addChild() |..
..+-----------------+........+---------------------+..
......................................................

Figure 5

3 Повторіть процес

Процес триватиме, для кожного класу "Canvas :: Visual" не буде сходити з "Canvas :: Viewee", а buit з "Commons :: Visual", "Canvas :: Structural" не зійде з "Canvas :: Viewee ", buit від" Commons :: Structural "тощо.

4 Діаграма 3D ієрархії

Ви закінчите отримувати своєрідну 3D-діаграму, з декількома шарами, верхній шар, має ієрархію "Загальна", а нижній - кожен додатковий ієрархій.

Ваша оригінальна ієрархія незалежних класів, де щось подібне до цього (мал. 6):

.................................................
..+-----------------+.......+-----------------+..
..|      Common::   |.......|       SVG::     |..
..|     Composite   |.......|     Composite   |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+--------+--------+.......+--------+--------+..
...........|.........................|...........
...........^.........................^...........
........../.\......................./.\..........
.........+-+-+.....................+-+-+.........
...........|.........................|...........
..+--------+--------+.......+--------+--------+..
..|      Common::   |.......|       SVG::     |..
..|      Viewee     |.......|      Viewee     |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+--------+--------+.......+--------+--------+..
...........|.........................|...........
...........^.........................^...........
........../.\......................./.\..........
.........+-+-+.....................+-+-+.........
...........|.........................|...........
..+--------+--------+.......+--------+--------+..
..|      Common::   |.......|       SVG::     |..
..|      Visual     |.......|      Visual     |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+--------+--------+.......+--------+--------+..
...........|.........................|...........
...........^.........................^...........
........../.\......................./.\..........
.........+-+-+.....................+-+-+.........
...........|.........................|...........
..+--------+--------+.......+--------+--------+..
..|      Common::   |.......|       SVG::     |..
..|       Rect      |.......|       Rect      |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+-----------------+.......+-----------------+..
.................................................

Figure 6

Зауважте, що деякі класи опущені, а вся ієрархія "Canvas" - опущена.

Кінцева ієрархія інтегрованого класу може бути подібною до цього:

.................................................
..+-----------------+.../+..+-----------------+..
..|      Common::   +--<.+--+       SVG::     |..
..|     Composite   |...\+..|     Composite   |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+--------+--------+.......+-----------------+..
...........|.....................................
...........^.....................................
........../.\....................................
.........+-+-+...................................
...........|.....................................
..+--------+--------+.../+..+-----------------+..
..|      Common::   +--<.+--+       SVG::     |..
..|      Viewee     |...\+..|      Viewee     |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+--------+--------+.......+-----------------+..
...........|.....................................
...........^.....................................
........../.\....................................
.........+-+-+...................................
...........|.....................................
..+--------+--------+.../+..+-----------------+..
..|      Common::   +--<.+--+       SVG::     |..
..|      Visual     |...\+..|      Visual     |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+--------+--------+.......+-----------------+..
...........|.....................................
...........^.....................................
........../.\....................................
.........+-+-+...................................
...........|.....................................
..+--------+--------+.../+..+-----------------+..
..|      Common::   +--<.+--+       SVG::     |..
..|       Rect      |...\+..|       Rect      |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+-----------------+.......+-----------------+..
.................................................
Figure 7

Зауважте, що деякі класи опущені, і всі класи "Canvas" опущені, просто, але вони будуть подібні до класів "SVG".

Класи "Загальні" можуть бути представлені як один шар 3D-діаграми, класи "SVG" в іншому шарі, а "Canvas" - на третьому шарі.

Перевірте, що кожен шар пов'язаний з першим, у якому кожен клас має батьківський клас ієрархії "Загальна".

Реалізація коду може зажадати використання будь-якого, успадкування інтерфейсу, успадкування класів або "mixins", залежно від того, що підтримує ваша мова програмування.

Підсумок

Як і будь-яке рішення програмування, не поспішайте з оптимізацією, оптимізація дуже важлива, проте, погана оптимізація може стати більшою проблемою, ніж початкова проблема.

Я не рекомендую застосовувати ні "Рішення 1", ні "Рішення 2".

У "Рішення 1" це не застосовується, оскільки спадкування потрібно в кожному випадку.

"Рішення 2", "Міксин" можуть бути застосовані, але після проектування класів та ієрархій.

Міксини - це альтернатива інтерфейсу для спадкування або множинного успадкування на основі класу.

Моє запропоноване рішення 3 іноді називається шаблоном дизайну "Паралельна ієрархія" або "Дизайн подвійної ієрархії".

Багато розробників / дизайнерів не згодні з цим, і вважають, що воно не повинно існувати. Але я використовував miself та інші розробники як загальне рішення проблем, як те, що стосується вашого питання.

Ще одна відсутність. У попередніх рішеннях головна проблема полягала не в тому, щоб використовувати "mixins" або "інтерфейси", а, щоб уточнити спочатку модель своїх класів, а пізніше використовувати існуючу функцію мови програмування.


Дякую за дуже ретельну відповідь. Я думаю, що я це добре зрозумів, тому дозвольте мені запитати це: canvas.vieweeі всі його нащадки потребують методу під назвою paint(). Ні класам, commonні svgкласам це не потрібно. Але у вашому рішенні є ієрархія common, canvasчи не так, svgяк у моєму рішенні 2. Отже, як саме paint()опиняється у всіх підкласах, canvas.vieweeякщо немає спадщини там?
Іжакі

@Izhaki Вибачте, якщо у моїй відповіді є кілька помилок. Потім paint()слід перемістити або оголосити в "canvas :: viewee". Загальна ідея шаблону залишається, але деякі члени можуть вимагати переміщення або зміни.
umlcat

Гаразд, так як підкласи отримують його, якщо жоден не походить від canvas::viewee?
Іжакі

Ви використовували інструмент для створення свого мистецтва ascii? (Я не впевнений, що точки справді допомагають, для чого це варто.)
Аарон Холл

1

У статті під назвою « Шаблони дизайну для боротьби з ієрархіями подвійної спадщини в C ++» дядько Боб представляє рішення під назвою « Сходи до неба» . Це заявлено про наміри:

Ця закономірність описує мережу відносин успадкування, яка необхідна, коли дану ієрархію необхідно повністю адаптувати до іншого класу.

Діаграма надана:

Діаграма класів з двома паралельними структурами успадкування, де кожен клас праворуч також практично притаманний своєму класу-побратиму зліва.  Ліва ієрархія також повністю заснована на віртуальному успадкуванні

Хоча у рішенні 2 немає віртуального успадкування, це дуже відповідає схемі " Сходи до неба" . Таким чином, рішення 2 видається розумним для цієї проблеми.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.