Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > IT форум > Общие вопросы по программированию, компьютерный форум
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 12.06.2009, 16:47   #1
atomicxp
Форумчанин
 
Аватар для atomicxp
 
Регистрация: 01.05.2009
Сообщений: 110
Вопрос Описание и обсуждение шаблонов проектирования (паттерны)

Как известно для проектирования ООП систем разумно использовать шаблоны проектирования. Существует ряд правил, одно из которых было описано в книге "Совершенный код", например, группировать не более семи элементов в одной сущности (классе).

Однако, помимо общих правил, есть разнообразные модели создания объектов. По сути это важно во всех языках использующих ООП, но меня это больше волнует по части C++ и применительно к кроссплатформенным библиотекам, таким как STL, Boost, GTK+, Qt SDK, WxWidgets и многих других.

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

* 1 Структурные паттерны проектирования классов/обьектов
o 1.1 Адаптер (Adapter) - GoF
o 1.2 Декоратор (Decorator) или Оболочка (Wrapper) - GoF
o 1.3 Заместитель (Proxy) или Суррогат (Surrogate) - GoF
o 1.4 Информационный эксперт (Information Expert)- GRASP
o 1.5 Компоновщик (Composite) - GoF
o 1.6 Мост (Bridge), Handle (описатель) или Тело (Body) - GoF
o 1.7 Низкая связанность (Low Coupling) - GRASP
o 1.8 Приспособленец (Flyweight) - GoF
o 1.9 Устойчивый к изменениям (Protected Variations) - GRASP
o 1.10 Фасад (Facade) - GoF
* 2 Паттерны проектирования поведения классов/обьектов
o 2.1 Интерпретатор (Interpreter ) - GoF
o 2.2 Итератор (Iterator) или Курсор (Cursor) - GoF
o 2.3 Команда (Command), Действие (Action) или Транзакция (Транзакция) - GoF
o 2.4 Наблюдатель (Observer), Опубликовать - подписаться (Publish - Subscribe) или Delegation Event Model - GoF
o 2.5 Не разговаривайте с неизвестными (Don't talk to strangers) - GRASP
o 2.6 Посетитель (Visitor) - GoF
o 2.7 Посредник (Mediator) - GoF
o 2.8 Состояние (State) - GoF
o 2.9 Стратегия (Strategy) - GoF
o 2.10 Хранитель (Memento) - GoF
o 2.11 Цепочка обязанностей (Chain of Responsibility) - GoF
o 2.12 Шаблонный метод (Template Method) - GoF
o 2.13 Высокое зацепление (High Cohesion) - GRASP
o 2.14 Контроллер (Controller) - GRASP
o 2.15 Полиморфизм (Polymorphism) - GRASP
o 2.16 Искусственный (Pure Fabrication) - GRASP
o 2.17 Перенаправление (Indirection) - GRASP
* 3 Порождающие паттерны проектирования
o 3.1 Абстрактная фабрика (Abstract Factory, Factory), др. название Инструментарий (Kit) - GoF
o 3.2 Одиночка (Singleton) - GoF
o 3.3 Прототип (Prototype) - GoF
o 3.4 Создатель экземпляров класса (Creator) - GRASP
o 3.5 Строитель (Builder) - GoF
o 3.6 (Фабричный метод) Factory Method или Виртуальный конструктор (Virtual Constructor) - GoF
atomicxp вне форума Ответить с цитированием
Старый 12.06.2009, 18:25   #2
atomicxp
Форумчанин
 
Аватар для atomicxp
 
Регистрация: 01.05.2009
Сообщений: 110
Вопрос

Первый шаблон проектирования для рассмотрения "Интерфейс" (категория: Основные шаблоны (Fundamental)). Следует так же отметить, что открытую часть класса так же называют интерфейсом, но речь пойдёт не о ней. В нашем случае интерфейс может иметь как закрытые, так и открытые члены, плюс некие комбинации, такие как защищённые. Интерфейс не содержит данные, его методы имеют полиморфную природу.

Код:
class InterfaceA
{
 public:
  virtual ma() = 0;
  virtual mb() = 0;
};
virtual указывает на возможность полиморфизма, а = 0 о том что метод имеет чисто абстрактную природу. Однако в вики видим следующую картину.

Код:
class A_Interface
{
 public:
  static A_Interface* create_A();
  virtual ~A_Interface() {}
  virtual do_something() = 0;
};
Код:
class A : public A_Interface
{
 public:
  do_something();
};
Наследование проходит как public, но помимо этого существует статическая функция возвращающая указатель на класс созданный по шаблону проектирования интерфейса. Лично мне это кажется странным, зачем создавать класс A внутри интерфейса. Полиморфизм через интерфейс может достигаться за счёт приведения типов, смысла плодить лишние методы нет.

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

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

Код:
class InterfaceA
{
 public:
  virtual void ma() = 0;
  virtual void mb() = 0;
};

class ClassA : public InterfaceA
{
    void ma()
    {
        cout << "ClassA: Method A" << endl;
    }
    void mb()
    {
        cout << "ClassA: Method B" << endl;
    }
};

class ClassB : public InterfaceA
{
    void ma()
    {
        cout << "ClassB: Method A" << endl;
    }
    void mb()
    {
        cout << "ClassB: Method B" << endl;
    }
};

int main()
{
    InterfaceA* interfaceA;

    ClassA* classA = new ClassA();
    interfaceA = classA;
    interfaceA->ma();
    interfaceA->mb();

    ClassB* classB = new ClassB();
    interfaceA = classB;
    interfaceA->ma();
    interfaceA->mb();

    return 0;
}
Вывод:
Код:
ClassA: Method A
ClassA: Method B
ClassB: Method A
ClassB: Method B

Process returned 0 (0x0)   execution time : 0.015 s
Press any key to continue.

Последний раз редактировалось Alex11223; 02.05.2019 в 11:28.
atomicxp вне форума Ответить с цитированием
Старый 12.06.2009, 19:06   #3
pu4koff
Старожил
 
Аватар для pu4koff
 
Регистрация: 22.05.2007
Сообщений: 9,520
По умолчанию

Цитата:
Сообщение от atomicxp Посмотреть сообщение
Первый шаблон проектирования для рассмотрения "Интерфейс" (категория: Основные шаблоны (Fundamental)).
Не знал, что это шаблон проектирования. Думал всегда, что это "основа" ООП, на ряду с классами. Ну да ладно в общем. Это не суть важно
Цитата:
Сообщение от atomicxp Посмотреть сообщение
В нашем случае интерфейс может иметь как закрытые, так и открытые члены, плюс некие комбинации, такие как защищённые.
Какой же это тогда интерфейс? Кроме того private методы перегрузить не удастся, а это уже будет значит абстрактный класс, а не интерфей.
protected в принципе еще (в случае с С++) допустим. Я посредством них шаблонные методы псевдо-виртуальными делаю
Код:
class IA
{
protected:
  virtual void foo(type_info const &type) = 0;
public:
  template < class T >
  void foo()
  {
    foo(typeid(T));
  }
};
Просто не хочется в коде потом писать все эти typeid. Вызываем как шаблон и получаем полиморфизм. Мне так просто больше нравится в общем
В языках с generic'ами (так сказать, шаблонами времени выполнения), которые позволяют "смастерить" виртуальные "шаблонные" методы, не вижу необходимости в protected методах интерфеев (разве что не хочется лишние сущности плодить и в интерфейс "служебные" методы классов можно "насовать", чтобы конечному пользователю они были не видны, но это всёже несколько нарушает принципы интерфейсов и делает их ближе к абстрактным классам).
Цитата:
Сообщение от atomicxp Посмотреть сообщение
Однако в вики видим следующую картину.
Я почему-то не нашел эту картинув вики и нашлись нормальные примеры. Ну да ладно.
Цитата:
Сообщение от atomicxp Посмотреть сообщение
Наследование проходит как public, но помимо этого существует статическая функция возвращающая указатель на класс созданный по шаблону проектирования интерфейса. Лично мне это кажется странным, зачем создавать класс A внутри интерфейса.
Чушь написал автор кода. Зачем-то на интерфей возложил задачу фабрики. Кроме того это "убивает" все возможные применения интерфея и делает его бесполезным.
Интерфей по определению не должен содержать никакой реализации, а соответственно никаких статиков быть не может. Даже константу в интерфей не стоит совать, не то, что реализовывать какой-то метод.

Цитата:
Сообщение от atomicxp Посмотреть сообщение
Простейший случай использования, который вероятно нужно усовершенствовать:
1. Вижу new и не вижу delete
2. Если я не ошибаюсь, то в случае отсутствия деструктора, он создаётся пустой, но не виртуальный. Соответственно, если удалить объект класса ClassA через указатель на интерфейс InterfaceA, то вызовется только деструктор интерфейса, а деструктор класса вызван не будет, соответственно возможно утечка памяти.
Таким образом, я бы в интерфейс добавил еще:
Код:
public:
virtual ~InterfaceA() {};

Последний раз редактировалось Alex11223; 02.05.2019 в 11:28.
pu4koff вне форума Ответить с цитированием
Старый 12.06.2009, 19:48   #4
atomicxp
Форумчанин
 
Аватар для atomicxp
 
Регистрация: 01.05.2009
Сообщений: 110
Хорошо

Цитата:
Сообщение от pu4koff Посмотреть сообщение
Какой же это тогда интерфейс? Кроме того private методы перегрузить не удастся, а это уже будет значит абстрактный класс, а не интерфей.
protected в принципе еще (в случае с С++) допустим. Я посредством них шаблонные методы псевдо-виртуальными делаю
Да, это я неправильно написал, надо внимательнее быть, в коде то такое невозможно написать если пишешь интерфейс, ну а на форуме пожалуйста. Вот отсюда был взят пример. Пожалуй следующим вопросом который надо рассмотреть это квалификаторы доступа классу, при наследовании класса, а так же целесообразность использования конструкторов и деструкторов, то есть нужно, не нужно. В следствии чего проверить предположение насчёт утечек памяти.
atomicxp вне форума Ответить с цитированием
Старый 12.06.2009, 21:12   #5
atomicxp
Форумчанин
 
Аватар для atomicxp
 
Регистрация: 01.05.2009
Сообщений: 110
Лампочка Виртуальные деструкторы в интерфейсе

Ну что ж, проблема виртуальных деструкторов рассмотрена. Конечно, данная концепция сильно отличается от C#, хотя при правильном использовании шаблоны проектирования сохраняют своё предназначение, и классам можно смело присваивать стереотип - Interface.

Так же следует отметить, что компилятор подсказывает о том, что нужно записать виртуальный деструктор.
Цитата:
E:\Work\Project\Interface\main.cpp| 6|warning: `class InterfaceA' has virtual functions but non-virtual destructor|
E:\Work\Project\Interface\main.cpp| 18|warning: `class ClassA' has virtual functions but non-virtual destructor|
Были протестированные различные варианты, где при помощи оператора delete вначале удалялся интерфейс, в другом случае класс использующий интерфейс, менялось использование методов, через класс и через интерфейс, и кое-какие другие тесты. Конечный вариант может выглядеть как-то так, хотя здесь нужны изменения.

Код:
#include <iostream>

using namespace std;

class InterfaceA
{
 public:
  virtual void ma() = 0;
  virtual void mb() = 0;
  //~InterfaceA(){ cout << "Desctruction InterfaceA" << endl; }
  // два варианта, виртуальный и не виртуальный деструктор
  virtual ~InterfaceA(){ cout << "Desctruction InterfaceA" << endl; }
  // в случае виртального деструктора при уничтожении интерфейса
  // вызовется уничтожение объекта от наследованного класса
};

class ClassA : public InterfaceA
{
public:
    ~ClassA(){ cout << "Desctruction ClassA" << endl; }
public:
    void ma()
    {
        cout << "ClassA: Method A" << endl;
    }
    void mb()
    {
        cout << "ClassA: Method B" << endl;
    }
};

void Testing(ClassA* classA)
{
    InterfaceA* interfaceA;

    interfaceA = classA;

    interfaceA->ma();
    interfaceA->mb();
}

int main()
{
    ClassA* classA = new ClassA();

    Testing(classA);

    classA->ma();
    classA->mb();

    delete classA;

    return 0;
}
Вывод:
Цитата:
ClassA: Method A
ClassA: Method B
ClassA: Method A
ClassA: Method B
Desctruction ClassA
Desctruction InterfaceA

Process returned 0 (0x0) execution time : 0.015 s
Press any key to continue.
В итоге виртуальный деструктор нужен, если кому-то придёт в голову удалить исходный объект через интерфейс. Хотя такое лучше не делать из-за множественного наследования и в следствии этого потери основного предназначение интерфейса.

Вопрос о квалификаторах доступа пока откладываю на потом в силу того, что для них нужны более совершенные примеры, а нужно ещё рассмотреть множество шаблонов проектирования и их использования применительно к C++ и кроссплатформенному программированию.
atomicxp вне форума Ответить с цитированием
Старый 13.06.2009, 00:18   #6
alexBlack
Участник клуба
 
Регистрация: 12.10.2007
Сообщений: 1,204
По умолчанию

А Вам не кажется, что пример несколько вырван из контекста и отсюда путаница.

Одно дело интерфейс как способ описания э... контракта, который должен выполнять некоторый класс и другое дело то понятие, которое рассматривается в статье:

Цитата:
В общем, интерфейс (как шаблон проектирования) — это класс, который обеспечивает программисту простой или более программно-специфический способ доступа к другим классам.
(курсив мой)

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

Исходя из этого и пример к этому шаблону должен быть сделан так, чтобы о классе ClassA упоминания не было.
alexBlack вне форума Ответить с цитированием
Старый 13.06.2009, 01:09   #7
atomicxp
Форумчанин
 
Аватар для atomicxp
 
Регистрация: 01.05.2009
Сообщений: 110
По умолчанию

Цитата:
Сообщение от alexBlack Посмотреть сообщение
В первом случае интерфейс не должен содержать реализации (но это и не шаблон проектирования). А второй позволяет скрыть реализацию класса, реализующего интерфейс (простите за повтор). Отсюда и включение фабрики в шаблон интерфейса. О чем ниже и говорит автор. То есть основное назначение - сокрытие реализации.
Для меня реализация шаблона проектирования интерфейса это прежде всего способ управления другими классами. Если он содержит что-то помимо чисто абстрактных методов, то это не интерфейс.

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

Во всех остальных случаях, на вроде статических методов, и прочих, их применение будет так же означать, что класс не может ассоциироваться с шаблоном проектирования интерфейса.
atomicxp вне форума Ответить с цитированием
Старый 13.06.2009, 09:21   #8
pu4koff
Старожил
 
Аватар для pu4koff
 
Регистрация: 22.05.2007
Сообщений: 9,520
По умолчанию

alexBlack, как интересно только будет выглядеть реализация этого фабричного метода? Класс интерфейса же абстрактный, соответственно создать его никак нельзя. Соответственно, будет строгая зависимость от какого-то другого класса, реализующего этот интерфейс. Таким образом, нет никакой необходимости в абстрактном классе интерфейса и почему бы его сразу не сделать обычным классом и создавать через вызов конструктора, а не через фабричный метод? Ну разве что для тестирования может пригодится (в фабричном методе возвращать какой-то тестовый класс для проверки работоспособности).
Мне кажется, было бы логичнее сделать этот фабричный метод вне класса, чтобы интерфейс уже не зависел от конкретной его реализации.
В общем, как я понял, любая классовая "обёртка" над всякими там API реализует данный паттерн. Тот же MFC получается что является интерфейсом для WinAPI
atomicxp, а почему нельзя удалять через интерфейс то?
Код:
яТранзакция *тр = БД->СтартТранзакции();
яЗапрос *з = тр->СоздатьЗапрос();
...
з->Выполнить();
delete з;
тр->Подтвердить();
delete тр;
Для транзакции еще можно удаление вынести в её подтверждение и откат и это в принципе логично. Но вот запрос где и как удалять? Специальный метод Dispose создавать? Или рядом с фабричным методом метод-щредер создавать? Только и там будет удаление через интерфейс. Или везьде dynamic_cast пихать для удаления?
pu4koff вне форума Ответить с цитированием
Старый 13.06.2009, 10:29   #9
alexBlack
Участник клуба
 
Регистрация: 12.10.2007
Сообщений: 1,204
По умолчанию

Цитата:
Сообщение от pu4koff Посмотреть сообщение
alexBlack, как интересно только будет выглядеть реализация этого фабричного метода? Класс интерфейса же абстрактный, соответственно создать его никак нельзя. Соответственно, будет строгая зависимость от какого-то другого класса, реализующего этот интерфейс.
Да, абстрактный и создавать его нельзя и нет смысла. Создаем конечно -же объект класса, который реализует интерфейс:

Код:
 class A_Interface
{
 public:
  static A_Interface* create_A();
  virtual ~A_Interface() {}
  virtual void do_something() {};
};

//--- Это часть реализации, скрытая от пользователей бибилиотеки
class A : public A_Interface
{
 public:
	 void do_something() {};
};

A_Interface* A_Interface::create_A()  
{
	return new A(); 
}
//------------------

// Использование
	A_Interface* a = A_Interface::create_A();
Суть шаблона интерфейс (как я понимаю) именно скрыть реализацию.
Можно посмотреть на это по другому. Если убрать слово интерфейс - то получается обычное дерево наследования и суть шаблона - принудительно заставить использовать базовый класс вместо потомков.

Цитата:
Таким образом, нет никакой необходимости в абстрактном классе интерфейса и почему бы его сразу не сделать обычным классом и создавать через вызов конструктора, а не через фабричный метод? Ну разве что для тестирования может пригодится (в фабричном методе возвращать какой-то тестовый класс для проверки работоспособности).
Это если фабрика всегда возвращает один и тот-же класс, как у меня в примере. Вот (несколько надуманный, но более практичного в голову не приходит) пример:

Есть ряд лексем, каждой соответствует рисунок. Класс, который рисует лексему просто передает ее как параметр фабричному методу. Фабричный метод создает экземпляр класса, соответствующего лексеме и возвращает его (количество классов соответствует количеству лексем, но использующий объект видит только интерфейс).

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

Цитата:
Сообщение от atomicxp Посмотреть сообщение
Для меня реализация шаблона проектирования интерфейса это прежде всего способ управления другими классами. Если он содержит что-то помимо чисто абстрактных методов, то это не интерфейс.
А как я понял, шаблон-интерфейс - это не способ управления классами, а в бОльшей степени способ уменьшения сложности. Кроме управления нужно еще и скрыть знание об управляемых классах.

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

Последний раз редактировалось alexBlack; 13.06.2009 в 10:33.
alexBlack вне форума Ответить с цитированием
Старый 13.06.2009, 14:19   #10
atomicxp
Форумчанин
 
Аватар для atomicxp
 
Регистрация: 01.05.2009
Сообщений: 110
Радость

Вариант интерфейса.

Код:
#include <iostream>

using namespace std;

class InterfaceA
{
 protected:  // запрещает удалять derived объект через интерфейс
    ~InterfaceA(){}
 public:
    virtual void ma() = 0;
    virtual void mb() = 0;
};

class ClassA : public InterfaceA
{
 protected:
    ~ClassA(){ cout << "Desctruction ClassA" << endl; }
 public:
    void ma()
    {
        cout << "ClassA: Method A" << endl;
    }
    void mb()
    {
        cout << "ClassA: Method B" << endl;
    }
};

void Testing(ClassA* classA)
{
    InterfaceA* interfaceA;

    interfaceA = classA;

    interfaceA->ma();
    interfaceA->mb();

    delete interfaceA; // |38|ошибка: в данном контексте|
}

int main()
{
    ClassA* classA = new ClassA();

    Testing(classA);

/*    classA->ma();
    classA->mb();

    delete classA;*/

    return 0;
}
Цитата:
/media/Data_01/Work/Start/AxCoreProjects/InterfaceNix/main.cpp||In function ‘void Testing(ClassA*)’:|
/media/Data_01/Work/Start/AxCoreProjects/InterfaceNix/main.cpp|8|ошибка: ‘InterfaceA::~InterfaceA()’ is protected|
/media/Data_01/Work/Start/AxCoreProjects/InterfaceNix/main.cpp|38|ошибка: в данном контексте|
||=== Build finished: 2 errors, 0 warnings ===|
Деструктор интерфейса защищён, значит нельзя вызвать его для объекта использующего интерфейс, через сам интерфейс и утечка памяти невозможна. Таким образом виртуальный деструктор не нужен.

Последний раз редактировалось atomicxp; 13.06.2009 в 15:21.
atomicxp вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Паттерны проектирования shinauri PHP 0 17.07.2012 17:06
Консольный текстовый редактор и паттерны delias C# (си шарп) 0 22.04.2011 00:41
паттерны для детсада pproger Общие вопросы по программированию, компьютерный форум 4 11.04.2011 19:40
паттерны проектирования prokach Общие вопросы C/C++ 3 18.01.2011 22:23