Как отделить детали реализации, когда они зависят от данных в вызывающем коде - PullRequest
0 голосов
/ 06 июня 2018

Как вы отделите реализацию интерфейса от вызывающего кода, если эффекты реализации должны зависеть от некоторых параметров в вызывающем коде?

Это может показаться запутанным, поэтому вот реальный пример:

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

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

Если мы передадим какие-либо данные (данные бренда или конфигурацию соединения с репозиторием) в репозиторий, мылибо связывает интерфейс репозитория с сущностью бренда, либо код обработки заказа с деталями реализации репозитория.

Итак, как реализовать этот сценарий в соответствии с принципом IoC?Предложения других моделей развязки также приветствуются.

1 Ответ

0 голосов
/ 17 июня 2018

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

Однако, чтобы упростить дальнейшее обслуживание кода, я использовал следующую архитектуру (в псевдокоде):

Основные интерфейсы:

// Main repository interface
interface OrdersRepositoryInterface {
    store(order: Order): bool;

    // …other methods
}

// An interface of a factory that will be used to create repository instances with different configurations (different underlying storages, for example)
interface OrdersRepositoryFactoryInterface {
    createRepository(configuration: OrdersRepositoryConfigurationInterface): OrdersRepositoryInterface;
}

// An interface of a container that will create different instances of repositories based on specified brand
// An implementation of this interface is the coupling point between business logic and data persistence logic
interface OrdersRepositoryContainerInterface {
    get(brand: Brand): OrdersRepositoryInterface;
}

Реализация фабрики репозитория (тесно связанная с самим репозиторием, можно избежать, если в самом интерфейсе был указан конструктор репозитория, но я лично считаю, что это плохая практика):

class OrdersRepositoryImplementation implements OrdersRepositoryInterface {
    constructor(configuration: OrdersRepositoryConfigurationInterface) {
        // …
    }

    store(order: Order): bool {
        // …
    }
}

class OrdersRepositoryFactory implements OrdersRepositoryFactoryInterface {
    createRepository(configuration: OrdersRepositoryConfigurationInterface): OrdersRepositoryInterface {
        return new OrdersRepositoryImplementation(configuration);
    }
}

Реализация контейнера репозитория:

class OrdersRepositoryContainer implements OrdersRepositoryContainerInterface {
    get(brand: Brand): OrdersRepositoryInterface {
        var factory = IoC.resolve(OrdersRepositoryFactoryInterface);

        var configuration1 = …;
        var configuration2 = …;

        if (brand.slug === "brand1") {
            return factory.createRepository(configuration1);
        } else {
            return factory.createRepository(configuration2);
        }
    }
}

Привязка IoC-контейнера (если контейнер поддерживает это, вероятно, лучше связывать классы, а не экземпляры, поскольку автоматическое внедрение зависимости может использоваться в конструкторах этих реализаций):

IoC.bindInstance(OrdersRepositoryFactoryInterface, new OrdersRepositoryFactory());
IoC.bindInstance(OrdersRepositoryContainerInterface, new OrdersRepositoryContainer());

И последний, но не менее важный, код обработки заказа:

var order = …;

var repositoryContainer = IoC.resolve(OrdersRepositoryContainerInterface);

var repository = repositoryContainer.get(order.brand);

repository.store(order);

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

Я отмечу этот ответ как принятый, но я с готовностью его поменяю, если кто-нибудь предложитлучшая идея.

...