哪种设计模式使用

时间:2018-05-26 16:09:36

标签: design-patterns design-principles

假设我有一个类产品,我有一个股票,可以用两种方式付款:Paypal或现金。我现在只能卖2件产品。这可能在未来发生变化,所以我不想用ifs改变整个代码,所以我想到了使用策略模式。

我只是在学习设计模式。在转到代码之前,我最熟悉的是UML设计。

那么,这是一个很好的模式吗?我可以在Cash类或Paypal类中添加更多验证。

编辑:我添加额外信息。

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

EDIT2:

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

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

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

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

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

策略是最好的吗?

3 个答案:

答案 0 :(得分:0)

首先,考虑规则的性质。现金的一般规则是,您只能接受第1项的现金而不接受第2项的现金吗?在我的商店,我会用现金购买任何东西。商店只能接受100美元现金吗?再次,不是在我的商店,我会收取任何现金。

现在从GUI中考虑一下 - 在晚上9点之后,你甚至可能不想显示"现金"按钮,所以收银员不知道现金。您甚至还没有创建现金对象 - 没有办法在现金对象中执行策略。这是一个很大的线索,现金对象不应该包含您正在谈论的策略。

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

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

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

有很多方法可以考虑这个问题。而不是询问设计模式,而是根据SOLID设计原则检查您的对象。如果您似乎对某个对象负有不适当的责任,那么您可能需要找到一个不同的逻辑位置。

答案 1 :(得分:0)

您选择使用策略模式是正确的。要解决您的扩展问题,您可以将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();
}
}

答案 2 :(得分:0)

您的解决方案现在似乎没问题,但是,我建议您制定某种规则政策,以便您的预订并不真正关心如何支付,而是规则由用例决定(您将请注意,此解决方案实际上也是基于策略模式的技术。)

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

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

在一天结束时,这种模式的好处是所有业务决策都被封装在一个类中,这使得它非常容易维护。只看这些政策&#39;建设希望能够很好地概述他们将执行的内容。然后,您可以将这些规则组合成更大的策略。

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

请记住,这只是众多可能的解决方案之一,而这个特定的解决方案可能对您的应用程序来说过于苛刻,或者它可能不适合您和您的团队熟悉的模式。在您尝试使用继承解决方案时,您可能会发现根据团队的习惯和经验来理解它是足够的,甚至更简单。