【问题标题】:Which Design Pattern use使用哪种设计模式
【发布时间】:2018-05-26 16:09:36
【问题描述】:

假设我有一类产品,我有库存并且可以通过 2 种方式付款:Paypal 或现金。我只能以现金出售 2 件产品。这在未来可能会改变,所以我不想用 ifs 和 else 改变整个代码,所以我想到了使用策略模式。

我只是在学习设计模式。在编写代码之前,我首先对 UML 设计最感兴趣。

那么,这对我来说是一个好的模式吗?我可能会向 Cash 类或 Paypal 类添加更多验证。

编辑:我添加了额外的信息。

这只是一个例子。我只有一个验证,例如,如果是现金,我可以销售的产品的最高金额是 100 美元,如果是 Paypal,我可以销售的最高金额是 10000 美元。 但是假设明天他们会要求我添加另一个验证,即我不能在晚上 9 点之后出售现金。我不知道如何把它放在设计中没有使用策略模式。

编辑2:

让我再举一个可以澄清的例子。

您可以通过 2 种方式预订门票:Paypal 或现金。 如果您支付现金,我只允许 2 张门票,但如果您使用 Paypal,您可以购买任何您想要的金额。

所以我有一个名为 Reservation 的类,它有 2 个孩子: 贝宝 现金

我有一个整数,称为 numberOfTickets on Reservation。 在现金上我有一个折扣整数 在贝宝上,我有帐户电子邮件。

现在我想添加一些规则,第一个是如果是现金,则限制为 2 张门票。明天我可能对 Paypal 有 10 个限制。

那么策略是最好的吗?

【问题讨论】:

  • “我只能以现金出售 2 件产品”是什么意思?
  • 这只是一个例子。我只有一个验证,例如,如果是现金,我可以销售的产品的最高金额是 100 美元,如果是 Paypal,我可以销售的最高金额是 10000 美元。但是假设明天他们会要求我添加另一个验证,即我不能在晚上 9 点之后出售现金。我不知道如何把它放在设计中没有使用策略模式。
  • 给定的信息不足以让我告诉你使用什么设计模式。

标签: design-patterns design-principles


【解决方案1】:

您的解决方案目前似乎还可以,但是,我宁愿建议您制定某种规则策略,以便您的预订并不真正关心如何支付,而是由用例确定规则(您将请注意,这个解决方案实际上在技术上也是基于策略模式的)。

例如,假设您有一个剧院课程,这是您预订门票的课程。在这个 Theatre 类上有以下方法:

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);

每个用例都有一个方法。如果您可以使用代金券等其他方式付款,则可以使用一些新规则制定新政策。

当然,您还必须向 Theatre 对象提供预订所需的所有信息。规则策略的 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) 
                                             );

归根结底,这种模式的好处是您的所有业务决策都封装在一个类中,这使得维护非常容易。只要看看这些政策的构建,就有希望让您对它们将执行的内容有一个很好的了解。然后,您可以将这些规则组合成一个更大的策略。

这种方法的另一个好处是单元测试。您可以非常轻松地单独测试规则。您甚至可以为规则创建一个工厂,并测试该工厂是否为每个用例创建了正确的策略,等等。

请记住,这只是众多可能的解决方案之一,这个特定的解决方案可能对您的应用程序来说太过分了,或者它可能不适合您和您的团队熟悉的模式。当您尝试继承解决方案时,您可能会发现考虑到您团队的习惯和经验,它已经足够了,甚至更容易理解。

【讨论】:

    【解决方案2】:

    您选择使用策略模式是正确的。为了解决您的扩展问题,您可以将装饰器模式作为我下面的 Java 示例代码混合使用。为了方便起见,我只对所有输入(如金额、时间和票数)使用 int 类型。

    类图

    实施

    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();
    }
    }
    

    【讨论】:

      【解决方案3】:

      首先,考虑规则的性质。您只能接受第 1 项现金而不能接受第 2 项现金,这是现金的一般规则吗?在我的商店里,我会拿现金买任何东西。商店是否规定只能接受 100 美元现金?再说一次,不是在我的商店,在那里我会拿走任何数量的现金。

      现在从 GUI 考虑一下 - 晚上 9:00 之后,您可能甚至不想显示“现金”按钮,因此收银员知道不允许使用现金。您甚至还没有创建现金对象 - 无法在现金对象中执行策略。这是一个很大的线索,现金对象不应该包含你正在谈论的策略。

      所以这里的限制似乎根本不是现金的属性,它们似乎是商业规则。在您弄清楚他们用什么样的投标书付款之前,需要应用这些规则。

      这些可能是您的交易对象的规则。您的企业不允许单笔交易同时包含第 2 项和现金投标。这对我来说似乎是合理的。

      或者,也许这些是适用于您的领域对象之外的项目、交易和投标的业务规则,并且它们需要存在于某种规则引擎中,以便在将项目和投标添加到交易中时对其进行验证。

      有很多方法可以考虑这个问题。与其询问设计模式,不如根据 SOLID 设计原则检查您的对象。如果您似乎对对象赋予了不适当的责任,您可能需要为逻辑找到不同的位置。

      【讨论】:

      • 让我再举一个可以澄清的例子。您可以通过 2 种方式预订门票:Paypal 或现金。如果您支付现金,我只想允许 2 张门票,但如果您使用 Paypal,您可以购买任何您想要的金额。所以我有一个名为 Reservation 的类,它有 2 个孩子: 1:Paypal 2:Cash 我有一个在 Reservation 上称为 numberOfTickets 的整数。在现金上我有一个折扣整数在贝宝上我有帐户电子邮件。现在我想添加一些规则,第一个是如果是现金,则限制为 2 张门票。明天我可能对 Paypal 有 10 个限制。那么策略是最好的吗?
      • 策略是合适的,但不是保留类中包含的策略方法。同样,您所说的策略是贵公司关于为预订付费的规则,但这些不是预订策略。
      猜你喜欢
      • 2014-04-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多