设计模式系列—创建者模式
一、上篇回顾
上篇我们主要讲述了抽象工厂模式和工厂模式。并且分析了该模式的应用场景和一些优缺点,并且给出了一些实现的思路和方案,我们现在来回顾一下:
抽象工厂模式:一个工厂负责所有类型对象的创建,支持无缝的新增新的类型对象的创建。这种情况是通过配置文件来实现的,通过字典映射的方式来实现,不过可能效率上有点低下,可以通过优化的方式来做,上篇中我们也给出了委托的工厂实现形式,相比之前的简单工厂模式和工厂模式有了更好的灵活性,并且对具有依赖关系或者组合关系的对象的创建尤为适合。
上篇中,有不少的朋友提出了一些意见和建议,首先很感谢大伙的支持和鼓励,有朋友提出来,我画的图不够专业,专业人士应该用UML建模图来搞,我怎么说呢?我也同意这样的说法,但是我发现我通过另外的直观的图形,大家一看就能更明白,结合代码,当然好的UML图,已经能表述清楚设计的思路和大体实现了,不过说实话,我看着就有点类,特别是UML图复杂的时候。所以我还是暂时先用这种一般的图形来表述我理解的设计模式的思想,看看大伙是什么看法和意见,如果说都说说UML图的话,那么后面的相关模式,我会主要以UML专业图来绘制。
我这里总结下我们以后项目中的可能会用到设计模式 之处或者系统架构的时候,一般情况下有这样的几类方案,我们可以在考虑系统的低耦合性的时候的设计:
基本上来说能掌握上面的几类情况,基本上设计出来的系统至少是可用的,不知道大家有没有更多意见和建议。有的请提出来,我会备注在文章中。
二、摘要
本文主要是针对创建型模式中的创建者模式进行讲述,创建者模式是创建型模式中最负责的一个设计模式了,创建者负责构建一个对象的各个部分,并且完成组装的过程,我们可以这么理解创建者模式,创建者模式类似与一个步骤基本固定,但是每个步骤中的具体形式却又可以变化的这类对象的创建。也许这样说还是太抽象了,我们这么来理解吧,我感觉让人最容易理解的形式还是图形化的形式,不但接受起来容易,并且让人映象深刻,不知道大家是不是和我有同感呢?下面我们给出一个简单的例子,通过图形化的流程来说明吧:我们这里以我们大伙平时最常见的做饭为例吧:
可能我这里给出的流程只是我个人理解的或者看到的过程,不代表全部,呵呵,这里只是事例性的说明。
三、本文大纲
a、上篇回顾。
b、摘要。
c、本文大纲。
d、创建者模式的特点及使用场景。
e、创建者模式的实现方案。
f、创建者模式使用总结。
g、系列进度。
h、下篇预告。
四、创建者模式的特点及使用场景
创建者模式主要是用于创建复杂的一些对象,这些对象的创建步骤基本固定,但是可能具体的对象的组成部分却又可以自由的变化,现实中的例子很多,但是可能大伙都比较容易理解的就是,我们的自己花钱配置的台式机或者笔记本,可以 这样理解,这些硬件设备的各个零件,不管是CPU是Intel的还是AMD的,显卡是华硕的还是小影霸的,不管硬盘是西部数据的还是希捷的,其实想表述的意思就是对象的具体的组成部分可以是变化的,但是可能我们发现对象的这些组成部分的组织起来的过程是相对固定的,那么我们就可以用创建者模式来做,并且我们引入一个引导者(Director)来引导这个对象的组装过程。可以简单用图形化的过程来描述如下:
我们上面说明了一个服装的大概生产过程,这里生产的顺序可能会发生变化,或者生产的帽子,上衣等都会发生变化,但是服装的组装过程基本上变化不大,都是需要帽子,上衣,裤子的,这时候我们可以通过引导着负责组装这样的过程,然后我们在具体的每个部分可以是抽象接口,根据不同的实现创建不同的帽子来完成变化。
五、创建者模式的实现方案
5.1、经典的创建者模式实现
- 我们先给出经典创建者模式的一个实现形式,然后针对这个经典模式后面提出几个改进方案,我们这里以上面讲述的服装的过程作为例子来说明下创建者模式的原理和思想,希望大家也能灵活的运用到实际的项目中去。达到学以致用的目的。
我们来看看具体的代码实现:
/// <summary>
/// 创建对象组织的所有构造步骤接口
/// </summary>
public interface IBuider
{
void BuilderPart1();
void BuilderPart2();
void BuilderPart3();
}
定义一个服装对象:
/// <summary>
/// 服装对象
/// </summary>
public class Dress
{
/// <summary>
/// 构建帽子
/// </summary>
public void BuildHat()
{
throw new NotImplementedException();
}
/// <summary>
/// 构建上衣
/// </summary>
public void BuilderWaist()
{
throw new NotImplementedException();
}
/// <summary>
/// 构建裤子
/// </summary>
public void BuilderTrousers()
{
throw new NotImplementedException();
}
}
实现创建对象的具体步骤:
public class Builder : IBuider
{
private Dress _dress;
public Builder(Dress dress)
{
this._dress = dress;
}
public void BuilderPart1()
{
this._dress.BuildHat();
}
public void BuilderPart2()
{
this._dress.BuilderWaist();
}
public void BuilderPart3()
{
this._dress.BuilderTrousers();
}
public Dress Build()
{
return this._dress;
}
}
通过指导者指导对象的创建,而具的对象的创建还是靠对象本身提供的相应方法,Builder只是调用对象的方法完成组装步骤。Builder内部提供一个返回构造后完整对象的方法,上面给出的方法是Build()方法。
/// <summary>
/// 指导者
/// </summary>
public class Director
{
public void Build(IBuider builder)
{
builder.BuilderPart1();
builder.BuilderPart2();
builder.BuilderPart3();
}
}
通过上面的代码,我们给出了经典创建者模式的核心代码形式,那么针对上面无疑有以下的几个缺点:
1、Ibuilder接口必须定义完整的组装流程,一旦定义就不能随意的动态修改。
2、Builder与具体的对象之间有一定的依赖关系,当然这里可以通过接口来解耦来实现灵活性。
3、Builder必须知道具体的流程。
那么针对上面的几个问题,我们如何来解决呢?我想前面的创建型模式已经给我了足够的经验,还是通过配置文件或者其他的形式来提供灵活性。
-
5.2、创建者模式特性+委托实现
针对上面讲述的例子我们可以考虑如下的方式进行改进:
我们先定义一个构造每个对象部分的委托,并且这个方法的参数是动态变化的:
/// <summary>
/// 定义通用的构造部分的委托
/// </summary>
public delegate void BuildHandler(params object[] items);
我们通过定义标记来标识对象中的每个部分的构造步骤:
/// <summary>
/// 为对象中的每个步骤打上标记
/// </summary>
[AttributeUsage(AttributeTargets.Method,AllowMultiple=false)]
public class BuildAttribute : Attribute
{
private MethodInfo hander;
private int stepSort;
public MethodInfo BuildHandler
{
get
{
return this.hander;
}
set
{
this.hander = value;
}
}
public int StepSort
{
get
{
return this.stepSort;
}
set
{
this.stepSort = value;
}
}
}
构造对象的统一接口:
/// <summary>
/// 创建对象组织的所有构造步骤接口
/// </summary>
public interface IBuider
{
void Build<T>() where T : class,new();
}
下面给出具体的Builder的缓存实现方案代码:
public class CommonBuilder : IBuider
{
/// <summary>
/// 缓存每个对象的具体的构造步骤
/// </summary>
private Dictionary<Type, List<BuildHandler>> steps = null;
public void Build<T>(T ob) where T : class, new()
{
//从缓存中读取指定类型的项
List<BuildHandler> handlers = steps[typeof(T)];
foreach (BuildHandler handler in handlers)
{
handler();
}
}
}
给出一些获取某个类型内部的所有具有我们的自定义特性标记的MethodInfo列表:
public List<MethodInfo> GetMethodInfoList<T>() where T : class, new()
{
//从缓存中读取指定类型的项
List<MethodInfo> methods = new List<MethodInfo>();
T target = new T();
MethodInfo[] methodList= typeof(T).GetType().GetMethods();
BuildAttribute[] attributes = null;
foreach (MethodInfo info in methodList)
{
attributes= (BuildAttribute[])info.GetCustomAttributes(typeof(BuildAttribute), true);
if (attributes.Length > 0)
methods.Add(info);
}
return methods;
}
获取所有的特性,一般使用这个方法即可获取所有的具有标记该特性的方法列表和相应的步骤:
public List<BuildAttribute> GetBuildAttributeList<T>() where T : class, new()
{
List<BuildAttribute> attributes = new List<BuildAttribute>();
BuildAttribute[] attributeList = null;
BuildAttribute attribute = null;
foreach (MethodInfo info in this.methods)
{
//设置特性中要执行的方法
attributeList = (BuildAttribute[])info.GetCustomAttributes(typeof(BuildAttribute), true);
if (attributeList.Length > 0)
{
attribute = attributeList[0];
attribute.BuildHandler = info;
attributes.Add(attribute);
}
}
//缓存步骤
steps.Add(typeof(T), attributes);
return attributes;
}
具体的Build中的调用代码实现:
public T Build<T>(T ob) where T : class, new()
{
List<BuildAttribute> attributeList = GetBuildAttributeList<T>();
T target=new T();
//构造对象的过程
foreach (BuildAttribute item in attributeList)
{
item.BuildHandler.Invoke(target,null);
}
return target;
}
这样我们就完成了一个通用的基于标记的自动发现某个类型的标记方法步骤的通用代码实现,可能大家感觉这样的方式还是挺麻烦的,那么我们还有没有更好的改进方案呢?因为每次打标记我还是感觉挺麻烦的,而且代码量分布的也比较广泛,我想通过统一配置管理的方式,当然也是可以的,那么我们可以通过下面的方式来进行扩展。
-
5.3、创建者模式配置文件方式实现
配置文件的方式实现创建者,这个怎么说呢,上面的抽象工厂的模式中,我们主要采用了这样的方式来实现配置的灵活性和扩展性,其实创建者也是一样的,我们来看看配置文件吧,我想就看配置文件就大概知道了,具体的应用代码了,请看下图,粗略描述了实现的思路:
我这里给出配置文件的父子级节点示例:
<?xml version="1.0" encoding="utf-8" ?>
<Build>
<BuildClass name="ClassName" type="ClassType">
<BuildStep StepOrder="1" MethodName="MethodName1"/>
<BuildStep StepOrder="2" MethodName="MethodName2" />
</BuildClass>
<BuildClass name="ClassName1" type="ClassType1">
<BuildStep StepOrder="1" MethodName="MethodName1"/>
<BuildStep StepOrder="2" MethodName="MethodName2" />
</BuildClass>
</Build>
我们这里通过在Build实现中读取配置文件中的所有的步骤,放在字典中。给出示例代码:
/// <summary>
/// 创建对象组织的所有构造步骤接口
/// </summary>
public class Buider : IBuider
{
private Dictionary<Type, List<MethodInfo>> steps = null;
public Buider()
{
steps = new Dictionary<Type, List<MethodInfo>>();
//读取配置文件!
//将配置文件中的类名和方法名取出,然后通过反射取到这个类型下的所有方法,根据配置中的步骤和方法名添加到
//步骤列表中,然后缓存到字典中
steps.Add(Type.GetType(""), new List<MethodInfo>());
}
public T Build<T>() where T: class,new()
{
T target = new T();
//从字典中找到对应的缓存列表,执行构造过程
List<MethodInfo> list = steps[typeof(T)];
//执行构造
foreach (MethodInfo info in list)
{
info.Invoke(target, null);
}
return target;
}
}
通过上面的几步配置就可以完成相应的构建过程,这时候我们的指导者的工作就简单了,就是只是简单的通过使用Build中的通用方法:
public class Director
{
public void Build<T>(IBuider builder) where T:class,new()
{
builder.Build<T>();
}
}
只要通过上面的步骤就完成了创建者模式的通用实现方案。
六、创建者模式使用总结
通过上面的给出的几类不同的实现方案我们知道,创建者模式是一个对对象的构建过程“精细化”的构建过程,每个部分的构建可能是变化的,但是对象的组织过程是固定的,通过这种统一的创建方式,无疑增加了我们设计上的灵活性,当我们在构建复杂对象的时候,我们如果发现每个部分可能都是变化的,并且是多个不同的构建步骤的时候,我们可以考虑使用创建者模式。相比我们之前讲述的工厂和抽象工厂模式区别还是很大的,我们发现创建者适合这类复杂对象的创建,对于抽象工厂可能就无法完成这样的组装工作,而且创建者模式是把复杂对象的内部创建方法进行调用,组织协调了对象的各个部分前后顺序的控制。简单的描述创建者就是这样的情况:
由于本人水平有限,或者讲述能力有限,表达思路错误或者想法错误,请大家批评指出,如果您有更好的意见或者想法,请提出讨论,欢迎大家提出宝贵意见和建议。