Шаблон "Фабричный метод". В чём преимущества данного паттерна?

На странице https://refactoring.guru/ru/design-patterns/factory-method написано, что основная задача, которую решает данный паттерн - это избавление кода от привязки к конкретным классам.

Вот пример кода, который там приведён:

// Паттерн Фабричный метод применим тогда, когда в программе
// есть иерархия классов продуктов.
interface Button is
    method render()
    method onClick(f)

class WindowsButton implements Button is
    method render(a, b) is
        // Отрисовать кнопку в стиле Windows.
    method onClick(f) is
        // Навесить на кнопку обработчик событий Windows.

class HTMLButton implements Button is
    method render(a, b) is
        // Вернуть HTML-код кнопки.
    method onClick(f) is
        // Навесить на кнопку обработчик события браузера.


// Базовый класс фабрики. Заметьте, что "фабрика" — это всего
// лишь дополнительная роль для класса. Скорее всего, он уже
// имеет какую-то бизнес-логику, в которой требуется создание
// разнообразных продуктов.
class Dialog is
    method render() is
        // Чтобы использовать фабричный метод, вы должны
        // убедиться в том, что эта бизнес-логика не зависит от
        // конкретных классов продуктов. Button — это общий
        // интерфейс кнопок, поэтому все хорошо.
        Button okButton = createButton()
        okButton.onClick(closeDialog)
        okButton.render()

    // Мы выносим весь код создания продуктов в особый метод,
    // который назвают "фабричным".
    abstract method createButton():Button


// Конкретные фабрики переопределяют фабричный метод и
// возвращают из него собственные продукты.
class WindowsDialog extends Dialog is
    method createButton():Button is
        return new WindowsButton()

class WebDialog extends Dialog is
    method createButton():Button is
        return new HTMLButton()


class Application is
    field dialog: Dialog

    // Приложение создаёт определённую фабрику в зависимости от
    // конфигурации или окружения.
    method initialize() is
        config = readApplicationConfigFile()

        if (config.OS == "Windows") then
            dialog = new WindowsDialog()
        else if (config.OS == "Web") then
            dialog = new WebDialog()
        else
            throw new Exception("Error! Unknown operating system.")

    // Если весь остальной клиентский код работает с фабриками и
    // продуктами только через общий интерфейс, то для него
    // будет не важно, какая фабрика была создана изначально.
    method main() is
        this.initialize()
        dialog.render()

Но мне непонятен этот пример. Фабрика мне тут кажется бессмысленной, поскольку я и без неё могу избавить код от привязки к конкретным классам. Вот тот же пример, который я переписал без фабрик и привязки клиентского кода к конкретным классам:

interface Button is
    method render()
    method onClick(f)

class WindowsButton implements Button is
    method render(a, b) is
        // Отрисовать кнопку в стиле Windows.
    method onClick(f) is
        // Навесить на кнопку обработчик событий Windows.

class HTMLButton implements Button is
    method render(a, b) is
        // Вернуть HTML-код кнопки.
    method onClick(f) is
        // Навесить на кнопку обработчик события браузера.


class Application is
    // нет привязки к конкретной кнопке
    field button: Button

    method initialize() is
        config = readApplicationConfigFile()

        if (config.OS == "Windows") then
            button = new WindowsButton()
        else if (config.OS == "Web") then
            button = new HTMLButton()
        else
            throw new Exception("Error! Unknown operating system.")

    method main() is
        this.initialize()
        button.render()

Вопрос: какая разница между двумя примерами? Какие преимущества дают фабрики? Заранее спасибо за помощь.


Ответы (2 шт):

Автор решения: Aziz Umarov

Когда следует использовать фабричный метод

  1. Когда заранее неизвестно, объекты каких типов необходимо создавать;

  2. Когда система должна быть независимой от процесса создания новых объектов и расширяемой: в нее можно легко вводить новые классы, объекты которых система должна создавать;

  3. Когда создание новых объектов необходимо делегировать из базового класса классам наследникам;

Если объяснения на данном вам сайте не устраивают то посмотрите подробнее тут

→ Ссылка
Автор решения: Bogdan

Из Design Pattern - GoF:

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

Важный момент в том, что инстанцирование осуществляется подклассами. В случае:

if (config.OS == "Windows") then
    button = new WindowsButton()
else if (config.OS == "Web") then
    button = new HTMLButton()
else
    throw new Exception("Error! Unknown operating system.")

осуществляется прямое создание интересующих объектов.

А что если приложение достаточно масштабное, и мест, где можно создать WindowsButton или HTMLButton в коде больше одного? Больше двух? Больше сотни?

А что если нужно создавать не только кнопки, но еще и Label, TextEdit, RadioButton и т.п.?

Прописать везде условный оператор? Или проще объявить пару классов:

class WindowsCreator implements Creator is
    method createButton() is
        // вернуть Windows кнопку
    
    method createLabel() is
        // вернуть Windows label

    // и т.д.

class HTMLCreator implements Creator is
    method createButton() is
        // вернуть HTML код кнопки

    method createLabel() is
        // вернуть HTML код label

    // и т.д.

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

→ Ссылка