Как стать автором
Обновить

Шаблоны GRASP: Creator (создатель)

Время на прочтение3 мин
Количество просмотров14K
Всего голосов 14: ↑13 и ↓1+12
Комментарии15

Комментарии 15

Как будет выглядеть создание зависимости, которой нужны другие зависимости, которым нужны ещё зависимости и т.д.? Зависимость от реализации, а не от интерфейса — с этим проблем не возникает?

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


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

Но суть паттерна — «создавать экземпляры класса должен класс, которому они нужны».
Создание через фабрику явно противоречит этому.
Ну и всё таки пример хотелось бы посмотреть

Ну вы не совсем правильно воспринимаете фабрику. Она не отменяет "создавать экземпляры класса должен класс, которому они нужны". Она только меняет КАК создаются экземпляры класса. То есть в обоих примерах ниже они будут создаваться в Client, но в одном — напрямую, а во втором — через фабрику:


public class Client {
    public void doSmth() {
        Order order = new Order("address");
    }
}

public class Client {
    public void doSmth() {
        Order order = orderFactory.create("address");
    }
}

В результате код получается какой-то такой:


class Order {
    constructor (
        // сообственные свойства
        public readonly OrderName name,
        public readonly Money cost,

        // зависимости
        private readonly Factory1 f1,
        private readonly SomeUniqueClass uniq
    ) {}
}

class OrderFactory {
    constructor (
        private readonly Factory1 f1,
        private readonly SomeUniqueClass uniq
    ) {}

    Order Create(OrderName name, Money cost) {
        return new Order(name, cost, f1, uniq);
    }
}

class Client {
    constructor (
        // зависимости
        private readonly OrderFactory orderFactory,
    ) {}

    public void Start() {
        Order order = orderFactory.Create(
            new OrderName("address"),
            new Money(100)
        );
    }
}

class Installer {
    static void main => new Installer().install();

    void install () {
        var f1 = new Factory1();
        var f2 = new Factory2();

        var uniq = new SomeUniqueClass(f2);

        var orderFactory = new OrderFactory(f1, uniq);

        var client = new Client(orderFactory);
        client.Start();
    }
}

Потому обмазываете это DI Container'ом и полетели

Справедливости ради, 'Класс создаёт экземпляры класса' и 'Класс запрашивает у фабрики экземпляры класса' — это концептуально разные подходы.

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

class Order {
    constructor (
        // сообственные свойства
        public readonly OrderName name,
        public readonly Money cost,

        // зависимости
        private readonly Factory1 f1,
        private readonly SomeUniqueClass uniq
    ) {}
}

class Client {
    constructor (
        private readonly Factory1 f1,
        private readonly SomeUniqueClass uniq
    ) {}

    public void Start() {
        Order order = new Order(
            new OrderName("address"),
            new Money(100),
            f1,
            uniq
        );
    }
}

class Installer {
    static void main => new Installer().install();

    void install () {
        var f1 = new Factory1();
        var f2 = new Factory2();

        var uniq = new SomeUniqueClass(f2);

        var client = new Client(f1, uniq);
        client.Start();
    }
}
В чём плюс подхода, когда через конструктор получаем фабрику, а в методах получаем из фабрики зависимости? Почему сразу не получить зависимости в конструкторе тогда?

В том, что вам нужно пробрасывать все зависимости везде, где создается класс. А если меняется список зависимостей — вместо того, чтобы поменять его в одном месте рядом с самим классом — нужно бегать по всем местам и тоже как-то пробрасывать новые зависимости.


Справедливости ради, 'Класс создаёт экземпляры класса' и 'Класс запрашивает у фабрики экземпляры класса' — это концептуально разные подходы.

Класс говорит: "когда создать", а фабрика: "как создать".

В том, что вам нужно пробрасывать все зависимости везде, где создается класс


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

А если меняется список зависимостей — вместо того, чтобы поменять его в одном месте рядом с самим классом — нужно бегать по всем местам и тоже как-то пробрасывать новые зависимости.


В случае использования инъекции в конструктор, я поменяю в одном месте — в конструкторе класса, где изменился список зависимостей. Остальное DI контейнер сделает.

Класс говорит: «когда создать», а фабрика: «как создать».
Тогда суть паттерна не ясна мне. «создавать экземпляры класса должен класс, которому они нужны» — для чего эта формулировка, если по факту она очень сильно притянута? Класс всё ещё не создаёт ничего, он определяет КОГДА создавать в данном случае.
В случае инъекции в конструктор и с использованием DI контейнера

Ну и как вы с DI Container'ом то же самое без фабрики сделаете?) Ну то есть, если вам необходимо, чтобы в классе через конструктор передавалось два уникальных параметра и две зависимости — как этого добъётесь? Покажите пример, как создали бы этот класс?


class Order {
    constructor (
        // сообственные свойства
        public readonly OrderName name,
        public readonly Money cost,

        // зависимости
        private readonly Factory1 f1,
        private readonly SomeUniqueClass uniq
    ) {}
}
Я выше скидывал пример, там вместо использования фабрики, Client передаёт свои зависимости в Order.

class Client {
    constructor (
        private readonly Factory1 f1,
        private readonly SomeUniqueClass uniq
    ) {}

    public void Start() {
        Order order = new Order(
            new OrderName("address"),
            new Money(100),
            f1,
            uniq
        );
    }
}


Вот тут действительно класс сам создаёт нужную зависимость.

Это если оставить в стороне вопрос, что такая сущность выглядит странно немного. Ну т.е. в идеале если мы создаём сущность 'Заказ', лучше чтоб она не включала в себя зависимости в виде сервисов.

Вообще, описание паттерна не звучит как 'создавать экземпляры класса должен класс, которому они нужны'
Я выше скидывал пример, там вместо использования фабрики, Client передаёт свои зависимости в Order.

Ну так в таком случае ВСЕ, кто используют этот класс должны знать о его зависимостях. И чем больше использований — тем больше изменений при рефакторинге. Фабрика значительно лучше тем, что нужно изменять только в одном месте. Плюс ещё в том, что:


Ну т.е. в идеале если мы создаём сущность 'Заказ', лучше чтоб она не включала в себя зависимости в виде сервисов.

Фабрика даёт нам адекватный интерфейс без зависимостей.


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

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


Я говорил про DI container, использование которого позволяет перечислить зависимости в конструкторе класса и не создавать явно никакие сервисы.
В случае создания сущности типа 'Заказ', если ей нужны
какие то runtime параметры — да, это делается через фабрику. Но это частный случай.
Но изначальный вопрос был — 'Что делать, если там зависимость у которой другие зависимости и прочее?'. Если я руками создавать будут класс, которому нужно передать ещё n классов, которым ещё n классов — то что получится? Как будет код выглядеть? Они все сами создавать будут зависимости? Что в таком случае с зависимостью от реализации, а не от абстракции?
Для примера — создание контроллера, которому нужен сервис приложения, которому нужны какие нибудь репозиторий, сервис логирования, интеграции и т.д.

Ну вот для вашего примера прекрасно подходят фабрики. Они создают всё, что нужно и сами думают о зависимостях.

Как-то даже хуже стало. Теперь давайте представим, что мы не хотим динамики и всяких стрингов, а хотим статики. Вместо Good("pants") и Good("hats") мы вводим PantsGood и HatsGood. Всё, ваша идея сломалась.


Я, конечно, понимаю, что в примере с товарами не всегда можно ввести статику (но иногда можно), но у вас в принципе нельзя ввести статику. А клиенту всё-равно нужно знать название товара.


Или другой пример. Мы вводим такую сущность как GoodName, чтобы не использовать string (мало ли у нас строк в апке?) и Money, чтобы не использовать инт (чисел ещё больше разных).


public class Good {
    private GoodName name;
    private Money price;
}

Ваш Client все-равно должен знать о куче классов


public class Client {
    public void doSmth() {
        Order order = new Order("address");
        order.addOrderItem(amount, new GoodName("hats"), new Money(1000));
        // client code 
    }
}
Мне не очень понятно, а если у меня не 3 свойства у OrderItem, а 10? Да и еще они меняются в процессе разработки. Поддерживать не очень-то просто.
Получается, что я должен в 1 метод передавать 10 аргументов и не перепутать их порядок.

Из приятного будет только то, что в классе клиент возможно не будет импорта класса OrderItem. Но только возможно, потому что вы не можете гарантировать, что бизнес логика не потребует оперировать в классе клиент OrderItem'ом без заказа (клиент делает запрос в поддержку об OrderItem).

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