0%

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);
        }
    }
    
}

复制代码

六、总结

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

Template Method,模板方法:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,TemplateMethod使得子类可以不改变一个算法的结构即可以重定义该算法的某些特定步骤。 应用场景:一个操作的步骤稳定,而具体细节的改变延迟的子类

http://www.cnblogs.com/zhili/p/TemplateMethodPattern.html
http://www.cnblogs.com/PatrickLiu/p/7837716.html

  在上一篇文章介绍到可以使用状态者模式和观察者模式来解决中介者模式存在的问题,在本文中将首先通过一个银行账户的例子来解释状态者模式,通过这个例子使大家可以对状态者模式有一个清楚的认识,接着,再使用状态者模式来解决上一篇文章中提出的问题。

  每个对象都有其对应的状态,而每个状态又对应一些相应的行为,如果某个对象有多个状态时,那么就会对应很多的行为。那么对这些状态的判断和根据状态完成的行为,就会导致多重条件语句,并且如果添加一种新的状态时,需要更改之前现有的代码。这样的设计显然违背了开闭原则。状态模式正是用来解决这样的问题的。状态模式将每种状态对应的行为抽象出来成为单独新的对象,这样状态的变化不再依赖于对象内部的行为。

2.1 状态者模式的定义

  上面对状态模式做了一个简单的介绍,这里给出状态模式的定义。

  状态模式——允许一个对象在其内部状态改变时自动改变其行为,对象看起来就像是改变了它的类。

2.2 状态者模式的结构

  既然状态者模式是对已有对象的状态进行抽象,则自然就有抽象状态者类和具体状态者类,而原来已有对象需要保存抽象状态者类的引用,通过调用抽象状态者的行为来改变已有对象的行为。经过上面的分析,状态者模式的结构图也就很容易理解了,具体结构图如下图示。

  

  从上图可知,状态者模式涉及以下三个角色:

  • Account类:维护一个State类的一个实例,该实例标识着当前对象的状态。
  • State类:抽象状态类,定义了一个具体状态类需要实现的行为约定。
  • SilveStater、GoldState和RedState类:具体状态类,实现抽象状态类的每个行为。

2.3 状态者模式的实现

  下面,就以银行账户的状态来实现下状态者模式。银行账户根据余额可分为RedState、SilverState和GoldState。这些状态分别代表透支账号,新开账户和标准账户。账号余额在【-100.0,0.0】范围表示处于RedState状态,账号余额在【0.0 , 1000.0】范围表示处于SilverState,账号在【1000.0, 100000.0】范围表示处于GoldState状态。下面以这样的一个场景实现下状态者模式,具体实现代码如下所示:

复制代码

1 namespace StatePatternSample 2 {
3 public class Account 4 {
5 public State State {get;set;}
6 public string Owner { get; set; }
7 public Account(string owner) 8 {
9 this.Owner = owner; 10 this.State = new SilverState(0.0, this);
11 }
12
13 public double Balance { get {return State.Balance; }} // 余额 14 // 存钱
15 public void Deposit(double amount) 16 {
17 State.Deposit(amount);
18 Console.WriteLine(“存款金额为 {0:C}——“, amount);
19 Console.WriteLine(“账户余额为 =:{0:C}”, this.Balance);
20 Console.WriteLine(“账户状态为: {0}”, this.State.GetType().Name);
21 Console.WriteLine();
22 }
23
24 // 取钱
25 public void Withdraw(double amount) 26 {
27 State.Withdraw(amount);
28 Console.WriteLine(“取款金额为 {0:C}——“,amount);
29 Console.WriteLine(“账户余额为 =:{0:C}”, this.Balance);
30 Console.WriteLine(“账户状态为: {0}”, this.State.GetType().Name);
31 Console.WriteLine();
32 }
33
34 // 获得利息
35 public void PayInterest() 36 {
37 State.PayInterest();
38 Console.WriteLine(“Interest Paid — “);
39 Console.WriteLine(“账户余额为 =:{0:C}”, this.Balance);
40 Console.WriteLine(“账户状态为: {0}”, this.State.GetType().Name);
41 Console.WriteLine();
42 }
43 }
44
45 // 抽象状态类
46 public abstract class State 47 {
48 // Properties
49 public Account Account { get; set; }
50 public double Balance { get; set; } // 余额
51 public double Interest { get; set; } // 利率
52 public double LowerLimit { get; set; } // 下限
53 public double UpperLimit { get; set; } // 上限
54
55 public abstract void Deposit(double amount); // 存款
56 public abstract void Withdraw(double amount); // 取钱
57 public abstract void PayInterest(); // 获得的利息
58 }
59
60 // Red State意味着Account透支了
61 public class RedState : State 62 {
63 public RedState(State state) 64 {
65 // Initialize
66 this.Balance = state.Balance; 67 this.Account = state.Account; 68 Interest = 0.00;
69 LowerLimit = -100.00;
70 UpperLimit = 0.00;
71 }
72
73 // 存款
74 public override void Deposit(double amount) 75 {
76 Balance += amount; 77 StateChangeCheck();
78 }
79 // 取钱
80 public override void Withdraw(double amount) 81 {
82 Console.WriteLine(“没有钱可以取了!”);
83 }
84
85 public override void PayInterest() 86 {
87 // 没有利息
88 }
89
90 private void StateChangeCheck() 91 {
92 if (Balance > UpperLimit) 93 {
94 Account.State = new SilverState(this);
95 }
96 }
97 }
98
99 // Silver State意味着没有利息得
100 public class SilverState :State 101 { 102 public SilverState(State state) 103 : this(state.Balance, state.Account) 104 { 105 } 106
107 public SilverState(double balance, Account account) 108 { 109 this.Balance = balance; 110 this.Account = account; 111 Interest = 0.00; 112 LowerLimit = 0.00; 113 UpperLimit = 1000.00; 114 } 115
116 public override void Deposit(double amount) 117 { 118 Balance += amount; 119 StateChangeCheck(); 120 } 121 public override void Withdraw(double amount) 122 { 123 Balance -= amount; 124 StateChangeCheck(); 125 } 126
127 public override void PayInterest() 128 { 129 Balance += Interest * Balance; 130 StateChangeCheck(); 131 } 132
133 private void StateChangeCheck() 134 { 135 if (Balance < LowerLimit) 136 { 137 Account.State = new RedState(this); 138 } 139 else if (Balance > UpperLimit) 140 { 141 Account.State = new GoldState(this); 142 } 143 } 144 } 145
146 // Gold State意味着有利息状态
147 public class GoldState : State 148 { 149 public GoldState(State state) 150 { 151 this.Balance = state.Balance; 152 this.Account = state.Account; 153 Interest = 0.05; 154 LowerLimit = 1000.00; 155 UpperLimit = 1000000.00; 156 } 157 // 存钱
158 public override void Deposit(double amount) 159 { 160 Balance += amount; 161 StateChangeCheck(); 162 } 163 // 取钱
164 public override void Withdraw(double amount) 165 { 166 Balance -= amount; 167 StateChangeCheck(); 168 } 169 public override void PayInterest() 170 { 171 Balance += Interest * Balance; 172 StateChangeCheck(); 173 } 174
175 private void StateChangeCheck() 176 { 177 if (Balance < 0.0) 178 { 179 Account.State = new RedState(this); 180 } 181 else if (Balance < LowerLimit) 182 { 183 Account.State = new SilverState(this); 184 } 185 } 186 } 187
188 class App 189 { 190 static void Main(string[] args) 191 { 192 // 开一个新的账户
193 Account account = new Account(“Learning Hard”); 194
195 // 进行交易 196 // 存钱
197 account.Deposit(1000.0); 198 account.Deposit(200.0); 199 account.Deposit(600.0); 200
201 // 付利息
202 account.PayInterest(); 203
204 // 取钱
205 account.Withdraw(2000.00); 206 account.Withdraw(500.00); 207
208 // 等待用户输入
209 Console.ReadKey(); 210 } 211 } 212 }

复制代码

  上面代码的运行结果如下图所示:

  从上图可以发现,进行存取款交易,会影响到Account内部的状态,由于状态的改变,从而影响到Account类行为的改变,而且这些操作都是发生在运行时的。

  在上一篇博文中,我曾介绍到中介者模式存在的问题,详细的问题描述可以参考上一篇博文。下面利用观察者模式和状态者模式来完善中介者模式,具体的实现代码如下所示:

1 // 抽象牌友类
2 public abstract class AbstractCardPartner 3 {
4 public int MoneyCount { get; set; }
5
6 public AbstractCardPartner() 7 {
8 MoneyCount = 0;
9 }
10
11 public abstract void ChangeCount(int Count, AbstractMediator mediator); 12 }
13
14 // 牌友A类
15 public class ParterA : AbstractCardPartner 16 {
17 // 依赖与抽象中介者对象
18 public override void ChangeCount(int Count, AbstractMediator mediator) 19 {
20 mediator.ChangeCount(Count);
21 }
22 }
23
24 // 牌友B类
25 public class ParterB : AbstractCardPartner 26 {
27 // 依赖与抽象中介者对象
28 public override void ChangeCount(int Count, AbstractMediator mediator) 29 {
30 mediator.ChangeCount(Count);
31 }
32 }
33
34 // 抽象状态类
35 public abstract class State 36 {
37 protected AbstractMediator meditor; 38 public abstract void ChangeCount(int count); 39 }
40
41 // A赢状态类
42 public class AWinState : State 43 {
44 public AWinState(AbstractMediator concretemediator) 45 {
46 this.meditor = concretemediator; 47 }
48
49 public override void ChangeCount(int count) 50 {
51 foreach (AbstractCardPartner p in meditor.list) 52 {
53 ParterA a = p as ParterA; 54 //
55 if (a != null)
56 {
57 a.MoneyCount += count; 58 }
59 else
60 {
61 p.MoneyCount -= count; 62 }
63 }
64 }
65 }
66
67 // B赢状态类
68 public class BWinState : State 69 {
70 public BWinState(AbstractMediator concretemediator) 71 {
72 this.meditor = concretemediator; 73 }
74
75 public override void ChangeCount(int count) 76 {
77 foreach (AbstractCardPartner p in meditor.list) 78 {
79 ParterB b = p as ParterB; 80 // 如果集合对象中时B对象,则对B的钱添加
81 if (b != null)
82 {
83 b.MoneyCount += count; 84 }
85 else
86 {
87 p.MoneyCount -= count; 88 }
89 }
90 }
91 }
92
93 // 初始化状态类
94 public class InitState : State 95 {
96 public InitState() 97 {
98 Console.WriteLine(“游戏才刚刚开始,暂时还有玩家胜出”);
99 } 100
101 public override void ChangeCount(int count) 102 { 103 //
104 return; 105 } 106 } 107
108 // 抽象中介者类
109 public abstract class AbstractMediator 110 { 111 public List list = new List(); 112
113 public State State { get; set; } 114
115 public AbstractMediator(State state) 116 { 117 this.State = state; 118 } 119
120 public void Enter(AbstractCardPartner partner) 121 { 122 list.Add(partner); 123 } 124
125 public void Exit(AbstractCardPartner partner) 126 { 127 list.Remove(partner); 128 } 129
130 public void ChangeCount(int count) 131 { 132 State.ChangeCount(count); 133 } 134 } 135
136 // 具体中介者类
137 public class MediatorPater : AbstractMediator 138 { 139 public MediatorPater(State initState) 140 : base(initState) 141 { } 142 } 143
144 class Program 145 { 146 static void Main(string[] args) 147 { 148 AbstractCardPartner A = new ParterA(); 149 AbstractCardPartner B = new ParterB(); 150 // 初始钱
151 A.MoneyCount = 20; 152 B.MoneyCount = 20; 153
154 AbstractMediator mediator = new MediatorPater(new InitState()); 155
156 // A,B玩家进入平台进行游戏
157 mediator.Enter(A); 158 mediator.Enter(B); 159
160 // A赢了
161 mediator.State = new AWinState(mediator); 162 mediator.ChangeCount(5); 163 Console.WriteLine(“A 现在的钱是:{0}”, A.MoneyCount);// 应该是25
164 Console.WriteLine(“B 现在的钱是:{0}”, B.MoneyCount); // 应该是15 165
166 // B 赢了
167 mediator.State = new BWinState(mediator); 168 mediator.ChangeCount(10); 169 Console.WriteLine(“A 现在的钱是:{0}”, A.MoneyCount);// 应该是25
170 Console.WriteLine(“B 现在的钱是:{0}”, B.MoneyCount); // 应该是15
171 Console.Read(); 172 } 173 }

View Code

   在以下情况下可以考虑使用状态者模式。

  • 当一个对象状态转换的条件表达式过于复杂时可以使用状态者模式。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简单化。
  • 当一个对象行为取决于它的状态,并且它需要在运行时刻根据状态改变它的行为时,就可以考虑使用状态者模式。

   状态者模式的主要优点是:

  • 将状态判断逻辑每个状态类里面,可以简化判断的逻辑。
  • 当有新的状态出现时,可以通过添加新的状态类来进行扩展,扩展性好。

  状态者模式的主要缺点是:

  • 如果状态过多的话,会导致有非常多的状态类,加大了开销。

  状态者模式是对对象状态的抽象,从而把对象中对状态复杂的判断逻辑已到各个状态类里面,从而简化逻辑判断。在下一篇文章将分享我对策略模式的理解。

一、引言

  这个系列也是自己对设计模式的一些学习笔记,希望对一些初学设计模式的人有所帮助的,在上一个专题中介绍了单例模式,在这个专题中继续为大家介绍一个比较容易理解的模式——简单工厂模式。

二、简单工厂模式的介绍

  说到简单工厂,自然的第一个疑问当然就是什么是简单工厂模式了? 在现实生活中工厂是负责生产产品的,同样在设计模式中,简单工厂模式我们也可以理解为负责生产对象的一个类, 我们平常编程中,当使用”new”关键字创建一个对象时,此时该类就依赖与这个对象,也就是他们之间的耦合度高,当需求变化时,我们就不得不去修改此类的源码,此时我们可以运用面向对象(OO)的很重要的原则去解决这一的问题,该原则就是——封装改变,既然要封装改变,自然也就要找到改变的代码,然后把改变的代码用类来封装,这样的一种思路也就是我们简单工厂模式的实现方式了。下面通过一个现实生活中的例子来引出简单工厂模式。

  在外面打工的人,免不了要经常在外面吃饭,当然我们也可以自己在家做饭吃,但是自己做饭吃麻烦,因为又要自己买菜,然而,出去吃饭就完全没有这些麻烦的,我们只需要到餐馆点菜就可以了,买菜的事情就交给餐馆做就可以了,这里餐馆就充当简单工厂的角色,下面让我们看看现实生活中的例子用代码是怎样来表现的。

自己做饭的情况:

复制代码

///


/// 自己做饭的情况 /// 没有简单工厂之前,客户想吃什么菜只能自己炒的 ///

public class Customer
{ ///
/// 烧菜方法 ///

///
///
public static Food Cook(string type)
{
Food food = null; // 客户A说:我想吃西红柿炒蛋怎么办? // 客户B说:那你就自己烧啊 // 客户A说: 好吧,那就自己做吧
if (type.Equals(“西红柿炒蛋”))
{
food = new TomatoScrambledEggs();
} // 我又想吃土豆肉丝, 这个还是得自己做 // 我觉得自己做好累哦,如果能有人帮我做就好了?
else if (type.Equals(“土豆肉丝”))
{
food = new ShreddedPorkWithPotatoes();
} return food;
} static void Main(string[] args)
{ // 做西红柿炒蛋
Food food1 = Cook(“西红柿炒蛋”);
food1.Print();

        Food food2 \= Cook("土豆肉丝");
        food2.Print();

        Console.Read();
    }
} /// <summary>
/// 菜抽象类 /// </summary>
public abstract class Food
{ // 输出点了什么菜
    public abstract void Print();
} /// <summary>
/// 西红柿炒鸡蛋这道菜 /// </summary>
public class TomatoScrambledEggs : Food
{ public override void Print()
    {
        Console.WriteLine("一份西红柿炒蛋!");
    }
} /// <summary>
/// 土豆肉丝这道菜 /// </summary>
public class ShreddedPorkWithPotatoes : Food
{ public override void Print()
    {
        Console.WriteLine("一份土豆肉丝");
    }
}

复制代码

  自己做饭,如果我们想吃别的菜时,此时就需要去买这种菜和洗菜这些繁琐的操作,有了餐馆(也就是简单工厂)之后,我们就可以把这些操作交给餐馆去做,此时消费者(也就是我们)对菜(也就是具体对象)的依赖关系从直接变成的间接的,这样就是实现了面向对象的另一个原则——降低对象之间的耦合度,下面就具体看看有了餐馆之后的实现代码(即简单工厂的实现):

复制代码

///


/// 顾客充当客户端,负责调用简单工厂来生产对象 /// 即客户点菜,厨师(相当于简单工厂)负责烧菜(生产的对象) ///

class Customer
{ static void Main(string[] args)
{ // 客户想点一个西红柿炒蛋
Food food1 = FoodSimpleFactory.CreateFood(“西红柿炒蛋”);
food1.Print(); // 客户想点一个土豆肉丝
Food food2 = FoodSimpleFactory.CreateFood(“土豆肉丝”);
food2.Print();

        Console.Read();
    }
} /// <summary>
/// 菜抽象类 /// </summary>
public abstract class Food
{ // 输出点了什么菜
    public abstract void Print();
} /// <summary>
/// 西红柿炒鸡蛋这道菜 /// </summary>
public class TomatoScrambledEggs : Food
{ public override void Print()
    {
        Console.WriteLine("一份西红柿炒蛋!");
    }
} /// <summary>
/// 土豆肉丝这道菜 /// </summary>
public class ShreddedPorkWithPotatoes : Food
{ public override void Print()
    {
        Console.WriteLine("一份土豆肉丝");
    }
} /// <summary>
/// 简单工厂类, 负责 炒菜 /// </summary>
public class FoodSimpleFactory
{ public static Food CreateFood(string type)
    {
        Food food \= null; if (type.Equals("土豆肉丝"))
        {
            food\= new ShreddedPorkWithPotatoes();
        } else if (type.Equals("西红柿炒蛋"))
        {
            food\= new TomatoScrambledEggs();
        } return food;
    }
}

复制代码

三、优点与缺点

  看完简单工厂模式的实现之后,你和你的小伙伴们肯定会有这样的疑惑(因为我学习的时候也有)——这样我们只是把变化移到了工厂类中罢了,好像没有变化的问题,因为如果客户想吃其他菜时,此时我们还是需要修改工厂类中的方法(也就是多加case语句,没应用简单工厂模式之前,修改的是客户类)。我首先要说:你和你的小伙伴很对,这个就是简单工厂模式的缺点所在(这个缺点后面介绍的工厂方法可以很好地解决),然而,简单工厂模式与之前的实现也有它的优点:

  • 简单工厂模式解决了客户端直接依赖于具体对象的问题,客户端可以消除直接创建对象的责任,而仅仅是消费产品。简单工厂模式实现了对责任的分割。
  • 简单工厂模式也起到了代码复用的作用,因为之前的实现(自己做饭的情况)中,换了一个人同样要去在自己的类中实现做菜的方法,然后有了简单工厂之后,去餐馆吃饭的所有人都不用那么麻烦了,只需要负责消费就可以了。此时简单工厂的烧菜方法就让所有客户共用了。(同时这点也是简单工厂方法的缺点——因为工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都会受到影响,也没什么不好理解的,就如事物都有两面性一样道理

虽然上面已经介绍了简单工厂模式的缺点,下面还是总结下简单工厂模式的缺点:

  • 工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都会受到影响(通俗地意思就是:一旦餐馆没饭或者关门了,很多不愿意做饭的人就没饭吃了)
  • 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,这样就会造成工厂逻辑过于复杂。

了解了简单工厂模式之后的优缺点之后,我们之后就可以知道简单工厂的应用场景了:

  • 当工厂类负责创建的对象比较少时可以考虑使用简单工厂模式()
  • 客户如果只知道传入工厂类的参数,对于如何创建对象的逻辑不关心时可以考虑使用简单工厂模式

四、简单工厂模式UML图

简单工厂模式又叫静态方法模式(因为工厂类都定义了一个静态方法),由一个工厂类根据传入的参数决定创建出哪一种产品类的实例(通俗点表达:通过客户下的订单来负责烧那种菜)。简单工厂模式的UML图如下:

五、.NET中简单工厂模式的实现

  介绍完了简单工厂模式之后,我学习的时候就像:.NET类库中是否有实现了简单工厂模式的类呢?后面确实有,.NET中System.Text.Encoding类就实现了简单工厂模式,该类中的**GetEncoding(int codepage)就是工厂方法,**具体的代码可以通过Reflector反编译工具进行查看,下面我也贴出该方法中部分代码:

public static Encoding GetEncoding(int codepage)
{
Encoding unicode = null; if (encodings != null)
{
unicode = (Encoding) encodings[codepage];
} if (unicode == null)
{ object obj2; bool lockTaken = false; try {
Monitor.Enter(obj2 = InternalSyncObject, ref lockTaken); if (encodings == null)
{
encodings = new Hashtable();
}
unicode = (Encoding) encodings[codepage]; if (unicode != null)
{ return unicode;
} switch (codepage)
{ case 0:
unicode = Default; break; case 1: case 2: case 3: case 0x2a: throw new ArgumentException(Environment.GetResourceString(“Argument_CodepageNotSupported”, new object[] { codepage }), “codepage”); case 0x4b0:
unicode = Unicode; break; case 0x4b1:
unicode = BigEndianUnicode; break; case 0x6faf:
unicode = Latin1; break; case 0xfde9:
unicode = UTF8; break; case 0x4e4:
unicode = new SBCSCodePageEncoding(codepage); break; case 0x4e9f:
unicode = ASCII; break; default:
unicode = GetEncodingCodePage(codepage); if (unicode == null)
{
unicode = GetEncodingRare(codepage);
} break;
}
encodings.Add(codepage, unicode); return unicode;

    }

}

View Code

.NET 中Encoding的UML图为:

Encoding类中实现的简单工厂模式是简单工厂模式的一种演变,此时简单工厂类由抽象产品角色扮演,然而.NET中Encoding类是如何解决简单工厂模式中存在的问题的呢(即如果新添加一种编码怎么办)?在GetEncoding方法里的switch函数有如下代码:

复制代码

switch (codepage)
{
……. default:
unicode = GetEncodingCodePage(codepage); if (unicode == null)
{
unicode = GetEncodingRare(codepage); //当编码很少见时
} break;
……
}

复制代码

  在GetEncodingRare方法里有一些不常用编码的实例化代码,微软正式通过这个方法来解决新增加一种编码的问题。(其实也就是列出所有可能的编码情况),微软之所以以这样的方式来解决这个问题,可能是由于现在编码已经稳定了,添加新编码的可能性比较低,所以在.NET 4.5仍然未改动这部分代码。

六、总结

到这里,简单工厂模式的介绍都到这里了,后面将介绍工厂方法模式来解决简单工厂模式中存在的问题。

本专题中的全部源码:简单工厂模式源码