2015-12-31 3 views
0

У меня есть то, что я считаю довольно распространенной проблемой. Существует интерфейс под названием IService и некоторые производные классы, которые реализуют этот интерфейс; ServiceA, ServiceB, ServiceC и ServiceD.Интерфейсы проектирования

ServiceA и ServiceB нуждается в функции под названием getSomeType(), но эта функция не требуется в других производных классах. Кроме того, ServiceD нуждается в другой функции, которая не может быть использована для других производных классов. Как мне решить эту проблему? Я считаю, что использование dynamic_cast - это неправильный способ сделать это, или это так? Я также подумал о создании нового интерфейса, чтобы ServiceA и ServiceB реализовали два интерфейса.

class IService 
{ 
public: 
    virtual IService() {}; 

    virtual void start() = 0; 

    virtual void stop() = 0; 
}; 

class ServiceA : public IService 
{ 
public: 
    void start() override; 

    void stop() override; 

    ISomeType * getSomeType(); 
}; 
+0

Кастинг - это знак того, что ваш дизайн выключен; вы не можете использовать полиморфизм в полной мере с проверками «if/else». Лучше разложить его на два ортогональных интерфейса. – duffymo

+3

Вы знаете, что C++ поддерживает множественное наследование, поэтому вы можете наследовать класс из нескольких базовых (интерфейсных) классов. –

+3

Вся идея этого OO заключается в том, что пользователь не знает и не заботится о том, какой из конкретных классов стоит за интерфейсом. –

ответ

2

Выведите IServiceAB из IService, черпает ServiceA и ServiceB из IServiceAB, и выведет обслужен из IService.

Другой способ - это также взглянуть на узор декоратора (шаблоны проектирования).

+0

На самом деле это не устраняет основную проблему «код должен знать, какой тип службы он имеет дело с» –

+0

Я не вижу этого требования в OP, но это можно устранить, добавив чистый виртуальный getServiceType в IService , – mikedu95

0

Важнейшим инструментом в вашем арсенале дизайна является так называемый «принцип замещения». Когда вы используете публичное наследование с полиморфизмом (btw, в мире C++ мы обычно не говорим «реализуем интерфейс», так как интерфейс не является термином C++), вы утверждаете так называемый принцип IS-A - то есть вы утверждаете, что для внешнего наблюдателя ServiceA является, во всех смыслах и целях, IService. Не больше, не меньше. Все, что существует в IService, существует в ServiceA. В IService существует все (видимое снаружи), существующее в ServiceA.

Всякий раз, когда вы чувствуете необходимость делать dynamic_cast, вы явно нарушаете этот принцип - потому что это означает, что IService больше не является ServiceA, вам нужно специально на него наброситься.

Решение. Не используйте публичное наследование с полиморфизмом для классовости, которые не следуют принципу IS-A. В вашем случае ServiceA - не IService, что означает, что IService не является подходящим базовым классом для ServiceA.

0

Это часто сложная проблема. Если вместо службы мы говорим, что имеем дело с животными - потому что мы можем использовать это как прокси-сервер для «вещей, которые могут что-то делать, а не других вещей». Скажем, у нас есть животные Fish, Bird, Cow, Dog и Cat.

Итак, мы думаем о вещах животных в целом можно сделать:

  • Move - по-разному, например, Swim, Fly, Walk
  • шуметь - называют это Talk
  • имеют свойства, такие как длина, количество ног, вес и т.д.
  • Некоторые животные могут быть обучены «делать вещи» (собака может принести, попугай может «говорить» по требованию),
  • Корова может производить молоко, но (для практических целей), ни одна из других.

Итак, мы имеем, например Fish, который, очевидно, может swim, но не может talk, которую Dog или Bird может сделать. A Bird может летать, но не может Swim [да, есть отличные плавающие птицы, я упрощаю реальность].A Dog может создавать звуки, но, безусловно, не может летать. A Cat может издавать звуки, плавать (неохотно), но не сможет Fetch.

Таким образом, становится проблемой того, как мы представляем эту способность делать определенные вещи, но не другие вещи.

Есть несколько решений, но в конечном счете, есть два возможных решения

  • Фиктивные функции, которые ничего (например, пустая функция) не делают для тех функций, которые не применимы: Talk на Fish или Swim на Bird.
  • Условный код - например, функция CanSwim, которая отвечает за то, может ли животное плавать или нет. В этом случае функция Swim может выдать исключение, если оно все еще вызывается.

В конечном счете, где-то мы должны знать, что они разные виды животных, а некоторые могут делать что-то, другие не могут. Либо просить плыть плавать не удастся (и не должно произойти), либо «ничего не сделает». Вы действительно должны определить, что правильно делать.

У меня есть эта проблема в моем проекте компилятора. У меня есть «абстрактное синтаксическое дерево», которое представляет исходный код в анализируемой форме. Таким образом, он имеет узлы для объявлений переменных, деклараций функций, назначений, двоичных операций, while-loops, for-loops и т. Д. И т. Д. Они имеют один и тот же базовый интерфейс, который обеспечивает функцию CodeGen, которая фактически делает (промежуточное представление) - так двоичное выражение a + b будет генерировать код для загрузки a, загрузить b, а затем добавить два вместе. Codegen для функции вызовет codegen для тела функции и т. Д.

Есть полдюжины других функций общего назначения, которые применяются к (почти) всем записям AST. Но есть несколько операций, которые действительно имеют смысл в конкретной ситуации, например, при работе с бинарными операторами, строки не поддерживаются промежуточным представлением как add для конкатенации. Так что нельзя просто загружать, загружать и добавлять - нужно вызвать функцию strcat. Для этих особых случаев я использую dynamic_cast, чтобы проверить, соответствует ли тип в этом случае типу строки, и если да, то попадайте в кодовый путь специальной операции. Я мог бы добавить функцию isString для ВСЕХ типов, но это было бы довольно неудобно и не очень полезно, так как МОСТ времени, это не важно. Я не думаю, что это отличное решение.

+0

Итак, если бы вы могли спроектировать свою систему с самого начала, вы бы выбрали текущее дизайнерское решение? – 0xBADF00

+0

Короче, да, я все еще думаю, что это правильно, в моем случае. В конечном счете, все сводится к тому, как часто вам нужно «специальное лечение» - и сколько других классов нужно «страдать» от дополнительных функций, которые на самом деле не требуются. Мой АСТ состоит из 45 классов. Всего существует 53 'dyn_cast' [и 28' isa'].И вам все равно нужно обрабатывать такие вещи, как «нам нужно принять адрес этого выражения, поэтому' 4' или 'sin (1.5)' не является допустимым выражением здесь »каким-то образом. Конечно, я могу добавить 'isAddressable' или попытаться взять адрес и посмотреть, равен ли он 0. –

+0

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

Смежные вопросы