门面模式是一个对象,它为较大的代码体(如类库)提供了一个简化的接口。它使软件库更易于使用和理解,更易读,并且减少了对外部或其他代码的依赖。
几年前,当开始使用门面模式时,没有正确应用它。当时试图解决的主要问题是测试中缺乏代码重用。成功地完成了任务,代码开始遵循主要的编程原则之一——DRY(不要重复自己)。
学会识别重复,并理解如何通过适当的实践和适当的抽象来消除重复,可以比那些不断在应用程序中注入不必要的重复的开发者产生更干净的代码。重复会导致错误的可能性增加,并增加了系统的复杂性。此外,重复使系统更难以理解和维护。
最初的门面版本的主要问题是,还没有使用页面对象模式。因此,门面文件的大小变得非常大,像数千行代码一样。
在下面的例子中,可以看到关于正确使用门面模式的例子。测试的逻辑/工作流被封装在PurchaseFacade类中。
public class PurchaseFacade {
private ItemPage itemPage;
private CheckoutPage checkoutPage;
private ShippingAddressPage shippingAddressPage;
private SignInPage signInPage;
public ItemPage ItemPage {
get {
if (itemPage == null) {
itemPage = new ItemPage();
}
return itemPage;
}
}
public SignInPage SignInPage {
get {
if (signInPage == null) {
signInPage = new SignInPage();
}
return signInPage;
}
}
public CheckoutPage CheckoutPage {
get {
if (checkoutPage == null) {
checkoutPage = new CheckoutPage();
}
return checkoutPage;
}
}
public ShippingAddressPage ShippingAddressPage {
get {
if (shippingAddressPage == null) {
shippingAddressPage = new ShippingAddressPage();
}
return shippingAddressPage;
}
}
public void PurchaseItem(string item, string itemPrice, ClientInfo clientInfo) {
this.ItemPage.Navigate(item);
this.ItemPage.Validate().Price(itemPrice);
this.ItemPage.ClickBuyNowButton();
this.SignInPage.ClickContinueAsGuestButton();
this.ShippingAddressPage.FillShippingInfo(clientInfo);
this.ShippingAddressPage.Validate().Subtotal(itemPrice);
this.ShippingAddressPage.ClickContinueButton();
this.CheckoutPage.Validate().Subtotal(itemPrice);
}
}
如果测试用例的工作流程发生变化,只需在一个地方快速更新即可。或者,如果想添加额外的断言,可以将其添加到PurchaseItem方法中。
之前展示的代码唯一的问题是它没有遵循依赖倒置原则。依赖倒置原则建议高层组件(门面)不应依赖低层组件(页面);相反,它们都应该依赖抽象。
下面,可以看到改进后的门面代码,它包含了与创建购买相关的逻辑。
public class ShoppingCart {
private readonly IItemPage itemPage;
private readonly ISignInPage signInPage;
private readonly ICheckoutPage checkoutPage;
private readonly IShippingAddressPage shippingAddressPage;
public ShoppingCart(IItemPage itemPage, ISignInPage signInPage, ICheckoutPage checkoutPage, IShippingAddressPage shippingAddressPage) {
this.itemPage = itemPage;
this.signInPage = signInPage;
this.checkoutPage = checkoutPage;
this.shippingAddressPage = shippingAddressPage;
}
public void PurchaseItem(string item, double itemPrice, ClientInfo clientInfo) {
this.itemPage.Open(item);
this.itemPage.AssertPrice(itemPrice);
this.itemPage.ClickBuyNowButton();
this.signInPage.ClickContinueAsGuestButton();
this.shippingAddressPage.FillShippingInfo(clientInfo);
this.shippingAddressPage.AssertSubtotalAmount(itemPrice);
this.shippingAddressPage.ClickContinueButton();
this.checkoutPage.AssertSubtotal(itemPrice);
}
}
通过使用页面的接口,门面遵循了依赖倒置原则。可以替换某些页面的版本,而不需要更改门面中的一行代码。
门面结合了不同页面的方法来完成订单的向导。如果执行操作的顺序发生了变化,只需在这里编辑即可。它将适用于使用门面的测试。不同的测试用例是通过传递给门面方法的各种参数来完成的。可以阅读文章"使代码更易于维护的页面对象"来了解如何创建这些类型的页面。
这些类型的门面包含的代码要少得多,因为大部分逻辑都由页面而不是门面本身持有。
最后,想简要提及一下门面的名称。它的名称中不包含“门面”这个词,因为它不应该暗示它隐藏了复杂的逻辑。
页面对象模式 高级页面对象模式 门面设计模式 单例设计模式 流畅页面对象模式 IoC容器和页面对象 策略设计模式 高级策略设计模式 观察者设计模式 通过事件和委托的观察者设计模式 通过IObservable和IObserver的观察者设计模式 装饰者设计模式-混合策略 使代码更易于维护的页面对象 自动化测试中的改进门面设计模式v.2.0 规则设计模式 高级规格设计模式