Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Задачка с паттерном Фабрика  (Прочитано 15570 раз)
0 Пользователей и 1 Гость смотрят эту тему.
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« : 27-02-2009 19:43 » 

Есть практическая задача, написать единый интерфейс для работы с несколькими движками баз данных. Сейчас это реализовано примерно так:
Код:
class IDatabase {
public:
    virtual void someMethod1() = 0;
    virtual void someMethod2() = 0;
};

class CDatabase1 : public  IDatabase {
public:
    void someMethod1() { }
    void someMethod2() { }
};

class CDatabase2 : public  IDatabase {
public:
    void someMethod1() { }
    void someMethod2() { }
};

typedef enum _DB_TYPE {
     DATABASE_1,
     DATABASE_2
}DB_TYPE;

class CDatabase {
private:
    static IDatabase *_inst;
public:
    CDatabase(DB_TYPE db_type) {
        if( _inst == 0 ) {
            switch(db_type) {
                case DATABASE_1:
                    _inst = new CDatabase1();
                break;
                case DATABASE_2:
                    _inst = new CDatabase2();
                break;
            }
        }
    }
    static IDatabase *get_inst() { return _inst; }
    static void free_inst() {
        if( _inst != 0 ) {
            delete _inst;
            _inst = 0;
        }
    }
}
Так вот, вопрос состоит в следующем: хотя этот код позволяет достичь поставленой задачи, но приходится освобождать память в конце программы, вызывая метод:
Код:
CDatabase::free_inst();
хотелось бы от этого избавиться. Возможный вариант это объявление ссылкой переменную:
Код:
static IDatabase *_inst;
в
Код:
static IDatabase &_inst;
Тогда память освобождалась бы автоматически. Но как инициализировать статическую ссылку нужным классом при вызове конструктора?
Код:
CDatabase::CDatabase(DB_TYPE db_type)
Записан

Любимая игрушка - debugger ...
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #1 : 27-02-2009 19:53 » 

Ну первое, а зачем вообше ты используеш статическую переменную? Стоит тебе в программе создать два экземпляра данного класса и память потекла струёй. И еше вопрос, знаеш ли ты, что сушествуют деструкторы, которые срабатывают автоматически при уничтожении класса.
Записан

Не будите спашяго дракона.
             Джаффар (Коша)
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #2 : 27-02-2009 19:59 » 

Finch,
1.Статическая переменная применяется т.к. нужен всего один экземпляр класса на всю программу (паттерн Синглетон)
2.Создать еще один экземпляр не получится, от этого защищает условие:
Код:
if( _inst == 0 ) { // _inst - статическая и при следующем вызове конструктора она уже будет отличаться от нуля
            switch(db_type) {
                case DATABASE_1:
                    _inst = new CDatabase1();
                break;
                case DATABASE_2:
                    _inst = new CDatabase2();
                break;
            }
        }
3.Деструктор нельзя использовать потому что если я случайно создам где нибудь в середине программы экземпляр класса CDatabase, то после выхода переменной этого класса за пределы видимости будет вызван деструктор, который удалит статический экземпляр класса, указатель на который храниться в _inst
« Последнее редактирование: 27-02-2009 20:03 от RuNTiME » Записан

Любимая игрушка - debugger ...
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #3 : 27-02-2009 20:22 » 

RuNTiME, ּSingleton строится по другой схеме. И конструктор и деструктор прячутся в private зоне. Наружу вытаскивается статический Init и Done в которых и создается один единственный класс.
Код:
#include <iostream>
class single
{
public:
   static single* Init();
   void Done();
private:
   single() {std::cout << "Constructor" << std::endl;};
   ~single() {std::cout << "Destructor" << std::endl;};
   
   static single *ehid;
   
};

single * single::ehid=NULL;

single *single::Init()
{
   if (!ehid) ehid = new single();
   return ehid;
}

void single::Done()
{
   delete this;
   ehid = NULL;
}

int main()
{
   single *exem1=single::Init();
   single *exem2=single::Init();
   exem1->Done();
   return 0;
}
Можеш вести счетчик вызовов и при обнулении только уничтожать класс. В том примере, что ты привел, все равно возможна течь памяти.
« Последнее редактирование: 27-02-2009 20:24 от Finch » Записан

Не будите спашяго дракона.
             Джаффар (Коша)
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #4 : 27-02-2009 20:39 » 

Finch,  я знаю, что можно так сделать синглетон... мой пример приводиться к твоему переносом кода из конструктора в статическую функцию Init..., но проблема сейчас не в этом, мне не хочеться вызывать функцию Done вообще как таковую.... Фишка в том, что если удастся инициализировать статическую ссылку вместо указателя нужным классом, то память будет освобождена автоматом, без вызова Done...
Записан

Любимая игрушка - debugger ...
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #5 : 27-02-2009 20:45 » 

Finch, и кстати твой пример можно реализовать еще проще, возвращая ссылку:
Код:
class CSingleton {
private:
    CSingleton() { }
public:
    static CSingleton &inst() {
        static CSingleton _inst;
        return _inst;
    }
    void someMethod() { }
};

CSingleton &c = CSingleton::inst();
c.someMethod();
И кстати еще по твоему примеру... не стоит запирать деструктор класса в private
Записан

Любимая игрушка - debugger ...
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #6 : 27-02-2009 20:55 » 

RuNTiME, Когда вызывается конструктор, то идет автоматическое выделение памяти на экземпляр класса. Даже если у тебя пустой конструктор. Получается, что у тебя провисает память. Кстати, если ты сделаеш даже статическую ссылку. Экземпляр класса с интерфейсом IDatabase все равно должен где то хранится. Значит будет кушать память. С другой стороны твой пример можно решить с помошью шаблонов.
Код:
include <iostream>
class IDatabase {
public:
    virtual void someMethod1() = 0;
    virtual void someMethod2() = 0;
};

class CDatabase1 : public  IDatabase {
public:
    void someMethod1() { }
    void someMethod2() { }
};

class CDatabase2 : public  IDatabase {
public:
    void someMethod1() { }
    void someMethod2() { }
};

typedef enum _DB_TYPE {
     DATABASE_1,
     DATABASE_2
}DB_TYPE;

template <typename T>
class CDatabase: public T
{
public:
    CDatabase()
    {
       std::cout << "Constructor" << std::endl;
    }
    ~CDatabase()
    {
       std::cout << "Destructor" << std::endl;
    }
};

int main()
{
   CDatabase<CDatabase1> ehid;

   return 0;
}
Записан

Не будите спашяго дракона.
             Джаффар (Коша)
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #7 : 27-02-2009 20:58 » 

Цитата
И кстати еще по твоему примеру... не стоит запирать деструктор класса в private
Ну шаловливые ручки программистов еше никто не отменял. Если я например использую счетчик вызовов, то будет не очень приятно, если кто либо сдуру вызовет деструктор заместо метода Done. А так компилятор на попытку delete exem1; еше на этапе компиляции покажет язык.
« Последнее редактирование: 27-02-2009 21:00 от Finch » Записан

Не будите спашяго дракона.
             Джаффар (Коша)
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #8 : 27-02-2009 21:11 » 

Finch,
1.) Насчет автоматического выделения памяти в конструкторе:
Код:
class CSingleton {
private:
    CSingleton() { }
public:
    static CSingleton &inst() {
        static CSingleton _inst;
        return _inst;
    }
    void someMethod() { }
};

CSingleton &c = CSingleton::inst();
c.someMethod();
Функция inst объявлена как статическая, а это значит, что она может вызываться без создания экземпляра класса... фактически класс для этой функции является всего лишь пространством имен. Отсюда выходит следующая последовательность:
1.Вызывается inst
2.Внутри inst происходит выделение памяти под Статическую переменную _inst куда записывается 1 экземпляр класса CSingleton
При дальнейших вызовах метода CSingleton::inst() выделения памяти больше происходить не будет, а это значит мы имеем 1 экземпляр класса CSingleton и никакой утечки памяти.

2.) Теперь что касается IDatabase ... по его определению видно, что это чисто виртуальный класс и переменных в этом классе нет... если ты попытаешься создать экземпляр чисто виртуального класса, то компилятор будет очень недоволен:) Под чисто виртуальные классы память не выделяется вообще, они просто служат сигналом для компилятора, что все классы наследники должны иметь в себе реализации объявленных в виртуальном классе функции. Отсюда мы НЕ имеем никакой утечки памяти на IDatabase.

3.) Насчет шаблонов, так решить можно. Но как ты себе представляешь передачу класса-шаблона в параметре функции.... это же придется указывать типы для каждой функции. Если использовать просто интерфейс то можно передавать его в параметре:
Код:
void someFunction(IDatabase *db);
//или
void someFunction(IDatabase &db);
Записан

Любимая игрушка - debugger ...
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #9 : 27-02-2009 21:21 » 

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

2) Я специяально подчеркнул
Цитата
Экземпляр класса с интерфейсом IDatabase
То что, нельзя создать экземпляр абстрактного класса я знаю. Но ты от него наследуюшся, значит можно создать дочернии классы.

Дай точную формулировку задачи, а то мы сейчас ходим кругами, проверяя знания друг друга. Может твоя задача будет решаться намного проше и легче.
« Последнее редактирование: 27-02-2009 21:23 от Finch » Записан

Не будите спашяго дракона.
             Джаффар (Коша)
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #10 : 27-02-2009 21:32 » 

Finch,
По поводу шаблонов проверю как работает....

Цитата
Я специяально подчеркнул
Цитировать
Экземпляр класса с интерфейсом IDatabase
То что, нельзя создать экземпляр абстрактного класса я знаю. Но ты от него наследуюшся, значит можно создать дочернии классы.
Дочерние классы да можно, но не нужно Улыбаюсь Но чтобы они не создавались, пока в голову приходит только  запереть определения служебных классов в private секцию CDatabase. Но лень потом разыменовывать:
Код:
CDatabase::CDatabase1::someFunction1() {
}
Записан

Любимая игрушка - debugger ...
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #11 : 27-02-2009 21:34 » 

Finch,А точную задачу я сформулировал еще в самом начале Улыбаюсь мне надо чтобы через один и тот же интерфейс можно было работать с разными объектами конкретных баз данных.... причем сделать это так, чтобы не надо было вызывать функции типа free_inst или Done Улыбаюсь
Записан

Любимая игрушка - debugger ...
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #12 : 27-02-2009 22:13 » 

Скажем так, что это только как ты представляеш решение. Но не сама задача. Можно например вообше не заниматься ерундой, а сделать обычный конструктор, обычную (не static) переменную указатель на экземляр класса баз данных. В конструкторе создавать базу, в деструкторе удалять. И будет счастье. Ты начинаеш переводить все в static обзывая это singleton. Не расказывая, для каких целей ты это делаеш. Поэтому наверно очень сложно найти подходяшее решение.
Записан

Не будите спашяго дракона.
             Джаффар (Коша)
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #13 : 27-02-2009 22:26 » 

Finch,
Цитата
Не расказывая, для каких целей ты это делаеш.
Хорошо... рассказываю:
Программа соединяется с БД один раз при старте, далее по всей программе должно использоваться это подключение, по этому static... Если делать как говоришь ты, создавать в конструкторе и удалять в деструкторе, то получается каждый раз программа будет переподключаться к БД... А это не совсем хорошо.
И еще один момент:
У меня существует соответсвующий класc CQuery для исполнения SQL запросов. Этот класс тоже использует созданное подключение к БД. И когда оно статическое, становиться очень удобно выполнять запросы, все сводится примерно к этому:
Код:
    CQuery query(
        "CREATE TABLE login("
            "ID INTEGER PRIMARY KEY AUTOINCREMENT,"
            "AUTH_DATA CHAR(32) UNIQUE NOT NULL,"
            "REG_TIME BIGINT,"
        "LAST_AUTH BIGINT);"
    );
    return query.exec();
отпадает даже надобность передавать в пареметре query класс CDatabase, что весьма удобно.

Что касается реализации нескольких БД.... хотелось бы научить программу работать с несколькими базами данных через один интерфейс... к примеру SqLite, MySQL, PgSQL и все через один класс.... Чтобы не переписывать код программы под API каждой конкретной СУБД.
Записан

Любимая игрушка - debugger ...
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #14 : 27-02-2009 23:06 » 

Ну экземпляр класса нужно где то хранить, если правда ты весь класс не сделаеш static, мое предложение создавать его только один раз в main примерно так
Код:
#include <iostream>

class IDatabase {
public:
    virtual void someMethod1() = 0;
    virtual void someMethod2() = 0;
};

class CDatabase1 : public  IDatabase {
public:
    void someMethod1() { }
    void someMethod2() { }
};

class CDatabase2 : public  IDatabase {
public:
    void someMethod1() { }
    void someMethod2() { }
};

template <typename T>
class CDatabase: public T
{
   typedef CDatabase<T> Database;
public:
    CDatabase()
    {
       std::cout << "Constructor" << std::endl;
       if (_inst) throw 0;
       _inst = this;
    }
    ~CDatabase()
    {
       std::cout << "Destructor" << std::endl;
       _inst = NULL;
    }
   
    static Database *get_inst() {return _inst;}
private:
    static Database *_inst;
};
template <typename T>
CDatabase<T> *CDatabase<T>::_inst=NULL;


int main()
{
   CDatabase<CDatabase1> ehid;

   return 0;
}
Заметь, повторно класс с таким же интерфейсом базы данных создать нельзя, так как сработает исключение. И ты получаеш свою любимую функцию get_inst() Улыбаюсь.
« Последнее редактирование: 27-02-2009 23:08 от Finch » Записан

Не будите спашяго дракона.
             Джаффар (Коша)
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #15 : 27-02-2009 23:19 » 

Finch,  Вот! Это уже похоже на то, что мне нужно! Завтра попробую реализовать... Улыбаюсь
Записан

Любимая игрушка - debugger ...
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #16 : 27-02-2009 23:28 » 

А нет, вру. Будет лажа. Лучше шаблон убрать.
Код:
typedef enum _DB_TYPE {
     DATABASE_1,
     DATABASE_2
}DB_TYPE;

class CDatabase
{
public:
    CDatabase()
    {
       std::cout << "Constructor" << std::endl;
       if (_inst) throw 0;
       switch(db_type)
{
         case DATABASE_1:
           _inst = new CDatabase1();
           break;
        case DATABASE_2:
           _inst = new CDatabase2();
           break;
       }       
    }

    ~CDatabase()
    {
       std::cout << "Destructor" << std::endl;
       delete _inst;
       _inst = NULL;
    }
   
    static IDatabase *get_inst() {return _inst;}
private:
    static IDatabase *_inst;
};

IDatabase *CDatabase::_inst=NULL;

Я конечно не вижу полной картины твоей программы. Исхожу из того что ты сказал.
1) Держать все время открытым соединение с базой данных не рекомендуют. Соединение делается только по мере надобности. Так ты во первых уменьшаеш нагрузку на сервер баз данных, во вторых держиш соединение в актуальном состоянии.
2) Так сильно сцеплять классы между собой также не рекомендуют. Классы должны выражать собой черные яшики. Известно что входит и выходит. Но что внутри, не известно. Также минимальная зависимость от внешних факторов. Таким образом достигается почти безболезненая переносимость кода. И возможность расширения системы.
« Последнее редактирование: 28-02-2009 06:33 от Finch » Записан

Не будите спашяго дракона.
             Джаффар (Коша)
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #17 : 28-02-2009 08:32 » 

Finch, Да пожалуй без шаблонов выглядит лучше Улыбаюсь Что касается подключения, то основной БД в программе будет SqLite, а она вообще встраеваемая и не имеет сервера. Да и можно ввести еще одну статическую функцию dbClose(); чтобы иметь возможность закрывать БД, но только там где это необходимо.
« Последнее редактирование: 28-02-2009 08:37 от RuNTiME » Записан

Любимая игрушка - debugger ...
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #18 : 28-02-2009 08:37 » 

Реализуйте модуль DatabaseConnectionModule, состоящий из заголовочного файла - интерфейса модуля, и файла реализации - внутренней части модуля, недоступной другим пользователям.

Создайте класс DatabaseConnectionProvider со статическим методом getConnection и закрытыми конструкторами и деструкторами. Он будет отвечать за доступность соединения во всей программе. Соединение будет описываться классом IDatabaseConnection, но внутри класса должно быть выбран определённый класс DatabaseConnection для создания экземпляра.

Создайте класс IDatabaseСonnection с открытыми абстрактными методами.

Объявите вышеперечисленные классы в заголовочном файле.

Создайте разные классы DatabaseConnection, открыто наследующие IDatabaseConnection, реализуйте в них абстрактные методы.

Эти классы не объявляйте в заголовочном файле, чтобы другие пользователи не получили к ним доступ.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #19 : 28-02-2009 08:41 » 

dimka,  все реализовано точно так как ты и пишешь, за исключением объявления служебных классов в .h файле... мысль хорошая убрать их оттуда Улыбаюсь так и поступим.
Записан

Любимая игрушка - debugger ...
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines