0%

  之前一直在忙于工作上的事情,关于设计模式系列一直没更新,最近项目中发现,对于设计模式的了解是必不可少的,当然对于设计模式的应用那更是重要,可以说是否懂得应用设计模式在项目中是衡量一个程序员的技术水平,因为对于一个功能的实现,高级工程师和初级工程师一样都会实现,但是区别在于它们实现功能的可扩展和可维护性,也就是代码的是否“优美”、可读。但是,要更好地应用,首先就必须了解各种设计模式和其应用场景,所以我还是希望继续完成设计模式这个系列,希望通过这种总结的方式来加深自己设计模式的理解。

2.1 命令模式的定义

命令模式属于对象的行为型模式。命令模式是把一个操作或者行为抽象为一个对象中,通过对命令的抽象化来使得发出命令的责任和执行命令的责任分隔开。命令模式的实现可以提供命令的撤销和恢复功能。

2.2 命令模式的结构

  既然,命令模式是实现把发出命令的责任和执行命令的责任分割开,然而中间必须有某个对象来帮助发出命令者来传达命令,使得执行命令的接收者可以收到命令并执行命令。例如,开学了,院领导说计算机学院要进行军训,计算机学院的学生要跑1000米,院领导的话也就相当于一个命令,他不可能直接传达给到学生,他必须让教官来发出命令,并监督学生执行该命令。在这个场景中,发出命令的责任是属于学院领导,院领导充当与命令发出者的角色,执行命令的责任是属于学生,学生充当于命令接收者的角色,而教官就充当于命令的发出者或命令请求者的角色,然而命令模式的精髓就在于把每个命令抽象为对象。从而命令模式的结构如下图所示:

从命令模式的结构图可以看出,它涉及到五个角色,它们分别是:

  • 客户角色:发出一个具体的命令并确定其接受者。
  • 命令角色:声明了一个给所有具体命令类实现的抽象接口
  • 具体命令角色:定义了一个接受者和行为的弱耦合,负责调用接受者的相应方法。
  • 请求者角色:负责调用命令对象执行命令。
  • 接受者角色:负责具体行为的执行。

2.3 命令模式的实现

  现在,让我们以上面的军训的例子来实现一个命令模式,在实现之前,可以参考下命令模式的结构图来分析下实现过程。

  军训场景中,具体的命令即是学生跑1000米,这里学生是命令的接收者,教官是命令的请求者,院领导是命令的发出者,即客户端角色。要实现命令模式,则必须需要一个抽象命令角色来声明约定,这里以抽象类来来表示。命令的传达流程是:

  命令的发出者必须知道具体的命令、接受者和传达命令的请求者,对应于程序也就是在客户端角色中需要实例化三个角色的实例对象了。

  命令的请求者负责调用命令对象的方法来保证命令的执行,对应于程序也就是请求者对象需要有命令对象的成员,并在请求者对象的方法内执行命令。

  具体命令就是跑1000米,这自然属于学生的责任,所以是具体命令角色的成员方法,而抽象命令类定义这个命令的抽象接口。

  有了上面的分析之后,具体命令模式的实现代码如下所示:

复制代码

1
2 // 教官,负责调用命令对象执行请求
3 public class Invoke 4 {
5 public Command _command; 6
7 public Invoke(Command command) 8 {
9 this._command = command; 10 } 11
12 public void ExecuteCommand() 13 { 14 _command.Action(); 15 } 16 } 17
18 // 命令抽象类
19 public abstract class Command 20 { 21 // 命令应该知道接收者是谁,所以有Receiver这个成员变量
22 protected Receiver _receiver; 23
24 public Command(Receiver receiver) 25 { 26 this._receiver = receiver; 27 } 28
29 // 命令执行方法
30 public abstract void Action(); 31 } 32
33 //
34 public class ConcreteCommand :Command 35 { 36 public ConcreteCommand(Receiver receiver) 37 : base(receiver) 38 { 39 } 40
41 public override void Action() 42 { 43 // 调用接收的方法,因为执行命令的是学生
44 _receiver.Run1000Meters(); 45 } 46 } 47
48 // 命令接收者——学生
49 public class Receiver 50 { 51 public void Run1000Meters() 52 { 53 Console.WriteLine(“跑1000米”); 54 } 55 } 56
57 // 院领导
58 class Program 59 { 60 static void Main(string[] args) 61 { 62 // 初始化Receiver、Invoke和Command
63 Receiver r = new Receiver(); 64 Command c = new ConcreteCommand(r); 65 Invoke i = new Invoke(c); 66
67 // 院领导发出命令
68 i.ExecuteCommand(); 69 } 70 }

复制代码

 在ASP.NET的MVC模式中,有一种叫Front Controller的模式,它分为Handler和Command树两个部分,Handler处理所有公共的逻辑,接收HTTP Post或Get请求以及相关的参数并根据输入的参数选择正确的命令对象,然后将控制权传递到Command对象,由其完成后面的操作,这里面其实就是用到了Command模式。

 

Front Controller 的处理程序部分结构图

Front Controller的命令部分结构图

Handler 类负责处理各个 Web 请求,并将确定正确的 Command 对象这一职责委派给 CommandFactory 类。当 CommandFactory 返回 Command 对象后,Handler 将调用 Command 上的 Execute 方法来执行请求。具体的实现如下

1 // Handler类
2 public class Handler : IHttpHandler 3
4 {
5 public void ProcessRequest(HttpContext context) 6
7 {
8
9 Command command = CommandFactory.Make(context.Request.Params); 10
11 command.Execute(context);
12
13 }
14
15 public bool IsReusable 16
17 {
18 get
19
20 {
21 return true;
22 }
23 }
24 }
25
26 Command接口:
27 ///


28 /// Command 29 ///

30 public interface Command 31
32 {
33 void Execute(HttpContext context); 34 }
35
36 CommandFactory类:
37 ///
38 /// CommandFactory 39 ///

40 public class CommandFactory 41
42 {
43 public static Command Make(NameValueCollection parms) 44
45 {
46
47 string requestParm = parms[“requestParm”];
48
49 Command command = null;
50
51 //根据输入参数得到不同的Command对象
52
53 switch (requestParm) 54
55 {
56 case “1”:
57
58 command = new FirstPortal(); 59
60 break;
61
62 case “2”:
63
64 command = new SecondPortal(); 65
66 break;
67
68 default:
69
70 command = new FirstPortal(); 71
72 break;
73 }
74
75 return command; 76
77 }
78 }
79
80 RedirectCommand类:
81 public abstract class RedirectCommand : Command 82
83 {
84 //获得Web.Config中定义的key和url键值对,UrlMap类详见下载包中的代码
85
86 private UrlMap map = UrlMap.SoleInstance; 87
88 protected abstract void OnExecute(HttpContext context); 89
90 public void Execute(HttpContext context) 91
92 {
93 OnExecute(context);
94
95 //根据key和url键值对提交到具体处理的页面
96
97 string url = String.Format(“{0}?{1}”, map.Map[context.Request.Url.AbsolutePath], context.Request.Url.Query);
98
99 context.Server.Transfer(url); 100
101 } 102 } 103
104 FirstPortal类: 105 public class FirstPortal : RedirectCommand 106
107 { 108 protected override void OnExecute(HttpContext context) 109
110 { 111 //在输入参数中加入项portalId以便页面处理
112
113 context.Items[“portalId”] = “1”; 114
115 } 116 } 117
118 SecondPortal类: 119 public class SecondPortal : RedirectCommand 120
121 { 122 protected override void OnExecute(HttpContext context) 123
124 { 125 context.Items[“portalId”] = “2”; 126 } 127 }

View Code

   在下面的情况下可以考虑使用命令模式:

  1. 系统需要支持命令的撤销(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo方法吧命令所产生的效果撤销掉。命令对象还可以提供redo方法,以供客户端在需要时,再重新实现命令效果。
  2. 系统需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命周期。意思为:原来请求的发出者可能已经不存在了,而命令对象本身可能仍是活动的。这时命令的接受者可以在本地,也可以在网络的另一个地址。命令对象可以串行地传送到接受者上去。
  3. 如果一个系统要将系统中所有的数据消息更新到日志里,以便在系统崩溃时,可以根据日志里读回所有数据的更新命令,重新调用方法来一条一条地执行这些命令,从而恢复系统在崩溃前所做的数据更新。
  4. 系统需要使用命令模式作为“CallBack(回调)”在面向对象系统中的替代。Callback即是先将一个方法注册上,然后再以后调用该方法。

   命令模式使得命令发出的一个和接收的一方实现低耦合,从而有以下的优点:

  • 命令模式使得新的命令很容易被加入到系统里。
  • 可以设计一个命令队列来实现对请求的Undo和Redo操作。
  • 可以较容易地将命令写入日志。
  • 可以把命令对象聚合在一起,合成为合成命令。合成命令式合成模式的应用。

  命令模式的缺点:

  • 使用命令模式可能会导致系统有过多的具体命令类。这会使得命令模式在这样的系统里变得不实际。

   命令模式的实现要点在于把某个具体的命令抽象化为具体的命令类,并通过加入命令请求者角色来实现将命令发送者对命令执行者的依赖分割开,在上面军训的例子中,如果不使用命令模式的话,则命令的发送者将对命令接收者是强耦合的关系,实现代码如下:

复制代码

1 // 院领导
2 class Program 3 {
4 static void Main(string[] args)
5 {
6 // 行为的请求者和行为的实现者之间呈现一种紧耦合关系
7 Receiver r = new Receiver(); 8
9 r.Run1000Meters(); 10 } 11 } 12
13 public class Receiver 14 { 15 // 操作
16 public void Run1000Meters() 17 { 18 Console.WriteLine(“跑1000米”); 19 } 20 }

复制代码

  到这里,本章的内容就介绍结束了,在下一章将继续为大家分享下我对迭代器模式的理解。

一、引言

在软件开发过程中,客户端程序经常会与复杂系统的内部子系统进行耦合,从而导致客户端程序随着子系统的变化而变化,然而为了将复杂系统的内部子系统与客户端之间的依赖解耦,从而就有了外观模式,也称作 ”门面“模式。下面就具体介绍下外观模式。

二、外观模式的详细介绍

2.1 定义

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。使用外观模式时,我们创建了一个统一的类,用来包装子系统中一个或多个复杂的类,客户端可以直接通过外观类来调用内部子系统中方法,从而外观模式让客户和子系统之间避免了紧耦合。

2.2 外观模式实现

介绍了外观模式的定义之后,让我们具体看看外观模式的由来以及实现,下面与学校中一个选课系统为例来解释外观模式,例如在选课系统中,有注册课程子系统和通知子系统,在不使用外观模式的情况下,客户端必须同时保存注册课程子系统和通知子系统两个引用,如果后期这两个子系统发生改变时,此时客户端的调用代码也要随之改变,这样就没有很好的可扩展性,下面看看不使用外观模式下选课系统的实现方式和客户端调用代码:

复制代码

///


/// 不使用外观模式的情况 /// 此时客户端与三个子系统都发送了耦合,使得客户端程序依赖与子系统 /// 为了解决这样的问题,我们可以使用外观模式来为所有子系统设计一个统一的接口 /// 客户端只需要调用外观类中的方法就可以了,简化了客户端的操作 /// 从而让客户和子系统之间避免了紧耦合 ///

class Client
{ static void Main(string[] args)
{
SubSystemA a = new SubSystemA();
SubSystemB b = new SubSystemB();
SubSystemC c = new SubSystemC();
a.MethodA();
b.MethodB();
c.MethodC();
Console.Read();
}
} // 子系统A
public class SubSystemA
{ public void MethodA()
{
Console.WriteLine(“执行子系统A中的方法A”);
}
} // 子系统B
public class SubSystemB
{ public void MethodB()
{
Console.WriteLine(“执行子系统B中的方法B”);
}
} // 子系统C
public class SubSystemC
{ public void MethodC()
{
Console.WriteLine(“执行子系统C中的方法C”);
}
}

复制代码

然而外观模式可以解决我们上面所说的问题,下面具体看看使用外观模式的实现:

复制代码

///


/// 以学生选课系统为例子演示外观模式的使用 /// 学生选课模块包括功能有: /// 验证选课的人数是否已满 /// 通知用户课程选择成功与否 /// 客户端代码 ///

class Student
{ private static RegistrationFacade facade = new RegistrationFacade(); static void Main(string[] args)
{ if (facade.RegisterCourse(“设计模式”, “Learning Hard”))
{
Console.WriteLine(“选课成功”);
} else {
Console.WriteLine(“选课失败”);
}

        Console.Read();
    }
} // 外观类
public class RegistrationFacade
{ private RegisterCourse registerCourse; private NotifyStudent notifyStu; public RegistrationFacade()
    {
        registerCourse \= new RegisterCourse();
        notifyStu \= new NotifyStudent();
    } public bool RegisterCourse(string courseName, string studentName)
    { if (!registerCourse.CheckAvailable(courseName))
        { return false;
        } return notifyStu.Notify(studentName);
    }
} #region 子系统
// 相当于子系统A
public class RegisterCourse
{ public bool CheckAvailable(string courseName)
    {
        Console.WriteLine("正在验证课程 {0}是否人数已满", courseName); return true;
    }
} // 相当于子系统B
public class NotifyStudent
{ public bool Notify(string studentName)
    {
        Console.WriteLine("正在向{0}发生通知", studentName); return true;
    }
} #endregion

复制代码

使用了外观模式之后,客户端只依赖与外观类,从而将客户端与子系统的依赖解耦了,如果子系统发生改变,此时客户端的代码并不需要去改变。外观模式的实现核心主要是——由外观类去保存各个子系统的引用,实现由一个统一的外观类去包装多个子系统类,然而客户端只需要引用这个外观类,然后由外观类来调用各个子系统中的方法。然而这样的实现方式非常类似适配器模式,然而外观模式与适配器模式不同的是:适配器模式是将一个对象包装起来以改变其接口,而外观是将一群对象 ”包装“起来以简化其接口。****它们的意图是不一样的,适配器是将接口转换为不同接口,而外观模式是提供一个统一的接口来简化接口

2.3 外观模式的结构

看完外观模式的实现之后,为了帮助理清外观模式中类之间的关系,下面给出上面实现代码中类图:

然而对于外观模式而言,是没有一个一般化的类图描述,下面演示一个外观模式的示意性对象图来加深大家对外观模式的理解:

在上面的对象图中有两个角色:

门面(Facade)角色:客户端调用这个角色的方法。该角色知道相关的一个或多个子系统的功能和责任,该角色会将从客户端发来的请求委派带相应的子系统中去。

子系统(subsystem)角色:可以同时包含一个或多个子系统。每个子系统都不是一个单独的类,而是一个类的集合。每个子系统都可以被客户端直接调用或被门面角色调用。对于子系统而言,门面仅仅是另外一个客户端,子系统并不知道门面的存在。

三、外观的优缺点

优点:

  1. 外观模式对客户屏蔽了子系统组件,从而简化了接口,减少了客户处理的对象数目并使子系统的使用更加简单。
  2. 外观模式实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件是紧耦合的。松耦合使得子系统的组件变化不会影响到它的客户。

缺点:

  1. 如果增加新的子系统可能需要修改外观类或客户端的源代码,这样就违背了”开——闭原则“(不过这点也是不可避免)。

四、使用场景

 在以下情况下可以考虑使用外观模式:

  • 外一个复杂的子系统提供一个简单的接口
  • 提供子系统的独立性
  • 在层次化结构中,可以使用外观模式定义系统中每一层的入口。其中三层架构就是这样的一个例子

五、总结

到这里外观模式的介绍就结束了,外观模式,为子系统的一组接口提供一个统一的接口,该模式定义了一个高层接口,这一个高层接口使的子系统更加容易使用。并且外观模式可以解决层结构分离、降低系统耦合度和为新旧系统交互提供接口功能。

本文所有源码:设计模式之外观模式

一、引言

  在简单工厂模式中讲到简单工厂模式的缺点,有一点是——简单工厂模式系统难以扩展,一旦添加新产品就不得不修改简单工厂方法,这样就会造成简单工厂的实现逻辑过于复杂,然而本专题介绍的工厂方法模式可以解决简单工厂模式中存在的这个问题,下面就具体看看工厂模式是如何解决该问题的。

二、工厂方法模式的实现

  工厂方法模式之所以可以解决简单工厂的模式,是因为它的实现把具体产品的创建推迟到子类中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式就可以允许系统不修改工厂类逻辑的情况下来添加新产品,这样也就克服了简单工厂模式中缺点。下面看下工厂模式的具体实现代码(这里还是以简单工厂模式中点菜的例子来实现):

复制代码

namespace 设计模式之工厂方法模式
{ ///


/// 菜抽象类 ///

public abstract class Food
{ // 输出点了什么菜
public abstract void Print();
} ///
/// 西红柿炒鸡蛋这道菜 ///

public class TomatoScrambledEggs : Food
{ public override void Print()
{
Console.WriteLine(“西红柿炒蛋好了!”);
}
} ///
/// 土豆肉丝这道菜 ///

public class ShreddedPorkWithPotatoes : Food
{ public override void Print()
{
Console.WriteLine(“土豆肉丝好了”);
}
} ///
/// 抽象工厂类 ///

public abstract class Creator
{ // 工厂方法
public abstract Food CreateFoddFactory();
} ///
/// 西红柿炒蛋工厂类 ///

public class TomatoScrambledEggsFactory:Creator
{ ///
/// 负责创建西红柿炒蛋这道菜 ///

///
public override Food CreateFoddFactory()
{ return new TomatoScrambledEggs();
}
} ///
/// 土豆肉丝工厂类 ///

public class ShreddedPorkWithPotatoesFactory:Creator
{ ///
/// 负责创建土豆肉丝这道菜 ///

///
public override Food CreateFoddFactory()
{ return new ShreddedPorkWithPotatoes();
}
} ///
/// 客户端调用 ///

class Client
{ static void Main(string[] args)
{ // 初始化做菜的两个工厂()
Creator shreddedPorkWithPotatoesFactory = new ShreddedPorkWithPotatoesFactory();
Creator tomatoScrambledEggsFactory = new TomatoScrambledEggsFactory(); // 开始做西红柿炒蛋
Food tomatoScrambleEggs = tomatoScrambledEggsFactory.CreateFoddFactory();
tomatoScrambleEggs.Print(); //开始做土豆肉丝
Food shreddedPorkWithPotatoes = shreddedPorkWithPotatoesFactory.CreateFoddFactory();
shreddedPorkWithPotatoes.Print();

        Console.Read();
    }
}  

}

复制代码

使用工厂方法实现的系统,如果系统需要添加新产品时,我们可以利用多态性来完成系统的扩展,对于抽象工厂类和具体工厂中的代码都不需要做任何改动。例如,我们我们还想点一个“肉末茄子”,此时我们只需要定义一个肉末茄子具体工厂类肉末茄子类就可以。而不用像简单工厂模式中那样去修改工厂类中的实现(具体指添加case语句)。具体代码为:

复制代码

///


/// 肉末茄子这道菜 ///

public class MincedMeatEggplant : Food
{ ///
/// 重写抽象类中的方法 ///

public override void Print()
{
Console.WriteLine(“肉末茄子好了”);
}
} ///
/// 肉末茄子工厂类,负责创建肉末茄子这道菜 ///

public class MincedMeatEggplantFactory : Creator
{ ///
/// 负责创建肉末茄子这道菜 ///

///
public override Food CreateFoddFactory()
{ return new MincedMeatEggplant();
}
} ///
/// 客户端调用 ///

class Client
{ static void Main(string[] args)
{ // 如果客户又想点肉末茄子了 // 再另外初始化一个肉末茄子工厂
Creator minceMeatEggplantFactor = new MincedMeatEggplantFactory(); // 利用肉末茄子工厂来创建肉末茄子这道菜
Food minceMeatEggplant = minceMeatEggplantFactor.CreateFoddFactory();
minceMeatEggplant.Print();

        Console.Read();
    }
} 

复制代码

三、工厂方法模式的UML图

讲解完工厂模式的具体实现之后,让我们看下工厂模式中各类之间的UML图:

从UML图可以看出,在工厂方法模式中,工厂类与具体产品类具有平行的等级结构,它们之间是一一对应的。针对UML图的解释如下:

Creator类:充当抽象工厂角色,任何具体工厂都必须继承该抽象类

TomatoScrambledEggsFactory和ShreddedPorkWithPotatoesFactory类:充当具体工厂角色,用来创建具体产品

Food类:充当抽象产品角色,具体产品的抽象类。任何具体产品都应该继承该类

TomatoScrambledEggs和ShreddedPorkWithPotatoes类:充当具体产品角色,实现抽象产品类对定义的抽象方法,由具体工厂类创建,它们之间有一一对应的关系。

四、.NET中实现了工厂方法的类

.NET 类库中也有很多实现了工厂方法的类,例如Asp.net中,处理程序对象是具体用来处理请求,当我们请求一个*.aspx的文件时,此时会映射到System.Web.UI.PageHandlerFactory类上进行处理,而对*.ashx的请求将映射到System.Web.UI.SimpleHandlerFactory类中(这两个类都是继承于IHttpHandlerFactory接口的),关于这点说明我们可以在“C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Web.Config”文件中找到相关定义,具体定义如下:

下面我们就具体看下工厂方法模式在Asp.net中是如何实现的,如果对一个Index.aspx页面发出请求时,将会调用PageHandlerFactory中GetHandler方法来创建一个Index.aspx对象,它们之间的类图关系如下:

五、总结

工厂方法模式通过面向对象编程中的多态性来将对象的创建延迟到具体工厂中,从而解决了简单工厂模式中存在的问题,也很好地符合了开放封闭原则(即对扩展开发,对修改封闭)。

Builder,建造模式:将一个复杂对象的构建与他的表示相分离,使得同样的构建过程可以创建不同的表示。 应用场景:一个类的各个组成部分的具体实现类或者算法经常面临着变化,但是将他们组合在一起的算法却相对稳定。提供一种封装机制 将稳定的组合算法于易变的各个组成部分隔离开来。

http://www.cnblogs.com/PatrickLiu/p/7614630.html
http://www.cnblogs.com/zhili/p/BuilderPattern.html

一、引言

  在软件系统中,有时需要创建一个复杂对象,并且这个复杂对象由其各部分子对象通过一定的步骤组合而成。例如一个采购系统中,如果需要采购员去采购一批电脑时,在这个实际需求中,电脑就是一个复杂的对象,它是由CPU、主板、硬盘、显卡、机箱等组装而成的,如果此时让采购员一台一台电脑去组装的话真是要累死采购员了,这里就可以采用建造者模式来解决这个问题,我们可以把电脑的各个组件的组装过程封装到一个建造者类对象里,建造者只要负责返还给客户端全部组件都建造完毕的产品对象就可以了。然而现实生活中也是如此的,如果公司要采购一批电脑,此时采购员不可能自己去买各个组件并把它们组织起来,此时采购员只需要像电脑城的老板说自己要采购什么样的电脑就可以了,电脑城老板自然会把组装好的电脑送到公司。下面就以这个例子来展开建造者模式的介绍。

二、建造者模式的详细介绍

2.1 建筑者模式的具体实现

  在这个例子中,电脑城的老板是直接与客户(也就是指采购员)联系的,然而电脑的组装是由老板指挥装机人员去把电脑的各个部件组装起来,真真负责创建产品(这里产品指的就是电脑)的人就是电脑城的装机人员。理清了这个逻辑过程之后,下面就具体看下如何用代码来表示这种现实生活中的逻辑过程:

复制代码

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5
6
7 ///


8 /// 以组装电脑为例子 9 /// 每台电脑的组成过程都是一致的,但是使用同样的构建过程可以创建不同的表示(即可以组装成不一样的电脑,配置不一样) 10 /// 组装电脑的这个场景就可以应用建造者模式来设计 11 ///

12 namespace 设计模式之建造者模式 13 {
14 ///
15 /// 客户类 16 ///

17 class Customer 18 {
19 static void Main(string[] args)
20 {
21 // 客户找到电脑城老板说要买电脑,这里要装两台电脑 22 // 创建指挥者和构造者
23 Director director = new Director(); 24 Builder b1 = new ConcreteBuilder1(); 25 Builder b2 = new ConcreteBuilder2(); 26
27 // 老板叫员工去组装第一台电脑
28 director.Construct(b1);
29
30 // 组装完,组装人员搬来组装好的电脑
31 Computer computer1 = b1.GetComputer(); 32 computer1.Show();
33
34 // 老板叫员工去组装第二台电脑
35 director.Construct(b2);
36 Computer computer2 = b2.GetComputer(); 37 computer2.Show();
38
39 Console.Read();
40 }
41 }
42
43 ///
44 /// 小王和小李难道会自愿地去组装嘛,谁不想休息的,这必须有一个人叫他们去组装才会去的 45 /// 这个人当然就是老板了,也就是建造者模式中的指挥者 46 /// 指挥创建过程类 47 ///

48 public class Director 49 {
50 // 组装电脑
51 public void Construct(Builder builder) 52 {
53 builder.BuildPartCPU();
54 builder.BuildPartMainBoard();
55 }
56 }
57
58 ///
59 /// 电脑类 60 ///

61 public class Computer 62 {
63 // 电脑组件集合
64 private IList<string> parts = new List<string>();
65
66 // 把单个组件添加到电脑组件集合中
67 public void Add(string part) 68 {
69 parts.Add(part);
70 }
71
72 public void Show() 73 {
74 Console.WriteLine(“电脑开始在组装…….”);
75 foreach (string part in parts) 76 {
77 Console.WriteLine(“组件”+part+”已装好”);
78 }
79
80 Console.WriteLine(“电脑组装好了”);
81 }
82 }
83
84 ///
85 /// 抽象建造者,这个场景下为 “组装人” ,这里也可以定义为接口 86 ///

87 public abstract class Builder 88 {
89 // 装CPU
90 public abstract void BuildPartCPU(); 91 // 装主板
92 public abstract void BuildPartMainBoard(); 93
94 // 当然还有装硬盘,电源等组件,这里省略 95
96 // 获得组装好的电脑
97 public abstract Computer GetComputer(); 98 }
99
100 ///
101 /// 具体创建者,具体的某个人为具体创建者,例如:装机小王啊 102 ///

103 public class ConcreteBuilder1 : Builder 104 { 105 Computer computer = new Computer(); 106 public override void BuildPartCPU() 107 { 108 computer.Add(“CPU1”); 109 } 110
111 public override void BuildPartMainBoard() 112 { 113 computer.Add(“Main board1”); 114 } 115
116 public override Computer GetComputer() 117 { 118 return computer; 119 } 120 } 121
122 ///
123 /// 具体创建者,具体的某个人为具体创建者,例如:装机小李啊 124 /// 又装另一台电脑了 125 ///

126 public class ConcreteBuilder2 : Builder 127 { 128 Computer computer = new Computer(); 129 public override void BuildPartCPU() 130 { 131 computer.Add(“CPU2”); 132 } 133
134 public override void BuildPartMainBoard() 135 { 136 computer.Add(“Main board2”); 137 } 138
139 public override Computer GetComputer() 140 { 141 return computer; 142 } 143 } 144 }

复制代码

上面代码中都有详细的注释代码,这里就不过多解释,大家可以参考代码和注释来与现实生活中的例子做对比,下图展示了上面代码的运行结果:

2.2 建造者模式的定义和类图

  介绍完了建造者模式的具体实现之后,下面具体看下建造者模式的具体定义是怎样的。

建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式使得建造代码与表示代码的分离,可以使客户端不必知道产品内部组成的细节,从而降低了客户端与具体产品之间的耦合度,下面通过类图来帮助大家更好地理清建造者模式中类之间的关系。

三、建造者模式的分析

介绍完了建造者模式的具体实现之后,让我们总结下建造模式的实现要点:

  1. 在建造者模式中,指挥者是直接与客户端打交道的,指挥者将客户端创建产品的请求划分为对各个部件的建造请求,再将这些请求委派到具体建造者角色,具体建造者角色是完成具体产品的构建工作的,却不为客户所知道。
  2. 建造者模式主要用于“分步骤来构建一个复杂的对象”,其中“分步骤”是一个固定的组合过程,而复杂对象的各个部分是经常变化的(也就是说电脑的内部组件是经常变化的,这里指的的变化如硬盘的大小变了,CPU由单核变双核等)。
  3. 产品不需要抽象类,由于建造模式的创建出来的最终产品可能差异很大,所以不大可能提炼出一个抽象产品类。
  4. 在前面文章中介绍的抽象工厂模式解决了“系列产品”的需求变化,而建造者模式解决的是 “产品部分” 的需要变化。
  5. 由于建造者隐藏了具体产品的组装过程,所以要改变一个产品的内部表示,只需要再实现一个具体的建造者就可以了,从而能很好地应对产品组成组件的需求变化。

四、.NET 中建造者模式的实现

  前面的设计模式在.NET类库中都有相应的实现,那在.NET 类库中,是否也存在建造者模式的实现呢? 然而对于疑问的答案是肯定的,在.NET 类库中,System.Text.StringBuilder(存在mscorlib.dll程序集中)就是一个建造者模式的实现。不过它的实现属于建造者模式的演化,此时的建造者模式没有指挥者角色和抽象建造者角色,StringBuilder类即扮演着具体建造者的角色,也同时扮演了指挥者和抽象建造者的角色,此时建造模式的实现如下:

复制代码

///


/// 建造者模式的演变 /// 省略了指挥者角色和抽象建造者角色 /// 此时具体建造者角色扮演了指挥者和建造者两个角色 ///

public class Builder
{ // 具体建造者角色的代码
private Product product = new Product(); public void BuildPartA()
{
product.Add(“PartA”);
} public void BuildPartB()
{
product.Add(“PartB”);
} public Product GetProduct()
{ return product;
} // 指挥者角色的代码
public void Construct()
{
BuildPartA();
BuildPartB();
}
} ///
/// 产品类 ///

public class Product
{ // 产品组件集合
private IList<string> parts = new List<string>(); // 把单个组件添加到产品组件集合中
public void Add(string part)
{
parts.Add(part);
} public void Show()
{
Console.WriteLine(“产品开始在组装…….”); foreach (string part in parts)
{
Console.WriteLine(“组件” + part + “已装好”);
}

        Console.WriteLine("产品组装完成");
    }
} // 此时客户端也要做相应调整
class Client 
{ private static Builder builder; static void Main(string\[\] args)
    {
        builder \= new Builder();
        builder.Construct();
        Product product \= builder.GetProduct();
        product.Show();
        Console.Read();
    }
}

复制代码

  StringBuilder类扮演着建造string对象的具体建造者角色,其中的ToString()方法用来返回具体产品给客户端(相当于上面代码中GetProduct方法)。其中Append方法用来创建产品的组件(相当于上面代码中BuildPartA和BuildPartB方法),因为string对象中每个组件都是字符,所以也就不需要指挥者的角色的代码(指的是Construct方法,用来调用创建每个组件的方法来完成整个产品的组装),因为string字符串对象中每个组件都是一样的,都是字符,所以Append方法也充当了指挥者Construct方法的作用。

五、总结

  到这里,建造者模式的介绍就结束了,建造者模式(Builder Pattern),将一个复杂对象的构建与它的表示分离,使的同样的构建过程可以创建不同的表示。建造者模式的本质是使组装过程(用指挥者类进行封装,从而达到解耦的目的)和创建具体产品解耦,使我们不用去关心每个组件是如何组装的。

本专题中所有源码: 建造者模式源码

一、引言

这里以电视遥控器的一个例子来引出桥接模式解决的问题,首先,我们每个牌子的电视机都有一个遥控器,此时我们能想到的一个设计是——把遥控器做为一个抽象类,抽象类中提供遥控器的所有实现,其他具体电视品牌的遥控器都继承这个抽象类,具体设计类图如下:

这样的实现使得每部不同型号的电视都有自己遥控器实现,这样的设计对于电视机的改变可以很好地应对,只需要添加一个派生类就搞定了,但随着时间的推移,用户需要改变遥控器的功能,如:用户可能后面需要对遥控器添加返回上一个台等功能时,此时上面的设计就需要修改抽象类RemoteControl的提供的接口了,此时可能只需要向抽象类中添加一个方法就可以解决了,但是这样带来的问题是我们改变了抽象的实现,如果用户需要同时改变电视机品型号和遥控器功能时,上面的设计就会导致相当大的修改,显然这样的设计并不是好的设计。然而使用桥接模式可以很好地解决这个问题,下面让我具体看看桥接模式是如何实现的。

二、桥接模式的详细介绍

2.1 定义

桥接模式即将抽象部分与实现部分脱耦,使它们可以独立变化。对于上面的问题中,抽象化也就是RemoteControl类,实现部分也就是On()、Off()、NextChannel()等这样的方法(即遥控器的实现),上面的设计中,抽象化和实现部分在一起,桥接模式的目的就是使两者分离,根据面向对象的封装变化的原则,我们可以把实现部分的变化(也就是遥控器功能的变化)封装到另外一个类中,这样的一个思路也就是桥接模式的实现,大家可以对照桥接模式的实现代码来解决我们的分析思路。

2.2 桥接模式实现

上面定义部分已经给出了我们桥接模式的目的以及实现思路了,下面让我们具体看看桥接模式是如何解决引言部分设计的不足。

抽象化部分的代码:

复制代码

///


/// 抽象概念中的遥控器,扮演抽象化角色 ///

public class RemoteControl
{ // 字段
private TV implementor; // 属性
public TV Implementor
{ get { return implementor; } set { implementor = value; }
} ///
/// 开电视机,这里抽象类中不再提供实现了,而是调用实现类中的实现 ///

public virtual void On()
{
implementor.On();
} ///
/// 关电视机 ///

public virtual void Off()
{
implementor.Off();
} ///
/// 换频道 ///

public virtual void SetChannel()
{
implementor.tuneChannel();
}
} ///
/// 具体遥控器 ///

public class ConcreteRemote : RemoteControl
{ public override void SetChannel()
{
Console.WriteLine(“-——————–”); base.SetChannel();
Console.WriteLine(“-——————–”);
}
}

复制代码

遥控器的实现方法部分代码,即实现化部分代码,此时我们用另外一个抽象类TV封装了遥控器功能的变化,具体实现交给具体型号电视机去完成:

复制代码

///


/// 电视机,提供抽象方法 ///

public abstract class TV
{ public abstract void On(); public abstract void Off(); public abstract void tuneChannel();
} ///
/// 长虹牌电视机,重写基类的抽象方法 /// 提供具体的实现 ///

public class ChangHong : TV
{ public override void On()
{
Console.WriteLine(“长虹牌电视机已经打开了”);
} public override void Off()
{
Console.WriteLine(“长虹牌电视机已经关掉了”);
} public override void tuneChannel()
{
Console.WriteLine(“长虹牌电视机换频道”);
}
} ///
/// 三星牌电视机,重写基类的抽象方法 ///

public class Samsung : TV
{ public override void On()
{
Console.WriteLine(“三星牌电视机已经打开了”);
} public override void Off()
{
Console.WriteLine(“三星牌电视机已经关掉了”);
} public override void tuneChannel()
{
Console.WriteLine(“三星牌电视机换频道”);
}
}

复制代码

采用桥接模式的客户端调用代码:

复制代码

///


/// 以电视机遥控器的例子来演示桥接模式 ///

class Client
{ static void Main(string[] args)
{ // 创建一个遥控器
RemoteControl remoteControl = new ConcreteRemote(); // 长虹电视机
remoteControl.Implementor = new ChangHong();
remoteControl.On();
remoteControl.SetChannel();
remoteControl.Off();
Console.WriteLine(); // 三星牌电视机
remoteControl.Implementor = new Samsung();
remoteControl.On();
remoteControl.SetChannel();
remoteControl.Off();
Console.Read();
}
}

复制代码

上面桥接模式的实现中,遥控器的功能实现方法不在遥控器抽象类中去实现了,而是把实现部分用来另一个电视机类去封装它,然而遥控器中只包含电视机类的一个引用,同时这样的设计也非常符合现实生活中的情况(我认为的现实生活中遥控器的实现——遥控器中并不包含换台,打开电视机这样的功能的实现,遥控器只是包含了电视机上这些功能的引用,然后红外线去找到电视机上对应功能的的实现)。通过桥接模式,我们把抽象化和实现化部分分离开了,这样就可以很好应对这两方面的变化了。

2.3 桥接模式的类图

看完桥接模式的实现后,为了帮助大家理清对桥接模式中类之间关系,这里给出桥接模式的类图结构:

三、桥接模式的优缺点

介绍完桥接模式,让我们看看桥接模式具体哪些优缺点。

优点:

把抽象接口与其实现解耦。

抽象和实现可以独立扩展,不会影响到对方。

实现细节对客户透明,对用于隐藏了具体实现细节。

缺点: 增加了系统的复杂度

四、使用场景

我们再来看看桥接模式的使用场景,在以下情况下应当使用桥接模式:

  1. 如果一个系统需要在构件的抽象化角色和具体化角色之间添加更多的灵活性,避免在两个层次之间建立静态的联系。
  2. 设计要求实现化角色的任何改变不应当影响客户端,或者实现化角色的改变对客户端是完全透明的。
  3. 需要跨越多个平台的图形和窗口系统上。
  4. 一个类存在两个独立变化的维度,且两个维度都需要进行扩展。

五、一个实际应用桥接模式的例子

桥接模式也经常用于具体的系统开发中,对于三层架构中就应用了桥接模式,三层架构中的业务逻辑层BLL中通过桥接模式与数据操作层解耦(DAL),其实现方式就是在BLL层中引用了DAL层中一个引用。这样数据操作的实现可以在不改变客户端代码的情况下动态进行更换,下面看一个简单的示例代码:

复制代码

// 客户端调用 // 类似Web应用程序
class Client
{ static void Main(string[] args)
{
BusinessObject customers = new CustomersBusinessObject(“ShangHai”);
customers.Dataacces = new CustomersDataAccess();

        customers.Add("小六");
        Console.WriteLine("增加了一位成员的结果:");
        customers.ShowAll();
        customers.Delete("王五");
        Console.WriteLine("删除了一位成员的结果:");
        customers.ShowAll();
        Console.WriteLine("更新了一位成员的结果:");
        customers.Update("Learning\_Hard");
        customers.ShowAll();

        Console.Read();
    }
} // BLL 层
public class BusinessObject
{ // 字段
    private DataAccess dataacess; private string city; public BusinessObject(string city)
    { this.city = city;
    } // 属性
    public DataAccess Dataacces
    { get { return dataacess; } set { dataacess = value; }
    } // 方法
    public virtual void Add(string name)
    {
        Dataacces.AddRecord(name);
    } public virtual void Delete(string name)
    {
        Dataacces.DeleteRecord(name);
    } public virtual void Update(string name)
    {
        Dataacces.UpdateRecord(name);
    } public virtual string Get(int index)
    { return Dataacces.GetRecord(index);
    } public virtual void ShowAll()
    {
        Console.WriteLine();
        Console.WriteLine("{0}的顾客有:", city);
        Dataacces.ShowAllRecords();
    }
} public class CustomersBusinessObject : BusinessObject
{ public CustomersBusinessObject(string city) 
        : base(city) { } // 重写方法
    public override void ShowAll()
    {
        Console.WriteLine("\------------------------"); base.ShowAll();
        Console.WriteLine("\------------------------");
    }
} /// <summary>
/// 相当于三层架构中数据访问层(DAL) /// </summary>
public abstract class DataAccess
{ // 对记录的增删改查操作
    public abstract void AddRecord(string name); public abstract void DeleteRecord(string name); public abstract void UpdateRecord(string name); public abstract string GetRecord(int index); public abstract void ShowAllRecords();
} public class CustomersDataAccess:DataAccess
{ // 字段
    private List<string\> customers =new List<string\>(); public CustomersDataAccess()
    { // 实际业务中从数据库中读取数据再填充列表
        customers.Add("Learning Hard");
        customers.Add("张三");
        customers.Add("李四");
        customers.Add("王五");
    } // 重写方法
    public override void AddRecord(string name)
    {
        customers.Add(name);
    } public override void DeleteRecord(string name)
    {
        customers.Remove(name);
    } public override void UpdateRecord(string updatename)
    {
        customers\[0\] = updatename;
    } public override string GetRecord(int index)
    { return customers\[index\];
    } public override void ShowAllRecords()
    { foreach (string name in customers)
        {
            Console.WriteLine(" " + name);
        }
    }
    
}

复制代码

六、总结

到这里,桥接模式的介绍就介绍,桥接模式实现了抽象化与实现化的解耦,使它们相互独立互不影响到对方