Бывает такая ситуация, когда нужно, чтобы можно было иметь только один экземпляр объекта. Примеры:
Паттерн 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 создан в памяти (а не вернулся нулевой указатель)
Допустим, у нас есть класс 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();
}
Большая программа, разбитая на несколько частей, имеет много преимуществ, например:
2 класса независимы друг от друга тогда, когда у них нет ссылок друг на друга:
Классы 1 и 2 имеют ссылку на класс 3. Класс 3 не имеет ссылок ни на кого, следовательно он независим от других классов. Т.е. при изменении классов 1 или 2, менять класс 3 не придется.
Независимость дает следующие преимущества:
Легче вносить изменения в программу: классы 1 и 2 могут быть полностью изменены, или даже убраны, при этом в класс 3 изменения не вносятся. Правда, если класс 3 будет меняться, то придется вносить изменения во все зависимые от него классы.
Класс 3, так как он независим от других, можно использовать в других проектах.
Если бы класс 3 имел ссылки на класс 1 или 2, или на оба сразу, то класс 3 перестал бы быть независимым. Такая организация классов называется сильно связанной. При этом мы лишились бы всех описанных до этого преимуществ, поэтому, сильная связанность -- плохо. Программа должна представлять собой ациклический граф.
Паттерн MVC (сокращение от "Model-View-Controller") предлагает так разделять программу:
Модель реализует внутреннее представление данных и обеспечивает к ним доступ.
Представление обеспечивает вывод данных на экран, в форме, понятной пользователю.
Контроллер отвечает за взаимодействие между Моделью и Представлением.
Как правило, в графических приложениях Представление и Контроллер объединены вместе, так как за вывод на экран (Представление) фактически отвечает графическая подсистема не программы, а среды, в которой она запущена.
Сетевые приложения реализуют Представление и Контроллер отдельно, так как это связано с особенностью устройства сети. Контроллер и Модель находятся на стороне сервера, а Представление - на стороне клиента. Представление не обращается к модели напрямую, все действия проходят через контроллер.
Например, сайт на экране бразуера -- Представление. Мы нажимаем на какой-либо ссылку на сайте, и браузер посылает на сервер запрос "загрузить эту ссылку". На стороне сервера Контроллер обрабатывает этот запрос, делает какие-то свои запросы к модели, чтобы получить необходимые данные, формирует ответ в виде 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. Elements of Reusable Object-Oriented Software")
Паттерны придумывались для того, чтобы писать "гибкий" код - такой, который легко изменять и дорабатывать.
Паттерны представляют собой типовые проектные решения, применимые к очень многим проблемам, возникающим при проектировании.
Возможность изменять и расширять код требуется не во всех частях программы. Бездумное применение -- излишнее усложнение программы.