Arhn - архитектура программирования

Абстрактная фабрика с параметризованными конструкторами

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

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

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

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? Если да, может ли кто-нибудь проиллюстрировать, как реализовать конструкторы/деструкторы?

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


  • Ваша фабрика не абстрактна. Это очень бетонный завод, который производит Shapes. Абстрактная фабрика — гораздо более ужасный зверь. 27.11.2011
  • @KerrekSB, хорошо. Скажите, что у меня есть этот concrete заводской класс. Есть ли у вас какие-либо предложения, как я могу решить вторую проблему моего вопроса? 27.11.2011
  • Не боюсь. У меня нет ответа, у меня был только комментарий к названию вашего вопроса... 27.11.2011

Ответы:


1

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

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

Я приведу вам короткий пример разумной фабрики с аргументами из моего недавнего кода: предположим, вы хотите подогнать функцию под пиксели изображения. Возможны два способа вычисления: непересекающиеся (строки и столбцы отделены друг от друга) и совместные. Непересекающаяся подгонка дешевле, но невозможна для некоторых данных. Эти способы вычисления поддерживаются двумя разными классами, 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) ) );
}
27.11.2011
  • хорошо, я вижу, теперь представьте, что конструкторы похожи (например, классы Circle и Cylinder). Как бы вы адаптировали этот шаблон для использования с параметризованными конструкторами? 27.11.2011
  • Итак, я предполагаю, что ваш базовый класс — Fitter, производные классы — DisjointFitter и JointFitter. Идея состоит в том, чтобы инкапсулировать параметры для создания объектов в классе с именем Data, верно? Что касается второй части моего поста, как будет выглядеть дизайн класса Abstract Factory? 27.11.2011
  • @Tin: параметры не инкапсулированы для объекта Factory, но они достаточно похожи с самого начала, чтобы не нуждаться в дальнейшей инкапсуляции. Если это не указано, фабричный объект или метод — это ерунда. Я написал пример для фигур, но он все еще очень надуманный. 27.11.2011
  • @ thiton: Спасибо за иллюстрацию и пояснение. Я также работаю с обработкой изображений, и пример является псевдонимом того, что я хотел бы решить. По сути, у меня есть абстрактный класс: Kernel2D‹T› и несколько производных классов, в основном: SeparableKernel‹T›, NonSeparableKernel‹T›. Итак, я хотел создать этот фабричный объект в качестве среднего слоя для своих ядер. Кроме того, мне нужен член класса в фабричном объекте, который определяет, в каком цветовом канале должно применяться данное ядро. Таким образом, doSomething1 на самом деле является convolution, а param1 — полосой изображения. Какие-либо предложения? 27.11.2011
  • @Tin: я предполагаю, что ваши преобразования (например, гауссовское или среднее) в любом случае будут происходить либо из SeparableKernel, либо из NonSeparableKernel, поэтому фабрика, вероятно, здесь бесполезна. 27.11.2011
  • @ Титон, да. Например, функция Gaussian определяется как дружественная функция (SepKernel‹T› createGaussian(double sigma){ SepKernel‹T› k; ....; return k; }). Я хотел бы добиться чего-то вроде: filterBank.push_back(new FilterBankElement(Kernel2D<float>(createGaussian(sigma)), imageBand) ). У меня есть этот класс FilterBankElement, где я хотел бы иметь указатель на конкретное ядро, чтобы, когда я это делаю, filterBank[0]->convolve(image, filterBank[0]->imageBand) выбирался правильный метод convolution в зависимости от типа фильтра. 27.11.2011

  • 2

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

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

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

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

    27.11.2011
    Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..