Какой шаблон проектирования использовать - PullRequest
0 голосов
/ 26 мая 2018

Допустим, у меня есть класс продуктов, где у меня есть запас и я могу заплатить двумя способами: Paypal или наличными.Я могу продать только 2 продукта за наличный расчет.Это может измениться в будущем, поэтому я не хочу менять весь код с помощью ifs и прочего, поэтому я подумал об использовании шаблона стратегии.

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

Итак, это хороший шаблон для моего случая?Я мог бы добавить больше проверок в класс Cash или в класс Paypal.

Редактировать: я добавляю дополнительную информацию.

Это всего лишь пример.У меня есть только одна проверка, например, максимальная сумма, которую я могу продать, составляет 100 долларов, если это наличные, или 10000 долларов, если это Paypal.Но, скажем, завтра они попросят меня добавить еще одно подтверждение, что я не могу продавать наличные после 9 вечера.Я не знаю, как поместить его в дизайн, не используйте шаблон стратегии.

Edit2:

Позвольте мне привести еще один пример, который мог бы прояснить.

Вы можете забронировать билеты двумя способами: Paypal или Cash.Если вы платите наличными, я хочу разрешить только 2 билета, но если вы используете Paypal, вы можете купить любую сумму, какую пожелаете.

Итак, у меня есть класс с названием Reservation, в котором есть 2 детей: Paypal Cash

У меня есть целое число, которое называется numberOfTickets при бронировании.На наличные деньги у меня есть целое число скидок На PayPal у меня есть адрес электронной почты для учетной записи.

Теперь я хочу добавить некоторые правила, во-первых, ограничить до 2 билетов, если это наличные.Завтра у меня может быть 10 лимитов для Paypal.

Итак, является ли стратегия лучшей?

Ответы [ 3 ]

0 голосов
/ 28 мая 2018

Ваш выбор использования шаблона стратегии правильный.Чтобы решить вашу проблему расширения, вы можете смешать с шаблоном Decorator, как мой пример кода на Java ниже.Для этого я использовал только тип int для всех входных данных, таких как количество, время и количество билетов.

Диаграмма классов enter image description here

Реализация

public abstract class Payment {
protected int amount;
protected int time;
protected int numTickets;

public Payment (int amount, int numTickets) {
    this.amount = amount; 
    this.numTickets = numTickets;
}

public Payment (int amount, int time, int numTickets) {
    this.amount = amount; 
    this.time = time;
    this.numTickets = numTickets;
}

public abstract void doPayment();
}
public class CashPayment extends Payment {

public CashPayment(int amount, int time, int numTickets) {
    super(amount, time, numTickets);
}

@Override
public void doPayment() {
    System.out.println("Make the payment in Cash");
}
}

public abstract class Verificator {
protected Payment payment;
protected int maxAmount;
protected int maxTime;
protected int maxNumTickets;

public abstract void verify();
public abstract void verifyUpperBound(int amount, int max);
public abstract void verifyTime(int time, int max);
public abstract void verifyNumberTickets(int numTicket, int max);

public Verificator(Payment payment, int maxAmount, int maxNumTickets) {
    this.payment = payment;
    this.maxAmount = maxAmount;
    this.maxNumTickets = maxNumTickets;
}

public Verificator(Payment payment, int maxAmount, int maxTime, int 
maxNumTickets) {
    this.payment = payment;
    this.maxAmount = maxAmount;
    this.maxTime = maxTime;
    this.maxNumTickets = maxNumTickets;
}   
}

public class CashPaymentVerificator extends Verificator {

public CashPaymentVerificator(Payment payment, int maxAmount, int maxTime, int maxNumTickets) {
    super(payment, maxAmount, maxTime, maxNumTickets);
}

@Override
public void verify() {
    verifyUpperBound(this.payment.getAmount(), this.maxAmount);
    verifyTime(this.payment.getTime(), this.maxTime);
    verifyNumberTickets(this.payment.getNumTickets(), this.maxNumTickets);
}

@Override
public void verifyUpperBound(int amount, int max) {
    if (amount > max)
        throw new IllegalStateException("Can not pay cash over $"+max);
}

@Override
public void verifyTime(int time, int max) {
    if (time > max)
        throw new IllegalStateException("Can not pay cash after "+max+" PM");
}

@Override
public void verifyNumberTickets(int numTicket, int max) {
    if (numTicket > max)
        throw new IllegalStateException("Can not reserve more than "+max+" 
tickets by cash");
}
}

public class Context {
private Payment payment;

public Context(Payment payment) {
    this.payment = payment;
}

public void doPayment() {
    this.payment.doPayment();
}
}

public class StrategyMain {

public static void main(String[] args) {
    Payment payment = new CashPayment(99, 8, 1);
    Verificator verificator = new CashPaymentVerificator(payment, 100, 9,2);
    verificator.verify();

    Context context = new Context(payment);
    context.doPayment();

    payment = new PaypalPayment(1000, 11);
    verificator = new PaypalPaymentVerificator(payment, 10000, 10);
    verificator.verify();

    context = new Context(payment);
    context.doPayment();
}
}
0 голосов
/ 28 мая 2018

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

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

public PaymentResult MakeReservation(IPaymentPolicy paymentPolicy, int itemsToBuy)
{
    var result = paymentPolicy.Verify(itemsToBuy);

    if(result.HasFailingRules)
    {
        return result;
    }

    // Do your booking here.
}

Здесь объект театра отвечает за одно решение - разрешено ли бронирование с учетом предоставленных мне правил?Если да, тогда сделайте заказ, в противном случае сообщите об ошибках.

Тогда вызывающий абонент сможет контролировать правила в зависимости от варианта использования.Например:

public void MakePaypalReservation(int itemsToBuy)
{
    var rulesPolicy = new PaymentRulesPolicy(
                                              new MaxItemsRule(10),
                                              new MaxAmountRule(10000)
                                             );

    var theatre = this.repo.Load("Theatre A"); // Load by id

    var paymentResult = theatre.MakeReservation(rulesPolicy, itemsToBuy);

    // Here you can use the result for logging or return to the GUI or proceed with the next step if no errors are present.
}

public void MakeCashReservation(int itemsToBuy)
{
    var rulesPolicy = new PaymentRulesPolicy(
                                              new MaxItemsRule(2),
                                              new MaxAmountRule(100),
                                              new TimeOfDayRule(8, 20) //Can only buy between 8 in the morning at 8 at night as an example.
                                             );

    var theatre = this.repo.Load("Theatre A"); // Load by id

    var paymentResult = theatre.MakeReservation(rulesPolicy, itemsToBuy);

    // Here you can use the result for logging or return to the GUI or proceed with the next step if no errors are present.
}

Предположим, что у PaymentRulesPolicy имеется конструктор с такой сигнатурой:

public PaymentRulesPolicy(params IRule[] rules);

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

Конечно, вам также нужно будет предоставить объекту театра всю информацию, необходимую для бронирования.,Метод Verify () политики правил, скорее всего, примет все эти фрагменты информации и передаст минимально необходимую информацию отдельным правилам.

Вот пример того, как может выглядеть политика правил:

public class PaymentRulesPolicy
{
    private readonly IRule[] rules;

    public PaymentRulesPolicy(params IRule[] rules)
    {
        this.rules = rules;
    }

    public PaymentResult Verify(int numItemsToBuy, DateTime bookingDate)
    {
        var result = new PaymentResult();

        foreach(var rule in this.rules)
        {
            result.Append(rule.Verify(numItemsToBuy, bookingDate);
        }

        return result;
    }
}

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

 var rulesPolicy = new PaymentRulesPolicy(
                                              new MaxItemsRule(2, itemsToBuy),
                                              new MaxAmountRule(100, itemsToBuy, costPerItem),
                                              new TimeOfDayRule(8, 20, currentDateTime) 
                                             );

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

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

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

0 голосов
/ 27 мая 2018

Сначала рассмотрим природу правила.Является ли общим правилом получения наличных денег то, что вы можете принимать только наличные за товар 1, но не товар 2?В моем магазине я возьму деньги на что угодно.Это правило, что магазины могут принимать только 100 долларов наличными?Опять же, не в моем магазине, где я возьму любую сумму наличных.

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

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

Это могут быть правила для вашего объекта транзакции.Ваш бизнес не позволяет одной транзакции содержать пункт 2 плюс денежный тендер.Это кажется мне правдоподобным.

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

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

...