软件设计模式系列之四 门面模式(也叫外观模式,Facade) .

我们接着上篇文章来讲,上篇最后将商场收银软件确实做到程序易维护,可扩展。但是,这样就完了吗?

如果你的程序再也不修改了,或者就是改改打折的额度和返利额度,那么的代码是足够可以了。不过需求却是会不断产生的。比如说,现在这个程序是单机版的程序,如果需要商场多层楼的所有收银机都要使用,那该怎么办?
那用XML的配置文件就不合适了,应该用数据库会比较好。
C/S架构的坏处,更新麻烦,不够安全等等,他也不是傻瓜,每次更新都需要针对每台机器部署,一次就半天,那些工作时间他是需要给程序员付薪水的。所以他提出要改为B/S架构,客户端用浏览器支持,你怎么办?
那需要改界面了,把应用程序改成Web程序。

“就你现在的代码,改起来容易吗?”
“好象不容易,需要重新写,尽管可以复制一些代码过去,不过要重新写的东西还是很多的。”
“好,那你有没有发现,我说了这么多的需求变动,但系统中有一些东西一直没有变,是哪些?”
“知道,是策略模式用到的那几个类,也就是正常收费、打折消费、返利消费等算法是没有变化的。”

“是呀,其实不是算法不会变,而是之前我们已经考虑它很多了,用了策略模式,用了反射技术使得它的变化相对稳定。你刚才也说,要把应用程序改为Web是需要复制粘贴的,可实际上,改改界面和这些算法有什么关系?”
“没有关系。”
“还有,把配置文件改为数据库访问,这其实是读取写入数据的操作,和算法又有什么关系呢?”
“也没有关系,是说,他们之间完全可以分离开,互不影响,改动其一,不要影响其它两者?哦,这是不是就是所谓的三层架构?”
“所谓的三层开发,就是关于表现层、业务逻辑层和数据访问层的开发。
这其实只是大方向的分层,每个层中都有可能再细分为多个层次和结构。”


既然要改成B/S结构,那么我们应该将原来的解决方案分为三个项目:
一个UI项目,目前是WinForm的程序
一个BLL项目,用来把算法类都封装
一个DAL项目,用来访问配置文件

这时,我们就不得不提到‘迪米特法则(LoD)’ 也叫最少知识原则。

什么时候LoD呢?

简单的说,就是如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
实迪米特法则还是在讲如何减少耦合的问题,类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。也就是说,信息的隐藏促进了软件的复用。

对于我们之前的代码中,红色部分读取配置文件的代码他应该属于DAL层

之前这段代码中,我们可以清晰的看到,蓝色应该属于BLL,也就是在业务逻辑层。黑色属于UI层,也就是在表示层。

而我们希望的是,在表示层里面只要告诉系统单价和数量,然后得到结果就行。

这就像是我们要去买个电脑,现在有2种方案:

一个方案是,我们去电子市场把自己需要的配件都买回来,然后自己组装,也就是DIY(Do It Yourself)。

另一个方案是,我们去找一家专业的装机公司,我具体的要求提出来,然后等着拿电脑就好了。

第一个方案就类似我们之前做的程序,将UI,BLL,DAT 各个配件拿到一起,然后自己组装。

第二个方案就类似我们现在希望的程序,我们只需告诉系统单价和数量,系统返回我们结果就行。

第二种方案中的这个专业的装机公司,其实就相当于我们本篇博文要讲的主角 门面模式(外观模式)

什么是门面模式呢?

简单的讲门面模式(也叫外观模式)要求一个子系统的外部与其内部的通信必须通过一个统一的门面(Facade)对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。

也就相当于我们配机的第二个方案。

当然现实生活中有很多这样的例子,比如CCTV可以报道医疗,教育等领域的信息。晨报也可以报道。对于他们获取的新闻,他们不可能直接去一个领域类为大家报道新闻,那么现实中他们是怎么让大家了解新闻的呢?对于CCTV的报道我们再熟悉不过了,那就是每天7点的新闻,而新闻发言人就充当着门面角色(Facade),晨报的报纸充当着门面角色。下面2张图可以形象的说明这个道理。

门面模式(也叫外观模式,Facade)

1. 意图
为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
2. 动机
将一个系统划分成为若干个子系统有利于降低系统的复杂性。一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小。达到该目标的途径之一是就是引入一个外观(facade)对象,它为子系统中较一般的设施提供了一个单一而简单的界面。

3. 适用性

1)当你要为一个复杂子系统提供一个简单接口时。
子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。
Facade可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过facade层。
2)客户程序与抽象类的实现部分之间存在着很大的依赖性。
引入facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
3)当你需要构建一个层次结构的子系统时
使用facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化了它们之间的依赖关系。

4. 结构

5. 参与者

• Facade ( Compiler )
— 知道哪些子系统类负责处理请求。
— 将客户的请求代理给适当的子系统对象。
• Subsystem classes ( Scanner、Parser、ProgramNode等)
— 实现子系统的功能。
— 处理由Facade对象指派的任务。
— 没有facade的任何相关信息;即没有指向facade的指针。
6. 协作

• 客户程序通过发送请求给Facade的方式与子系统通讯, Facade将这些消息转发给适当的子系统对象。尽管是子系统中的有关对象在做实际工作,但Facade模式本身也必须将它的接口转换成子系统的接口。
• 使用Facade的客户程序不需要直接访问子系统对象。
7. 效果

1) 它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。
2) 它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。
3) 如果应用需要,它并不限制它们使用子系统类。因此你可以在系统易用性和通用性之间加以选择。
8. 实现

1) 降低客户-子系统之间的耦合度
2) 公共子系统类与私有子系统类

下面我们对于我们之前的商场收银系统进行我们希望的改进:

DAL层代码(目前是读配置文件,以后可以很容易的修改为访问数据库) 
using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Data; 
namespace 商场管理软件.DAL 
{ 
	public class CashAcceptType 
	{ 
		public DataSet GetCashAcceptType() 
		{ 
			//读配置文件到DataSet 
			DataSet ds = new DataSet(); 
			ds.ReadXml("CashAcceptType.xml"); 
			return ds; 
		} 
	} 
} 


这里后期我们完全可以用工厂模式来达到支持多种数据库。

BLL层主要代码(Facade类代码) 
namespace 商场管理软件.BLL 
{ 
	public class CashFacade 
	{ 
		const string ASSEMBLY_NAME = "商场管理软件.BLL"; 
		//得到现金收取类型列表,返回字符串数组 
		public string[] GetCashAcceptTypeList() 
		{ 
			CashAcceptType cat = new CashAcceptType(); 
			DataSet ds = cat.GetCashAcceptType(); 
			int rowCount = ds.Tables[0].DefaultView.Count; 
			string[] arrarResult = new string[rowCount]; 
			for (int i = 0; i < rowCount; i++) 
			{ 
				arrarResult[i] = 									(string)ds.Tables[0].DefaultView[i]["name"]; 
			} 
			return arrarResult; 
		} 
		/// <summary> 
		/// 用于根据商品活动的不同和原价格,计算此商品的实际收费 
		/// </summary> 
		/// <param name="selectValue">下拉列表选择的折价类型</param> 
		/// <param name="startTotal">原价</param> 
		/// <returns>实际价格</returns> 
		public double GetFactTotal(string selectValue, double startTotal) 
		{ 
			CashAcceptType cat = new CashAcceptType(); 
			DataSet ds = cat.GetCashAcceptType(); 
			CashContext cc = new CashContext(); 
			DataRow dr = ( (DataRow[])ds.Tables[0].Select("name='" + selectValue + "'") )[0]; 
			object[] args = null; 
			if (dr["para"].ToString() != "") 
			args = dr["para"].ToString().Split(','); 
			cc.setBehavior( 
			(CashSuper)Assembly.Load(ASSEMBLY_NAME).CreateInstance( 
			ASSEMBLY_NAME + "." + dr["class"].ToString(), 
			false, BindingFlags.Default, null, args, null, null ) ); 
			return cc.GetResult(startTotal); 
		} 
	} 
} 


UI层代码(可以很容易的转换为Web页面) 
double total = 0.0d;//用于总计 
CashFacade cf = new CashFacade(); 
private void Form1_Load(object sender, EventArgs e) 
{ 
	//读数据绑定下拉列表 
	cbxType.DataSource = cf.GetCashAcceptTypeList(); 
	cbxType.SelectedIndex = 0; 
} 
private void btnOk_Click(object sender, EventArgs e) 
{ 
	double totalPrices = 0d; 
	//传进下拉选择值和原价,计算实际收费结果 
	totalPrices = cf.GetFactTotal( 
	cbxType.SelectedItem.ToString(), 
	Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text) ); 
	total = total + totalPrices; 
	lbxList.Items.Add( 
		"单价:" + txtPrice.Text + 
		" 数量:" + txtNum.Text + " " + cbxType.SelectedItem + 
		" 合计:" + totalPrices.ToString()); 
	lblResult.Text = total.ToString(); 
} 

我们可以对比下最原始的代码和我们经过好几次重构后的现在的代码,两段表示层的代码同样的简单,那么孰优孰劣呢?


门面模式就给大家介绍到这里吧,大家看到这里,应该或多或少有些收获了,软件设计模式,其实很多都是在我们实际开发过程中遇到了问题,然后解决,然后总结出来的经验,所形成的模式。

现在我们再将系列一设计模式的定义拿出来看看,设计模式是指在软件开发中经过验证的用于解决在特定环境重复出现特定问题解决方案确实是这么回事。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值