Абстрактная фабрика с параметризованными конструкторами - PullRequest
5 голосов
/ 27 ноября 2011

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

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

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

class Shape {
public:
    Shape(){std::cout << "Calling Shape Constructor\n";};
    virtual ~Shape(){std::cout << "Calling Shape Desstructor\n";};
    virtual void draw() const = 0;
    virtual void doSomething1(int) const = 0;
    virtual void doSomething2(int, float) const = 0;
};

class Rectangle : public Shape {
public:
    Rectangle(int l = 0, int b = 0 ):l(l),b(b){ std::cout << "Calling Rectangle Constructor\n"; };
    ~Rectangle(){ std::cout << "Calling Rectangle Destructor\n\n"; };
    virtual void draw() const{ /* Draw Rectangle */ }; 
    virtual void doSomething1(int) const { /* doSomething1 */};
    virtual void doSomething2(int, float) const { /* doSomething2 */};
private:
    int l,b;
};

class Circle : public Shape {
public:
    Circle(int r = 0):r(r){ std::cout << "Calling Circle Constructor\n"; };
    ~Circle(){ std::cout << "Calling Rectangle Destructor\n\n"; };
    virtual void draw() const{ /* Draw Circle*/ }; 
    virtual void doSomething1(int) const { /* doSomething1 */};
    virtual void doSomething2(int, float) const { /* doSomething2 */};
private:
    int r;
};

class ShapeFactory{

public:
    ShapeFactory(int = 0, double = 0);
    std::unique_ptr<Shape> CreateShape(const std::string & );
    ~ShapeFactory();
};

std::unique_ptr<Shape> ShapeFactory::CreateShape(const std::string & type /*, int rad, int side1, int side2, .... */) {

    if ( type == "circle" ) return std::unique_ptr<Shape>(new Circle( /* rad */)); // Should call Circle(int rad)!
    if ( type == "rectangle" ) return std::unique_ptr<Shape>(new Rectangle( /* side1, side2 */)); // Should call Rectangle(int, int)!
    // if ( type == "someNewShape") return std::unique_ptr<Shape>(new someNewShape( /* param1, param2, ... */)); // Should call someNewShape(param1, param2)!
    throw std::invalid_argument("MobileFactory: invalid type: " + type);
}

У меня также есть еще одно сомнение. Представьте, что из-за некоторых потребностей мне нужны члены класса для класса "ShapeFactory". То, что я хотел бы сделать интуитивно, это что-то вроде:

std::vector< std::unique_ptr<ShapeFactory2> > mylist;
mylist.push_back( new ShapeFactory2(CreateShape("circle",radius), param1, param2) );
mylist.push_back( new ShapeFactory2(CreateShape("rectangle",side1,side2), param1, param2) );

for (std::vector< std::unique_ptr<ShapeFactory2> >::const_iterator it = v.begin(), end = v.end(); it != end; ++it)
{
    int param1   = it->param1;
    float param2 = it->param2;
    it->doSomething2(param1, param2);

    // or equivalently 
    Shape * myShape = *it;
    int param1   = it->param1;
    float param2 = it->param2;
    myShape->doSomething2(param1, param2);
}

Как изменится объявление класса ShapeFactory для этого конкретного случая? Буду ли я теперь smart_pointer в качестве члена класса, кроме param1, param2? Если да, кто-нибудь может проиллюстрировать, как реализовать конструкторы / деструкторы?

Все предложения / идеи действительно приветствуются! ; -)

Ответы [ 2 ]

2 голосов
/ 27 ноября 2011

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

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

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

std::auto_ptr<Fitter> Fitter::create( const Data& data ) {
    if ( data.supports_disjoint_fitting() ) {
        return std::auto_ptr<Fitter>( new DisjointFitter(data) );
    } else {
        return std::auto_ptr<Fitter>( new JointFitter(data) );
    }
}

В несколько надуманных формах это может выглядеть так:

enum BasicShape { Round, Edgy };
mylist.push_back( ShapeFactory::CreateShape( Round, 16 ) );

и абстрактный метод фабрики будет выглядеть так:

static std::unique_ptr<Shape> CreateShape(BasicShape shape, double area) {
    if ( shape == Round ) 
        return std::unique_ptr<Shape>( new Circle( sqrt(area / M_PI) ) );
    else
        return std::unique_ptr<Shape>( new Square( sqrt(area) ) );
}
0 голосов
/ 27 ноября 2011

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

Например, допустим, вы пишете загрузчик XML длягеометрии, и во время цикла вы в конечном итоге обрабатываете дочерние элементы элемента <Shapes>.В этот момент у вас будет общий Element объект, который без дальнейших запросов может быть чем угодно.Здесь вы можете передать Element* фабричной функции, которая возвращает Shape*, идея состоит в том, что загрузчик затем может продолжать работать с полученным Shape*, не обращая внимания на то, что на самом деле было сделано Shape,

Это ключевая концепция: для загрузчика ему дается Element*, который он может передать на завод, чтобы получить Shape* без дальнейшего принятия решения со своей стороны.Фактически, это может сделать и любой другой человек, не только загрузчик.

Ваша проблема здесь в том, что вы решили указать свою фабрику на std::string, что (учитывая, что строка является простоtype) недостаточно информации для полного построения объекта.Для вашего приложения вам нужно будет решить, существует ли способ указать все возможные необходимые параметры конструкции для всех классов фабрики.Если нет, то вам не нужно было заводить для начала.

...