Форум программистов, компьютерный форум CyberForum.ru

Проектирование ОО архитектуры - C++

Восстановить пароль Регистрация
 
Bers
Заблокирован
18.10.2011, 14:32     Проектирование ОО архитектуры #1
Интересно мнение публики. "Программирование в терминах интерфейсов"
Вопрос такой: как правильно конструировать едино-образный интерфейс?

По аналогии с контейнерами STL - все они по разному решают задачи, но для удобства пользователя имеют единообразный интерфейс (что может предполагать некое родство между ними, а может и не предполагать)

Допустим в рамках одной архитектуры понадобилось создать несколько не связанных между собой сущностей (классов), но очень хочется выдержать единообразный интерфейс управления ими.

Причем, некоторые методы классов абсолютно идентичны (и по прототипу, и по реализации), некоторые идентичны по прототипу, но с уникальной реализацией. Некоторые вообще - свои собственные:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
class SomeClass1
{
public:
   void CommonMethod() {}   //идентичен методам других классов
   void UniqueMedthod() {}    //уникальная реализация
}
 
class SomeClass2
{
public:
   void CommonMethod() {}   //идентичен методам других классов
   void UniqueMedthod() {}    //уникальная реализация
}
Каким образом можно добиться единообразности интерфейсов, при условии, что некоторые методы должны будут делать нечто уникальное. И даже те методы, которые изначально планировались как дефолтные для всех классов, в последствии для отдельных классов нужно будит переделывать, заменяя их реализацию.

1 Способ: Copy-Paste

Тупо копипастим прототипы по всем классам. Где нужно - копипастим и реализации. Где нужно - пишем ручками что-то своё.

Достоинства:
Быстро и просто.
Классы получаются абсолютно никак не связанны друг с другом. Можно делать любые модификации одного класса, и они никак не отразятся на других классах.

Недостатки:
Если выяснится, что для всех классов метод CommonMethod(); должен начать работать как то иначе (а такое случается сплошь и рядом, особенно, если в нем созрел ужасссссный баг) - придется придется заного все перекопипастивать, и пере тестировать.
Кроме того - дублирование существующего кода.

2 Способ: полиморфизм.

Можно просто унаследоваться от интерфейса, и переопределить реализацию тех методов, которые у клиента должны иметь уникальную реализацию. А можно разработать целую иерархию интерфейсов и микшировать их для отдельных клиентов-потомков при помощи наследования.

Достоинства: относительно простое и по своему элегантное решение. Классы-клиенты связаны некоторыми семейными узами. Что позволяет менять поведение объекта "на лету", и тп.
Кроме того, появляется возможность пользоваться "дефолтным интерфейсом".

Недостатки: потери производительности. Там, где нет необходимости воспринимать целое семейство объектов, как одну сущность, и где нет необходимости реализовывать различное поведение объекта "на лету" - полиморф пожалуй... не вполне подходит по смыслу...

3. Шаблоны.

В общем виде, это делается так:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <class Client> struct TData {};   //объявляем данные-члены будущего класса-клиента
 
template <class Client> 
class TInterface: public TData<Client> { ...  }; //объявляем интерфейс будущих клиентов
 
 
class ConcreteClient; //предварительное объявление
 
template <>
struct TData<ConcreteClient> {}; //уточняем данные члены для конкретного класса-клиента
 
//если нужно уточняем отдельные методы интерфейса для конкретного класса-клиента
 
class ConcreteClient: public TInterface<ConcreteClient> { }; //подключаем интерфейс к клиенту
Таким образом, класс клиент получит свой интерфейс, и свои данные-члены.

Недостатки:Архитектура с шаблонами значительно усложняется.
Кроме того, если класс-клиент сам является шаблоном, начнутся серьёзные траблы. Потому что нельзя выполнить частичную специализацию отдельного метода. И тогда придётся городить обходные пути. Все это в конечном счете настолько может усложнить понимание архитектуры, что начинают приходить крамольные мысли в голову: может быть копипаста - не такое уж зло?

Недостатки: иногда проще скопипастить прототип с реализацией, чем копипастить этот же самый прототип с этой же самой реализацией и ещё уродливую километровую шапку шаблона в придачу.

Достоинства: эффект как от полиморфизма, только нет потерь производительности.

Шаблоны великолепная костыле-забивалка. Через задний проход позволяют извратить любую архитектуру до неузнаваемости. Но зато, если очень нужно можно всегда переиначить что угодно и как угодно. Так же, позволяют юзать дефолтный интерфейс, а уточнять только те методы, которые для клиента должны быть уникальными.

4. Препроцессор.

Делается это так:

C++
1
2
3
4
class Client
{
        #include "IClient.h" //подключаем интерфейс клиента
};
Внутри файла "IClient.h" содержится:

C++
1
2
3
#include "CommonInterface.h" //подключаем идентичные для всех клиентов методы
 
//ниже идёт перечень всех уникальных методов
Достоинства: Очень простой и элегантный по своему способ. Позволяет без каких бы то ни было особых ухищрений и дублирования кода добиться эффекта "копипасты", но с той разницей. Что если что то где-то нужно будит поменять - менять придётся только в одном месте, а не во всех файлах.

Недостатки: При таком подходе у меня в студии ассистент тупить начал. А без него работать уже не комфортно.

Недостатки: Получается, что файлы cpp уже и не нужны. Вся реализация классов оказывается в хэдерах. Это может плохо сказаться на скорости компиляции, и на сокрытии данных.

Главный недостаток: если вдруг окажется, что клиенту больше не нужен "идентичный как у всех метод", и нужно будит сделать его уникальным, то попытка отвязать класс от commonInterface.h может доставить таких лулзов, что шаблоны сказкой покажутся.

Когда инклюд начинает сидеть на инклюде, и все это инклюдится в 100500 классов - начинаешь в такой архитектуре быстро путаться. Получается каша из классов. Многие классы оказываются "как то странно друг с другом связанны".

Нельзя будит никак менять те интерфейсные методы, которые уже внедрены во множество других классов, из-за боязни их отказа.

Это способы конструирования единообразного интерфейса, которые я знаю. А как делаете вы?
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
oxotnik
 Аватар для oxotnik
1584 / 1061 / 33
Регистрация: 21.08.2008
Сообщений: 4,545
Записей в блоге: 1
18.10.2011, 14:39     Проектирование ОО архитектуры #2
Цитата Сообщение от Bers Посмотреть сообщение
А как делаете вы?
2-й способ самый понятный и логичный, а что касается производительности, то... как то не замечал
ЗЫ: 4-й метод смахивает на идусизм и быдлокод
Mr.X
Эксперт С++
 Аватар для Mr.X
2807 / 1583 / 248
Регистрация: 03.05.2010
Сообщений: 3,693
18.10.2011, 14:44     Проектирование ОО архитектуры #3
1. Ну, поскольку вы ясно не сформулировали задачу, т.е. для чего именно вам нужен схожий интерфейс не связанных между собой классов, то и ясный ответ сформулировать невозможно.
2. В качестве примера вы привели контейнеры STL, и в то же время обобщенное программирование не рассматриваете в качестве варианта.
Kastaneda
18.10.2011, 14:44
  #4

Не по теме:

Цитата Сообщение от oxotnik Посмотреть сообщение
ЗЫ: 4-й метод смахивает на идусизм и быдлокод
В духе PHP

Bers
Заблокирован
18.10.2011, 14:47  [ТС]     Проектирование ОО архитектуры #5
Цитата Сообщение от oxotnik Посмотреть сообщение
2-й способ самый понятный и логичный, а что касается производительности, то... как то не замечал
ЗЫ: 4-й метод смахивает на идусизм и быдлокод
А вы можете подробнее раскрыть этот момент: почему смахивает на быдлокод?
oxotnik
 Аватар для oxotnik
1584 / 1061 / 33
Регистрация: 21.08.2008
Сообщений: 4,545
Записей в блоге: 1
18.10.2011, 14:56     Проектирование ОО архитектуры #6
Цитата Сообщение от Bers Посмотреть сообщение
А вы можете подробнее раскрыть этот момент: почему смахивает на быдлокод?
ну я так представил, что мне подобный код от кого нибудь достанется, брр.. мое имхо было бы при взгляде на это, что автор не владеет принципами ООП, либо их намеренно игнорит, дабы запутать. К стати обфускаторы примерно по такому принципу работают, только больше на дефайнах.
Bers
Заблокирован
18.10.2011, 15:45  [ТС]     Проектирование ОО архитектуры #7
допустим есть два контейнера:

TGameContainer<T>
TContainer2D<T>

Оба контейнеры, но служат для разных целей. Выполняют их по разному, имеют разные данные-члены.

Игровой контейнер - пул для игровых объектов, а контейнер2д - тот же вектор, только позволяет сделать имитацию, якобы элементы хранятся в двух мерной матрице, а не просто в каком нибудь векторе.

Несмотря на значительные отличия, для удобства пользования хочется сделать им максимально схожий интерфейс. Оба они имеют метод GetElement(); или метод Clear(); или метод Resize();

Есть шаблонный метод: ConvertArg() который на входе может принимать число, символ, строку (в общем на то он и шаблон), а на выходе рожать стринг. И этот метод нужен обоим контейнерам, и он блин, абсолютно одинаковый что там, что там.

Как сделать лучше?

Полиморфизм использовать там, где он не нужен в рантайме - это мне видится фейлом.

Шаблоны - у меня классы-клиенты через одного шаблоны. Я уже экспериментировал - чуть мозг себе не спалил. Там все либо резко усложняется, либо ведёт к хардкорной копипасте.

Остаётся либо копипаста, либо препроцессор.

Думаю препроцессор лучше сразу исключить.

Остаётся копипаста?
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
18.10.2011, 16:32     Проектирование ОО архитектуры #8
Bers, именно для таких вещей в джаве и шарпе используются интерфейсы. Поскольку в чистом виде в плюсах их нет, то надо использовать абстрактные базовые классы (которые по сути своей те же интерфейсы, только возможностей у них чуть больше).
А вообще, там, где нужна реальная производительность, забивают на ООП и пишут на структурных языках. Если вам действительно нужна производительность - так и поступите. Если же вы о производительности говорите лишь постольку-поскольку - забейте и используйте полиморфизм.
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
18.10.2011, 16:52     Проектирование ОО архитектуры #9
Цитата Сообщение от Bers Посмотреть сообщение
Недостатки: потери производительности. Там, где нет необходимости воспринимать целое семейство объектов, как одну сущность, и где нет необходимости реализовывать различное поведение объекта "на лету" - полиморф пожалуй... не вполне подходит по смыслу...
Потери производительности не будет в этом случае. Если полиморфного использования нет, то компилятор может очень эффективный код генерировать. Даже при наличии полиморфизма, в некоторых случаях удаётся встраивать виртуальные функции. При использовании наследования без полиморфного использования у компилятора очень много возможностей для оптимизации.
И вообще, до первого запуска ты точно сказать не сможешь, "медленно или быстро".)
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
18.10.2011, 16:54     Проектирование ОО архитектуры #10
Цитата Сообщение от Deviaphan Посмотреть сообщение
до первого запуска ты точно сказать не сможешь, "медленно или быстро"
А зачастую и после него)) Было бы с чем сравнивать))
Bers
Заблокирован
18.10.2011, 16:54  [ТС]     Проектирование ОО архитектуры #11
Цитата Сообщение от silent_1991 Посмотреть сообщение
Bers, именно для таких вещей в джаве и шарпе используются интерфейсы. Поскольку в чистом виде в плюсах их нет, то надо использовать абстрактные базовые классы (которые по сути своей те же интерфейсы, только возможностей у них чуть больше).
А вообще, там, где нужна реальная производительность, забивают на ООП и пишут на структурных языках. Если вам действительно нужна производительность - так и поступите. Если же вы о производительности говорите лишь постольку-поскольку - забейте и используйте полиморфизм.
Ладно хорошо. Допустим, я забил на производительность. Но я же не могу забить на здравый смысл.

Если в рантайме не требуется полиморфизм?
Что из этого вытекает? Из этого вытекает, что есть несколько методов с идентичными реализациями, и есть кучка одноименных методов с уникальными реализациями.

Что значит переопределить виртуальный метод?
Это значит:
1. КОПИПАСТА ЕГО ПРОТОТИПА.
2. В ручную пишется реализация этого метода.

То есть, если я сделаю класс, в который закидаю все "гарантированно идентичное".
От него унаследуюсь. А в потомке тупо копипастой пропишу прототипы методов, и сделаю ручками их реализации, я получу абсолютно тоже самое, как если бы делал полиморф, с единственной разницей - не смогу пользоваться механизмом полиморфа в рантайме (который по задаче мне и не нужен ). И не буду нести потерь производительности.

Другими словами, полиморф предназначен именно для тех случаев, когда целое семейство воспринимается как некоторая единая сущность.
Но от копипасты он никак не избавляет. Все, что вы хотите сделать уникальным в плане реализации - та же самая хардкорная копипаста прототипов методов, и ручная их реализация.

Я же хотел избавиться от необходимости каждый раз в новом классе заново копипастить весь набор прототипов, которые я хочу сделать уникальным.
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
18.10.2011, 16:58     Проектирование ОО архитектуры #12
Цитата Сообщение от Bers Посмотреть сообщение
Полиморфизм использовать там, где он не нужен в рантайме - это мне видится фейлом.
Наследование и полиморфизм это не одно и то же. Наличие виртуальных функций не обязывает использовать полиморфизм.

Добавлено через 41 секунду
Цитата Сообщение от silent_1991 Посмотреть сообщение
А зачастую и после него)) Было бы с чем сравнивать))
С ощущениями и мировозрением.)
Жопой чую, медленно работает! Как-то так.)

Добавлено через 2 минуты
Цитата Сообщение от Bers Посмотреть сообщение
Я же хотел избавиться от необходимости каждый раз в новом классе заново копипастить весь набор прототипов, которые я хочу сделать уникальным.
Но уникальную реализацию всё равно придётся писать. А это "несколько дольше".
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
18.10.2011, 16:58     Проектирование ОО архитектуры #13
Bers, что-то вы всё диким образом утрируете... копипаста прототипов... таким макаром можно дойти и до того, что в коде слишком много повторяющихся символов, и что это тоже копипаста...
Bers
Заблокирован
18.10.2011, 17:03  [ТС]     Проектирование ОО архитектуры #14
Цитата Сообщение от silent_1991 Посмотреть сообщение
Bers, что-то вы всё диким образом утрируете... копипаста прототипов... таким макаром можно дойти и до того, что в коде слишком много повторяющихся символов, и что это тоже копипаста...
Вы хотите сказать, что копипастить реализации - это не хорошо. Но копипастить просто прототипы, и писать уникальную их реализацию не возброняеццо?

И что это лучше, чем извращаться с препроцессором и шаблонами?

(а то у меня тут в арсенале есть ещё одна мыслишка, как организовать имитацию частичной специализации отдельного метода шаблонного класса с помощью наследования + препроцессор. Но чота у меня такое лёгкое подозрение, шо это уже паранойя)
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
18.10.2011, 17:07     Проектирование ОО архитектуры #15
Bers, я же вам сказал, что в других языках (которые по сути ближе к ООП, чем плюсы) для этого выделен даже специальный механизм - интерфейсы. Специальный-то он специальный, но по сути своей это те же базовые абстрактные классы. Так что можете смело использовать их - не возбраняеццо.
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
18.10.2011, 17:35     Проектирование ОО архитектуры #16
Цитата Сообщение от Bers Посмотреть сообщение
Вы хотите сказать, что копипастить реализации - это не хорошо.
Как можно копипастить уникальную реализацию? Если различаются только типы, то используй шаблоны, если отличается алгоритм, используй наследование или независимые классы.
В разумных пределах можно и макросами методы заменить, но я бы не советовал. Microsoft так делает в MFC проектах, но там это обусловлено уменьшением вероятности ошибки при ручной копипасте.

Главное тут это понять, что разные, независимые классы это разные, независимые классы. То, что их реализации похожи это чистая случайность. Т.е. это даже нельзя назвать копипастой или повторением кода.
ForEveR
Модератор
Эксперт C++
 Аватар для ForEveR
7927 / 4709 / 318
Регистрация: 24.06.2010
Сообщений: 10,524
Завершенные тесты: 3
18.10.2011, 18:14     Проектирование ОО архитектуры #17
Паттерны юзай. Если есть несколько одинаковых методов но другие различаются - логично работать через паттерны поведения. Не?
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
18.10.2011, 18:18     Проектирование ОО архитектуры #18
Цитата Сообщение от ForEveR Посмотреть сообщение
логично работать через паттерны поведения.
Кстати да.
Можно дружественную шаблонную функцию сделать и передавать в неё объекты для выполнения работы. Никакого копипаста не будет.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
18.10.2011, 19:59     Проектирование ОО архитектуры
Еще ссылки по теме:

C++ Организация архитектуры движка
Написать курсовую по теме "Разработка архитектуры и компонентов системы и автоматизации автосервиса" C++
Предложения по изменению архитектуры C++

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
fasked
Эксперт C++
 Аватар для fasked
4925 / 2505 / 180
Регистрация: 07.10.2009
Сообщений: 4,306
Записей в блоге: 1
18.10.2011, 19:59     Проектирование ОО архитектуры #19
Цитата Сообщение от Bers Посмотреть сообщение
По аналогии с контейнерами STL - все они по разному решают задачи, но для удобства пользователя имеют единообразный интерфейс (что может предполагать некое родство между ними, а может и не предполагать)
Единообразный интерфейс не предполагает вообще каких-либо объектно-ориентированных прибамбасов. Единообразный интерфейс это просто схожие названия методов у классов. И делается для более легкого использования класса. То есть, если бы в одном контейнере использовался метод с именем at для доступ к элементу, а в другом контейнере - метод item, в третьем - element: выглядело бы это все достаточно хреново, чтобы не пользоваться этой библиотекой. При чем здесь наследование/полиморфизм/паттерны поведения я лично вообще не понимаю. Вопрос в идеологии кода. В конце концов конечному рядовому пользователю библиотеки вообще наплевать как это "внутри" устроено (до тех пор пока не встает вопрос производительности, расширяемости (если лицензия позволяет) и т.д.).
Yandex
Объявления
18.10.2011, 19:59     Проектирование ОО архитектуры
Ответ Создать тему
Опции темы

Текущее время: 00:05. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru