LINUX.ORG.RU

[C++] Маленький вопрос по проектированию, статические переменные-члены

 


0

0

Всем привет!

Есть некоторая иерархия классов, в которой от одного базового наследуется ещё куча. Возникла такая проблема: надо добавить общую для всех экземпляров классов этой иерархии одну «общую» переменную.

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

Поясню на простом примере: есть базовый класс для юнитов определенной игры, в котором роль «общей» переменной играет общее количество юнитов. Допустим, у нас есть юниты нескольких «семейств», и количество их должно подсчитываться для каждого «семейства» отдельно (т.е., например, n лодок и k вертолётов).

Я дошёл до следующего решения - сделать идентификацию «семейств» через enum, а базовый класс сделать шаблонным с шаблонным параметром - идентификатором семейства. Таким образом можно при наследовании указывать семейство и получать уникальный экземпляр переменной для каждого из семейств. Выглядит это всё примерно так:

#include <iostream>
#include <iomanip>

enum group {air, navy};

template <group gr>
class unit
{   static int _count;

public:
    unit ()
    {   ++unit<gr>::_count;
    }
    ~unit ()
    {   --unit<gr>::_count;
    }
    int count () const
    {   return unit<gr>::_count;
    }
};

template <group gr>
int unit<gr>::_count = 0;

class helicopter: public unit<air> {};
class fighter: public unit<air> {};
class boat: public unit<navy> {};

int main (int argc, char *argv[])
{   helicopter hh, h[3];
    boat bb, b[7];
    fighter f[2];

    std::cout << "== The battlefield ==" << std::endl
              << std::setw (15) << "Air units: " << hh.count() << std::endl
              << std::setw (15) << "Navy units: " << bb.count() << std::endl;
    return 0;
}

На выходе получим:

/home/dmatveev $ ./separate 
== The battlefield ==
    Air units: 6
   Navy units: 8

Т.е. как бы работает... И тут собственно сам вопрос - насколько крив и костылен этот метод? Может есть другие, более правильные пути решения, или вообще это ошибка проектирования? Хотелось бы услышать мнение более опытных форумчан.

P.S. Извиняюсь за многобуков =)

★★★★★

ИМХО, довольно костыльно. Может есть смысл попробовать сделать через фабрику?

irq
()

Я бы сделал так( могут быть очипятки ):

/*******************************************************/
enum UnitGroup
{
    air,
    navy
};

/*******************************************************/
interface unit
{
public:////////////////////////////////////////////

    ////////////////////////////////
    // Destruсtor

virtual                  ~unit( void )            {}

public:////////////////////////////////////////////

    ////////////////////////////////
    // Properties

virtual UnitGroup        get_Group( void ) = 0;
};

/*******************************************************/
class unit_imp : public unit
{
public:////////////////////////////////////////////

     ///////////////////////////////////////////
     // Constructor/Destructor

                         unit_imp( void )
                         {
                             unit_counter::Instance()->Inc( get_Group() );
                         }

virtual                  ~unit_imp( void )
                         {
                             unit_counter::Instance()->Dec( get_Group() );
                         }
}

/*******************************************************/
class boat : public unit_imp
{
public:////////////////////////////////////////////

    ///////////////////////////////////////////
    // Destructor
    
virtual                  ~boat( void )            {}

public:///////////////////////////////////////
    
    ///////////////////////////////////////////
    // unit
    
virtual UnitGroup        get_Group( void )        { return navy; }   

public:///////////////////////////////////////
};


....
    
    
    size_t countNavy = unit_counter::Instance()->Count( navy );

но не претендую но идеологичную првильность моего решения :)

lester ★★★★
()
Ответ на: комментарий от irq

> ИМХО, довольно костыльно. Может есть смысл попробовать сделать через фабрику?

фабрику стоит сделать, но однозначно надо счетчик дергать именно в конструкторе/деструкторе

lester ★★★★
()

> насколько крив и костылен этот метод?

Интуция говорит, что кривоват. Но что именно криво, не могу сказать. Что-то не совсем нравится. Может просто вместо шаблона сделать оттдельные классы? Типа AirUnit, NavyUnit от Unit'a, а уже от них делать остальные. Как-то более логично будет выглядеть, имхо.

smh ★★★
()
Ответ на: комментарий от smh

>NavyUnit от Unit'a, а уже от них делать остальные.

Проблема-то не в том, как блобы друг от друга наследовать, а как статическую переменную, нах-ся в базовом классе, грамотно поделить между разными наследниками =)

yoghurt ★★★★★
() автор топика
Ответ на: комментарий от yoghurt

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

Я к тому, чтоб сделать базовый класс Unit с абстрактным методом count. От него наследовать другие классы, которые будут служить базовыми (AirUnit, NavyUnit; они как раз и будут представлять разные группы) для вертолетов, лодок и проч и в них же (AirUnit, NavyUnit) решать уже, как со счетчиками работать (т.е. для каждой группы отдельно по статической переменной, например). Более многословный подход, но более простой (ни шаблоны, ни enum'ы не нужны становятся). Лично для меня. К тому же, в твоем случае ты не сможешь изменить поведение счетчиков различных юнитов или их отдельных групп позже, а только всем сразу. Я не претендую, конечно. :-)

smh ★★★
()
Ответ на: комментарий от Absurd

> Что-то никто про треды не вспомнил.

Ога.

Хотя самый важный момент в семантике. Нахрена конкретному юниту (экземпляру) знать про счетчик собратьев и соответственно заниматься инкрементом/декреметом??? Это вообще не его ответственность. Делай фабрику, как люди советуют. Потом пригодится. Особенно при тестировании. Можно будет делать нетривиальный маппинг классов на группы юнитов. И самое главное, избавишься от сильной связи.

baverman ★★★
()
Ответ на: комментарий от baverman

Пример с юнитами, если честно, придуман:) В реальной жизни у меня немного другая ситуация, счетчиков там нет таких вообще (и фабрика тем более), нужна возможность описанная в первом посте.

yoghurt ★★★★★
() автор топика
Ответ на: комментарий от lester

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

yoghurt ★★★★★
() автор топика
Ответ на: комментарий от jtootf

Два человека, в техническом превосходстве которых надо мной я не сомневаюсь (регулярно читая /Development), одобрили, значит оставлю как есть.

Всем спасибо за идеи и предложения!

yoghurt ★★★★★
() автор топика
Ответ на: комментарий от yoghurt

> нужна возможность описанная в первом посте.

Все таки интересно, что за "общая переменная" и в каком контексте она будет использоваться? Все таки завязываться на класс, хоть и параметризованный... Вобщем веет от этого дальнейшим рефакторингом :) Но хозяин-барин, не забудь про синхронизацию, если предполагается многопоточное приложение.

baverman ★★★
()
Ответ на: комментарий от smh

> Я к тому, чтоб сделать базовый класс Unit с абстрактным методом count. От него наследовать другие классы,

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

Corey
()
Ответ на: комментарий от baverman

Ладно, раскрою секрет - сейчас будет ещё раз многобуков

IRL есть некая прога - конфигуратор другой проги (всё досталось мне по наследству). Она состоит из окна со множеством вкладок. Для каждой вкладки, ессно, свой класс, и все они унаследованы от одного базового, в котором введена некая базовая функциональность - вирт. ф-ии типо saveToProfile, readFromProfile и т.п.

В унаследованных классах невозбранно используют одну переменную (определенную в одном глобальном объекте, назовём его G), которая, скажем так, есть путь к настройкам всего приложения в реестре (не кидайте помидорами). Назовём эту переменную А.

Мне, собсно, уже досталось всё это рефакторить - и неприлично повторяющиеся участки кода были обобщены и перенесены в корень иерархии (с сохранением семантики, конечно). Вот, в одной из таких ф-ий и используется значение переменной А. Поскольку этот базовый класс абстрагирован от всего, а переменную А можно считать почти константой (её значение определяется в момент запуска и потом не меняется), я решил ввести такую переменную (назовём её S) в базовый класс, притом сделав статической - для экономии, и инициализировать её из объекта G в том же месте, где инициализируется А.

Однако возможны (но маловероятны в моём случае) ситуации, в которых в пределах одного приложения часть наследников базового класса должна иметь одно значение переменной S, а другая - другое.. Ну собственно вот и всё =)

yoghurt ★★★★★
() автор топика
Ответ на: комментарий от yoghurt

> в пределах одного приложения часть наследников базового класса должна иметь одно значение переменной S, а другая - другое

Это же совсем другой коленкор!

baverman ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.