2010-12-02 4 views
11

Я столкнулся с этой проблемой через коллегу сегодня. У него была конструкция для передней системы, которая выглядит следующим образом:OOP против проблемы с макросом

class LWindow 
{ 
    //Interface for common methods to Windows 
}; 

class LListBox : public LWindow 
{ 
    //Do not override methods in LWindow. 
    //Interface for List specific stuff 
} 

class LComboBox : public LWindow{} //So on 

Система Window должна работать на нескольких платформах. Предположим, что на данный момент мы нацелены на Windows и Linux. Для Windows у нас есть реализация для интерфейса в LWindow. И у нас есть несколько реализаций для всех LListBox Э.С., LComboBox эсов и т.д. Моя реакция была пройти LWindow* (объект реализацию) в к базовым LWindow классу, так что он может сделать это:

void LWindow::Move(int x, int y) 
{ 
    p_Impl->Move(x, y); //Impl is an LWindow* 
} 

И сделать то же самое вещь для реализации LListBox и т. д.

Исходное решение было сильно отличающимся. Оно сводилось к следующему:

#define WindowsCommonImpl {//Set of overrides for LWindow methods} 

class WinListBox : public LListBox 
{ 
    WindowsCommonImpl  //The overrides for methods in LWindow will get pasted here. 
    //LListBox overrides 
} 

//So on 

Теперь, прочитав все о макросы быть злыми и передовой практики дизайна, я сразу же был против этой схемы. В конце концов, это дублирование кода. Но я не мог убедить своего коллегу в этом. И я был удивлен, что это так. Итак, я задаю вам этот вопрос. Каковы возможные проблемы последнего метода? Я бы хотел получить практические ответы. Мне нужно убедить кого-то, кто очень практичен (и привык делать такие вещи. Он упомянул, что в MFC много макросов!), Что это плохо (и я). Не научить его эстетике. Кроме того, что-то не так с тем, что я предложил? Если да, то как мне его улучшить? Благодарю.

EDIT: Пожалуйста, дайте мне несколько причин, так что я могу чувствовать себя хорошо о себе поддержку полокоть :(

Переход на Баунти Пожалуйста, спросите, если вам нужны разъяснения, я хочу знать аргументы за и против ООП против макроса.. :)

+4

Я никогда не думал, что ООП против Macro будет название один день – Chubsdad 2010-12-02 10:53:21

+0

@Chubsdad: Что делать, баловать меня и показать мне свет. – nakiya 2010-12-02 11:10:10

+0

Хорошие вопросы. BTW, для того, что сделано в последнем примере с препроцессором, D имеет замену, называемую «mix mixins» .. и у C# тоже есть что-то похожее, не так ли? Это не только вопрос, связанный с C++. – Kos 2010-12-08 16:06:14

ответ

2

Ваш коллега, вероятно, думает о макросах карты сообщений MFC; они используются в важных местах в каждом производном классе MFC, поэтому я могу видеть, откуда приходит ваш коллега. Однако это не для реализации интерфейсов, а скорее для деталей, взаимодействующих с остальной ОС Windows.

В частности, эти макросы реализуют часть системы насосов сообщений Windows, где «сообщения», представляющие запросы для классов MFC для работы, направляются на правильные функции обработчика (например, сопоставление сообщений с обработчиками). Если у вас есть доступ к визуальной студии, вы увидите, что эти макросы завершают записи в карте сообщений в несколько сложном массиве структур (который код вызывающей ОС знает, как читать) и предоставляют функции для доступа к этой карте.

Как пользователи MFC, макросистема делает этот вид чистым для нас. Но это работает главным образом потому, что базовый Windows API хорошо определен и не будет сильно изменяться, и большая часть макрокода создается с помощью IDE, чтобы избежать опечаток. Если вам нужно реализовать что-то, что связано с беспорядочными объявлениями, тогда макросы могут иметь смысл, но пока это, похоже, не так.

Практические проблемы, что ваш коллега может быть интересно:

  • дублированные макровызовов. Похоже, вам понадобится скопировать строку «WindowsCommonImpl» в каждое объявление класса - если макрос расширяется до некоторых встроенных функций. Если они только декларации, а реализации идут в отдельный макрос, вам также нужно будет сделать это в каждом .cpp-файле - и каждый раз меняйте имя класса, переданное в макрос.
  • больше времени перекомпиляции. Для вашего решения, если вы что-то измените в реализации LWindow, вам, вероятно, потребуется перекомпилировать LWindow.cpp. Если вы что-то измените в макросе, все, что включает файл заголовка макроса, необходимо перекомпилировать, что, вероятно, является вашим проектом.
  • сложнее отладить. Если ошибка связана с логикой макроса, отладчик, вероятно, перейдет к вызывающему, где вы не увидите ошибку сразу. Возможно, вы даже не подумали проверить определение макроса, потому что считали, что знаете точно, что он сделал.

Таким образом, в основном ваше решение LWindow является лучшим решением для минимизации головных болей по дороге.

2

Ответы на ваш вопрос напрямую не могут быть, но не могут помочь вам сообщить о том, что вы читаете на схеме дизайна моста в GOF. Это означало именно для этого.

расцепить абстракция от его реализации , так что эти два могут изменяться независимо.

Из того, что я могу понять, вы уже на правильном пути, кроме материала MACRO.

Моя реакция была пройти LWindow * (объект реализации) в к классу базовой LWindow так что он может сделать это:

+0

+1 Для обозначения моста в GOF. Он использует этот точный пример – Jon 2010-12-10 16:17:27

2

LListBox и LComboBox должен получить экземпляр WindowsCommonImpl.

В первом решении наследование используется так, что LListBox и LComboBox могут использовать некоторые распространенные методы. Однако наследование не предназначено для этого.

+2

-1 Что? Итак, наследование не предназначено для повторного использования кода? – 2010-12-02 10:53:54

+6

@Let_Me_Be: Наследование обычно не предназначено для использования в повторном использовании кода. Повторное использование кода является случайным. Использование наследования только для повторного использования кода (особенно для публичного наследования) является злом. Наследование состоит в том, чтобы обеспечить разные типы поведения для общего интерфейса. – Chubsdad 2010-12-02 10:58:00

0

О, мужчина, это сбивает с толку.

ОК, поэтому L *** - это иерархия интерфейсов, это нормально. Теперь, для чего вы используете p_Impl для, если у вас есть интерфейс, почему вы включили в него реализацию?

Материал макросов, конечно, уродливый, плюс его обычно невозможно сделать. Все дело в том, что у вас будут разные реализации, если вы этого не сделаете, то зачем создавать несколько классов в первую очередь?

0

OP кажется смущенным. Здесь «что делать, это очень сложно, но это работает.

Правило 1: Создайте абстракции. Если у вас есть отношение «is-A», вы: должно быть использовать общественное виртуальное наследование.

struct Window { .. }; 
struct ListBox : virtual Window { .. }; 

Правило 2: Сделайте реализации, если вы реализуете абстракцию вы должны использовать виртуальное наследование. Вы можете использовать наследование для сохранения дублирования.

class WindowImpl : virtual Window { .. }; 
class BasicListBoxImpl : virtual ListBox, public WindowImpl { .. }; 
class FancyListBoxImpl : public BasicListBoxImpl { }; 

Поэтому вы должны читать «виртуальный» означает «Isa» и другое наследство только экономить на методах перезаписи.

Правило 3: Попробуйте убедиться, что в конкретном типе есть только одна полезная функция: конструктор. Иногда это сложно, вам могут потребоваться некоторые настройки по умолчанию и некоторые заданные методы, чтобы что-то менять. Как только объект настроен отбросить реализацию. В идеале вы могли бы сделать это на строительстве:

ListBox *p = new FancyListBoxImpl (.....); 

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

Правило 4: Существует стандартная реализация для многих абстракций, известный как делегации которая является одним вы говорили:

struct Abstract { virtual void method()=0; }; 

struct AbstractImpl_Delegate: virtual Abstract { 
    Abstract *p; 
    AbstractImpl_Delegate (Abstract *q) : p(q) {} 
    void method() { p->method(); } 
}; 

Это мило реализация, так как он не требует, чтобы вы знали что-нибудь об абстракции или о том, как ее реализовать ... :)

1

Я согласен с вами. Решение с WindowsCommonImpl макросом действительно плохо. Он подвержен ошибкам, трудно распространяться и очень трудно отлаживать. MFC - хороший пример того, как вы должны не дизайн вашей библиотеки окон. Если это похоже на MFC, вы действительно ошибаетесь.

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

В любом случае, похоже, проблема, которую вы пытаетесь решить, заключается в том, как совместить наследование интерфейса с наследованием реализации на C++. Я бы предложил использовать шаблонный класс для реализации окна.

// Window interface 
class LWindow 
{ 
}; 

// ListBox interface (inherits Window interface) 
class LListBox : public LWindow 
{ 
};  

// Window implementation template 
template<class Interface> 
class WindowImpl : public Interface 
{ 
}; 

// Window implementation 
typedef WindowImpl<LWindow> Window; 

// ListBox implementation 
// (inherits both Window implementation and Window interface) 
class ListBox : public WindowImpl<LListBox> 
{ 
}; 

Как я помню, библиотека Windows WTL основана на аналогичной схеме объединения интерфейсов и реализаций. Я надеюсь, что это помогает.

0

Я обнаружил, что

Использования препроцессор #define директивы определения констант являются не так точно.

[src]

Макросы, по-видимому, не так точно, я даже не знаю, что ... Классические скрытые опасности препроцессором как:

#define PI_PLUS_ONE (3.14 + 1)` 

Поступая таким образом, вы исключаете вероятность , что порядок выдачи операций будет уничтожить смысл вашей постоянной:

x = PI_PLUS_ONE * 5;` 

Без скобок вышеуказанный будет преобразован в

x = 3.14 + 1 * 5; 

[src]

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