о статических функциях-членах в чисто абстрактных классах - шаблон проектирования? - PullRequest
4 голосов
/ 22 января 2012

Недавно я нашел пример использования static member functions в pure abstract classes для инициализации указателей на объекты его производных классов.Мне было интересно, если это шаблон проектирования или это хорошая практика программирования?Приведенный ниже код является лишь иллюстрацией (например, обе функции-члена defineRectangle() and defineCircle()):

class Shape;

typedef std::unique_ptr<Shape> shape_ptr;

class Shape{

    public:

        Shape(){};
        virtual ~Shape(){};

        virtual void draw() const = 0;
        virtual float area() const = 0;

        virtual shape_ptr clone() const = 0;
        virtual shape_ptr create() const = 0;

        static shape_ptr defineRectangle(int, int );
        static shape_ptr defineCircle(float);
};

shape_ptr Shape::defineRectangle(int height, int width){
    shape_ptr ptrRectangle = shape_ptr(new Rectangle(height, width));
    return (ptrRectangle);
}

shape_ptr Shape::defineCircle(float radius){
    shape_ptr ptrCircle = shape_ptr(new Circle(radius));
    return (ptrCircle);
}

Конечная цель - определить контейнер derived classes.Например:

std::vector<std::unique_ptr<Shape> > vect;

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

vect.push_back(Shape::defineCircle(10));
vect.push_back(Shape::defineRectangle(5, 4));

или напрямую, без каких-либоинтерфейс:

vect.push_back(std::unique_ptr<Shape>(new Circle(10)));
vect.push_back(std::unique_ptr<Shape>(new Rectangle(5,4)));

Какой из двух способов добавления производного класса в контейнер должен быть предпочтительным и почему?
Полный код можно найти по следующей ссылке .
Любые огни на нем действительно приветствуются; -)

Ответы [ 3 ]

1 голос
/ 22 января 2012

Мне было интересно, если это шаблон проектирования или это хорошая практика программирования?

Да, это вариант фабричного шаблона .

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

Причина использования смарт-указателей с фабрикой состоит в том, чтобы избежать памятиутечки, которые могут возникнуть, когда владение указателем неоднозначно.Например, фабрика должна возвращать указатель, так как она создаёт объект в двухстороннем порядке.Тогда возникает вопрос, кто очищает указатель, чтобы избежать либо свисающих указателей, либо утечек памяти?Интеллектуальные указатели устраняют эту проблему владения и гарантируют, что память не будет случайно очищена, когда другие объекты все еще указывают на эту память, или что память не просто теряется, поскольку последний указатель на эту область памяти выходит из области видимости безdelete призвал его.

1 голос
/ 23 января 2012

Я бы не советовал помещать фабричные методы в базовый класс, потому что технически Shape ничего не знает о Rectangle или Circle.Если вы добавите новую фигуру, например Donut, что вы будете делать?Добавить новый фабричный метод к Shape?Вы будете загромождать интерфейс в кратчайшие сроки.Итак, IMO, второй метод был бы лучше.

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

class Circle : public Shape
{
    // ...
public:
    static shape_ptr make(float radius) 
    { 
        return shape_ptr(new Circle(radius)); 
    }
};

// ...

vect.push_back(Circle::make(5.0f));
1 голос
/ 22 января 2012

Поскольку существует std::unique_ptr, я предполагаю, что компилятор поддерживает C ++ 11. В этом случае позвольте мне предложить третий вариант:

vect.emplace_back(new Circle(10));
vect.emplace_back(new Rectangle(5,4));

.emplace_back: push_back против emplace_back )

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


Редактировать: В C ++ 14 вы можете использовать std::make_unique, чтобы избавиться от необработанного new вызова.

vect.emplace_back(std::make_unique<Circle>(10));
vect.emplace_back(std::make_unique<Rectangle>(5, 4));
...