Когда использовать переменные-элементы против дизайна по композиции? - PullRequest
0 голосов
/ 20 мая 2019

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

Допустим, это приложение, похожее на ломбард. Я сократил поток покупки и перепродажи в нечто, называемое ExecutionStrategy. В этом приложении есть четыре реализации ExecutionStrategy: регистрация клиента, участие в торгах и покупке, ценообразование и размещение в магазине.

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

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

это то, где я немного запутался в решении, которое я хотел бы принять с точки зрения дизайна. кажется, что есть три варианта, и я не уверен, какой из них наиболее правильный. 1) Расширение стратегии выполнения с помощью что-то вроде ExecutionStrategy с ExecutionStrategyWithConsultation, которое объединяет рабочий процесс выполнения стратегии с фазой консультации. 2) Создание шаблона Decorator для ExecutionStrategy и расширяет его с помощью чего-то вроде ConsultationServiceDecorator. 3) создать переменную члена в реализации закупок / торгов и ценообразования для вызова консультационной службы в любое время с помощью интерфейса вокруг службы.

Я обрисую схему ниже.

Некоторые соображения:

  • Консультационная служба работает очень и очень медленно. Кэширование на самом деле не вариант здесь, так как данные очень плохо сформированы, и мы не хотим создавать хранилище данных документа только для этого.
  • ConsultationService возвращает объект, который соответствует тому, что было дано. В итоге получается 1 метод, который выглядит как T consultOn(T item)
  • Может возникнуть желание вызвать ConsultationService в любое время в рабочем процессе выполнения. В настоящее время единственным вариантом использования является вызов службы перед основным потоком, но сейчас это не обязательно единственный вариант использования.

Плюсы / минусы каждого подхода выше:

  1. Прямое расширение стратегии исполнения:

    • PRO: у нас может быть доступ к защищенной переменной ConsultationService в коде
    • PRO: При чтении кода у нас есть понимание, скажем, PurchasingExecutionStrategy extends ExecutionStrategyWithConsultation, поэтому мы немного знаем о том, какой это рабочий процесс именно из этого.
    • CON: Кажется, это нарушает шаблон «композиция поверх наследования». Мы используем наследование для хранения переменных-членов.
    • CON: Служба возвращает совершенно новый объект, поэтому после первой строки кода, когда мы выполняем вызов службы, мы имеем дело с совершенно другим объектом, чем тот, который был передан изначально.
  2. Создание декоратора:

    • PRO: Мы более тесно соответствуем составу, а не принципу наследования.
    • PRO: мы можем обеспечить, чтобы служба вызывалась первой, и передавать этот новый объект в основной рабочий процесс, чтобы он выполнял только свой основной рабочий процесс для переданного объекта.
    • CON: Я не придумал, как спроектировать это таким образом, чтобы можно было принимать несколько или несколько вызовов времени.
    • CON: Когда мы смотрим на код, мы теряем знания, полученные из PurchasingExecutionStrategy extends ExecutionStrategyWithConsultation, если только мы не посмотрим, где на самом деле создается экземпляр PurchasingExecutionStrategy как аргумент конструктора ConsultationServiceDecorator
  3. Создание переменной-члена с интерфейсом:

    • PRO: Те же плюсы, что и # 1. Легко понять, что делает код без копания.
    • CON: Те же минусы, что и # 1. Невозможно навести порядок. Исполнение имеет дело с наследственно отличным объектом, чем переданный.
    • CON: Если нам нужно сделать несколько вызовов в одном рабочем процессе, это будет очень медленно из-за скорости обслуживания и отсутствия кэша.

Примеры каждого:

//Number 1
public interface ExecutionStrategy<T> {

    /**
    * Perform the main execution workflow
    */
    public T execute(T item);
}

public interface ConsultationService {

    public StoreItem consultOn (StoreItem item);
}

public abstract class ExecutionStrategyWithConsultation implements ExecutionStrategy<StoreItem> {

    protected ConsultationService consultationService;

}

public class ListingExecutionStrategy extends ExecutionStrategyWithConsultation {

    public StoreItem execute(StoreItem item) {
      if (item.hasDirectBuyer()) { //hasDirectBuyer is populated by ConsultationService
        item.sellTo = item.directBuyer.getId();
      } else {
        //no direct buyer
        SuggestedPriceRange priceRange = item.getConsultationPriceRange(); //consultationPriceRange is populated by ConsultationService
        item.priceRange = priceRange;
        item.listToWebsite = true;
      }
      return item;
    }
}

//Number 2
public interface ExecutionStrategy<T> {

    /**
    * Perform the main execution workflow
    */
    public T execute(T item);
}

public abstract class ExecutionStrategyDecorator<T> implements ExecutionStrategy<T>{

    protected final ExecutionStrategy<T> executionStrategy;
    public ExecutionStrategyDecorator(ExecutionStrategy<T> execStrategy) {
      executionStrategy = execStrategy;
    };
}

public class ExecutionStrategyWithConsultation extends ExecutionStrategyDecorator<StoreItem> {

    protected ConsultationService consultationService;

    public ExecutionStrategyWithConsultation(ExecutionStrategy<StoreItem> execStrat, ConsultationService service) {
      super(execStrat);
      consultationService = service;
    }

    public StoreItem execute(StoreItem item) {
      StoreItem itemAfterConsultation = consultationService.consultOn(item);
      return execStrategy.execute(itemAfterConsultation);
    }

}

public class ListingExecutionStrategy implements ExecutionStrategy<StoreItem> {

    public StoreItem execute(StoreItem item) {
      if (item.hasDirectBuyer()) { //hasDirectBuyer is populated by ConsultationService
        item.sellTo = buyer.getId();
      } else {
        //no direct buyer
        SuggestedPriceRange priceRange = item.getConsultationPriceRange(); //consultationPriceRange is populated by ConsultationService
        item.priceRange = priceRange;
        item.listToWebsite = true;
      }
      return item;
    }
}

public class ListingExecutionStrategyFactory {

    public ExecutionStrategy instantiate() {
      return new ExecutionStrategyWithConsultation(new ListingExecutionStrategy(), new ConsultationServiceImpl());
    }
}

//Number 3
public interface ExecutionStrategy<T> {

    /**
    * Perform the main execution workflow
    */
    public T execute(T item);
}

public interface ConsultationService {

    public DirectBuyer getDirectBuyerIfExists(StoreItemType itemType);
    public SuggestedPriceRange getSuggestedPriceRange(StoreItem item);
}

public class ListingExecutionStrategy implements ExecutionStrategy<StoreItem> {

    ConsultationService service;

    public PurchasingExecutionStrategy(ConsultationService consultService) {
        service = ConsultationService;
    }

    public StoreItem execute(StoreItem item) {
      DirectBuyer buyer = service.getDirectBuyerIfExists(item.getItemType())
      if (Optional.ofNullable(buyer).isPresent()) {
        item.sellTo = buyer.getId();
        return item;
      } else {
        //no direct buyer
        SuggestedPriceRange priceRange = service.getSuggestedPriceRange(item);
        item.priceRange = priceRange;
        item.listToWebsite = true;
        return item;
      }
    }
}

Спасибо за ввод. Ценю помощь.

1 Ответ

1 голос
/ 20 мая 2019

В качестве альтернативы вашему ConsultationService вы можете рассмотреть возможность создания цепочки экземпляров ExecutionService, позволяющих создавать сложные сценарии обработки:

public interface ExecutionStrategy<T> {
    public T execute(T item);
}


public interface ExecutionStrategyChain<T> extends ExecutionStrategy<T> {
    public static <T> ExecutionStrategyChain<T> newInstance(ExecutionStrategy<T> executionStrategy) {
        return new ExecutionStrategyChainImpl<T>(executionStrategy); 
    }

    public ExecutionStrategyChain<C> chainTo(ExecutionStrategy<C> executionStrategy);
}


public abstract class AbstractExecutionStrategyChain<T> implements ExecutionStrategyChain<T> {
    protected AbstractExecutionStrategyChain() {
        this(null);
    }


    public abstract T execute(T item);


    public ExecutionStrategyChain<T> chainTo(ExecutionStrategy<T> executionStrategy) {
        return new ExecutionStrategyChainImpl<T>(this, executionStrategy);
    }
}


public final class ExecutionStrategyChainImpl<T> extends AbstractExecutionStrategyChain<T> {

    private final ExecutionStrategy<T> firstExecutionStrategy;
    private final Executionstrategy<T> secondExecutionStrategy;

    public ExecutionStrategyChainImpl(ExecutionStrategy<T> first, ExecutionStrategy<T> second) {
        if(first == null) throw new NullPointerException();
        this.firstExecutionStrategy = first;
        this.secondExecutionStrategy = second;
    }

    public ExecutionStrategyChainImpl(ExecutionStrategy<T> first) {
        this(first, null);
    }

    @Override
    public final T execute(T item) {
        if(item == null) {
            return null;
        }

        T result = firstExecutionStrategy.execute(item);
        if(result != null && secondExecutionStrategy != null) {
            result = secondExecutionStrategy.execute(result);
        }
        return result;
    }
}


public class PreProcessor<T> implements ExecutionStrategy<T> {

    public PreProcessor() {
    }

    @Override
    public T execute(T item) {
       //Do some pre-processing of item
       return item;
    }
}


public class PostProcessor<T> implements ExecutionStrategy<T> {

    public PostProcessor() {
    }

    @Override
    public T execute(T item) {
       //Do some post-processing of item
       return item;
    }
}


public class MyNormalProcessor<T> extends AbstractExecutionStrategyChain<T> {

    public MyNormalProcessor() {
    }

    @Override
    public T execute(T item) {
       //Do something normal with the item
       return item;
    }
}


public static final ExecutionStrategy<StoreItem> COMPLEX_EXECUTION_STRATEGY = 
    ExecutionStrategyChain<StoreItem>.newInstance(new PreProcessor<StoreItem>())
                                     .chainTo(new MyNormalProcessor<StoreItem>())
                                     .chainTo(new PostProcessor<StoreItem>());
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...