设计模式的征途—6.建造者(Builder)模式
建造者模式又称为生成器模式,它是一种较为复杂、使用频率也相对较低的创建型模式。建造者模式为客户端返回的不是一个简单的产品,而是一个由多个部件组成的复杂产品。因为,没有人买车会只买一个方向盘或者轮胎,大家买的都是一辆包含轮胎、方向盘和发动机等多个部件组成的完整汽车。如何将这些部件组装成一辆完整的汽车并返回给用户,这是建造者模式需要解决的问题。
建造者模式(Builder) | 学习难度:★★★★☆ | 使用频率:★★☆☆☆ |
一、从游戏角色设计谈起
M公司游戏开发部想要开发一款名为Manulife群侠传的网络游戏,该游戏采用主流的RPG(角色扮演游戏)模式,玩家可以在游戏中扮演各种特定的角色,而各个角色又可以根据不同的游戏情节和统计数据(如力量、魔法、技能等)具有不同的能力,角色也会随着不断升级而拥有更加强大的能力。
作为RPG游戏的一个重要组成部分,需要对游戏角色进行设计,而且随着该游戏的升级将不断增加新的角色。不同类型的游戏角色,其性别、脸型、服装、发型等外部特性都有所差异,例如“天使”拥有美丽的面容和披肩的长发,并身穿一袭白裙;而“恶魔”则极其丑陋,留着光头并穿着一件刺眼的黑衣。
M公司决定开发一个小工具来创建游戏角色,可以创建不同类型的角色并可以灵活地增加新角色。
【几种不同的角色:恶魔,天使和英雄】
M公司的开发人员分析发现,游戏角色是一个复杂对象,它包含性别、脸型等多个组成部分,不同的游戏角色其组成部分有所差异,如上图所示。无论是何种造型的游戏角色,其创建步骤都大同小异,都需要逐步创建其组成部分,再将各组成部分装配成个一个完整的游戏角色。如何一步一步地创建一个包含多个组成部分的复杂对象,建造者模式为解决此类问题而诞生。
二、建造者模式概述
2.1 建造者模式关键定义
建造者模式(Builder):将一个复杂对象的构建与它的表示相分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。
建造者模式是一种较为复杂的创建型模式,他将客户端与包含多个组成部分的复杂对象的创建过程分离,客户端无需知道复杂对象的内部组成与装配方式,主需要知道所需的建造者即可。其关注点在于如何一步一步地创建一个复杂对象,不同的具体建造者定义了不同的创建过程,且具体建造者相互独立,增加新的建造者非常方便,无需修改已有代码,系统具有较好的可扩展性。
从上图可以看出,建造者模式包含以下三类重要角色;
(1)Builder(抽象建造者):为创建一个产品对象的各个部件指定抽象接口,在其接口中一般包含两类方法:一类是BuildPartX(),用于创建复杂对象的各个部件;另一类是GetResult(),用于返回生成好的复杂对象。它就可以是抽象类,也可以是接口。
(2)ConcreteBuilder(具体建造者):实现了Builder接口,即实现了各个部件的具体构造和装配方法,定义并明确其所创建的复杂对象。
(3)Product(产品角色):被构建的复杂对象,包含多个组成部件。
(3)Director(指挥者):负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其Construct()方法中调用建造者对象的部件构造和装配方法,完成复杂对象的建造。因此,客户端只需要和指挥者进行交互,这也确保了单一职责。
2.2 建造者模式典型实现
(1)复杂对象:包含多个成员变量的对象,这些成员也称为部件或零件。一个典型的复杂对象类如下:
public class Product
{
public string PartA { get; set; }
public string PartB { get; set; }
public string PartC { get; set; }
}
(2)抽象建造者:定义了产品的创建和返回方法。
public abstract class Builder
{
// 创建产品对象
protected Product product = new Product(); public abstract void BuildPartA();
public abstract void BuildPartB();
public abstract void BuildPartC(); // 返回产品对象
public Product GetResult()
{
return product;
}
}
(3)指挥者对象:控制整个产品的创建过程。
public class Director
{
private Builder builder; public Director(Builder builder)
{
this.builder = builder;
} public void SetBuilder(Builder builder)
{
this.builder = builder;
} // 产品构建与组装方法
public Product Construct()
{
builder.BuildPartA();
builder.BuildPartB();
builder.BuildPartC(); return builder.GetResult();
}
}
2.3 建造者模式与抽象工厂模式的对比
建造者模式与抽象工厂模式有点相似,但是建造者模式返回一个完整的复杂产品,而抽象工厂模式则返回一系列相关的产品。如果将抽象工厂模式看成一个汽车配件生产厂,生成不同类型的汽车配件,那么建造者模式则是一个汽车组装厂,通过对配件进行组成返回一辆完整的汽车。
三、游戏角色设计实现方案
3.1 方案结构图
以建造者模式为基础来实现游戏角色的设计,其中ActorController充当指挥者(Director),ActorBuilder充当抽象建造者,HeroBuilder、AngelBuilder和DevilBuilder充当具体建造者,Actor充当复杂产品。
3.2 具体实现
(1)Actor:复杂产品
/// <summary>
/// Actor 角色类 : 复杂产品,这里只列出部分成员变量
/// </summary>
public class Actor
{
// 角色类型
public string Type { get; set; }
// 性别
public string Sex { get; set; }
// 脸型
public string Face { get; set; }
// 服装
public string Costume { get; set; }
// 发型
public string HairStyle { get; set; }
}
(2)ActorBuilder:抽象建造者
/// <summary>
/// 角色建造器 : 抽象建造者
/// </summary>
public abstract class ActorBuilder
{
protected Actor actor = new Actor(); public abstract void BuildType();
public abstract void BuildSex();
public abstract void BuildFace();
public abstract void BuildCostume();
public abstract void BuildHairStyle(); // 工厂方法 : 返回一个完整的游戏角色对象
public Actor CreateActor()
{
return actor;
}
}
(3)HeroBuilder、AngelBuilder和DevilBuilder:具体建造者
/// <summary>
/// 天使角色建造器 :具体建造者
/// </summary>
public class AngelBuilder : ActorBuilder
{
public override void BuildCostume()
{
actor.Costume = "白裙";
} public override void BuildFace()
{
actor.Face = "漂亮";
} public override void BuildHairStyle()
{
actor.HairStyle = "披肩长发";
} public override void BuildSex()
{
actor.Sex = "女";
} public override void BuildType()
{
actor.Type = "天使";
}
} /// <summary>
/// 恶魔角色建造器 :具体建造者
/// </summary>
public class DevilBuilder : ActorBuilder
{
public override void BuildCostume()
{
actor.Costume = "黑衣";
} public override void BuildFace()
{
actor.Face = "丑陋";
} public override void BuildHairStyle()
{
actor.HairStyle = "光头";
} public override void BuildSex()
{
actor.Sex = "妖";
} public override void BuildType()
{
actor.Type = "恶魔";
}
} /// <summary>
/// 英雄建造器 : 具体建造者
/// </summary>
public class HeroBuilder : ActorBuilder
{
public override void BuildCostume()
{
actor.Costume = "盔甲";
} public override void BuildFace()
{
actor.Face = "英俊";
} public override void BuildHairStyle()
{
actor.HairStyle = "飘逸";
} public override void BuildSex()
{
actor.Sex = "男";
} public override void BuildType()
{
actor.Type = "英雄";
}
}
(4)ActorController:指挥者
/// <summary>
/// 游戏角色创建控制器:指挥者(Director)
/// </summary>
public class ActorController
{
/// <summary>
/// 逐步构建复杂产品对象
/// </summary>
public Actor Construct(ActorBuilder builder)
{
builder.BuildType();
builder.BuildSex();
builder.BuildFace();
builder.BuildCostume();
builder.BuildHairStyle(); return builder.CreateActor(); ;
}
}
(5)客户端测试
public class Client
{
public static void Main(string[] args)
{
ActorBuilder builder = (ActorBuilder)AppConfigHelper.GetConcreteBuilderInstance();
ActorController director = new ActorController();
Actor actor = director.Construct(builder); Console.WriteLine("角色类型:{0}", actor.Type);
Console.WriteLine("角色性别:{0}", actor.Sex);
Console.WriteLine("角色面容:{0}", actor.Face);
Console.WriteLine("角色服装:{0}", actor.Costume);
Console.WriteLine("角色发型:{0}", actor.HairStyle); Console.ReadKey();
}
}
这里仍然采用了基于配置文件的方式:将具体的构建者配置在XML文件中,如需要更改只需要更改一下配置文件即可。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="ConcreteBuilder" value="Manulife.ChengDu.DesignPattern.Builder.HeroBuilder, Manulife.ChengDu.DesignPattern.Builder" />
</appSettings>
</configuration>
其中AppConfigHelper类的代码如下:
public class AppConfigHelper
{
public static string GetConcreteBuilderName()
{
string factoryName = null;
try
{
factoryName = System.Configuration.ConfigurationManager.AppSettings["ConcreteBuilder"];
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return factoryName;
} public static object GetConcreteBuilderInstance()
{
string assemblyName = AppConfigHelper.GetConcreteBuilderName();
Type type = Type.GetType(assemblyName); var instance = Activator.CreateInstance(type);
return instance;
}
}
调试运行结果如下:
此时如果我们需要构建一个恶魔角色,那么修改一下配置文件:
<add key="ConcreteBuilder" value="Manulife.ChengDu.DesignPattern.Builder.DevilBuilder, Manulife.ChengDu.DesignPattern.Builder" />
再次运行结果如下:
四、建造者模式小结
4.1 主要优点
(1)客户端不需要知道产品内部的组成细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
(2)具体建造者相对独立,增加新的具体建造者无需修改原有类库的代码,系统扩展比较方便,符合开闭原则。
(3)可以更加精细地控制产品的创建过程 -> 将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
4.2 主要缺点
(1)对于所创建的产品有一定限制:一般这些产品都具有一些较多的共同点,其组成部分相似。如果差异性很大,那么则不适合使用建造者模式。
(2)如果产品的内部结构复杂多变,可能会需要定义很多具体构建者来实现这些变化,会导致系统变得庞大,增加系统的理解难度和运行成本。
4.3 应用场景
(1)需要生成的产品对象由复杂的内部结构,这些产品对象通常包含多个成员变量。
(2)需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
(3)隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
参考资料
刘伟,《设计模式的艺术—软件开发人员内功修炼之道》
设计模式的征途—6.建造者(Builder)模式的更多相关文章
- 设计模式C++描述----07.建造者(Builder)模式
一. 概述 Builder 模式要解决的问题是:当我们要创建的对象很复杂的时候(通常是由很多其他的对象组合而成),我们要要复杂对象的创建过程和这个对象的表示(展示)分离开 来,这样做的好处就是通过一步 ...
- Java设计模式-建造者(Builder)模式
目录 由来 使用 1. 定义抽象 Builder 2. 定义具体 Builder类 3. 定义具体 Director类 4. 测试 定义 文字定义 结构图 优点 举例 @ 最近在看Mybatis的源码 ...
- Android设计模式源码解析之Builder模式
https://github.com/simple-android-framework/android_design_patterns_analysis/tree/master/builder/mr. ...
- 建造者(Builder)模式
建造者模式是对象的创建模式.建造模式可以将一个产品的内部表象(internal representation)与产品的生产过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象. 产品的 ...
- 《图解设计模式》读书笔记3-3 Builder模式
目录 示例程序 类图 代码 角色 思路拓展 谁知道什么 构造和实现分离 和Template Method模式的区别和联系? Builder模式即建造者模式,利用这个模式可以组装具有复杂结构的实例. 示 ...
- Android 建造者(Builder)模式
关于 Builder 模式 详述:http://blog.csdn.net/jjwwmlp456/article/details/39890699 先来张图 看到 Android 中 使用了 Bui ...
- 设计模式--建造者(Builder)模式
将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示 --<设计模 ...
- 设计模式的征途(C#实现)—文章目录索引
1.预备篇 UML类图10分钟快速入门 2.创建型模式 ① 设计模式的征途-01.单例(Singleton)模式 ② 设计模式的征途-02.简单工厂(Simple Factory)模式 ③ 设计模式的 ...
- 设计模式Builder(建造者)模式
1.出现原因 在软件系统中,有时候会面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成:由于需求的变化,这个复杂的对象的各个部分可能面临着剧烈的变化,但是把他们组合在一起的算法 ...
随机推荐
- 微信JS分享功能--微信JS系列文章(二)
概述 在上一篇文章微信JS初始化-- 微信JS系列文章(一)中已经介绍了微信JS初始化的相关工作,接下来本文继续就微信JS的分享功能进行描述,供大家参考. 代码 $(document).ready(f ...
- Java中的常量治理
版权声明:本文为博主原创文章,转载请注明出处,欢迎使劲喷 虽然推崇在java中使用枚举(可查看<Java中的枚举的治理>)来对数据字典及常量进行控制,但是有些时候,我们还是会觉得常量控制更 ...
- nmap安装过程
nmap是一个网络扫描和主机检测工具. 功能:1.扫描目标主机开放的端口 2.扫描目标主机特定端口是否关闭 3.路由跟踪(到目标主机所经过的网络节点及其通过时间) 4.扫描一个网段下的所有IP 5.探 ...
- 在TFS中通过程序动态创建Bug并感知Bug解决状态
为便于跟踪问题解决情况,预警引擎产生的比较严重的预警日志,需要在TFS中登记Bug,通过TFS的状态流转,利用TFS Bug的Web挂钩功能,动态感知Bug解决状态,从而跟踪预警问题的解决状态, 整体 ...
- C++实现四叉树
什么是四叉树? 四叉树可以有效解决这个问题. 四叉树每一层都把地图划分四块,根据地图尺寸来决定树的层数,层数越大划分越细. 但需要对某一范围的单位筛选时,只需要定位到与范围相交的树区域,再对其区域内的 ...
- 计算两个date类型的时间差
//两个时间之间分钟差 public static int compareDate(Date d1, Date d2) { // TODO Auto-generated method s ...
- 【VB超简单入门】六、基本数据类型
接下来要介绍VB的基本数据类型,为接下来学习变量和常量准备. 计算机只能处理二进制的数据,所以无论什么数据,在CPU里面处理都是一样的,类似101010这样的机器代码,但是让我们直接去写机器代码程序, ...
- PPT自动载入图片并矩阵分布
最近有学生问到,能不能快速的向PPT一个页面里插入成百张图片,并让它们按统一大小的矩形排布到页面上.我写了以下代码可以在第1页中按照指定横向和纵向矩形数目,填充指定路径下的图片. Sub LoadPi ...
- sqoop 操作从hdfs 导入到mysql中语句
将hdfs下/dw/dms/usr_trgt下的文件导入到mysql中test数据库下usr_trgt表中 sqoop-export --connect jdbc:mysql://mysqlDB: ...
- 实用开发之-oracle表回滚到一个指定时间的操作语句
在开发或客户使用过程中,难免会出现误操作或脏数据,那么怎么迅速处理这个问题呢? 1.备份还原就用了,太麻烦. 就是使用ORACLE的备份功能,然后在还原,还原的时候新建一个库,然后使用dblink进行 ...