使用哪种设计模式

Which Design Pattern use

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

我正在学习设计模式。在开始编码之前,我最感兴趣的是 UML 设计。

那么,这对我来说是个好模式吗?我可能会向 Cash class 或 Paypal class.

添加更多验证

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

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

编辑2:

让我再举一个例子来说明。

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

所以我有一个名为 Reservation 的 Class,它有 2 个 children: 支付宝 现金

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

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

那么策略是最好的吗?

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

现在从 GUI 考虑 - 在 9:00 PM 之后,您可能甚至不想显示 "cash" 按钮,因此收银员知道不允许使用现金。您甚至还没有创建现金对象 - 无法在现金对象中执行策略。这是现金对象不应包含您正在谈论的策略的重要线索。

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

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

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

有很多方法可以考虑这个问题。不要询问设计 模式 ,而是根据 SOLID 设计 原则 检查您的对象。如果看起来你给了一个对象不适当的责任,你可能需要为逻辑找到一个不同的地方。

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

Class 图

实施

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

您的解决方案目前看来还不错,但是,我宁愿建议您制定某种规则政策,这样您的预订就不会真正关心如何支付,而是由用例决定规则(你会注意到这个解决方案实际上也是基于策略模式的技术。

例如,假设您有一个剧院 class,这就是您预订门票的目的。以下方法在本剧场class:

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

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

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

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