2009-07-03 3 views
1

Я рассматриваю фабричную функцию для создания разных классов в одной и той же иерархии. Я понимаю, что обычно завод, как правило, реализуется следующим образом:Как объединить шаблон фабрики с гибкостью кода

Person* Person::Create(string type, ...) 
{ 
    // Student, Secretary and Professor are all derived classes of Person 
    if (type == "student") return new Student(...); 
    if (type == "secretary") return new Secretary(...); 
    if (type == "professor") return new Professor(...); 
    return NULL; 
} 

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

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

Карта будет содержать строку типа в первом элементе и экземпляр класса (прототип), во втором:

std::map<string, Person> PersonClassMap; 
// This may be do-able from a configuration file, I am not sure 
PersonClassMap.insert(make_pair("student", Student(...))); 
PersonClassMap.insert(make_pair("secondary", Secretary(...))); 
PersonClassMap.insert(make_pair("professor", Professor(...))); 

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

Person* Person::Create(string type) 
{ 
    map<string, Person>::iterator it = PersonClassMap.find(type) ; 
    if(it != PersonClassMap.end()) 
    { 
     return new Person(it->second); // Use copy constructor to create a new class instance from the prototype. 
    } 
} 

к сожалению, метод прототип работает только, если вы хотите только класс, созданный на заводе т o быть одинаковым каждый раз, поскольку он не поддерживает аргументы.

Кто-нибудь знает, можно ли это сделать красиво, или я придерживаюсь заводской функции?

+0

Между, я надеюсь, что вы удалите этот человек где-то иначе У меня будет утечка памяти.;) – Partial

ответ

0

Я не знаком с C++, но во многих langugaes есть понятия делегатов или закрытий. Это означает, что вместо сопоставления экземпляру вы сопоставляете функцию (делегировать, закрытие), которая отвечает за создание объекта.

+0

C++ не имеет закрытий или чего-то удаленного ар. Существуют указатели на методы, но они ничего не добавляют к решению. – PanCrit

+0

@PanCrit, ты шутишь? Вы знакомы с абстрактным шаблоном завода? Фабрика с единственным методом может быть хорошей заменой для собственных делегатов. И да, это доступно в C++. –

-1

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

+0

перечисление и коммутатор могут работать быстрее, но для работы гораздо больше работы. Я думаю, что предлагаемые поддерживаемые, основанные на объектах решения будут гораздо более ценными. – PanCrit

1

Я обычно строить фабричный метод (или фабричный объект), когда клиенты будут предоставлять некоторую информацию об объекте, который будет создан, но они не знают, какой конкретный класс будет результатом. Определение того, как выразить интерфейс на заводе, полностью зависит от того, какая информация у клиентов. Может быть, они предоставляют строку (например, текст программы, подлежащий анализу) или набор значений параметров (количество измерений и размеров, если мы создаем геометрические объекты в n-пространстве). Затем фабричный метод проверяет информацию и решает, какой объект создать или какой конкретный завод будет вызывать.

Так что решение о том, что строить, не должно быть сделано вызывающим; если она знает, то нет причин для завода. Если список вещей, которые нужно построить, является открытым, у вас может быть даже протокол регистрации, который позволяет конкретным реализациям предоставлять как их метод построения, так и функцию дискриминатора, которая позволила бы фабричному методу решить, какой метод вызывать.

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

-1

Если вы посмотрите на свои две реализации, логически они идентичны. Первая реализация такая же, как и ваша вторая, если рекурсивный цикл был развернут. Так что действительно нет преимущества вашей второй реализации.

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

Один из способов сделать это, что может быть полезно, чтобы иметь эту карту в отдельном XML-файле

<person> 
    <type> student </type> 
    <constructor> Student </type> 
</person> 
.... 

Вы можете прочитать этот XML-файл в памяти и использовать отражение, чтобы получить обратно ваш конструктор. Для данного типа. Учитывая, что вы используете C++, это не так просто, как C++ не имеет отражения в качестве стандарта. Вам нужно будет найти расширение, чтобы предоставить вам отражение в C++.

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

+0

Ваш подход слишком сложный для ничего ... – Partial

+0

способ полностью пропустить точку! Прежде всего я отвечал на цитату из комментария OP: «Это может быть сделано из файла конфигурации, я не уверен», я показал ему способ, которым это можно сделать. Но моя точка зрения, которую вы явно упустили, заключалась в том, что любой из этих подходов все сводится к его первоначальной реализации, к которой он пытался найти альтернативу! – hhafez

1

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

typedef std::string discriminator; 
typedef Base* (*creator)(type1, type2, type3); // concrete factory, in this case a free function 
typedef std::map< discriminator, creator > concrete_map; 
class Factory // abstract 
{ 
public: 
    void register_factory(discriminator d, creator c) { 
     factories_[ d ] = c; 
    } 
    Base* create(discriminator d, type1 a1, type2 a2, type3 a3) 
    { 
     return (*(factories_[ d ]))(a1, a2, a3); 
    } 
private: 
    concrete_map factories_; 
}; 

Я использовал бесплатные функции создателей, чтобы уменьшить образец кода, но вы можете определить тип concrete_factory и использовать его вместо элемента «создателя» выше. Опять же, как вы можете видеть, вы ограничены фиксированным набором аргументов в методе «create» фабрики.

Каждый бетонный завод может передать аргументы конструктору данного типа:

Base* createDerived1(type1 a1, type2 a2, type3 a3) 
{ 
    return new Derived1(a1, a2, a3); 
} 

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

1

Я бы добавил чистый абстрактный метод clone в класс Person (который определенно выглядит как абстрактный класс, существующий в основном для подкласса - если вам нужен конкретный «ни один из вышеперечисленных» видов Человек это лучше всего сделать с помощью отдельного конкретного подкласса OtherKindOfPerson, а не в качестве самого базового класса):

virtual Person* clone() const = 0; 

и переопределить его в каждом конкретном подклассе, например, в Студенте, с new, который вызывает копию CTOR в конкретном бетоне подкласса:

Person* clone() const { return new Student(*this); } 

Вы также должны изменить карту реестра:

std::map<string, Person*> PersonClassMap; 

[[Вы могли бы использовать некоторый умнее указатель, чем равнинный старый Person *, но поскольку карта и все ее записи, вероятно, должны выжить до тех пор, пока процесс не выполняется, это определенно не имеет большого значения - основное добавленное значение, которое вы можете получить от более умных указателей, более умное поведение при уничтожении " указатель "! -)]]

Теперь ваша функция завод может просто закончиться:

return it->second->clone(); 

Изменения необходимы, чтобы избежать «нарезки» эффект использования копий CTOR базового класса на подкласс, который имеет дополнительные атрибуты, а также сохранить любой разрешение виртуального метода.

Подклассы конкретного класса для получения других конкретных классов - плохая идея, потому что эти эффекты могут быть сложными и источником ошибок (см. Рекомендацию Haahr: он пишет о Java, но совет также хорош для C++ и других языков [на самом деле я считаю, что его рекомендация еще более важно, в C++]

0

вы можете сделать перечисление каждого типа человека:.

enum PersonType { student, secretary, professor }; 
Смежные вопросы