План лекции
  • Singleton
    • Один экземпляр объекта
    • Паттерн Singleton
    • Чем плоха статическая переменная?
    • Динамическое создание (ленивая инициализация)
    • Singleton на шаблонах
  • Model-View-Controller
    • Большую программу нужно разбивать на части
    • Зависимость одних частей программы от других
    • MVC --- как способ разбить программу на части
    • MVC пример 1. Граф. приложение
    • MVC пример 2. Веб-приложение
    • Структура взаимодействия
    • Пример. Шахматная программа.
  • Шаблоны проектирования
    • Книга Design Patterns. "Гибкий" код
    • Плюсы паттернов
    • Минусы паттернов
    • Выводы

Singleton

Один экземпляр объекта

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

Паттерн Singleton

Паттерн Singleton ("Одиночка") решает эту проблему.


class Logger {
    public:
	static Logger* instance() {
	    return myInstance;
	}
    private:
	static Logger* myInstance;
    private:
	Logger();
    public:
	bool openLogFile(...);
	bool writeLogFile(...);
	bool closeLogFile(...);
    private:
	Logger& operator=(Logger const&);
	Logger(Logger const&);
};

Для того чтобы хранить только один объект в памяти, создается статическое поле объекта Logger -- myInstance, доступ к которому можно получить через публичный метод instance()

Инициализация статической переменной (код должен находиться в .cpp файле, где определяются методы класса Logger):


Logger* Logger::myInstance = new Logger(); 

Использование в программе:


int main() {
    Logger* loggerPtr = Logger::instance();
    loggerPtr->openLogFile();
}
Чем плоха статическая переменная?

Динамическое создание

ешение проблемы со статической переменной:


Logger* Logger::myInstance = 0;
Logger& Logger::instance() {
    if (myInstance == 0) {
	myInstance = new Logger();
    }
    return *myInstance;
}

Здесь используется "ленивая инициализация" - объект создается в памяти при первом запросе к нему.

Кстати, здесь сделано еще одно изменение: возвращается не указатель, а ссылка. Это нужно для того, чтобы явно показать, что класс Logger создан в памяти (а не вернулся нулевой указатель)

Singleton на шаблонах

Допустим, у нас есть класс Logger:


class Logger {
    public:
	Logger();
    public:
	bool openLogFile(...);
	bool writeLogFile(...);
	bool closeLogFile(...);
};

Можно сделать класс Singleton на шаблонах, с помощью которого можно было классу Logger добавить возможности Singleton.

Шаблон класса Singleton:


template <class T>
class Singleton {
    public:
	static T& instance() {
		if (myInstance == 0) {
			myInstance = new T;
		}
		return *myInstance;
	}
    private:
	static T* myInstance;
    protected:
	Singleton() { }
    private:
	Singleton& operator=(const Singleton&) { return *this; }
	Singleton(const Singleton&) { }	
};

template <class T>
T* Singleton::myInstance = 0;

И, чтобы добавить классу Logger возможности Singleton, нужно унаследовать Logger от Singleton с параметром 'Logger':


class Logger : public Singleton<Logger> {
	private:
		Logger() { }
	public:
		bool openLogFile();
		bool writeLogFile();
		bool closeLogFile();

	friend class Singleton<Logger>;
};

Комментарии:

Использование такого класса Logger в программе:


int main() {
    Logger& logger = Logger::instance();
    logger.openLogFile();
}

Model-View-Controller

Большую программу нужно разбивать на части.

Большая программа, разбитая на несколько частей, имеет много преимуществ, например:

Зависимость одних частей программы от других.

2 класса независимы друг от друга тогда, когда у них нет ссылок друг на друга:

зависимость классов

Классы 1 и 2 имеют ссылку на класс 3. Класс 3 не имеет ссылок ни на кого, следовательно он независим от других классов. Т.е. при изменении классов 1 или 2, менять класс 3 не придется.

Независимость дает следующие преимущества:

Если бы класс 3 имел ссылки на класс 1 или 2, или на оба сразу, то класс 3 перестал бы быть независимым. Такая организация классов называется сильно связанной. При этом мы лишились бы всех описанных до этого преимуществ, поэтому, сильная связанность -- плохо. Программа должна представлять собой ациклический граф.

Паттерн MVC .

Паттерн MVC (сокращение от "Model-View-Controller") предлагает так разделять программу:

MVC пример 1. Графическое приложение.

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

MVC в графическом приложении

MVC пример 2. Веб-приложение.

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

MVC в сетевом приложении

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

Структура взаимодействия

Кто и кому говорит "обновись" в паттерне MVC?

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

Но, Модель сама может изменяться независимо от Контроллера. Пример - модель "Часы", которая изменяется каждую секунду. В этом случае либо Контроллер должен периодически опрашивать ее, не изменилась ли она, либо модель должна иметь ссылку на Представление, и сообщать ему, что Модель обновилась. Но это создает циклическую зависимость Модели и Представления.

Пример. Шахматная программа.

С помощью паттерна MVC можно поделить программу "Шахматы" таким образом:

Модель - хранит доску (массив размером 8x8), и позволяет взаимодействовать с доской: посмотреть указанную клетку, сделать ход, посмотреть возможные ходы, проверить, если ли на доске шах:


class Board {
	public:
	    int getBoardCell(int x, int y);
	    makeMove(int x, int y, int x2, int y2);
	    getMoves();
	    bool isCheck();
	private:
	    int b[8][8];
};

Она хранит ссылку на Модель, так как в процессе своей работы оно запрашивает данные у Модели (например, для отрисовки доски, просматривает всю доску с помощью метода Модели getBoardCell():

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


class View {
	public:
	    update();
	    onMousePressed(int x, int y);
	private:
	    Board *b;
};

Шаблоны проектирования

Книга Design Patterns. "Гибкий" код

Об этих и других шаблонах проектирования можно прочитать в книге "Приемы объектно-ориентированного программирования: паттерны проектирования" (авторы: Э. Гамма, . Хелм, . Джонсон, Дж. Влиссидес (название на английском: "Design Patterns. Elements of Reusable Object-Oriented Software")

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

Плюсы паттернов

Минусы паттернов

Выводы

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