在我的文章“ 策略设计模式 ”和“ 高级策略设计模式 ”中,我解释了在自动化测试中应用 策略设计模式 的好处。一些优点是更易于维护的代码、封装的算法逻辑、易于互换的算法,以及不太复杂的代码。策略设计模式遵循 开放封闭原则 ,即“类应该对 扩展 开放 ,但对 修改 关闭 ”。另一种为扩展类创建开放的方法是使用 装饰器设计模式 。在本出版物中,我将重构前面提到的文章中的代码示例,使其更具可扩展性。所使用的策略将通过装饰器“包装”。 装饰器设计模式 允许我们轻松地动态地将额外的责任附加到对象上。我相信由于它的所有好处,它可以在自动化测试中大量使用。
定义
装饰器设计模式动态地将额外的责任附加到对象上。装饰器为扩展功能提供了一种灵活的子类化替代方案。
- 你可以用任意数量的装饰器包装一个组件。
- 通过在组件的方法调用之前和/或之后添加新功能来更改其组件的行为。
- 装饰器类反映了它们装饰的组件的类型。
- 为扩展行为提供了子类化的替代方法。
抽象uml类图
参与者
参与此模式的类和对象是:
- 组件 ——为可以动态添加职责的对象定义接口。
- 装饰器 ——装饰器实现与它们要装饰的组件相同的接口(抽象类)。装饰器与正在扩展的对象具有 has-a 关系,这意味着前者有一个实例变量,该实例变量持有对后者的引用。
- 具体组件 ——是将要动态增强的对象。它继承了组件。
- concretedecorator—— 装饰器可以增强组件的状态。他们可以添加新方法。新行为通常添加在组件中现有方法之前或之后。
装饰器设计模式c#代码
测试的测试用例
示例的测试用例将与之前的文章相同。主要目标是从 亚马逊 购买不同的商品。此外,应验证购买过程最后一步的价格——税费、运费、礼品包装费等。
1. 导航 到项目页面
2.点击 继续结帐
3.使用 现有 客户端 登录
4. 填写 运输信息
5. 选择 运输速度
6. 选择 支付方式
7. 验证 订单摘要
前面的文章详细解释了如何使整个购买过程自动化。然而,要介绍 装饰器设计模式 的好处,只有最后一步是必要的——订单摘要验证。在关于策略设计模式的帖子中,购买过程最后一步的价格是通过实施 iorderpurchasestrategy 的不同验证策略来验证的。
public class purchasecontext
{
private readonly iordervalidationstrategy ordervalidationstrategy;
public purchasecontext(iordervalidationstrategy ordervalidationstrategy)
{
this.ordervalidationstrategy = ordervalidationstrategy;
}
public void purchaseitem(string itemurl, string itemprice, clientlogininfo clientlogininfo, clientpurchaseinfo clientpurchaseinfo)
{
itempage.instance.navigate(itemurl);
itempage.instance.clickbuynowbutton();
previewshoppingcartpage.instance.clickproceedtocheckoutbutton();
signinpage.instance.login(clientlogininfo.email, clientlogininfo.password);
shippingaddresspage.instance.fillshippinginfo(clientpurchaseinfo);
shippingaddresspage.instance.clickcontinuebutton();
shippingpaymentpage.instance.clickbottomcontinuebutton();
shippingpaymentpage.instance.clicktopcontinuebutton();
this.ordervalidationstrategy.validateordersummary(itemprice, clientpurchaseinfo);
}
}
应用改进版高级策略设计模式
public class purchasecontext
{
private readonly iordervalidationstrategy ordervalidationstrategy;
public purchasecontext(iordervalidationstrategy ordervalidationstrategy)
{
this.ordervalidationstrategy = ordervalidationstrategy;
}
public void purchaseitem(string itemurl, string itemprice, clientlogininfo clientlogininfo, clientpurchaseinfo clientpurchaseinfo)
{
itempage.instance.navigate(itemurl);
itempage.instance.clickbuynowbutton();
previewshoppingcartpage.instance.clickproceedtocheckoutbutton();
signinpage.instance.login(clientlogininfo.email, clientlogininfo.password);
shippingaddresspage.instance.fillshippinginfo(clientpurchaseinfo);
shippingaddresspage.instance.clickcontinuebutton();
shippingpaymentpage.instance.clickbottomcontinuebutton();
shippingpaymentpage.instance.clicktopcontinuebutton();
this.ordervalidationstrategy.validateordersummary(itemprice, clientpurchaseinfo);
}
}
purchasecontext 的使用并不像您从下面的代码中看到的那样简单。
public class purchasecontext
{
private readonly iordervalidationstrategy ordervalidationstrategy;
public purchasecontext(iordervalidationstrategy ordervalidationstrategy)
{
this.ordervalidationstrategy = ordervalidationstrategy;
}
public void purchaseitem(string itemurl, string itemprice, clientlogininfo clientlogininfo, clientpurchaseinfo clientpurchaseinfo)
{
itempage.instance.navigate(itemurl);
itempage.instance.clickbuynowbutton();
previewshoppingcartpage.instance.clickproceedtocheckoutbutton();
signinpage.instance.login(clientlogininfo.email, clientlogininfo.password);
shippingaddresspage.instance.fillshippinginfo(clientpurchaseinfo);
shippingaddresspage.instance.clickcontinuebutton();
shippingpaymentpage.instance.clickbottomcontinuebutton();
shippingpaymentpage.instance.clicktopcontinuebutton();
this.ordervalidationstrategy.validateordersummary(itemprice, clientpurchaseinfo);
}
}
不同的价格验证组合是通过初始化策略的迭代来实现的。然而,所提供解决方案的缺点是,对于 iorderpurchasestrategy 接口中的每个新方法,您都需要在 purchasecontext 类中创建一个带有“foreach”语句的新方法。另外,我个人认为测试方法中 purchasecontext 的初始化有点不可读。
如果你对上面的代码示例理解不透,可以在我关于策略设计模式的文章——《 策略设计模式 》和《 高级策略设计模式 》 中找到更详细的解释。
purchasecontext 初始化问题的解决方案之一是创建更多结合不同行为的策略类,例如 vatsalestaxorderpurchasestrategy、salestaxgiforderpurchasestrategy、giftorderpurchasestrategy,
notaxesorderpurchasestrategy 等,但正如您所看到的那样,它迅速升级 - 类爆炸的典型示例。
如果你需要添加额外的验证器,你将不得不添加更多的类来实现混合行为。这是 装饰器设计模式 发挥作用的地方。通过继承的附加行为只能在编译时静态确定。然而,通过组合的帮助,装饰器可以在运行时扩展组件。
具体的uml类图
参与者
参与此模式的类和对象是:
- orderpurchasestrategy(组件) ——定义所有具体策略的接口,这些策略将在采购流程的最后一步验证不同的价格。
- orderpurchasestrategydecorator(组件装饰器) ——装饰器有一个实例变量,它包含对 orderpurchasestrategy 的 引用。此外,还包含另一个有用的信息,具体装饰器将使用这些信息来计算不同的预期数量。
- totalpriceorderpurchasestrategy (concretecomponent) – 它是 orderpurchasestrategy 的后代,用于验证订单的总成本。
- vattaxorderpurchasestrategy (concretedecorator) – 可以扩展具体订单购买策略。添加一个新的逻辑来验证订单的增值税,并将新税添加到总价中。
重构购买策略以支持装饰器设计模式
所有具体策略及其装饰器的基类是 orderpurchasestrategy 。
public class purchasecontext
{
private readonly iordervalidationstrategy ordervalidationstrategy;
public purchasecontext(iordervalidationstrategy ordervalidationstrategy)
{
this.ordervalidationstrategy = ordervalidationstrategy;
}
public void purchaseitem(string itemurl, string itemprice, clientlogininfo clientlogininfo, clientpurchaseinfo clientpurchaseinfo)
{
itempage.instance.navigate(itemurl);
itempage.instance.clickbuynowbutton();
previewshoppingcartpage.instance.clickproceedtocheckoutbutton();
signinpage.instance.login(clientlogininfo.email, clientlogininfo.password);
shippingaddresspage.instance.fillshippinginfo(clientpurchaseinfo);
shippingaddresspage.instance.clickcontinuebutton();
shippingpaymentpage.instance.clickbottomcontinuebutton();
shippingpaymentpage.instance.clicktopcontinuebutton();
this.ordervalidationstrategy.validateordersummary(itemprice, clientpurchaseinfo);
}
}
它只有两个抽象方法。
calculatetotalprice – 返回订单的总价。这取决于适用的税收和折扣,因为每个策略都应该实施它。
validateordersummary – 验证订单摘要页面上的所有价格——总价、税金、折扣等。
示例中的第一个具体组件是 totalpriceorderpurchasestrategy ,它验证总价的正确性。
public class purchasecontext
{
private readonly iordervalidationstrategy ordervalidationstrategy;
public purchasecontext(iordervalidationstrategy ordervalidationstrategy)
{
this.ordervalidationstrategy = ordervalidationstrategy;
}
public void purchaseitem(string itemurl, string itemprice, clientlogininfo clientlogininfo, clientpurchaseinfo clientpurchaseinfo)
{
itempage.instance.navigate(itemurl);
itempage.instance.clickbuynowbutton();
previewshoppingcartpage.instance.clickproceedtocheckoutbutton();
signinpage.instance.login(clientlogininfo.email, clientlogininfo.password);
shippingaddresspage.instance.fillshippinginfo(clientpurchaseinfo);
shippingaddresspage.instance.clickcontinuebutton();
shippingpaymentpage.instance.clickbottomcontinuebutton();
shippingpaymentpage.instance.clicktopcontinuebutton();
this.ordervalidationstrategy.validateordersummary(itemprice, clientpurchaseinfo);
}
}
为了能够在运行时动态添加新行为,所有装饰器都需要从类 orderpurchasestrategydecorator 派生。
public class purchasecontext
{
private readonly iordervalidationstrategy ordervalidationstrategy;
public purchasecontext(iordervalidationstrategy ordervalidationstrategy)
{
this.ordervalidationstrategy = ordervalidationstrategy;
}
public void purchaseitem(string itemurl, string itemprice, clientlogininfo clientlogininfo, clientpurchaseinfo clientpurchaseinfo)
{
itempage.instance.navigate(itemurl);
itempage.instance.clickbuynowbutton();
previewshoppingcartpage.instance.clickproceedtocheckoutbutton();
signinpage.instance.login(clientlogininfo.email, clientlogininfo.password);
shippingaddresspage.instance.fillshippinginfo(clientpurchaseinfo);
shippingaddresspage.instance.clickcontinuebutton();
shippingpaymentpage.instance.clickbottomcontinuebutton();
shippingpaymentpage.instance.clicktopcontinuebutton();
this.ordervalidationstrategy.validateordersummary(itemprice, clientpurchaseinfo);
}
}
这个抽象类包含几个相关变量。最突出的是在构造函数中初始化的 orderpurchasestrategy 。它包含对当前扩展的对象的引用。其他变量用于计算不同的预期金额。
如果我们想为上述策略添加逻辑,例如增值税的应用及其验证。我们可以使用 vattaxorderpurchasestrategy, 它本质上是一个能够扩展其他购买策略的装饰器。
public class purchasecontext
{
private readonly iordervalidationstrategy ordervalidationstrategy;
public purchasecontext(iordervalidationstrategy ordervalidationstrategy)
{
this.ordervalidationstrategy = ordervalidationstrategy;
}
public void purchaseitem(string itemurl, string itemprice, clientlogininfo clientlogininfo, clientpurchaseinfo clientpurchaseinfo)
{
itempage.instance.navigate(itemurl);
itempage.instance.clickbuynowbutton();
previewshoppingcartpage.instance.clickproceedtocheckoutbutton();
signinpage.instance.login(clientlogininfo.email, clientlogininfo.password);
shippingaddresspage.instance.fillshippinginfo(clientpurchaseinfo);
shippingaddresspage.instance.clickcontinuebutton();
shippingpaymentpage.instance.clickbottomcontinuebutton();
shippingpaymentpage.instance.clicktopcontinuebutton();
this.ordervalidationstrategy.validateordersummary(itemprice, clientpurchaseinfo);
}
}
vattaxorderpurchasestrategy 是 orderpurchasestrategydecorator 的后代。此外,它重写了它的方法。有趣的是总价是通过递归方法计算的。首先,总金额由具体组件(订单采购策略)确定,然后将计算的增值税添加到其中。
相同的递归技术用于订单摘要 ui 的验证。首先,将执行所有扩展策略的 validateordersummary 方法,然后验证增值税。
可以通过类似的装饰器检查销售税。
public class purchasecontext
{
private readonly iordervalidationstrategy ordervalidationstrategy;
public purchasecontext(iordervalidationstrategy ordervalidationstrategy)
{
this.ordervalidationstrategy = ordervalidationstrategy;
}
public void purchaseitem(string itemurl, string itemprice, clientlogininfo clientlogininfo, clientpurchaseinfo clientpurchaseinfo)
{
itempage.instance.navigate(itemurl);
itempage.instance.clickbuynowbutton();
previewshoppingcartpage.instance.clickproceedtocheckoutbutton();
signinpage.instance.login(clientlogininfo.email, clientlogininfo.password);
shippingaddresspage.instance.fillshippinginfo(clientpurchaseinfo);
shippingaddresspage.instance.clickcontinuebutton();
shippingpaymentpage.instance.clickbottomcontinuebutton();
shippingpaymentpage.instance.clicktopcontinuebutton();
this.ordervalidationstrategy.validateordersummary(itemprice, clientpurchaseinfo);
}
}
后者与前者之间的唯一区别是税收的确定方式。
修饰策略的使用 purchasecontext
public class purchasecontext
{
private readonly iordervalidationstrategy ordervalidationstrategy;
public purchasecontext(iordervalidationstrategy ordervalidationstrategy)
{
this.ordervalidationstrategy = ordervalidationstrategy;
}
public void purchaseitem(string itemurl, string itemprice, clientlogininfo clientlogininfo, clientpurchaseinfo clientpurchaseinfo)
{
itempage.instance.navigate(itemurl);
itempage.instance.clickbuynowbutton();
previewshoppingcartpage.instance.clickproceedtocheckoutbutton();
signinpage.instance.login(clientlogininfo.email, clientlogininfo.password);
shippingaddresspage.instance.fillshippinginfo(clientpurchaseinfo);
shippingaddresspage.instance.clickcontinuebutton();
shippingpaymentpage.instance.clickbottomcontinuebutton();
shippingpaymentpage.instance.clicktopcontinuebutton();
this.ordervalidationstrategy.validateordersummary(itemprice, clientpurchaseinfo);
}
}
改进版本中现在缺少以下代码。
public class purchasecontext
{
private readonly iordervalidationstrategy ordervalidationstrategy;
public purchasecontext(iordervalidationstrategy ordervalidationstrategy)
{
this.ordervalidationstrategy = ordervalidationstrategy;
}
public void purchaseitem(string itemurl, string itemprice, clientlogininfo clientlogininfo, clientpurchaseinfo clientpurchaseinfo)
{
itempage.instance.navigate(itemurl);
itempage.instance.clickbuynowbutton();
previewshoppingcartpage.instance.clickproceedtocheckoutbutton();
signinpage.instance.login(clientlogininfo.email, clientlogininfo.password);
shippingaddresspage.instance.fillshippinginfo(clientpurchaseinfo);
shippingaddresspage.instance.clickcontinuebutton();
shippingpaymentpage.instance.clickbottomcontinuebutton();
shippingpaymentpage.instance.clicktopcontinuebutton();
this.ordervalidationstrategy.validateordersummary(itemprice, clientpurchaseinfo);
}
}
现在, purchasecontext 只包含一个对 orderpurchasestrategy 的 引用,并使用它来验证订单摘要页面上的总金额和所有其他价格。
装饰器设计模式在测试中的使用
public class purchasecontext
{
private readonly iordervalidationstrategy ordervalidationstrategy;
public purchasecontext(iordervalidationstrategy ordervalidationstrategy)
{
this.ordervalidationstrategy = ordervalidationstrategy;
}
public void purchaseitem(string itemurl, string itemprice, clientlogininfo clientlogininfo, clientpurchaseinfo clientpurchaseinfo)
{
itempage.instance.navigate(itemurl);
itempage.instance.clickbuynowbutton();
previewshoppingcartpage.instance.clickproceedtocheckoutbutton();
signinpage.instance.login(clientlogininfo.email, clientlogininfo.password);
shippingaddresspage.instance.fillshippinginfo(clientpurchaseinfo);
shippingaddresspage.instance.clickcontinuebutton();
shippingpaymentpage.instance.clickbottomcontinuebutton();
shippingpaymentpage.instance.clicktopcontinuebutton();
this.ordervalidationstrategy.validateordersummary(itemprice, clientpurchaseinfo);
}
}
上述代码中最突出的部分是购买上下文如何修饰和使用订单购买策略。
public class purchasecontext
{
private readonly iordervalidationstrategy ordervalidationstrategy;
public purchasecontext(iordervalidationstrategy ordervalidationstrategy)
{
this.ordervalidationstrategy = ordervalidationstrategy;
}
public void purchaseitem(string itemurl, string itemprice, clientlogininfo clientlogininfo, clientpurchaseinfo clientpurchaseinfo)
{
itempage.instance.navigate(itemurl);
itempage.instance.clickbuynowbutton();
previewshoppingcartpage.instance.clickproceedtocheckoutbutton();
signinpage.instance.login(clientlogininfo.email, clientlogininfo.password);
shippingaddresspage.instance.fillshippinginfo(clientpurchaseinfo);
shippingaddresspage.instance.clickcontinuebutton();
shippingpaymentpage.instance.clickbottomcontinuebutton();
shippingpaymentpage.instance.clicktopcontinuebutton();
this.ordervalidationstrategy.validateordersummary(itemprice, clientpurchaseinfo);
}
}
首先实例化 总价格订单购买 策略。然后它被传递给 salestaxorderpurchasestrategy 的构造函数, 这样它被扩展并且销售税将被添加到总价中。销售税策略也是如此;一个新的 vattaxorderpurchasestrategy 装饰器被初始化。最后,总价将等于商品价格加上销售税和增值税。
装饰器设计模式的优缺点
缺点
-
装饰器会产生许多小对象,过度使用会很复杂。
-
会使实例化组件的过程复杂化,因为您不仅要实例化组件,还要将其包装在一些装饰器中。
-
让装饰器跟踪其他装饰器可能会很复杂,因为回顾装饰器链的多层开始将装饰器模式推到其实际意图之外。
-
如果客户端严重依赖组件具体类型,则可能会导致问题。
优点
-
为扩展功能提供了一个灵活的子类替代方案。
-
允许在运行时修改行为,而不是返回现有代码并进行更改。
-
帮助解决班级爆炸问题。
-
支持 开闭原则 。
源代码
您可以从我的 github 存储库 下载高级策略设计模式的完整源代码。
如果您喜欢我的出版物,请随时 订阅 – http://automatetheplanet.com/newsletter/