Брутально и бессердечно о программировании и проектировании
ГлавнаяФорумАртАнтипаттерныТест-драйвЗаметкиВопрос-ответКнигорецензииСправочная

Singleton

Предположим, что вам понадобился такой класс, объект которого должен существовать в единственном экземпляре. Кроме того, вы не хотите протаскивать этот объект во все места, где он может понадобиться, а желаете чтобы он был легко доступен из любого места программы. Таким классом может быть менеджер памяти, журнал событий (лог), звуковой менеджер, и так далее. В этих случаях вам поможет паттерн Singleton.
Я приведу простейшую реализацию синглтона, объект которого занимается управлением экрана сотового телефона.
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <iostream>
#include <boost/noncopyable.hpp>

class window : private boost::noncopyable
{
public:

    static void initialize()
    {
        delete inst;
        inst = new window;
    }

    static void shutdown()
    {
        delete inst;
        inst = 0;
    }

    static window& instance()
    {
        return *inst;
    }

    void clear()
    {
        // ...
    }

    void draw_rect(int x1, int y1, int x2, int y2)
    {
        // ...
    }

    void print(int x, int y, std::string const& msg)
    {
        // ...
    }

private:

    static window* inst;

    window() {}
    ~window() {}
};

window* window::inst = 0;

int main()
{
    window::initialize();

    window::instance().clear();
    window::instance().draw_rect(0, 0, 175, 219);
    window::instance().print(10, 10, "Hello Moto");

    window::shutdown();

    return 0;
}
Приведенный класс имеет закрытые конструктор по умолчанию и деструктор, а также наследует boost::noncopyable. Таким образом ни пользователь, ни наследники класса не смогут самостоятельно создавать или удалять объекты данного класса. Создание и удаление происходит в функциях initialize и shutdown. Иными словами, мы имеем все гарантии того, что в программе будет существовать не более одного объекта класса window. Закрытый оператор присваивания избавит от проблем, связанных с присваиванием самому себе. Как вы наверно уже заметили, единственный объект хранится по простому указателю, а не, скажем, по std::auto_ptr, и это не случайно. Наш синглтон имеет закрытый деструктор, и std::auto_ptr просто не сможет удалить объект. А не подружить ли синглтон со смарт-поинтером? Нет — ведь в таком случае и у пользователя появится возможность оперировать смарт-поинтерами на синглтон. Да, пользователь не сможет создавать объекты, однако у него будет возможность конструировать смарт-поинтеры от указателя, полученного, например, с помощью &window::instance(). Конечно, такой случай выглядит весьма сомнительно, однако вспомним старый анекдот — после запирания входной двери на цербер, французский, английский и израильский замок, дополнительно закрыть на швабру все же не помешает.
Существует четыре способа инициализации и уничтожения синглтона:
  • Инициализация и уничтожение выполняются соответствующими свободными или статическими функциями;
  • Инициализация и уничтожение выполняются посредством идиомы владения. В функции-точке входа на стеке объявляется объект, инициализирующий синглтон в конструкторе, и уничтожающий его в деструкторе. На мой взгляд это наиболее разумный вариант;
  • Так называемая lazy-инициализация. Инициализация происходит при первом обращении к синглтону, уничтожение — вызовом соответствующей функции. Достоинство этого способа в том, что если пользователь не использует синглтон, то и не выделяются никакие ресурсы. Недостаток состоит в том, что если в системе присутствует несколько синглтонов, использующих друг друга, то может возникнуть ситуация, при которой сингтоны должны быть проинициализированны в определенном порядке, и вам придется выстраивать этот порядок искусственным путем;
  • Синглтон Меерса. Синглтон инициализируется в конструкторе, уничтожается в деструкторе и кроме того, сам синглтон является статическим объектом. В данном случае проблема всего одна — стандарт ничего не говорит о порядке конструирования статических объектов из разных единиц компиляции. Если у вас будет несколько сингтонов, использующих друг друга, то скорее всего этот метод приведет к проблемам.
Что касается недостатков паттерна — он, недостаток, собственно, один. И кроется этот недостаток даже не в самом паттерне, а в недальновидности программиста. Дело в том, что не так просто понять, действительно ли в программе будет только один экземпляр класса. Посмотрим на наш экранчик телефона. Сегодня наш синглтон прекрасно справляется с возложенной на него задачей. Но представьте себе, что завтра к нам придет начальник и скажет, что компания выпустила новую модель телефона с двумя экранами, и нужно, как это всегда бывает, по-быстрому адаптировать существующую библиотеку под новый телефон. Вот тут и начнутся проблемы, которых, кстати говоря, можно было очень легко избежать, попросту не делая класс синглтоном.
Что делать, если используемое API подталкивает вас к использованию синглтона? В этом случае лучше всего оперировать объектами так, как будто они не являются синглтонами, если это, конечно же, не противоречит здравому смыслу. Например, API для работы с камерой сотового телефона может навязывать вам работу с синглтоном, однако ваши адаптеры могут «делать вид», что камера не является синглтоном:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class camera
{
public:

    // Сделали вид, что камер может быть несколько
    enum type
    {
        front,
        back
    };

    bitmap_ptr cam.take_picture()
    {
        // Хотя API навязывает семантику синглтона
        BREW_Camera_Take_Picture(/* ... */);
        // ...
    }

    // ...
};

int main()
{
    camera cam(camera::back);

    bitmap_ptr bmp = cam.take_picture();

    return 0;
}
 
Подведем итоги
Сущностей, которые могут существовать действительно только в единственном экземпляре, практически не существует. Всегда найдется разумная ситуация, в которой понадобятся несколько экземпляров сущности. Кроме того — рефакторинг для внедрения синглтона будет менее трудоемок, нежели для избавления от него. Следует всегда руководствоваться здравым смыслом, а не условиями сегодняшнего дня при выборе этого паттерна. Если отказ от синглтона не противоречит здравому смыслу (например — на телефоне вполне может быть три камеры, хотя таких пока что и не делают) — откажитесь от синглтона.

Оглавление
Статистика
© 2007—2009 Inside C++ Коммерческие услугиКонтактная информация

купить микроволновую печь. недвижимость. Interesting in the net: heathrow meet and greet parking: recommendations. авиабилеты в болгарию