`
阿尔萨斯
  • 浏览: 4205650 次
社区版块
存档分类
最新评论

设计模式学习整理之创建型模式

 
阅读更多

设计模式学习整理之创建型模式
概述
定义----通过抽象实例化的过程,帮助一个系统独立于如何创建、组合和表示它的那些对象。
创建型模式属于对象创建模型。所谓对象创建模型就是说将实例化的工作委托给另一个对象来做。与之相对应的是类创建模型,这是一种通过继承改变被实例化的类。
创建型模式有两个重要的特点:
1)客户不知道创建的具体类是什么(除非看源代码)
2)隐藏了类的实例是如何被创建和放在一起的
这两个重要的特点是通过抽象类的虚接口技术做到的,这样设计者可以决定何时、何地、如何创建和由谁来创建。
迷宫模型,见下图
MapSite是所有迷宫中基本构件的基类,提供了一个纯虚函数Enter
Maze是Romm的集合类。
MazeGame::CreateMazeNormal()方法根据需求创建了迷宫的一切,并把它们组合起来,这是我们最常见的面向对象思维。
Maze* CreateMazeNormal()
{
Maze* pmaze=new Maze();
Room* pr1=new Room(1);
Room* pr2=new Room(2);
Door* pdoor=new Door(pr1,pr2);
pr1->SetSide(North,new Wall);
pr1->SetSide(East,pdoor);
pr1->SetSide(South,new Wall);
pr1->SetSide(West,new Wall);
pr2->SetSide(North,new Wall);
pr2->SetSide(East,new Wall);
pr2->SetSide(South,new Wall);
pr2->SetSide(West,pdoor);
}
但是这样的做法无法面向未来的变化。如果迷宫中增加了一个新的需要念咒语才能打开的门,一个装了神奇宝贝的房间,结果就是CreateMazeNormal需要重新修改以适应现在的新变化。因此这样的代码没有可重用性,当重新修改的时候,还会引发错误,导致维护困难。
创建型模式可以帮助我们无需改变创建代码,就可以轻松的增加各种迷宫的构件。(但是,并不是所有的情况都一定要采用这种做法,应该根据情况而定。)
Abstract Factory
除了创建型模型带来的优点是使用Abstract Factory的理由以外,该模式还有两个特点:
1)当一个系统由多个产品系列组成,并且需要使用其中的某一个时
如上图,Product系统有两个产品系列,Product1和Product2,ConcreateFactory1::CreateProductX( )创建出来的对象将会使用Product1系列。这使得交换产品系列很方面,只需要实例化不同的ConcreateFactory即可。
2)增强了某些类之间的关联,比如ProductA1和ProductB1都属于同一个产品系列,互相之间可能需要协同工作,有利于保证产品的一致性。
缺点:
由于AbstractFactory类提供了针对不同系列的统一创建接口,如果新的产品要加入到产品系列中,就需要增加创建虚函数。比如我们现在有一个ProductC产品,我们需要增加类AbstractProductC,增加AbstractFactory::CreanteProductC方法,并且两个产品系列的实际创建者ConCreateFactory1、ConCreateFactor2都要实现该方法。
可以通过给方法加参数的方式来指明创建的是什么产品,这样客户代码就无需改变,只要传递不同的参数。AbstractFactory类只需要提供一个CreateProduct(const string& name)方法即可。
AbstractFactory的实现:
如果从头至尾只需要一个该类的对象,Singleton是一个好方法,比如
CMyAbstractFactory* pfactory=CMyAbstractFactory::Instance ( paramerter);
pfactory->CreateProductA( );
也可以用派生类的方法,如上图所示的ConCreateFactory1.这种方法的特点是需要为每一个系列都创建一个派生类。
也可以使用Prototype来处理产品系列较多的情况。
现在我们用Abstractory Pattern优化创建迷宫的设计,我们的迷宫可能会有三个系列:普通迷宫、带魔法的迷宫和有的迷宫
使用MazeFactory将可以创建普通迷宫,使用EnchantedMazeFactory创建魔法迷宫,使用BomedMazeFactory用来创建带的迷宫
三种不同的迷宫。
CreateMaze函数内部创建代码依赖于参数factory,用户可以将子类对象传递进来。该函数内部无需改变,只需要传递不同的创建对象即可。
Maze* MazeGame::CreateMaze (MazeFactory& factory) {
 Maze* aMaze = factory.MakeMaze();
 Room* r1 = factory.MakeRoom(1);
 Room* r2 = factory.MakeRoom(2);
 Door* aDoor = factory.MakeDoor(r1, r2);

 aMaze->AddRoom(r1);
 aMaze->AddRoom(r2);

 r1->SetSide(North, factory.MakeWall());
 r1->SetSide(East, aDoor);
 r1->SetSide(South, factory.MakeWall());
 r1->SetSide(West, factory.MakeWall());

 r2->SetSide(North, factory.MakeWall());
 r2->SetSide(East, factory.MakeWall());
 r2->SetSide(South, factory.MakeWall());
 r2->SetSide(West, aDoor);
 return aMaze;
}
Builder
Abstract Factory的引入使得CreateMaze无需修改,但是这只是在迷宫系列切换的时候,并且每个迷宫里面的内部表示(用什么类、几个类表达房间、墙壁、门等)都一样。如果房间不再用Room类表达,而是换用其他类构建,CreateMaze函数还是要进行大幅度修改。原因是创建迷宫的算法和迷宫内部表示(那些类表示房间、门、墙壁等)紧密关联。这样要想在大幅度修改创建算法(通常可能由客户编写)的情况下,创建新的调整后的迷宫几乎是不可能的事情。
Builder能够将创建算法和对象内部表示分离开来。
Product-----------我们要创建的产品
Builder------------提供抽象接口创建Product,如果Product够复杂,比如迷宫,Builder或许应该提供的是多个创建迷宫不同部分的虚函数
ConcreteBuilder------------真正创建Product的对象,实现Builder的抽象接口,并且提供一个方法,让客户获取创建好的Product对象
Director-----用户不直接使用Builder,而是通过Director对象调用Builder的虚函数来创建Product,用户只调用Director的方法,这样对用户又隐藏了如何通过Builder虚函数创建的算法。
这样用户调用代码应该如下:
ConcreateBuilder * pBuilder=new ConcreateBuilder;
Director director;
director.Construct(pBuilder);
Product* pProduct=pBuider->GetResult( );
如果这时候Product的内部表示改变了,我们只需要修改原来的ConCreateBuilder类或者创建一个新的ConCreateBuilder2类,抽象接口不变,外部客户调用代码就无需改变。
我们也可以定义另一个Director2类,这个Director2类可能会创建出新的Product2对象。
Builder模式不是一下子就创建出对象的,创建过程经过了Director和ConcreateBuilder对象,使得我们可以对构造过程进行更精细的控制。
现在我们来看看在迷宫中Builder的表现:
class MazeBuilder //等价于Builder,但是不是纯虚类,因为我们希望它能够提供默认的构造方法
{
public:
 virtual void BuildMaze() { }
 virtual void BuildRoom(int room) { }
 virtual void BuildDoor(int roomFrom, int roomTo) { }

 virtual Maze* GetMaze() { return 0; }
protected:
 MazeBuilder();
};
这三个虚函数隐藏了Maze、Room、Door等迷宫的内部表示类,因此实现了构造过程和复杂对象内部表现的分离。

Maze* MazeGame::CreateMaze (MazeBuilder& builder) 
{
 builder.BuildMaze();
 builder.BuildRoom(1);
 builder.BuildRoom(2);
 builder.BuildDoor(1, 2);
 return builder.GetMaze();
};
MazeGame类扮演了Director的角色,CreateMaze通过调用虚函数来创建复杂对象,他无须了解复杂对象的内部表示。也许CreateMaze随着需求的变化可能会创建拥有更多房间的迷宫,但是只是通过调用虚函数来完成,如下:
Maze* MazeGame::CreateComplexMaze (MazeBuilder& builder) {
 builder.BuildRoom(1);
 // ...
 builder.BuildRoom(1001);

 return builder.GetMaze();
}

如果MazeBuilder提供的虚函数不能满足复杂对象的内部表示,我们可以创建子类来扮演ConcreateBuilder角色,重载三个虚函数,将复杂对象的内部表示封装起来。

客户调用代码可能如下:
MazeGame game;
MyMazeBuilder builder;

Maze* pMaze=game.CreateMaze(builder);
builder.GetCounts(rooms, doors);

重点:
何谓对象的内部表示?
我认为这是指复杂对象内部的基本构件对象是否发生变化,比如Room类是否变成了其他的类的组合,可以用对象静态结构图是否发生变化来判断。
如果没有发生变化,只是Room对象增加或者减少了,则不能称为内部表示发生变化。
Factory Method
创建者提供虚函数作为创建产品的抽象接口。具体创建什么产品由创建者的派生类决定。
如果AbstractFactory采用由派生工厂类来创建某个系列中的产品时,它实际上就是内部采用Factory Method模式。
Factory Method的几个注意点:
1)创建函数可以是虚函数也可以是纯虚函数,虚函数提供了默认实现
2)创建函数可以接收参数来决定创建什么产品
3)Factory Method容易导致创建过多的Creator的子类以对应不同的产品,这个方法可以通过模板技术来解决
class Product
{
public:
virtual ~ Product( ){};
...
};
class MyProduct:public Product
{
public:
MyProduct(){};
};
class Creator
{
public:
virtual Product* CreateProduct()=0;
};
template<class TheProduct>
class StandardCreator :public Creator
{
public:
virtual Product* CreateProduct( );
};
template<class TheProduct>
Product* StandardCreator<TheProduct>::CreateProduct()
{
return new TheProduct;
}
客户调用代码:
StandardCreator<MyProduct> myCreator;
Product* p=myCreator.CreateProduct();
这样,迷宫的创建代码将会如下:(感觉没有Builder好)
class MazeGame {
public:
 Maze* CreateMaze();

// factory methods:

 virtual Maze* MakeMaze() const
 { return new Maze; }
 virtual Room* MakeRoom(int n) const
 { return new Room(n); }
 virtual Wall* MakeWall() const
 { return new Wall; }
 virtual Door* MakeDoor(Room* r1, Room* r2) const
 { return new Door(r1, r2); }
};
Maze* MazeGame::CreateMaze () {
 Maze* aMaze = MakeMaze();

 Room* r1 = MakeRoom(1);
 Room* r2 = MakeRoom(2);
 Door* theDoor = MakeDoor(r1, r2);

 aMaze->AddRoom(r1);
 aMaze->AddRoom(r2);

 r1->SetSide(North, MakeWall());
 r1->SetSide(East, theDoor);
 r1->SetSide(South, MakeWall());
 r1->SetSide(West, MakeWall());

 r2->SetSide(North, MakeWall());
 r2->SetSide(East, MakeWall());
 r2->SetSide(South, MakeWall());
 r2->SetSide(West, theDoor);

 return aMaze;
}
class BombedMazeGame : public MazeGame {
public:
 BombedMazeGame();

 virtual Wall* MakeWall() const
 { return new BombedWall; }

 virtual Room* MakeRoom(int n) const
 { return new RoomWithABomb(n); }
};
class EnchantedMazeGame : public MazeGame {
public:
 EnchantedMazeGame();

 virtual Room* MakeRoom(int n) const
 { return new EnchantedRoom(n, CastSpell()); }

 virtual Door* MakeDoor(Room* r1, Room* r2) const
 { return new DoorNeedingSpell(r1, r2); }
protected:
 Spell* CastSpell() const;
};
Prototype
Factory Method会导致创建类与产品类产生平行结构,有时候这样很不错,但是当产品类越来越多时,为每个产品类定制一个创建类是一件苦恼的事情,有时候也是不可能的事情,因为有可能创建逻辑甚至都不知道具体是什么产品类,比如说动态创建一个由客户添加进来的产品。
Prototyp模式要求产品类必须支持基类的Clone操作,创建逻辑只需要调用需函数,不需要知道具体是什么产品类,这样就允许动态创建,并且解决了平行结构不适应大量产品类情况的问题。
缺点:并不是所有的产品子类都能轻松的支持Clone操作,比如如果产品内部有循环引用的对象时。
在动态创建和销毁的运用中,建议使用一个原型管理器。该管理器提供注册和注销操作,可以减需要的Key和Value(原型)保存在这里,这样加载代码酒依赖于管理器中有多少原型,这样的设计有足够的弹性。
Clone通常应该是深拷贝。
Singleton
Singleton模式通常用于只需要一个对象生存的场合,但是这句话不是Singleton的全部意义,模式不是公式,它是可以变化的。比如:一个系统打印对象,进程内只需要一个,但是一个多线程并发访问数据库的程序,每个线程可能都需要一个连接对象,这样Singleton就意味着进程内有多个,每个线程里面有一个。但是如果允许连接多个数据库呢?很有可能就变成了每个线程内只允许有一个连着某个特定数据库的连接对象。
Singleton的实现者需要提供一个全局的访问点给用户。最简单的就是静态成员函数Instance,为什么不用全局变量,因为全局变量不能阻止别人创建同类型的变量,而且也污染了全局空间(别人不能和你用一样的变量明)。所以我们要把类的构造函数变为protected。
一个对象内部可以保存一个静态指针,然后在Instance函数内部实例化,并返回它。这种解决方案可以解决刚才打印机的要求。
一个对象可以保存一个静态map,map每一项保存线程ID和静态对象指针,并且提供维护该map的一系列方法,这样就可以解决每个线程需要有一个对象的要求。
但是这样就够了么,我还碰到一个不同的需求,要求运行时决定创建不同的对象。这时候可以在Instance函数上加上参数,通过参数来创建不同的对象。这些对象也可以派生自父Singleton类。
也许每个线程允许对象数目不能超过3个,没关系,我们可以在Instance内部实现这些控制逻辑。
我想表达的是,Singleton可以有很多变种,有时候甚至让你感觉名不副实,但是这就是设计模式的魅力。我也是从一开始的教条主义到能接受很多精彩的变化,有时候甚至都不应该用它,或许很多是多态就够了。
由于Singlton很简单,所以就不再详细分析在迷宫中的应用了。
分享到:
评论

相关推荐

    Java设计模式整理

    1.1 创建型模式 2 1.1.1 工厂方法 2 1.1.2 抽象工厂 4 1.1.3 建造者模式 6 1.1.4 单态模式 9 1.1.5 原型模式 10 1.2 结构型模式 11 1.2.1 适配器模式 12 1.2.2 桥接模式 13 1.2.3 组合模式 15 1.2.4 装饰模式 17 ...

    设计模式:可复用面向对象软件的基础 书和源码

    《设计模式:可复用面向对象软件的基础》是引导读者走出软件设计迷宫的指路明灯,凝聚了...第三章 创建型模式 第四章 结构型模式 第五章 行为模式 第六章 结论 附录A 词汇表 附录B 图示符号指南 附录C 基本类 参考文献

    白话讲解创建型设计模式:单例原型构建

    写在前面 分享一些设计模式的笔记。陆续整理,按照设计模式类型,创建型,结构型,行为型发布 博文会用通俗的话梳理一些自己的理解,结合开发中的实际场景, 理解不足小伙伴帮忙指正,虚心接受 ^_^ 傍晚时分

    JDK中的设计模式

    该文档整理了JDK中有关的设计模式,包括创建型、结构型、行为型等模式在JDK中的应用

    设计模式uml.vsdx

    1.设计模式的分类 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合...

    java-patterns:Java 23种基本的设计模式整料整理学习,责任链模式过滤器,工厂模式BeanFactory,观察者模式ContextListen等。结合Spring源码理解学习

    创建型模式,共五种:工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。 结构型模式,共七种:适配器模式,装饰器模式,代理模式,外观模式,转换器模式,组合模式,享元模式。 行为类型模式,共十种:...

    软件设计与体系结构期末复习笔记(xinhua)

    (1)创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF中提供了5种创建型模式。 (2)结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,GoF中提供了7种结构型模式。 (3...

    Java中的设计模式与7大原则归纳整理

    本篇文章主要对Java中的设计模式如,创建型模式、结构型模式和行为型模式以及7大原则进行了归纳整理,需要的朋友可以参考下

    asp.net知识库

    ASP.NET 2.0使用Web Part创建应用程序之二(共二) 体验 .net2.0 的优雅(2) -- ASP.net 主题和皮肤 NET2.0系列介绍(一).NET 2.0 中Web 应用程序主题的切换 ASP.NET 2.0 中Web 应用程序主题的切换 2.0正式版中...

    java8源码-jcohy-study-sample:个人学习整理

    创建型模式(5种) 结构型模式(7种) 关系型模式(11种) 适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。其中对象的适配器模式是各种模式的起源,我们看下面的图: 策略模式、模板方法模式、...

    高级java笔试题-Notes-And-Blog:阅读笔记及高质量博客整理

    创建型设计模式 结构型设计模式 行为型设计模式 中介者模式 备忘录模式 迭代器模式 解释器模式 :collision:数据结构与算法 点击关闭/打开所有内容 :comet:源码学习 博客 :ant:数据结构与算法 :spider:设计模式 :...

    java8集合源码分析-interview_knowledge:整理一些基础知识

    java8 集合源码分析 interview_knowledge 本项目为个人学习之总结,内容...创建型模式 结构型模式 行为型模式 算法与数据结构 常见数据结构 算法分析 常见算法 计算机网络 操作系统 tools 编码实践 interview 简历 生活

    sql学生成绩管理系统课程设计数据库.doc

    2 数据库完整E—R图 CHAP 3 逻辑模型设计 逻辑结构设计阶段 E- R图向关系模型转化要解决的问题是如何将实体型和实体间的联系转化为关系模式,如何 确定这些关系模式的属性和码。 设计学生成绩管理数据库,包括课程、...

    leetcode题库-Interview:http://www.hollischuang.com/archives/2223答案整理

    leetcode题库 BAT面试题答案正在持续更新... 专对BAT等大厂面试 算法题目详细解答 计算机基础知识 ...创建型模式 参考资料 结构型模式 参考资料 行为型模式 参考资料 联系作者 Hollis: Kevin: hueizhe:

    实验选课系统数据库设计.doc

    转换规则: 1、一个实体型转换为一个关系模式 2、实体型间的联系有以下几种: (1)1:1联系可以转换为一个独立的关系模式,也可以与任意一端对应的关系模式合并 ; (2)1:n联系可以转换为一个独立的关系模式,也...

    sql学生成绩管理系统课程设计数据库(1).doc

    E-R图如下 课程信息图 成绩图 学生信息表 2.1.2 数据库完整E-R图 CHAP 3 逻辑模型设计 逻辑结构设计阶段 E- R图向关系模型转化要解决的问题是如何将实体型和实体间的联系转化为关系模式,如何 确定这些关系模式的...

    net学习笔记及其他代码应用

    要请求垃圾收集,可以调用下面的方法之一: System.gc() Runtime.getRuntime().gc() 37.String s = new String(\"xyz\");创建了几个String Object? 答:两个对象,一个是“xyx”,一个是指向“xyx”的引用对象s。...

Global site tag (gtag.js) - Google Analytics