Реализация фабричного метода анти-if

Я применяю шаблон проектирования Factory в своем проекте на C ++, и ниже вы можете увидеть, как я это делаю. Я пытаюсь улучшить свой код, следуя кампании «анти-if», поэтому хочу удалить операторы if, которые у меня есть. Есть идеи, как мне это сделать?

typedef std::map<std::string, Chip*> ChipList;

Chip* ChipFactory::createChip(const std::string& type) {
    MCList::iterator existing = Chips.find(type);
    if (existing != Chips.end()) {
        return (existing->second);
    }
    if (type == "R500") {
        return Chips[type] = new ChipR500();
    }
    if (type == "PIC32F42") {
        return Chips[type] = new ChipPIC32F42();
    }
    if (type == "34HC22") {
        return Chips[type] = new Chip34HC22();
    }
    return 0;
}

Я бы представил создание карты со строкой в ​​качестве ключа и конструктором (или чем-то еще для создания объекта). После этого я могу просто получить конструктор с карты, используя тип (тип - это строки), и создать свой объект без какого-либо if. (Я знаю, что немного параноик, но хочу знать, можно ли это сделать или нет.)


person phunehehe    schedule 17.08.2010    source источник
comment
По крайней мере, нет заводского шаблона по GOF. Есть либо фабричный метод, либо абстрактная фабрика. Мне кажется, что это заводской метод.   -  person Chubsdad    schedule 17.08.2010
comment
@chubsdad Он написал в заголовке Factory Method.   -  person Petar Minchev    schedule 17.08.2010
comment
Ой, я не заметил это достаточно внимательно, спасибо   -  person Chubsdad    schedule 17.08.2010


Ответы (7)


Вы правы, вам следует использовать карту от ключа до функции создания. В вашем случае это было бы

typedef Chip* tCreationFunc();
std::map<std::string, tCreationFunc*> microcontrollers;

для каждого нового класса ChipXXX, управляемого микросхемой, добавьте статическую функцию:

static Chip* CreateInstance()
{
    return new ChipXXX();
}

а также зарегистрируйте эту функцию на карте.

Ваша фабричная функция должна выглядеть примерно так:

Chip* ChipFactory::createChip(std::string& type)
{
    ChipList::iterator existing = microcontrollers.find(type);    
    if (existing != microcontrollers.end())
        return existing->second();

    return NULL;
}

Обратите внимание, что конструктор копирования не требуется, как в вашем примере.

person Lior Kogan    schedule 17.08.2010
comment
Мне сложно следовать твоему примеру. Где используется карта указателей на функции? А также почему не нужен копировальный центр? - person celavek; 17.08.2010
comment
Карта используется в функции createChip (), чтобы проверить, есть ли у нас функция Creation для данной строки. Если это так, мы просто вызываем эту функцию, которая возвращает указатель на новый объект. Поскольку мы возвращаем этот указатель, нам не нужно копировать объект. - person Lior Kogan; 17.08.2010
comment
спасибо, а как тогда поставить CreateInstance фукнцию в карту? - person phunehehe; 18.08.2010
comment
хорошо, я разобрался creators["R500"] = ChipR500::createInstance; - person phunehehe; 18.08.2010

Смысл фабрики не в том, чтобы избавиться от «если», а в том, чтобы поместить их в отдельное место вашего реального кода бизнес-логики и не загрязнять его. Это просто разделение забот.

person Petar Minchev    schedule 17.08.2010

Если вы в отчаянии, вы можете написать комбинацию таблицы переходов / clone (), которая будет выполнять эту работу без операторов if.

class Factory {
    struct ChipFunctorBase {
        virtual Chip* Create();
    };
    template<typename T> struct CreateChipFunctor : ChipFunctorBase {
        Chip* Create() { return new T; }
    };
    std::unordered_map<std::string, std::unique_ptr<ChipFunctorBase>> jumptable;
    Factory() {
        jumptable["R500"] = new CreateChipFunctor<ChipR500>();
        jumptable["PIC32F42"] = new CreateChipFunctor<ChipPIC32F42>();
        jumptable["34HC22"] = new CreateChipFunctor<Chip34HC22>();
    }
    Chip* CreateNewChip(const std::string& type) {
        if(jumptable[type].get())
            return jumptable[type]->Create();
        else
            return null;
    }
};

Однако такой подход становится ценным только тогда, когда у вас есть большое количество различных типов чипов. Для некоторых более полезно написать пару «если».

Краткое примечание: я использовал std :: unordered_map и std :: unique_ptr, которые могут не быть частью вашего STL, в зависимости от того, насколько новый ваш компилятор. Замените на std :: map / boost :: unordered_map и std :: / boost :: shared_ptr.

person Puppy    schedule 17.08.2010

Нет, ты не можешь избавиться от «если». метод createChip создает новый экземпляр в зависимости от константы (имени типа), которую вы передаете в качестве аргумента. но вы можете оптимизировать свой код, немного удалив эти 2 строки из состояния if.

 microcontrollers[type] = newController;
 return microcontrollers[type];
person Arseny    schedule 17.08.2010
comment
Фактически, вы можете просто вернуть микроконтроллеры [type] = newController, поскольку operator = возвращает T &. - person Puppy; 17.08.2010

Чтобы ответить на ваш вопрос: да, вам следует создать фабрику с отображением функций, которые создают нужные вам объекты. Созданные объекты должны сами предоставлять и регистрировать эту функцию в фабрике.

Также есть некоторые материалы по этой теме в нескольких других вопросах SO, поэтому я позволю вам прочитать это вместо того, чтобы все объяснять здесь.

Универсальная фабрика на C ++

Есть ли способ создания экземпляров объектов из строки, содержащей имя их класса?

person mahju    schedule 17.08.2010

Вы можете иметь ifs на фабрике - только не оставляйте их замусоренными по всему вашему коду.

person Skilldrick    schedule 17.08.2010

То, что вы просите, по сути, называется Virtual Construction, то есть возможность построить объект, тип которого известен только во время выполнения.

Конечно, C ++ не позволяет конструкторам быть виртуальными, поэтому для этого потребуется немного хитрости. Обычный объектно-ориентированный подход заключается в использовании шаблона Prototype:

class Chip
{
public:
  virtual Chip* clone() const = 0;
};

class ChipA: public Chip
{
public:
  virtual ChipA* clone() const { return new ChipA(*this); }
};

Затем создайте экземпляр карты этих прототипов и используйте его для создания своих объектов (std::map<std::string,Chip*>). Обычно карта создается как одноэлементный.

Другой подход, как было показано до сих пор, аналогичен и заключается в непосредственной регистрации методов, а не объекта. Это может быть или не быть вашим личным предпочтением, но обычно это немного быстрее (не намного, вы просто избегаете виртуальной отправки), и с памятью легче обращаться (вам не нужно делать delete для указателей на функции).

Однако следует обратить внимание на аспект управления памятью. Вы не хотите утечки, поэтому обязательно используйте идиомы RAII.

person Matthieu M.    schedule 17.08.2010