Singleton как реализовать?

Общие вопросы программирования, алгоритмы и т.п.

Модератор: Модераторы

xterro
постоялец
Сообщения: 148
Зарегистрирован: 23.02.2014 13:49:33

Singleton как реализовать?

Сообщение xterro »

Доброго времени суток, озадчился я значит созданием синглтона, чтобы в любом месте программы мог бы сделать что-то типа:

Код: Выделить всё

app := tapplication.get_instance();

но ничего не выходит, немного кода:

Код: Выделить всё

unit tr_application;

interface
   
    uses
      //. . .

    type 
        papplication = ^tapplication;
       
        tapplication = class
        private
            fwindow_name    : string;
            fwindow_width   : integer;
            fwindow_height  : integer;
            ffullscreen     : boolean;
            fparams         : tstringList;
           
        public
           
            property window_width : integer  read fwindow_width  write fwindow_width;
            property window_height : integer read fwindow_height write fwindow_height;
       
            constructor create();
            destructor  destroy();
            function    get_instance() : papplication; static;
            . . .
        private
           
        end;
   
       
implementation   
   
    { Где объявлять переменную для хранения ссылки на экземпляр класса(на созданный объект)? }   
    var
        app : papplication = nil;
   
    constructor tapplication.create();
    begin
        fwindow_name    := 'tr_application_empty';
        fwindow_width   := 800;
        fwindow_height  := 600;
        ffullscreen     := false;
    end;
   
    function tapplication.get_instance() : papplication;
    begin
        if(app = nil) then begin
            app := papplication(tapplication.create());
        end;
        result := app;
    end;

    . . .


Использую это хозяйство так:

Код: Выделить всё


program ttt;
uses
    tr_application;

var
    app : papplication;
begin
    app := tapplication.get_instance();
    app^.create_window('', 1024, 768, false);
    app^.main_loop();
   
    app^.destroy();
end.


Сложность вызывает момент непосредственно создания объекта, где хранить ссылку на экземпляр класса? Например, в С++ можно хранить ссылку прямо в переменной класса, объявив её сатической, можно ли так же сделать в паскале, или нужно в каком-то блоке var это делать? На wiki так и сделано(http://wiki.freepascal.org/Singleton_Pattern), но в моём случае, компилятор ругается на app := tapplication.get_instance():

Error: Only class methods, class properties and class variables can be referred with class reference


Как же всё таки запилить синглтон? :?
zub
долгожитель
Сообщения: 2890
Зарегистрирован: 14.11.2005 22:51:26
Контактная информация:

Сообщение zub »

Код: Выделить всё

    type  
        papplication = ^tapplication;

Это лишнее - переменная типа класс уже указатель, не надо разводить масло масляное.
Если всетаки есть необходимость делать через указательк, то надо еще подправить так:

Код: Выделить всё

        if(app = nil) then begin
            app := tapplication.create();
        end;
        result := @app;


Ну и GetInstance сделать классовым, как в вики - тогда всё заработает
xterro
постоялец
Сообщения: 148
Зарегистрирован: 23.02.2014 13:49:33

Сообщение xterro »

Т.е переменную достаточно объявить так:

Код: Выделить всё

var
    app : tapplication = nil;

а функцию вот так:

Код: Выделить всё

class function  get_instance() : tapplication; 

?
Интересно, что даёт этот "class" и зачем тогда модификатор "static"?

Добавлено спустя 9 минут 15 секунд:
P.S. переделал так, заработало:

Код: Выделить всё

var
    app : papplication;
begin
    app := tapplication.get_instance();
    app^.create_window('', 1024, 768, false);
    app^.main_loop();
   
    app^.destroy();
end.


Код: Выделить всё

 type  
        papplication = ^tapplication;
       
        tapplication = class
        private
            . . .   
        public
            class function  get_instance() : papplication;

implementation
 var
        app : tapplication = nil;
   
    constructor tapplication.create();
    begin
        fwindow_name    := 'tr_application_empty';
        fwindow_width   := 800;
        fwindow_height  := 600;
        ffullscreen     := false;
    end;
   

   
    class function tapplication.get_instance() : papplication;
    begin
        if(app = nil) then begin
            app := tapplication.create();
        end;
        result := @app;
    end;


Т.е как я понял, в паскале нельзя такой указатель хранить в самом классе, а он должен быть опредлен только в самом модуле :)
zub
долгожитель
Сообщения: 2890
Зарегистрирован: 14.11.2005 22:51:26
Контактная информация:

Сообщение zub »

>>P.S. переделал так, заработало:
укакзатель на указатель на данные класса, хотя вполне достаточно иметь только один указатель))

>>Интересно, что даёт этот "class" и зачем тогда модификатор "static"?
Я как то не задумывался в чем разница - не использую. по сути одно и тоже. Надо эксперементально выяснять, подозреваю что в одном случае доступа к self не будет, в другом он всетаки будет.

>>Т.е как я понял, в паскале нельзя такой указатель хранить в самом классе, а он должен быть опредлен только в самом модуле :)
Какой указатель? если имеешь ввиду

Код: Выделить всё

var
    app : papplication;

то думаю его можно засунуть внутрь класса в виде классовой переменной
xterro
постоялец
Сообщения: 148
Зарегистрирован: 23.02.2014 13:49:33

Сообщение xterro »

Всё, теперь понял, сделал всё по фен-шую с tapplication, спасибо :)
Mirage
энтузиаст
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia
Контактная информация:

Сообщение Mirage »

Прежде всего: Singleton - антипаттерн.
Проблем с ними много. В частности, приведенный выше вариант не рабочий в многопоточном окружении.
В FPC есть штатная замена - юниты.
Можно объявить переменную и инициализировать ее в соотв. секции. И даже корректно уничтожить.
xterro
постоялец
Сообщения: 148
Зарегистрирован: 23.02.2014 13:49:33

Сообщение xterro »

Mirage писал(а):Прежде всего: Singleton - антипаттерн.
Проблем с ними много. В частности, приведенный выше вариант не рабочий в многопоточном окружении.
В FPC есть штатная замена - юниты.
Можно объявить переменную и инициализировать ее в соотв. секции. И даже корректно уничтожить.

Можно поподробнее про это? Я запускал несколько копий своего приложения, как раз с этим своим классом TApplication, всё работало нормально. Добавил в класс целочисленное поле ID, в конструкторе его инициализировал единицей и при вызове get_instance выводил это значение в консоль. Запустил неколько копий программы из разных консолей, везде вывелось 1. Вроде всё в порядке :(
Mirage
энтузиаст
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia
Контактная информация:

Сообщение Mirage »

Многопоточность это не когда запускается несколько копий приложения, а когда в рамках одного приложения запускается несколько потоков.
И тестами корректность многопоточных программ проверить очень сложно. Тут речь только о какой-то вероятности корректной работы.
Т.е. если тест работает это еще не означает корректности программы.
zub
долгожитель
Сообщения: 2890
Зарегистрирован: 14.11.2005 22:51:26
Контактная информация:

Сообщение zub »

Mirage
А в чем проблема? если начинка класса который ТС пихает в синглтон не потокобезопасна - то и реализация этого безобразия в виде юнита или какимто другим способом будет также не безопасна (т.е. там где нужна потокобезопасность - нужны спецмеры, автоматически ее не добится). Это просто синтаксис и дело привычки - как легче воспринимается исходник, а суть одна и таже
Mirage
энтузиаст
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia
Контактная информация:

Сообщение Mirage »

zub: Проблема в данном случае в "ленивой" инициализации экземпляра-синглтона. Если одновременно из разных потоков запросить экземпляр, то будет создано несколько.
Если же инициализировать экземпляр в секции инициализации юнита, то он всегда готов к работе.
xterro
постоялец
Сообщения: 148
Зарегистрирован: 23.02.2014 13:49:33

Сообщение xterro »

Т.е если я где-нибудь в коде, сделаю новый поток(скажем создам в новом потоке диалог) в котором вызову:

Код: Выделить всё

app := tapplication.get_instance();
app.log.write('dialog created');

Будут проблемы, с чем отни связаны? Как избежать этой ситуации, может вместо статической функции просто передавать указатель на экземляр класса во все "внутренние" классы(в конструкторах этих классов)?
Например, tapplication отвечает за окно приложения, он содержит в себе переменную-член типа tcore, которая отвечает за всё содержимое окна. Правильнее будет в конструкторе tcore просто определить параметр типа tapplication и передавать указатель на него в tcore :?
Mirage
энтузиаст
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia
Контактная информация:

Сообщение Mirage »

xterro писал(а):Будут проблемы, с чем отни связаны?


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

xterro писал(а):Как избежать этой ситуации, может вместо статической функции просто передавать указатель на экземляр класса во все "внутренние" классы(в конструкторах этих классов)?


Как вариант. Можно, как я уже говорил, инициализировать сразу, в секции initialization.
Можно защитить get_instance() критической секцией, что негативно отразится на производительности при частом использовании.
Можно использовать сам юнит в качестве синглтона, вместо экземпляра класса.
zub
долгожитель
Сообщения: 2890
Зарегистрирован: 14.11.2005 22:51:26
Контактная информация:

Сообщение zub »

xterro
>>Как избежать этой ситуации, может вместо статической функции просто передавать указатель на экземляр класса во все
Избегать этой ситуации надо если ты пишешь многопоточное приложение. если нет или вообще незнаешь что это такое - пока не заморачивайся.

Mirage
>>Если же инициализировать экземпляр в секции инициализации юнита, то он всегда готов к работе.
Вовсе нет, инициализация "барахла" в initialization не отменяет необходимость критической секции для многопоточного приложения и проверок на то что "барахло" уже создано. Ничто не мешает другому потоку или просто другому юниту в своей секции инициализации запросить "барахло" раньше чем оно инициализировано в соответствующем юните. Это обычная ситуация если злоупотреблять uses в секции implementation, т.е. при циклических зависимостях модулей - в таких случаях взгляды на порядок инициализации юнитов со стороны компилятора (или разных компиляторов) и со стороны програмиста могут сильно расходиться))
Тут конечно можно поспорить - циклические зависимости зло и т.д... Но факт - initialization+finalization в общем случае не замена синглтонам и не панацея при многопоточности.
Mirage
энтузиаст
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia
Контактная информация:

Сообщение Mirage »

zub: initialization+finalization не замена синглтонам, а один из способов реализации.
Думается, тут достаточно не использовать uses в секции implementation в юните, где объявлен синглтон. И можно иметь быстрый доступ к нему. По-моему достоинства перевешивают небольшое ограничение. Тем более, что циклические зависимости - зло.:)
А панацей при многопоточности не изобрели пока.
zub
долгожитель
Сообщения: 2890
Зарегистрирован: 14.11.2005 22:51:26
Контактная информация:

Сообщение zub »

>>Думается, тут достаточно не использовать uses в секции implementation в юните, где объявлен синглтон
не проверял, но думаю цепочку можно закрутить и сторонними юнитами, не трогая "синглтонный"
ТСу теперь есть из чего выбрать))
Ответить