【IT168 管理】
一、设计模式定义、来历
怎样重复问题的解决方案?
类似的这些问题在开发中经常遇到,经过前人的整理得出的一些好的解决方案,面向对象设计模式就是为了实现面向对象的一些原则的。
在群和论坛上很多人在讨论数据库中等其他领域是否有设计模式?回答是肯定的,设计模式只是前人对重复问题总结的一些经验。只要这个行业存在就有自己的设计模式。
设计模式起源于建筑,首先由建筑设计师亚历山大提出的,然后经过前人推广到软件设计行业来。在他的那本著名“模式语言”(A Pattern Language)一书中,他写道:“每个模式都描述着一种在我们的环境中一遍又一遍地出现的问题,并因此描述了对该问题的核心解决方案。以此方式你可以使用该方案上百万次,而从不需要重复作同样的事情。”
面向对象设计模式让你的设计更好的符合面向对象设计的一些原则,更好的解决日益变化的软件需求。
以下是面向对象的一些设计原则:
•“开-闭”原则(Open-Closed Principle,OCP)封装的问题
一个软件实体应当对扩展开放,对修改关闭。 你添加新功能的时候应该只是向代码集中添加新的代码不应该修改原来的代码。
•里氏代换原则(Liskov Substitution Principle, LSP) 职责的问题
LSP原则要求子类可以无条件的替代父类,子类不能对父类没有暴露的接口进行扩展,客户要调用功能只能通过父类暴露的接口来调用用不能擅自向子类调用。
•依赖倒转原则(dependence inversion principle, DIP) 耦合度问题
依赖倒转原则就是要实现依赖于抽象,抽象不要依赖于实现。要针对接口编程,不要针对实现编程。
•合成/聚合复用原则(Composite/Aggregate Reuse Principle或CARP) 复用问题
在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用这些对象的目的。
•迪米特法则(Law of Demeter,LoD)
一个软件实体应当尽可能少的与其他实体发生相互作用。
•接口隔离原则(interface separate principle, ISP) 职责单一
使用多个专门的接口比使用单一的总接口要好。也就是说,一个类对另外一个类的依赖性应当是建立在最小的接口上。
软件行业最早出现的设计模式著作是GOF(Gang Of Fours)四人组所著的那本《设计模式-可复用面向对象软件基础》,这本书将设计模式分为三大类:
•创建型模式: 创建型模式是关注对象的创建实例化的。它将对象的创建与对象的实现、描绘相分离。
•结构型模式: 结构型模式关注复杂对象的构建。将小粒度的对象组合成大的对象。
•行为型模式: 行为型模式关注对象的职责以及他们之间如何通信的问题
二、抽象工厂模式的使用场景
今天要谈的抽象工厂模式属于对象创建型模式。
创建型模式抽象了对象实例化的过程,它帮助系统不依赖于对象如何创建,如何实现,何时创建。个类创建型模式使用继承使对象创建多样化,一个对象创建模式将对象的创建代理到其他类。
那抽象工厂模式是为了解决什么问题的呢?给了我们怎样的设计思路?在软件开发中我们经常会碰到一系列相关的对象需要创建,如果按照常规做法我们就要为不同的对象创建编写不同的代码,复用性和可维护性都降低了。而且这些相关对象创建的方式也许不同,那么客户代码创建的时候就要针对不同的对象编码,对象创建的方式还是一个容易改变的地方。基于这样的情况提出了抽象工厂模式,抽象工厂模式为创建一系列相关对象提供了统一的接口,客户只要调用这个接口即可,封装了变化,隔离了变化,让客户代码稳定起来。
比如这样一个情况,我们做了一个桌面软件,这个软件的界面是可以改变的,它有几种风格:XP风格、Win2000风格、苹果机风格。
这个软件就是显示一个窗口,窗口有标题栏、滚动条,XP风格的界面有它自己的标题栏和滚动条,而苹果机风格的又不一样。
我们常常怎么做?
switch(type) { case "XP" : setTitle(new XPTitle()); setScrollbar(new XPScrollbar()); break; case "win2000" : setTitle(new win2000Title()); setScrollbar(new win2000Scrollbar()); break; case "macos" : setTitle(new macosTitle()); setScrollbar(new macosScrollbar()); break; }
这样做有什么坏处呢,这个例子太小实际上没有什么坏处,这样写可以。但是人总会出错,有一次你这样写了:
case "win2000" : setTitle(new win2000Title()); setScrollbar(new XPScrollbar()); break;
你的界面将是Win2000的标题栏,XP风格的滚动条,这就造成了不一致性抽象工厂就是为了解决这种一系列相关对象创建工作的。
图1:抽象工厂的类图
在上面这个例子中标题栏和滚动条都是我们的产品,我们还应该写一个WindowManager类,专门来管理这些产品的创建的,而我们的WinXP团队、Win2000团队、苹果机团队实现这个WindowManager,WinXP团队只会制造出XP风格的产品。
三、设计模式实例
下面举一个网上非常流行的例子。
带着你的女友去快餐店吃东西,比如麦当劳、肯德基,你走进快餐店只需要对服务员说“来份鸡腿”(统一的接口),如果是麦当劳服务员就会用麦当劳专用的盘子装着麦当劳的鸡腿拿给你,走进肯德基也一样,你还是只需要说“来份鸡腿”,不需要改变你的用词,而且也不会出现麦当劳的盘子装着肯德基的(一系列相关类)鸡腿。
在这里 麦当劳、肯德基就是生产食品产品的工厂、盘子和鸡腿都是他们的产品。
这个实例我用一个演进的过程来描述,首先我们按照Gof所描述的抽象工厂模式来完成这个示例的代码,然后,为了验证开闭原则(对添加打开,对修改关闭),假设本地新开了一家快餐厅,向系统的服务器层添加一个快餐厅(全聚德)来看看是否对系统造成修改的影响,在.net里我们可以利用反射功能将这个抽象工厂模式演进为一个反射工厂模式,利用反射后我们甚至可以去掉麦当劳和肯德基等餐厅的实现,但是如果女友想去别的餐厅吃的时候girlfriend类还是要改变,进一步我们可以利用配置文件来配置所要去的餐厅这样系统就稳定多了,当要添加餐厅并去新餐厅的时候只需要添加新的特性修改配置文件。
///代码清单 using System; namespace Jurassic.Training.AbstractFactory { /// <summary> /// 餐厅类抽象工厂 /// </summary> public abstract class Restaurant { /// <summary> /// 创建一个盘子类 /// </summary> /// <returns>盘子</returns> public abstract Plate CreatePlate(); /// <summary> /// 创建一个鸡腿类 /// </summary> /// <returns>鸡腿</returns> public abstract Drumstick CreateDrumstick(); } } using System; namespace Jurassic.Training.AbstractFactory { /// <summary> /// 肯德基餐厅 /// </summary> public class KFC : Restaurant { /// <summary> /// 创建一个肯德基的盘子 /// </summary> /// <returns>肯德基盘子</returns> public override Plate CreatePlate() { return new KFCPlate(); } /// <summary> /// 创建一个肯德基鸡腿 /// </summary> /// <returns>肯德基鸡腿</returns> public override Drumstick CreateDrumstick() { return new KFCDrumstick(); } } } using System; namespace Jurassic.Training.AbstractFactory { /// <summary> /// 麦当劳餐厅 /// </summary> public class MDL : Restaurant { /// <summary> /// 创建一个麦当劳盘子 /// </summary> /// <returns>麦当劳盘子</returns> public override Plate CreatePlate() { return new MDLPlate(); } /// <summary> /// 创建一个麦当劳鸡腿 /// </summary> /// <returns>麦当劳鸡腿</returns> public override Drumstick CreateDrumstick() { return new MDLDrumstick(); } } } using System; namespace Jurassic.Training.AbstractFactory { /// <summary> /// 盘子产品父类 /// </summary> public abstract class Plate { /// <summary> /// 盘子装载方法 /// </summary> /// <returns></returns> public abstract string Load(); } } using System; namespace Jurassic.Training.AbstractFactory { /// <summary> /// 肯德基的盘子 /// </summary> public class KFCPlate : Plate { public override string Load() { return "肯德基的盘子装"; } } } using System; namespace Jurassic.Training.AbstractFactory { /// <summary> /// 麦当劳的盘子 /// </summary> public class MDLPlate : Plate { public override string Load() { return "麦当劳的盘子装"; } } } using System; namespace Jurassic.Training.AbstractFactory { /// <summary> /// 鸡腿产品父类 /// </summary> public abstract class Drumstick { public abstract string Name{get;} } } using System; namespace Jurassic.Training.AbstractFactory { /// <summary> /// 肯德基鸡腿 /// </summary> public class KFCDrumstick : Drumstick { public override string Name { get{ return "肯德基的鸡腿"; } } } } using System; namespace Jurassic.Training.AbstractFactory { /// <summary> /// 麦当劳鸡腿 /// </summary> public class MDLDrumstick : Drumstick { public override string Name { get { return "麦当劳的鸡腿"; } } } } using System; namespace Jurassic.Training.AbstractFactory { /// <summary> /// 女友,模拟客户类 /// </summary> public class Girlfriend { public static void Main() { //走进肯德基 Restaurant restaurant = new KFC(); GoRestaurant(restaurant); Console.ReadLine(); } public static void GoRestaurant(Restaurant restaurant) { Plate p = restaurant.CreatePlate(); Drumstick d = restaurant.CreateDrumstick(); Console.WriteLine(p.Load() + d.Name); } } }
现在我们向系统添加一个餐厅:全聚德餐厅。
using System; namespace Jurassic.Training.AbstractFactory { /// <summary> /// 全聚德餐厅 /// </summary> public class QJD : Restaurant { /// <summary> /// 创建全聚德盘子 /// </summary> /// <returns></returns> public override Plate CreatePlate() { return new QJDPlate(); } /// <summary> /// 创建全聚德鸡腿 /// </summary> /// <returns></returns> public override Drumstick CreateDrumstick() { return new QJDDrumstick(); } } } using System; namespace Jurassic.Training.AbstractFactory { /// <summary> /// 全聚德盘子 /// </summary> public class QJDPlate : Plate { public override string Load() { return "全聚德的盘子装"; } } } using System; namespace Jurassic.Training.AbstractFactory { /// <summary> /// 全聚德鸡腿 /// </summary> public class QJDDrumstick : Drumstick { public override string Name { get{return "全聚德鸡腿";} } } }
为了可以进全聚德吃鸡腿我们需要对客户类进行修改:
using System; namespace Jurassic.Training.AbstractFactory { public class Girlfriend { public static void Main() { //走进肯德基 //Restaurant restaurant = new KFC(); //GoRestaurant(restaurant); //走进麦当劳 //Restaurant restaurant = new MDL(); //GoRestaurant(restaurant); //走进全聚德 Restaurant restaurant = new QJD(); GoRestaurant(restaurant); Console.ReadLine(); } public static void GoRestaurant(Restaurant restaurant) { Plate p = restaurant.CreatePlate(); Drumstick d = restaurant.CreateDrumstick(); Console.WriteLine(p.Load() + d.Name); } } }
但是整个过程我们并没有对系统的基础框架进行修改,只是添加了新的特性,符合开-闭原则。
仔细审视了工厂部分的实现,我们发现几个餐厅的实现部分都很雷同,我们请来了.net的反射,将几个餐厅实现部分都去除了,代码大大减少。
要使用.net的反射我们将餐厅的抽象类修改为如下所示:
using System; using System.Reflection; namespace Jurassic.Training.AbstractFactory { public class Restaurant { private string _plateFile; private string _drumstickFile; private string _plate; private string _drumstick; public Restaurant(string type) { _plateFile = string.Format("{0}Plate.dll",type); _drumstickFile = string.Format("{0}Drumstick.dll",type); _plate = string.Format("Jurassic.Training.AbstractFactory.{0}Plate",type); _drumstick = string.Format("Jurassic.Training.AbstractFactory.{0}Drumstick",type); } public Plate CreatePlate() { Plate plate = Helper(_plateFile,_plate) as Plate; return plate; } public Drumstick CreateDrumstick() { Drumstick drumstick = Helper(_drumstickFile,_drumstick) as Drumstick; return drumstick; } /// <summary> /// 根据程序集文件名称和类型实例化类型 /// </summary> /// <param name="fileName"></param> /// <param name="type"></param> /// <returns></returns> private object Helper(string fileName,string type) { Assembly asm = Assembly.LoadFrom(fileName); object o = asm.CreateInstance (type); return o; } } }
这样我们可以把KFC.cs,MDL.cs,QJD.cs全部去掉了,要调用新的反射工厂模式的实现需要修改girlfriend如下:
using System; namespace Jurassic.Training.AbstractFactory { public class Girlfriend { public static void Main() { //走进全聚德 Restaurant restaurant = new Restaurant("QJD"); GoRestaurant(restaurant); Console.ReadLine(); } public static void GoRestaurant(Restaurant restaurant) { Plate p = restaurant.CreatePlate(); Drumstick d = restaurant.CreateDrumstick(); Console.WriteLine(p.Load() + d.Name); } } }
虽说代码减少不少,但是如果我们要更换就餐的餐厅还是要修改girlfriend类,然后重新编译,这个时候我们想到把这个变化存储在应用程序之外,.net里的配置文件就是存储这个东西的好地方。
代码进一步演进,如下:
using System; using System.Configuration; namespace Jurassic.Training.AbstractFactory { public class Girlfriend { public static void Main() { //走进全聚德,从配置文件里获取信息 Restaurant restaurant = new Restaurant(ConfigurationManager.AppSettings["type"]); GoRestaurant(restaurant); Console.ReadLine(); } public static void GoRestaurant(Restaurant restaurant) { Plate p = restaurant.CreatePlate(); Drumstick d = restaurant.CreateDrumstick(); Console.WriteLine(p.Load() + d.Name); } } } 加上配置文件 Girlfriend.exe.config <?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="type" value="KFC"/> </appSettings> </configuration>
上面这个例子从Gof原版的抽象工厂模式说起,演进到.net里面抽象工厂模式的变化,最后用反射和配置文件构造出一个非常松耦合的系统。
四、现实中的抽象工厂模式
微软推出.net的时候,为了和Java相比较,针对Java的PetStore推出了PetShop 3.0,Petshop是一个经典的三层架构实例,它提供了两种数据库支持:SQL Server,Oracle,提供多数据库支持的时候就是使用的.net中抽象工厂模式的变体:反射工厂模式