使用哪种设计模式
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 中,这使得维护起来非常容易。只要看看这些政策的结构,就有希望让您对它们将执行的内容有一个很好的了解。然后,您可以将这些规则组合成更大的策略。
这种方法的另一个好处是单元测试。您可以非常轻松地单独测试规则。您甚至可以为规则创建一个工厂,并测试该工厂是否为每个用例创建了具有正确规则的正确策略,等等。
请记住,这只是众多可能的解决方案之一,这个特定的解决方案可能对您的应用程序来说太过分了,或者它可能不适合您和您的团队所熟悉的模式。当您试验您的继承解决方案时,您可能会发现考虑到您团队的习惯和经验,它就足够了,甚至更容易理解。
假设我有一个 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 中,这使得维护起来非常容易。只要看看这些政策的结构,就有希望让您对它们将执行的内容有一个很好的了解。然后,您可以将这些规则组合成更大的策略。
这种方法的另一个好处是单元测试。您可以非常轻松地单独测试规则。您甚至可以为规则创建一个工厂,并测试该工厂是否为每个用例创建了具有正确规则的正确策略,等等。
请记住,这只是众多可能的解决方案之一,这个特定的解决方案可能对您的应用程序来说太过分了,或者它可能不适合您和您的团队所熟悉的模式。当您试验您的继承解决方案时,您可能会发现考虑到您团队的习惯和经验,它就足够了,甚至更容易理解。