2010年3月3日

1.1 BUILDER生成器

1、 意图

将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。

构建是通过对生成器抽象接口的调用实现的。而构建出来的产品的类型(表示),是由具体的进行构建的生成器的子类的实例来决定的。这样导向器只需要调用生成器的抽象接口生成产品,而具体生成什么样的产品,则是根据导向器配置的具体的生成器实例相关。

终于理解了它是如何把复杂对象的构建和它的表示分离的,也理解了为什么同样的构建过程。

2、 动机

3、 适用性

当创建复杂对象的算法应该独立于该对象的组成以及他们的装配方式时——对象的组成及装配方式是生成器的抽象接口来表示的。而对象的创建的算法是有生成器的子类实现的。

当构造过程必须允许被构造的对象有不同的表示时——也就是,同样的构造过程,可以生成不同的产品。这里是通过配置导向器的生成器的子类实例来实现的。

4、 结构

wps_clip_image-372

5、 参与者

l Builder:为创建一个product对象的各个部件指定抽象接口。这些部件最终构成了product,而对这些抽象接口的调用,则是装配product的操作,调用的次数,入参等不同,则最终生成的product也不同。product本身的构造过程可能会非常复杂。但是,这些复杂度对Director(导向者)是隐藏的。这些抽象的接口描述了产品的组成以及装配方式。

l ConcreteBuilder:实现Builder的接口以构造和装配产品的各个部件;定义并明确它所创建的表示;提供一个检索产品的接口。这个类实现了Builder的抽象接口,从而可以创建不同的表示,但是组成和装配过程还是一样的。

l Director:构造一个使用Builder抽象接口的对象。更对象根据Builder的接口来装配并生产产品。

l Product:表示被构造的复杂的对象。ConcreteBuilder创建该产品的内部表示,并定义它的装配过程;包含定义组成部件的类,包括将这些部件装配成最终产品的接口。

6、 协作

l 客户创建Director对象,并用它想要的Builder对象进行配置。

l 一旦产品不仅被生成,导向器就会通知生成器。

l 生成器处理导向器请求,并且将部件添加到该产品中。

l 客户从生成器中检索产品。

wps_clip_image-911

7、 效果

l 它使你可以改变一个产品的内部表示。Builder提供给Director的抽象接口可以使生成器隐藏这个产品的表示和内部结构。它同时也隐藏了该产品时如何装配的。因为产品时通过抽象接口构造的,你改变该产品的内部表示时,所要做的只是定义一个新的生成器。

l 它将构造代码和表示代码分开。Builder模式通过封装一个复杂对象的创建和表示方式提高了对象的模块性。每个ConcreteBuilder包含了创建和装配一个特定产品的所有代码。

l 它可以使你对构造过程进行更精细的控制。

8、 实现

l 通常一个抽象的Builder类为导向者可能要求创建的每一个构件定义一个操作。这些操作缺省什么也不做。一个ConcreteBuilder类对它有兴趣创建的构建重定义这些操作。

l 装配和构造接口:一般构造请求的结果只是被添加到产品中,特殊情况下,需要返回给导向器。

l 产品没有抽象类:一般他们没有公共的部分。如果有也可以设置一个抽象类。

l Builder中缺省的方法为空。

9、 代码示例

class MazeBuilder {

public:

    virtual void BuildMaze() { }//部件的构造方法

    virtual void BuildRoom(int room) { }

    virtual void BuildDoor(int roomFrom, int roomTo) { }

    virtual Maze* GetMaze() { return 0; }

protected:

    MazeBuilder();

};//MazeBuilder是生成器

class StandardMazeBuilder : public MazeBuilder {

public:

    StandardMazeBuilder();

/*

*/

    virtual void BuildMaze();

    virtual void BuildRoom(int);

    virtual void BuildDoor(int, int);

/*

*/

    virtual Maze* GetMaze();

private:

    Direction CommonWall(Room*, Room*);

    Maze* _currentMaze;

};//StandardMazeBuilder是ConcreteBuilder,提供部件的具体构造代码

Maze* MazeGame::CreateMaze (MazeBuilder& builder) {

    builder.BuildMaze();

    builder.BuildRoom(1);

    builder.BuildRoom(2);

    builder.BuildDoor(1, 2);

    return builder.GetMaze();

}//CreateMaze是导向器,调用生成器的抽象接口完成产品的构造过程。

//下面代码描述产品的构造过程

Maze* maze;//最终的产品

MazeGame game;//Director,导航者

StandardMazeBuilder builder;//ConcreteBuilder,实际的构造类

game.CreateMaze(builder);//开始装配

maze = builder.GetMaze();//获取装配后的产品

posted @ 2010-03-31 21:07 常高伟 阅读(224) | 评论 (0)编辑 收藏

1.1 ABSTRACT FACTORY 抽象工厂

1、 意图

提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。

2、 动机

“客户仅与抽象定义的接口交互,而不使用特定的具体类的接口。”

这里的主要的思想是封装对象的创建的过程。客户端可以不需要知道具体要创建那些对象,而只需要知道创建某一系列的对象所用到的“工厂对象”即可。

3、 适用性

一个系统要独立于它的产品的创建、组合和表示时。 

一个系统要由多个产品系列中的一个来配置时。 

当你要强调一系列相关的产品对象的设计以便进行联合使用时。 

当你提供一个产品类库,而只想显示它们的接口而不是实现时。 

4、 结构

wps_clip_image-278

5、 参与者

AbstractFactory:创建一系列对象的抽象类。

ConcreteFactory:实现具体创建产品对象的操作。

AbstractProduct:为一类产品对象声明一个接口。

ConcreteProduct:定义一个被相应的具体工厂创建的对象;实现AbstractProduct接口。

Client:仅使用AbstractFactory和AbstractProduct类声明的接口。

6、 协作

在运行时刻,创建一个ConcreteFactory实例,它创建具有特定实现的对象。为创建不同的对象,客户应使用不同的具体工厂。

AbstractFactory将具体对象的创建延迟到它的子类ConcreteFactory中。

7、 效果

1) 它分离了具体的类:一个工厂封装创建产品的责任和过程,它将客户和类的实现分离。客户通过抽象接口操作实例。产品的类名也在具体工厂实现中分离,他们不出现在客户代码中。

2) 它使得易于交互产品系列。

3) 它有利于产品的一致性。

4) 难于支持新的种类。

posted @ 2010-03-03 20:27 常高伟 阅读(171) | 评论 (0)编辑 收藏

1.1 设计模式怎样解决设计问题

1.1.1 寻找合适的对象

面向对象设计最困难的部分是将系统分解为对象的集合。

设计的许多对象来源于现实世界的分析模型,这里和领域驱动设计有点关联。分析所得到的类,很多事现实中并不存在的类。这是抽象的结果。设计中的抽象对于产生灵活的设计至关重要。就像我设计的一个流程调度模型。

1.1.2 决定对象的粒度

记笔记可以让我达到沉流的状态。

1.1.3 指定对象接口
1.1.4 描述对象实现

OMT表示法:

1、 对象:最上面的黑体表示类名,下面依次是操作,数据。

wps_clip_image-219

2、 实例化:虚线箭头表示一个类实例化另外一个对象。

wps_clip_image-245

3、 继承:竖线和三角表示继承关系。

wps_clip_image-263

4、 抽象类:类名以黑体斜体表示,操作也用斜体表示。

5、 引用

wps_clip_image-293

箭头加黑点表示一个类引用另外一个类。

重点:

1、 类的继承和接口继承的比较

对象的类和对象的类型的区别:

对象的类定义了对象是怎样实现的,同时也定义了对象内部状态和操作的实现。对象的类型只与它的接口有关。一个对象可以由多个类型(支持多个接口),不同类的对象可以有相同的类型。

类和类型紧密相连,类定义了对象的操作,也定义了对象的类型。

类的继承和接口的继承的差别:

c++中接口继承接近于公有继承纯抽象类。纯实现继承或纯类继承接近于私有继承。

2、 对接口编程,而不是对实现编程——面向对象设计的第一个原则

1.1.5 运用复用机制

1、 继承和组合的比较

继承是一种白箱复用,父类的内部细节对子类可见。

对象组合彼此不知道对方内部细节,成为黑箱复用。

继承的优缺点:

1) 子类可以直接重定义父类的操作。

2) 编译时刻决定了,无法在运行期间更改。

3) 子类要知道父类的实现细节,这样就部分破坏了封装性。子类和父类依赖过于紧密,父类的某些变化必然导致子类的变化。开发过程中遇到过类似的问题。这种依赖,限制了灵活性以及复用性。比如,服务体系中经常出现这样的问题,导致代码拷贝。

组合(通过获得对象的引用而在运行时刻动态的定义)的优缺点:

1) 对象间通过接口彼此交互。

2) 对象只能通过接口访问,不要也不能知道对方细节,这样不会破坏封装性。

3) 运行时刻可以使用另外一个对象替换这个对象,提高了灵活性。

4) 对象的实现基于接口编写,所以实现上存在较少的依赖关系。

5) 优先使用组合有助于保持每个类被封装,并被集中在单个任务上,提高整体内聚性。类和类的层次都维持一个较小的规模,

6) 基于对象组合的设计会有更多的对象(而又较少的类),且系统的行为依赖于对象间的关系而不是定义在某个类的内部。

理想的情况下,应该通过组合原有构件实现新的功能,而不是创建新的构件。

面向对象设计的第二个原则:优先使用对象组合,而不是类继承。

2、 委托

委托时一种组合方法,它是组合具有与继承同样的能力。

委托的主要优点在于它便于在运行时刻组合对象操作,以及更改操作的组合方式。它是软件更加的灵活。

和其他的技术方案相同,它也存在不足之处:增加了软件的复杂度——动态的,高度参数化的软件比静态的软件更难于理解。

3、 继承和参数化类型的比较

1.1.6 关联运行时刻的结构和编译时刻的结构
1.1.7 设计应支持变化

设计应该支持变化——所说的是,一个设计方案,对变化要有一定的适应性,即封装变化。

变化是导致重新设计的原因。设计要对一定范围内的变化友好。

4、 

对于程序的分层设计,对于处于同一分层的模块,对外应保持一定的抽象,并且,使用同种类型的通信协议。

posted @ 2010-03-03 20:26 常高伟 阅读(415) | 评论 (0)编辑 收藏

1.1 变量存储域

1.1.1 一个示例

pang123hui首先提供了一个网上流传的学习代码示例:

int a = 0; //全局区 

void main() 

{

int b; //栈 

char s[] = “abc”; //s在栈,abc在文字常量区 

char *p1,*p2; //栈 

char *p3 = "123456"; //123456在常量区,p3在栈上 

static int c =0; //全局区 

p1 = (char *)malloc(10); //p1在栈,分配的10字节在堆 

p2 = (char *)malloc(20); //p2在栈,分配的20字节在堆 

strcpy(p1, "123456"); //123456放在常量区 

}

这个代码示例中出现了“全局区”,“栈”,“文字常量区”,“堆”等词语。为了统一,我们使用《C专家编程》中的说法:堆栈段,BSS段,数据段,文本段。

各个段的作用如下:

1、 文本段:包含程序的指令,它在程序的执行过程中一般不会改变。

2、 数据段:包含了经过初始化的全局变量和静态变量,以及他们的值。

3、 BSS段:包含未经初始化的全局变量和静态变量。

4、 堆栈段:包含了函数内部声明的局部变量。

当然,上面段的作用不仅于此,具体的作用会在下面的知识点中介绍。

1.1.2 通过代码测试变量的存储位置

Linux下可以通过系统命令“size”查看可以执行程序各个段的大小。但是,可执行程序中的段结构和运行中程序在内存中的段结构并不完全相同,但是有一定的映射关系。具体如下图所示(图片信息来自《C专家编程》):

wps_clip_image-696

下面通过代码示例和“size”来研究变量的存储区域。

test.c

int main()

{

return 1;

}

编译,并且查看可执行程序各个段的大小:

wps_clip_image-779

更改test.c:

int g_data;

int main()

{

return 1;

}

编译,并且查看可执行程序各个段的大小:

wps_clip_image-849

可以发现,文本段,数据段都没有发送变化,而BSS段增加了4个字节。

结论1:未初始化的全局变量保存在BSS段中

继续:

int g_data = 1;

int main()

{

return 1;

}

编译:

wps_clip_image-958

可以发现,BSS段和文本段相同,而数据段增加了4个字节。

结论2:经过初始化的全局变量保存在数据段中

继续:

int main()

{

static int g_data;

return 1;

}

编译:

wps_clip_image-1066

可以发现,文本段,数据段都没有发送变化,而BSS段增加了4个字节。

结论3:未初始化的静态变量保存在BSS段中

继续:

int main()

{

static int g_data = 1;

return 1;

}

编译:

wps_clip_image-1183

可以发现,BSS段和文本段相同,而数据段增加了4个字节。

结论4:经过初始化的静态变量保存在数据段中

继续:

int main()

{

int i_data = 1;

return 1;

}

编译:

wps_clip_image-1288

可以发现,BSS段和和数据段相同,而文本段增加了16个字节。局部变量会在执行的时候在堆栈段中生成,函数执行完毕后释放。

结论5:函数内部声明的局部变量保存在堆栈段中

继续:

const int g_data = 1;

int main()

{

return 1;

}

编译:

wps_clip_image-1430

把全局变量定义为“const”后,也许你会感到奇怪,怎么BSS段和数据段都没有发生变化,而文本段却增加了4个字节。

结论6:const修饰的全局变量保存在文本段中

那么,const的局部变量?

继续:

int main()

{

const int i_data = 1;

return 1;

}

编译:

wps_clip_image-1587

结论7:const修饰的局部变量保存在堆栈段中

继续:

char *pstr = "";

int main()

{

return 1;

}

编译:

wps_clip_image-1666

在做一下更改:

char *pstr = "123456789";

int main()

{

return 1;

}

编译:

wps_clip_image-1733

可以发现,前后数据段和BSS段大小均未发生变化,而文本段增加了9个字节。

结论8:字符串常量保存在文本段中

1.1.3 结论

1、 经过初始化的全局变量和静态变量保存在数据段中。

2、 未经初始化的全局变量和静态变量保存在BSS段。

3、 函数内部声明的局部变量保存在堆栈段中。

4、 const修饰的全局变量保存在文本段中,const修饰的局部变量保存在堆栈段中。

5、 字符串常量保存在文本段中。

1.1.4 扩展阅读

《C专家编程》第6章——详细介绍各个段的作用。

posted @ 2010-03-03 02:38 常高伟 阅读(278) | 评论 (0)编辑 收藏


posts - 19, comments - 0, trackbacks - 0, articles - 0

Copyright © 常高伟