外观模式(Façade Pattern)

——.NET设计模式系列之十二

Terrylee,2006年3月

概述

在软件开发系统中,客户程序经常会与复杂系统的内部子系统之间产生耦合,而导致客户程序随着子系统的变化而变化。那么如何简化客户程序与子系统之间的交互接口?如何将复杂系统的内部子系统与客户程序之间的依赖解耦?这就是要说的Façade 模式。

意图

为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。[GOF 《设计模式》]

示意图

门面模式没有一个一般化的类图描述,下面是一个示意性的对象图:

图1 Façade模式示意性对象图

生活中的例子

外观模式为子系统中的接口定义了一个统一的更高层次的界面,以便于使用。当消费者按照目录采购时,则体现了一个外观模式。消费者拨打一个号码与客服代表联系,客服代表则扮演了这个"外观",他包含了与订货部、收银部和送货部的接口。

图2使用电话订货例子的外观模式对象图

Facade模式解说

我们平时的开发中其实已经不知不觉的在用Façade模式,现在来考虑这样一个抵押系统,当有一个客户来时,有如下几件事情需要确认:到银行子系统查询他是否有足够多的存款,到信用子系统查询他是否有良好的信用,到贷款子系统查询他有无贷款劣迹。只有这三个子系统都通过时才可进行抵押。我们先不考虑Façade模式,那么客户程序就要直接访问这些子系统,分别进行判断。类结构图下:

图3

在这个程序中,我们首先要有一个顾客类,它是一个纯数据类,并无任何操作,示意代码:

//顾客类
public class Customer
{
private string _name; public Customer(string name)
{
this._name = name;
} public string Name
{
get { return _name; }
}
}

下面这三个类均是子系统类,示意代码:

//银行子系统
public class Bank
{
public bool HasSufficientSavings(Customer c, int amount)
{
Console.WriteLine("Check bank for " + c.Name);
return true;
}
} //信用子系统
public class Credit
{
public bool HasGoodCredit(Customer c)
{
Console.WriteLine("Check credit for " + c.Name);
return true;
}
} //贷款子系统
public class Loan
{
public bool HasNoBadLoans(Customer c)
{
Console.WriteLine("Check loans for " + c.Name);
return true;
}
}

来看客户程序的调用:

//客户程序
public class MainApp
{
private const int _amount = ; public static void Main()
{
Bank bank = new Bank();
Loan loan = new Loan();
Credit credit = new Credit(); Customer customer = new Customer("Ann McKinsey"); bool eligible = true; if (!bank.HasSufficientSavings(customer, _amount))
{
eligible = false;
}
else if (!loan.HasNoBadLoans(customer))
{
eligible = false;
}
else if (!credit.HasGoodCredit(customer))
{
eligible = false;
} Console.WriteLine("\n" + customer.Name + " has been " + (eligible ? "Approved" : "Rejected"));
Console.ReadLine();
}
}

可以看到,在不用Façade模式的情况下,客户程序与三个子系统都发生了耦合,这种耦合使得客户程序依赖于子系统,当子系统变化时,客户程序也将面临很多变化的挑战。一个合情合理的设计就是为这些子系统创建一个统一的接口,这个接口简化了客户程序的判断操作。看一下引入Façade模式后的类结构图:

图4

门面类Mortage的实现如下:

//外观类
public class Mortgage
{
private Bank bank = new Bank();
private Loan loan = new Loan();
private Credit credit = new Credit(); public bool IsEligible(Customer cust, int amount)
{
Console.WriteLine("{0} applies for {1:C} loan\n",
cust.Name, amount); bool eligible = true; if (!bank.HasSufficientSavings(cust, amount))
{
eligible = false;
}
else if (!loan.HasNoBadLoans(cust))
{
eligible = false;
}
else if (!credit.HasGoodCredit(cust))
{
eligible = false;
} return eligible;
}
}

顾客类和子系统类的实现仍然如下:

//银行子系统
public class Bank
{
public bool HasSufficientSavings(Customer c, int amount)
{
Console.WriteLine("Check bank for " + c.Name);
return true;
}
} //信用证子系统
public class Credit
{
public bool HasGoodCredit(Customer c)
{
Console.WriteLine("Check credit for " + c.Name);
return true;
}
} //贷款子系统
public class Loan
{
public bool HasNoBadLoans(Customer c)
{
Console.WriteLine("Check loans for " + c.Name);
return true;
}
} //顾客类
public class Customer
{
private string name; public Customer(string name)
{
this.name = name;
} public string Name
{
get { return name; }
}
}

而此时客户程序的实现:

//客户程序类
public class MainApp
{
public static void Main()
{
//外观
Mortgage mortgage = new Mortgage(); Customer customer = new Customer("Ann McKinsey");
bool eligable = mortgage.IsEligible(customer, ); Console.WriteLine("\n" + customer.Name +
" has been " + (eligable ? "Approved" : "Rejected"));
Console.ReadLine();
}
}

可以看到引入Façade模式后,客户程序只与Mortgage发生依赖,也就是Mortgage屏蔽了子系统之间的复杂的操作,达到了解耦内部子系统与客户程序之间的依赖。

.NET架构中的Façade模式

Façade模式在实际开发中最多的运用当属开发N层架构的应用程序了,一个典型的N层结构如下:

图5

在这个架构中,总共分为四个逻辑层,分别为:用户层UI,业务外观层Business Façade,业务规则层Business Rule,数据访问层Data Access。其中Business Façade层的职责如下:

l         从“用户”层接收用户输入

l         如果请求需要对数据进行只读访问,则可能使用“数据访问”层

l         将请求传递到“业务规则”层

l         将响应从“业务规则”层返回到“用户”层

l         在对“业务规则”层的调用之间维护临时状态

对这一架构最好的体现就是Duwamish示例了。在该应用程序中,有部分操作只是简单的从数据库根据条件提取数据,不需要经过任何处理,而直接将数据显示到网页上,比如查询某类别的图书列表。而另外一些操作,比如计算定单中图书的总价并根据顾客的级别计算回扣等等,这部分往往有许多不同的功能的类,操作起来也比较复杂。如果采用传统的三层结构,这些商业逻辑一般是会放在中间层,那么对内部的这些大量种类繁多,使用方法也各异的不同的类的调用任务,就完全落到了表示层。这样势必会增加表示层的代码量,将表示层的任务复杂化,和表示层只负责接受用户的输入并返回结果的任务不太相称,并增加了层与层之间的耦合程度。于是就引入了一个Façade层,让这个Facade来负责管理系统内部类的调用,并为表示层提供了一个单一而简单的接口。看一下Duwamish结构图:

图6

从图中可以看到,UI层将请求发送给业务外观层,业务外观层对请求进行初步的处理,判断是否需要调用业务规则层,还是直接调用数据访问层获取数据。最后由数据访问层访问数据库并按照来时的步骤返回结果到UI层,来看具体的代码实现。

在获取商品目录的时候,Web UI调用业务外观层:

productSystem = new ProductSystem();
categorySet = productSystem.GetCategories(categoryID);
业务外观层直接调用了数据访问层:

public CategoryData GetCategories(int categoryId)
{
//
// Check preconditions
//
ApplicationAssert.CheckCondition(categoryId >= ,"Invalid Category Id",ApplicationAssert.LineNumber);
//
// Retrieve the data
//
using (Categories accessCategories = new Categories())
{
return accessCategories.GetCategories(categoryId);
} }
在添加订单时,UI调用业务外观层:

public void AddOrder()
{
ApplicationAssert.CheckCondition(cartOrderData != null, "Order requires data", ApplicationAssert.LineNumber); //Write trace log.
ApplicationLog.WriteTrace("Duwamish7.Web.Cart.AddOrder:\r\nCustomerId: " +
cartOrderData.Tables[OrderData.CUSTOMER_TABLE].Rows[][OrderData.PKID_FIELD].ToString());
cartOrderData = (new OrderSystem()).AddOrder(cartOrderData);
} 业务外观层调用业务规则层: public OrderData AddOrder(OrderData order)
{
//
// Check preconditions
//
ApplicationAssert.CheckCondition(order != null, "Order is required", ApplicationAssert.LineNumber); (new BusinessRules.Order()).InsertOrder(order);
return order;
} 业务规则层进行复杂的逻辑处理后,再调用数据访问层: public bool InsertOrder(OrderData order)
{
//
// Assume it's good
//
bool isValid = true;
//
// Validate order summary
//
DataRow summaryRow = order.Tables[OrderData.ORDER_SUMMARY_TABLE].Rows[]; summaryRow.ClearErrors(); if (CalculateShipping(order) != (Decimal)(summaryRow[OrderData.SHIPPING_HANDLING_FIELD]))
{
summaryRow.SetColumnError(OrderData.SHIPPING_HANDLING_FIELD, OrderData.INVALID_FIELD);
isValid = false;
} if (CalculateTax(order) != (Decimal)(summaryRow[OrderData.TAX_FIELD]))
{
summaryRow.SetColumnError(OrderData.TAX_FIELD, OrderData.INVALID_FIELD);
isValid = false;
}
//
// Validate shipping info
//
isValid &= IsValidField(order, OrderData.SHIPPING_ADDRESS_TABLE, OrderData.SHIP_TO_NAME_FIELD, );
//
// Validate payment info
//
DataRow paymentRow = order.Tables[OrderData.PAYMENT_TABLE].Rows[]; paymentRow.ClearErrors(); isValid &= IsValidField(paymentRow, OrderData.CREDIT_CARD_TYPE_FIELD, );
isValid &= IsValidField(paymentRow, OrderData.CREDIT_CARD_NUMBER_FIELD, );
isValid &= IsValidField(paymentRow, OrderData.EXPIRATION_DATE_FIELD, );
isValid &= IsValidField(paymentRow, OrderData.NAME_ON_CARD_FIELD, );
isValid &= IsValidField(paymentRow, OrderData.BILLING_ADDRESS_FIELD, );
//
// Validate the order items and recalculate the subtotal
//
DataRowCollection itemRows = order.Tables[OrderData.ORDER_ITEMS_TABLE].Rows; Decimal subTotal = ; foreach (DataRow itemRow in itemRows)
{
itemRow.ClearErrors(); subTotal += (Decimal)(itemRow[OrderData.EXTENDED_FIELD]); if ((Decimal)(itemRow[OrderData.PRICE_FIELD]) <= )
{
itemRow.SetColumnError(OrderData.PRICE_FIELD, OrderData.INVALID_FIELD);
isValid = false;
} if ((short)(itemRow[OrderData.QUANTITY_FIELD]) <= )
{
itemRow.SetColumnError(OrderData.QUANTITY_FIELD, OrderData.INVALID_FIELD);
isValid = false;
}
}
//
// Verify the subtotal
//
if (subTotal != (Decimal)(summaryRow[OrderData.SUB_TOTAL_FIELD]))
{
summaryRow.SetColumnError(OrderData.SUB_TOTAL_FIELD, OrderData.INVALID_FIELD);
isValid = false;
} if ( isValid )
{
using (DataAccess.Orders ordersDataAccess = new DataAccess.Orders())
{
return (ordersDataAccess.InsertOrderDetail(order)) > ;
}
}
else
return false;
}

效果及实现要点

1.Façade模式对客户屏蔽了子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。

2.Façade模式实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。

3.如果应用需要,它并不限制它们使用子系统类。因此你可以在系统易用性与通用性之间选择。

适用性

1.为一个复杂子系统提供一个简单接口。

2.提高子系统的独立性。

3.在层次化结构中,可以使用Facade模式定义系统中每一层的入口。

总结

Façade模式注重的是简化接口,它更多的时候是从架构的层次去看整个系统,而并非单个类的层次。

参考资料

Erich Gamma等,《设计模式:可复用面向对象软件的基础》,机械工业出版社

Robert C.Martin,《敏捷软件开发:原则、模式与实践》,清华大学出版社

阎宏,《Java与模式》,电子工业出版社

Alan Shalloway James R. Trott,《Design Patterns Explained》,中国电力出版社

MSDN WebCast 《C#面向对象设计模式纵横谈(11):Facade外观模式(结构型模式)》

NET设计模式 第二部分 结构性模式(11):外观模式(Façade Pattern)的更多相关文章

  1. Java门面模式(或外观模式)

    门面模式(或外观模式)隐藏系统的复杂性,并为客户端提供一个客户端可以访问系统的接口. 这种类型的设计模式属于结构模式,因为此模式为现有系统添加了一个接口以隐藏其复杂性.门面模式涉及一个类,它提供客户端 ...

  2. 再起航,我的学习笔记之JavaScript设计模式11(外观模式)

    经过一段时间的学习与分享,我们对创建型设计模式已经有了一定的认识,未来的一段时间里我们将展开新的篇章,开始迈入结构性设计模式的学习. 结构性设计模式与创建型设计模式不同,结构性设计模式更偏向于关注如何 ...

  3. NET设计模式 第二部分 结构性模式(14):结构型模式专题总结

    ——探索设计模式系列之十五 Terrylee,2006年5月 摘要:结构型模式,顾名思义讨论的是类和对象的结构,它采用继承机制来组合接口或实现(类结构型模式),或者通过组合一些对象,从而实现新的功能( ...

  4. NET设计模式 第二部分 结构性模式(9):装饰模式(Decorator Pattern)

    装饰模式(Decorator Pattern) ——.NET设计模式系列之十 Terrylee,2006年3月 概述 在软件系统中,有时候我们会使用继承来扩展对象的功能,但是由于继承为类型引入的静态特 ...

  5. C#设计模式(11)——外观模式(Facade Pattern)

    一.引言 在软件开发过程中,客户端程序经常会与复杂系统的内部子系统进行耦合,从而导致客户端程序随着子系统的变化而变化,然而为了将复杂系统的内部子系统与客户端之间的依赖解耦,从而就有了外观模式,也称作 ...

  6. C#设计模式(11)——外观模式(Facade Pattern)(转)

    一.引言 在软件开发过程中,客户端程序经常会与复杂系统的内部子系统进行耦合,从而导致客户端程序随着子系统的变化而变化,然而为了将复杂系统的内部子系统与客户端之间的依赖解耦,从而就有了外观模式,也称作 ...

  7. NET设计模式 第二部分 结构性模式(8):桥接模式(Bridge Pattern)

    桥接模式(Bridge Pattern) ——.NET设计模式系列之九 Terrylee,2006年2月 概述 在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维 ...

  8. C#设计模式(11)——外观模式

    一.概念 外观模式提供了一个统一的接口,用来访问子系统中的一群接口.外观定义了一个高层接口,让子系统更容易使用.使用外观模式时,我们创建了一个统一的类,用来包装子系统中一个或多个复杂的类,客户端可以直 ...

  9. NET设计模式 第二部分 结构性模式(13):代理模式(Proxy Pattern)

    代理模式(Proxy Pattern) ——.NET设计模式系列之十四 Terrylee,2006年5月 摘要:在软件系统中,有些对象有时候由于跨越网络或者其他的障碍,而不能够或者不想直接访问另一个对 ...

随机推荐

  1. json_encode 处理后的数据是null

    原因: json_encode($str) 中的字符串 必须是 utf-8的格式: -------------------------------- 问题描述: 返回的json数据: <?php ...

  2. LeetCode--1、26、27、35、53 Array(Easy)

      1. Two Sum Given an array of integers, return indices of the two numbers such that they add up to ...

  3. 简短而有效的python queue队列解释

    Queue.qsize() 返回队列的大小  Queue.empty() 如果队列为空,返回True,反之False  Queue.full() 如果队列满了,返回True,反之False Queue ...

  4. http头之keep-alive

    1.什么是keep-alive模式? 我们知道HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器都要新建一个连接,完成 之后立即断开连接(HT ...

  5. Buildroot stress-ng Linux系统压力测试

    /********************************************************************** * Buildroot stress-ng Linux系 ...

  6. 复杂的动态布尔表达式性能评估(2)--Groovy实现

    前言: 规则引擎中, 往往涉及到多个条件构成了复杂布尔表达式的计算. 对于这类布尔表达式, 一是动态可变的(取决于运营人员的设定), 二是其表达式往往很复杂. 如何快速的计算其表达式的值, 该系列文章 ...

  7. 【leetcode】121-Best Time to Buy and Sell Stock

    problem 121. Best Time to Buy and Sell Stock code class Solution { public: int maxProfit(vector<i ...

  8. python中把数据存入csv中

    import csv # 如果不添加newline=""的话,就会每条数据中间都会有空格行 with open("test.csv","w" ...

  9. C++学习(二十三)(C语言部分)之 指针4

    指针 指针 存放地址 只能存放地址 使用 &取地址运算符 *取值 解引用运算符 malloc 申请堆内存 free释放堆内存 1.1 指针 存放的地址(变量地址 常量区的地址 堆区内存首地址 ...

  10. Python基础线程和协程

    线程: 优点:共享内存,IO操作时,创造并发操作 缺点:枪战资源 线程不是越多越好,具体案例具体分析,请求上下文切换耗时 IO密集型适用于线程,IO操作打开文件网络通讯类,不需要占用CPU,只是由CP ...