深入理解计算机系统__C++11新特性(C++ Primer Plus第十八章)__设计模式
目录
2、对于上面的名词解释---可重定位的目标文件(.o文件)、可执行目标文件
1)在静态库的改变被提出来之前,主函数调用使用其他模块中的函数的方法
5)链接器如何使用静态库来解析引用---即链接器是如何将.o文件和.a文件合并的以及.o文件和.a文件的顺序会不会对链接产生影响?
Last1、C++11新特性(C++ Primer Plus第十八)
01)桥模式和装饰模式是类似的,所以这里就直接写问题的解决步骤了
7、避免使用new +具体类创建类对象的方法---工厂方法(Factory Method)
8、避免使用new +具体类创建类对象的方法---抽象工厂(Abstract Factory)
9、避免使用new +具体类创建类对象的方法---原型模式(Prototype)
10、避免使用new +具体类创建类对象的方法---建造者模式(Builder)
第二章
1、字节序
a)基本概念:变量存储方式可以分为大端序和小端序,其区别如下:
b)应用:可以查看一个变量对应的十六进制,以及查改该机器的存储方式是大端序还是小端序的方法---打印出来一个已知变量对应的十六进制:
1 #include <stdio.h> 2 3 typedef unsigned char* byte_pointer; //给unsigned char*起别名为byte_pointer 4 5 /* 6 *对于show_bytes()函数: 7 *函数作用:打印start指向的数据的前len个十六进制数字 8 *start是一个unsigned char类型的指针,相当于是一个数组的首地址,而在该数组内每一个元素类型都是unsigned char类型的 9 *一个unsigned char占一个字节(8位),所以start[i]正好是一个int型数字对应的十六进制其中的两个十六进制数字 10 *需要注意的是我们可以在C语言中能够使用数组表示法来引用指针,也可以使用指针表示法来引用数组元素 11 */ 12 void show_bytes(byte_pointer start,int len){ 13 int i; 14 for(i=0;i<len;++i){ 15 printf("%.2x",start[i]); //%.2x表示至少使用两个数字的十六进制格式输出 16 } 17 printf("\n"); 18 } 19 20 /* 21 *对于show_int(): 22 *函数作用:打印x的前sizeof(x)个x对应的十六进制数字,由于sizeof()返回的是x占用的内存的字节数,sizeof(int)=4字节=32位,32位可以表示的最大数为2^32 23 *表达式&x创建了一个指向保存变量x位置的指针,&x的类型取决于x的类型 24 *(byte_pointer) &x表示无论&x以前是什么类型的指针,它现在就是一个unsigned char类型的指针,但是这些强制转换并不会真的改变指针, 25 *而是会告诉编译器以新的数据类型来看待被执行的数据。 26 */ 27 void show_int(int x){ 28 show_bytes((byte_pointer) &x,sizeof(x)); 29 } 30 31 /* 32 *对于show_int(): 33 *函数作用:打印浮点数x对应的十六进制数字 34 */ 35 void shwo_float(float x){ 36 show_bytes((byte_pointer) &x,sizeof(x)); 37 } 38 39 /* 40 *对于show_pointer(): 41 *函数作用:打印x指向变量的地址对应的十六进制数字 42 */ 43 void show_pointer(void* x){ 44 show_bytes((byte_pointer) &x,sizeof(void*)); 45 } 46 47 void test_show_bytes(int val){ 48 int ival=val; 49 float fval=(float) ival; 50 int *pval=&ival; 51 show_int(ival); //打印int型变量ival对应的十六进制 52 show_float(fval); //打印float型变量fval对应的十六进制 53 show_pointer(pval); //打印ival地址对应的十六进制 54 } 55 56 int mian(){ 57 int value=12345; //十进制整数12345对应的十六进制为0x3039 30在高位、39在底位 58 show_int(val); //如果打印00003039则为大端序、如果打印39 30 00 00 则为小端序 59 60 return 0; 61 }
第六章
1、在unix下执行下面的命令行,生成可执行文件p
如下面的代码:
1 //main.c 2 void swap(); 3 4 int buf[2]={1,2}; 5 6 int main(){ 7 swap(); 8 return 0; 9 }
1 //swap.c 2 extern int buf[]; 3 int *bufp0=&buf[0]; 4 int *bufp1; 5 6 void swap(){ 7 int temp; 8 bufp1=&buf[1]; 9 int temp=*bufp0; 10 *bufp0=*bufp1; 11 *bufp1=temp; 12 }
执行下面的命令行:
1 gcc -o2 -g -o p main.c swap.c
对于main.c文件的运行过程:
1)驱动程序首先运行C预处理器(cpp),将main.c翻译成一个ASCII码的中间文件main.i
2)驱动程序运行C编译器(ccl),将main.i翻译成一个ASCII码的汇编语言文件main.s
3)驱动程序运行汇编器(as),将main.s翻译成一个可重定位的目标文件main.o
对于swap.c的运行过程同上,会生成可重定位的目标文件swap.o
4)驱动程序运行链接器(ld),将main.o和swap.o以及一些必要的系统文件组合起来,创建一个可执行目标文件p
5)shell调用操作系统中一个叫加载器的函数,它将可执行文件p中的代码和数据拷贝到存储器。
2、对于上面的名词解释---可重定位的目标文件(.o文件)、可执行目标文件
1 /* 2 可重定位的目标文件(.o文件):包含二进制代码和数据,在链接的时候可以与其他的可重定位的目标文件一起合并起来,创建一个可执行目标文件。 3 可执行目标文件:包含二进制代码和数据,可以直接拷贝到存储器并执行。 4 共享目标文件:一种特殊类型的可重定位的目标文件,可以在加载或者是运行时被动态的加载到存储器并执行。 5 可重定位的目标文件是由编译器和汇编器一起生成的。 6 */
3、可重定位目标文件中的信息
概念:可执行、可重定位、可链接文件称为ELF文件,夹在ELF文件的头和字节部表(尾)之间的内容叫做节。
以下均是可重定位目标文件中的节:
/* 1).text:如main.c对应的机器代码 2).rodata:只读数据 3).data:已初始化的全局变量和static修饰的初始化变量 4).bss:未初始化的全局变量和static修饰的未初始化变量,在目标文件中部占据空间,仅占用一个占位符 5).symtab:一个符号表,存放程序中定义的函数和变量的全部信息,每个可重定位目标文件都存在这个符号表 6).rel.text:一个.text节中位置的列表---一个函数对应的机器代码,任何调用外部函数或者引用全局变量的质量都需要修改这些文件,此外调用本地函数的质量则不需要修改。 7).rel.data:被模块引用或定义的任何全局变量重定位信息,任何已初始化的全局变量,如果它的初始值是一个全局变量或外部定义函数的地址,都需要被修改。 8).debug:调试符号表,其条目是程序中定义的局部变量、全局变量和类型定义,在编译时只有加了-g选项才会得到这张表。 9).line:原始C程序行号和.text节中机器指令之间的映射,在编译时只有加了-g选项才会得到这张表。 10).strtab:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节的名字。 */
4、符号表.symtab
/* 下面是main.o的符号表中的最后三个条目(信息),可以通过GNU READELF显示出来: Num Value Size Type Bind Ot Ndx Name 8 0 8 OBJECT Global 0 3 buf 9 0 17 FUNC Global 0 1 main 10 0 0 NOTYPE Global 0 UND swap Ndx=1表示该对象是一个函数,存放在.text节;Ndx=3表示该对象一个变量,存放在.data节 */
其中上面的Num Value Size等是一个结构体中的一个变量:
1 typedef struct { 2 int name; //.symtab中的字节偏移(序号) 3 int value; //距定义目标的节的起始位置偏移,(变量值) 4 int size; //目标的大小 5 char type:4; //数据(OBJECT对象)或函数(FUNC) 6 binding:4; //标示该变量或函数是本地的还是全局的 7 char reserved; 8 char section; 9 };
5、链接器如何解析多重定义的全局符号---强符号和弱符号
编译器向汇编器输出变量符号的时候,将变量符号分为强变量和弱变量,汇编器把这些信息存储在可重定位目标文件的符号表中。
函数和已初始化的全局变量是强符号,未初始化的全局变量是若符号,根据强弱符号的定义,有如下规则:
1)不允许有多个强符号;
2)如果有一个强符号和一个弱符号,那么选择弱符号;
3)如果有多个弱符号,那么从这些弱符号中任选一个。
1 举例1:强符号(函数)被定义多次 2 //foo1.c 3 int mian(){ 4 return 0; 5 } 6 //bar1.c 7 int mian(){ 8 return 0; 9 } 10 会报错,因为有两个强符号main
1 举例2:强符号(变量)被定义多次 2 //foo2.c 3 int x=15213; 4 5 int mian(){ 6 return 0; 7 } 8 //bar2.c 9 int x=15213; 10 11 int f(){ 12 return 0; 13 } 14 会报错,因为有两个强符号x
1 举例3:一个强符号(变量)初始化、另一个强符号未被初始化,那么选择初始化了的强符号 2 //foo3.c 3 #include <stdio.h> 4 void f(void); //在foo3.c中声明,在bar3.c中定义 5 6 int x=15213; 7 8 int mian(){ 9 printf("x=%d\n",x); //打印x=15213 10 f(); //执行bar3.c中定义的函数 11 printf("x=%d\n",x); //打印x=1 12 return 0; 13 } 14 //bar3.c 15 int x; 16 17 int f(){ 18 x=1; 19 }
1 举例4:两个弱符号也会发生上面相同的事情 2 //foo4.c 3 #include <stdio.h> 4 void f(void); //在foo3.c中声明,在bar3.c中定义 5 6 int x; //声明但是未初始化的全局变量,是一个弱变量 7 8 int mian(){ 9 x=15213; 10 printf("x=%d\n",x); //打印x=15213 11 f(); //执行bar3.c中定义的函数 12 printf("x=%d\n",x); //打印x=1 13 return 0; 14 } 15 //bar4.c 16 int x; //声明但是未初始化的全局变量,是一个弱变量 17 18 int f(){ 19 x=1; 20 }
1 举例5:一个强符号被定义为int、另一个弱符号被定义为double 2 //foo5.c 3 #include <stdio.h> 4 void f(void); //在foo3.c中声明,在bar3.c中定义 5 6 int x=1; //声明且已初始化的全局变量,是一个强变量 7 int y=2; //声明且已初始化的全局变量,是一个强变量 8 9 int mian(){ 10 f(); //执行bar3.c中定义的函数 11 printf("x=0x%x y=0x%x \n",x,y); //打印x=0x0 y=0x80000000 12 return 0; 13 } 14 //bar5.c 15 double x; //声明但是未初始化的全局变量,是一个弱变量 16 17 int f(){ 18 x=-0.0; 19 } 20 由于double占8个字节、int占四个字节,因此bar5.c中的x=-0.0将覆盖原来x和y在存储器中的值!!!
6、静态库创建方法举例
1)在静态库的改变被提出来之前,主函数调用使用其他模块中的函数的方法
将所有的其他模块中的函数都放在一个单独的可重定位的目标文件中,如放在libc.o中,这样就可以使用下面的方法将他们链接到可执行文件中
1 gcc main.c /usr/lib/libc.o
这种方法可以实现将主函数实现与其他模块的函数实现分离开来,但是每个可执行文件都包含了libc.o中所有函数的拷贝,生成的可执行文件过大;
另外一个缺点是对于其他模块的函数发生任何一点改变都需要重新编译整个源文件,这是一个非常耗时的操作。
2)以上问题的解决方法
把其他模块中的每一个函数都存放在一个可重定位的目标文件中,如printf.o、scanf.o等
但是这样也比较耗时且文件太多了
3)以上问题的解决方法:使用静态库
将相关的可重定位的目标文件封装成一个单独的静态库。静态库以后缀.a标识
例如在main.c中要使用路径下/usr/lib中的静态库libc.a和/usr/lib中的静态库libm.a方法如下:
gcc main.c /usr/lib/libc.a /usr/lib中的静态库libm.a
此时链接器在链接时候只拷贝main.c引用的函数,其余的函数都不拷贝,从而减小了可执行文件的大小。
4)静态库创建近举例
a) 创建以下四个文件
1 //addvec.c 2 void addvec(int *x, int * y, int * z, int n) { 3 4 int i; 5 6 for (i = 0; i < n; i++) { 7 z[i] = x[i] + y[i]; 8 } 9 }
1 // multvec.c 2 void multvec(int * x, int * y, int * z, int n) { 3 4 int i; 5 6 for (i = 0; i < n; i++) 7 z[i] = x[i] * y[i]; 8 }
1 // vector.h 2 extern void addvec(int *, int *, int *, int); 3 extern void multvec(int *, int *, int *, int);
1 // main.c 2 #include <stdio.h> 3 #include "vector.h" 4 5 int main(void) { 6 7 int x[2] = {1, 2}; 8 int y[2] = {3, 4}; 9 int z[2] = {0}; 10 11 addvec(x, y, z, 2); 12 printf("%d %d\n", z[0], z[1]); 13 return 0; 14 }
注意:
在main.c中负责调用函数,因此需要一个头文件vector.h去声明addvec和multvec。
然后将这四个文件移动到Linux(Ubuntu)下
b) 将addvec.c和multvec.c编译为addvec.o和multvec.o文件,然后通过ar生成libvector.a静态库
1 gcc -c addvec.c multvec.c 2 ar rcs libvector.a addvec.o multvec.o
参数r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置。
参数c:创建一个库。不管库是否存在,都将创建。
参数s:创建目标文件索引,这在创建较大的库时能加快时间。(如果不需要创建索引,可改成大写S参数;如果.a文件缺少索引,可以使用ranlib命令添加)
c) 为了创建可执行文件,需要链接main.o和libvector.a
1 gcc -c main.c #创建main.o 2 gcc -static main.o ./libvector.a -o vector #生成可执行文件vector 3 ./vector #执行该可执行文件
-static参数告诉编译驱动程序,链接器应该构建一个完全链接的可执行目标,它可以加载到存储器并执行,在加载时无需更进一步的链接。
当连接器运行时,链接器解析addvec.o定义的addvec符号被main.o引用,所以它拷贝addvec.o到可执行文件,由于multvec.o的符号没有被引用,所以不会被拷贝到可执行文件,此外还有libc.a的printf.o也被main.o引用,所以会被拷贝到可执行文件。还有许多C运行时系统中的模块。
最后文件中的文件
5)链接器如何使用静态库来解析引用---即链接器是如何将.o文件和.a文件合并的以及.o文件和.a文件的顺序会不会对链接产生影响?
在符号解析阶段,链接器从左到右按照它们在编译器驱动程序命令行上出现的相同顺序来扫描可重定位目标文件main.o和存档文件libvector.a。在这扫描中,链接器维持一个可重定位目标文件的集合E,一个未解析的符号(即引用了尚未定义的符号)集合U,以及一个在前面输入文件中已定义的符号集合D。
(1)对于命令行上每个输入文件f,链接器会判断f是目标文件还是存档文件。如果是一个目标文件,那么链接器把f添加到E,此时E={mian.o},修改D和U来反映f中的符号定义和引用,U={addvec},并继续下一个输入文件。
(2)如果f是一个存档文件,那么链接器就尝试匹配U中未解析的符号和由存档文件成员定义的符号。在存档文件中定义了addvec符号,用来解析U中对addvec符号的引用,将addvec.o加入到E中,并且链接器修改D和U来反映addvec.o中的符号的定义和引用。对存档文件所有目标文件都反复进行上述过程,直到U和D不在发生变化,也就是找不到一个符号的定义与引用相联系。此时,任何不包含在E中的成员目标文件都将被丢弃,也就是在main.o未引用的符号,该符号定义的目标文件,此处为multvec.o,而链接器继续下一个输入文件。
(3)如果链接器完成对命令行上文件的扫描,U是非空,那么链接器会输出一个错误并终止,也就是一个未定义的引用。否则,它会合并和E中的目标文件,从而构建输出的可执行文件。
kernel@Ubuntu:~/Desktop/StaticLib$ gcc -static ./libvector.a main.o -o vector main.o:在函数‘main’中: main.c:(.text+0x41):对‘addvec’未定义的引用 collect2: error: ld returned 1 exit status
在命令行输入,将libvector.a 和main.o位置调换,就会出现上述错误。因为首先链接器判断f为存档文件,此时U为空集,所以libvector.a中的addvec.o定义的符号无法解析对addvec的引用。因此产生错误。因此最好在链接的时候,将存档文件放到最后。
如果库不是独立的,也就是一个成员引用另一个成员的符号,他们必须排序,使得对于每个被存档文件的成员外部引用的符号s,在命令行中至少有一个s的定义是在对s的引用之后。例如fun.c引用了libx.a和liby.a中的函数,而这两个库又调用libz.a的函数,则有:
如果库不是独立的(一个.a文件中的函数调用了另外一个.a文件中的函数),也就是一个成员引用另一个成员的符号,他们必须排序,使得对于每个被存档文件的成员外部引用的符号s,在命令行中至少有一个s的定义是在对s的引用之后。例如fun.c引用了libx.a和liby.a中的函数,而这两个库又调用libz.a的函数,则命令行书写顺序必须为:、
1 gcc fun.o libx.a liby.a libz.a
如果需要满足依赖需求,可以在命令行上重复库。fun.c调用libx.a中的函数,该库又调用liby.a的函数,而liby.a又调用libx.a的函数,则命令行书写顺序必须为:
1 gcc fun.o libx.a liby.a libx.a
7、动态链接库创建方法举例
1)静态链接库的缺点及动态库的解决方法
1)当一个静态库需要更新的时候,在重新生成该静态库之后,需要重新执行gcc -static main.o ./libvector.a -o vector生成可执行文件vector
但是动态库在需要更新的时候,在重新生成该动态库之后,直接将该动态库替换掉旧的动态库即可,不需要重新生成新的可执行文件。
2)在可执行文件运行的时候,被引用的静态库中的函数和数据会被复制到每个运行进程的代码段(即复制到了可执行文件中的代码段,假如运行了多个可执行文件),在存在很多个进程的时候, 此时就会对内存造成极大的浪费。
但是对于动态库,所有引用该动态库的可执行目标文件(进程)共享这个动态库中的数据和代码,而不是想静态库那样将静态库的内容嵌入到可执行文件中。
2)动态库(共享库)的概念
共享库是一个目标模块,在可执行文件和该共享库运行时候(注意,是运行时),可以加载到任意的内存地址,并和一个在存储器中的程序链接起来,这个过程成为动态链接,是由一个叫动态链接器的程序来执行的。共享库常用.so后缀来表示,微软将共享库成为DDL(动态链接库)。
3)共享库的特点
1)一个共享库只有一个.so文件,所有引用该动态库的可执行目标文件(进程)共享这个动态库中的数据和代码,而不是想静态库那样将静态库的内容嵌入到可执行文件中;
2)一个共享库的.text节的一个副本可以被不同的正在运行的进程共享。
4)共享库libvector.so的创建方法举例
a)创建动态库libvector.so
1 gcc -shared -fPIC -o libvector.so addvec.c multvec.c
-shared选项指示生成一个共享库;-fPIC指示编译器生成与位置无关的代码。
b)使用该动态库
随后使用该共享库
1 gcc -o p2 main.c ./libvector.so
这样就创建了一个可执行目标文件p2,当p2在运行的时候,它可以和libvector.so链接。
基本的步骤如下:
1)静态的执行一些p2和libvector.so的链接:此时没有任何libvector.so中的代码和数据拷贝到p2中,而是链接器从libvector.so中向p2拷贝了一些重定位和符号信息,
使得在p2运行时可以解析对libvector.so中代码和数据的引用。
2)当加载器加载和运行p2时,动态链接器执行重定位完后链接任务。
最后的文件夹中的内容如下:
第九章
1、一些基本概念
1.1 为什么要引入虚拟存储器?虚拟存储器的作用?
为什么要引入虚拟存储器?
一个系统中的进程与其他进程共享CPU和主存资源,当运行太多进程的时候,就需要太多的主存存储空间,如果某个进程不小心写了另一个进程使用的存储器,那该进程就可能以某种不正确的逻辑运行,导致出错。为了更加有效的管理存储器现代系统提供了一种对主存的抽象概念,即虚拟存储器(VM)。虚拟存储器被组织为一个由磁盘上N个连续字节大小的单元组成的数组。
虚拟存储器的作用?
1)虚拟存储器(存储空间位于磁盘)将主存看成是一个存储在磁盘上的高速缓存,在主存中只保持活动区域,可以根据需要在磁盘和主存之间来回传送数据,高效的使用了主存;
2)为每个进程提供了一致的地址村健,从而简化了存储器管理;
3)保护了每个进程的地址空间不被其他进程破坏。
1.2 物理地址? MMU的概念?地址空间?
物理地址?
计算机系统的主存被组织成一个由M个连续的字节单元组成的数组,每个字节单元都由一个唯一的物理地址。
图1
MMU?
CPU芯片上叫做存储器管理单元(MMU)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该标的内容是由操作系统管理的。
地址空间?
地址空间是一个非负整数地址的有序集合:
{0,1,2,3,…}
如果地址空间中的整数是连续的,那么我们说它是一个线性地址空间。
在一个带有虚拟存储器的系统中,CPU从一个偶N=2n个地址的地址空间中生成虚拟地址,这个地址空间叫做虚拟地址空间:
{0,1,2,3,…,N-1}
一个包含N=2n个地址的虚拟地址空间叫做n位地址空间。
一个系统中还有一个物理空间,它与系统中物理存储器的M个字节相对应:
{0,1,2,3,…,M-1}
M不要求是2的幂,但是为了简化讨论,我们假设M=2m。
1.3 页和页表
页?
虚拟存储器被组织为一个由磁盘上N个连续字节大小的单元组成的数组。磁盘(较低层)上的数据被分割为块,块的大小单位成为页。类似的,物理存储器(内存)被分割为屋里页。
在任何时刻虚拟页的集合都分为三个不相交的子集:
未分配的:虚拟存储器系统还未创建的页,未创建的块没有任何数据和该页相关联,因此不占用磁盘空间。
缓存的:当前缓存在物理存储器中的已分配的页。
未缓存的:当前没有缓存在物理存储器中的已分配的页。
页表?
页表是一个数据结构:页表条目数组,存储在DRAM中(主存或内存中),每次MMU将一个虚拟地址翻译为物理地址的时候,都会读取页表,操作系统负责维护页表的内容,以及在磁盘和DRAM(主存或内存)之间来回传送页。
虚拟地址空间中的每个页在页表中一个固定偏移量处都由一个页表条目(数组中的一项),我们假设每个页表条目(PTE)由一个有效位和n位地址字段组成。有效位表明该虚拟页是否被缓存到了DRAM(内存或主存中),如果该有效位为1表明在DRAM中存储了该虚拟页对应的物理页,或者说该物理页缓存了一个虚拟页。如果该有效位为0,那么一个空地址表示该虚拟页还未分配。
图2 页表保存虚拟页映射到物理页信息的方法
1.4 页命中和缺页
页命中
现在考虑一个问题:当CPU读VP2中的虚拟存储器中的一个字时会怎样去读?
地址翻译硬件(MMU)将虚拟地址作为一个索引来定位PTE2,并从存储器中读取它,因为PTE2的有效位为1,那么MMU就知道虚拟页VP2是缓存在DRAM中的了,进而使用PTE2中存储的地址(该地址指向了PP1缓存也的其实地址),找到了这个字的物理地址。
缺页
DRAM缓存不命中称为缺页。俗话说即DRAM出错,本来该在DRAM中产生一个物理页,该物理页的位置上存储了一个虚拟页,但是没有产生这个物理页,导致缺页。
缺页异常的产生过程:CPU引用了一个虚拟页VP3中的一个字,但VP3并未缓存在DRAM中,MMU去DRAM中读取页表中的PTE3的时候,发现PTE3对应的有效位为0,从而触发一个缺页异常。
缺页异常会调用内核中缺页异常对应的异常处理程序,该程序会选择一个在DRAM中选择一个物理页(该物理页对应了或者没对应一个已缓存了的虚拟页)作为牺牲页,假如该牺牲页对应了一个虚拟页,那么就将该虚拟页拷贝回磁盘;假如该牺牲页对应了一个虚拟页,那么就不做任何处理。最后修改牺牲页对应的PTE中的有效位,将该有效位设置为0,表示该虚拟页还未分配。
接下来内存会拷贝VP3到DRAM中的牺牲页对应的位置上,更新PTE3,随后返回。返回的同时,它会重新启动导致缺页的指令,该指令把缺页的虚拟地址重新发给MMU,然后翻译到物理地址,但是现在VP3已经存储在主存中了,所以此时程序会正常执行。
1.5 从虚拟存储器的角度去理解为啥要写出高性能的代码
尽管在整个程序运行过程中,程序引用的不同进程的虚拟页对应的物理页可能会超过物理存储器总的大小,但是局部性原则保证了在任意时刻,程序将往往在一个较小的活动页面上工作,这个集合叫做工作集。如果工作集的大小超过了物理存储器的大小,那么程序将处于一种颠簸的状态,这时将不断的切换页表(页面)。
Last1、C++11新特性(C++ Primer Plus第十八)
1、移动语意 Last11
以前积累的关于右值的概念,注意要向上翻一个才会到右值的概念
接下来的示例包括了:复制构造函数、移动构造函数、对等号的重载---复制赋值运算符函数、对等号的重载---移动赋值运算符函数
1 //移动语意 2 //stdmove.cpp---suing std::move() 3 #include <iostream> 4 #include <utility> //for std::move() 5 6 //类声明 7 class Useless{ 8 private: 9 int n; //保存字符个数 10 char *pc; //指向字符串的指针 11 static int ct; //用于统计Useless对象的个数 12 void ShowObject() const; //用于显式Useless对象的信息,只能在Useless内部调用 13 public: 14 Useless(); 15 explicit Useless(int k); //explicit关闭隐式构造Useless对象,即Useless u1=12;是非法的 16 Useless(int k,char ch); 17 Useless(const Useless & f); //复制构造函数 18 Useless(Useless && f); //移动构造函数,其中f的用法和一般的类对象调用类方法是一样的 19 ~Useless(); 20 Useless operator+(const Useless & f) const; //对+的重载,该函数返回的是一个临时对象,所以返回值不是引用 21 Useless & operator=(const Useless & f); //对等号的重载---复制赋值运算符函数 22 Useless & operator=(Useless && f); //对等号的重载---移动赋值运算符函数 23 void ShowData() const; 24 }; 25 26 //实现 27 int Useless::ct=0; 28 //不带参数的构造函数实现 29 Useless::Useless(){ 30 ++ct; 31 n=0; 32 pc=nullptr; 33 } 34 //带一个int型参数的构造函数实现 35 Useless::Useless(int k) : n(k){ 36 ++ct; 37 pc=new char[n]; 38 } 39 //带一个int型参数和一个char型参数的构造函数 40 Useless::Useless(int k,char ch) : n(k){ 41 ++ct; 42 pc=new char[n]; 43 for(int i=0;i<n;++i) 44 pc[i]=ch; 45 } 46 //复制构造函数实现 47 Useless::Useless(const Useless & f) : n(f.k){ 48 ++ct; 49 pc=new char[n]; 50 for(int i=0;i<n;++i) 51 pc[i]=f.pc[i]; 52 } 53 //移动构造函数---节省内存,加快转移速度 54 Useless::Useless(Useless && f) : n(f.n){ 55 ++ct; 56 pc=f.pc; //只是改变pc指针的指向,并没有复制构造函数中的for循环去复制字符 57 f.pc=nullptr; //必须的,如果两个指针都指向了同一块内存,指针1先释放了该内存,那么指针2如果还是指向原内存的话,再次被释放就会出错 58 f.n=0; //但是释放空指针是不会出错的 59 } 60 //析构函数 61 Useless::~Useless(){ 62 delete [] pc; 63 } 64 //对+的重载, 65 Useless operator+(const Useless & f) const{ 66 Useless temp = Useless(n+f.n); 67 for(int i=0;i<n;++i) 68 temp[i]=pc[i]; 69 for(int i=n;i<n;++i) 70 temp[i]=f.pc[i-n]; //该函数的精妙处在此 71 return temp; 72 } 73 //对等号的重载---复制赋值运算符函数(实参是一个左值的时候调用此函数) 74 Useless & operator=(const Useless & f){ 75 if(this == &f) 76 return *this; 77 delete [] pc; 78 n=f.n; 79 pc=new char[n]; 80 for(int i=0;i<n;++i) 81 pc[i]=f.pc[i]; 82 return *this; 83 } 84 //对等号的重载---移动赋值运算符函数(实参是一个右值的时候调用此函数) 85 Useless & operator=(Useless && f){ 86 if(this == &f) 87 return *this; 88 n=f.n; 89 pc=f.pc; 90 f.pc=nullptr; 91 return *this; 92 } 93 94 voie Useless::ShowObject() const{ 95 std::cout<<"Number of char elements: "<<n; 96 std::cout<<" char data address: "<<(void*)pc<<std::endl; 97 } 98 void Useless::ShowData() const{ 99 if(n==0) 100 std::cout<<"object empty"; 101 else 102 for(int i=0;i<n;++i) 103 std::cout<<pc[i]; 104 std::cout<<std::endl; 105 } 106 107 //主函数 108 int main(){ 109 using std::cout; 110 { 111 Useless one(10,'x'); 112 Useless two=one+one; //先调用对等号的重载函数,然后调用移动构造函数,因为one+one是一个右值 113 cout<<"Object one: "; 114 one.ShowData(); 115 cout<<"Object two: "; 116 two.ShowData(); 117 118 Useless three,four; 119 cout<<"three=one\n"; 120 three = one; //调用对等号的重载---复制赋值运算符函数,因为one是一个左值 121 cout<<"Now object three= "; 122 three.ShowData(); 123 cout<<"and object one= "; 124 one.ShowData(); 125 126 cout<<"four=one+two\n"; 127 four=one+two; 128 cout<<"Now object four= "; 129 four.ShowData(); 130 131 cout<<"four = move(one)\n"; 132 four=std::move(one); //调用对等号的重载---移动赋值运算符函数,因为此时std::move(one)是一个右值 133 cout<<"Now object four= "; 134 four.ShowData(); 135 cout<<"and object one= "; 136 one.ShowData(); 137 } 138 139 return 0; 140 }
Last2、设计模式
1、设计模式的基本概念
具体参考博客吧
2、模板方法
下面的这个例子一般的设计思路是:库开发人员先将库函数写好,然后交给应用开发人员去做开发,应用开发人员去写主函数,并在主函数中调用库开发人员写好的函数,一般的实现方法如下:
(1)使用一般的设计方法实现
1 //程序库开发人员做的开发 2 class Library{ 3 public: 4 void Step1(){ 5 //... 6 } 7 8 void Step3(){ 9 //... 10 } 11 12 void Step5(){ 13 //... 14 } 15 };
1 //应发程序开发人员 2 //(1)要自己实现两个步骤:Step2()和Step4() 3 //(2)根据程序库开发人员提供的库函数实现具体的业务流程 4 class Application{ 5 6 bool Step2(){ 7 //... 8 } 9 10 void Step4(){ 11 //... 12 } 13 }; 14 15 //实现具体的业务流程---要用到程序库开发人员写的函数也要用到自己写的函数 16 int main(){ 17 Lirary lib(); 18 Application app(); 19 20 lib.Step1(); 21 22 if(app.Step2()){ 23 lib.Step3(); 24 } 25 26 for(int i=0;i<4;++i){ 27 app.Step4(); 28 } 29 30 lib.Step5(); 31 }
但是该方法违背了模式设计中的“不应该使用早绑定的设计思路”,上面的设计思路如下:
(2)使用模板方法实现
1 //程序库开发人员 2 //整体的流程是框架开发人员已经设定好了的,因此流程也可以由库开发人员去做,常常不需要更改且是稳定的 3 class Library{ 4 public: 5 //稳定模板方法---实现流程 6 void Run(){ 7 Step1(); 8 9 if(Step2()){ //支持变化===>虚函数多态调用 10 Step3(); 11 } 12 13 for(int i=0;i<4;++i){ 14 Step4(); //支持变化===>虚函数多态调用 15 } 16 17 Step5(); 18 } 19 20 virtual ~Library(){} 21 22 protected: //Step1()、Step3()等这些函数对外界没有意义,只有在内部的一个流程中才有意义,所以设为保护对象 23 void Step1(){ //稳定,不需要更改 24 //... 25 } 26 27 void Step3(){ //稳定,不需要更改 28 //... 29 } 30 31 void Step5(){ //稳定,不需要更改 32 //... 33 } 34 35 virtual bool Step2() =0; //变化,该函数是应用开发人员做的,因此写成纯虚函数,程序库开发人员可以不实现 36 virtual void Step4() =0; //变化,且框架开发人员不知道该去怎么写 37 };
1 //应发程序开发人员 2 class Application : public Library{ 3 protected: 4 virtual bool Step2(){ 5 //...子类重写父类中的Step2()纯虚函数 6 } 7 8 virtual void Step4(){ 9 //...子类重写父类中的Step2()纯虚函数 10 } 11 }; 12 13 int main(){ 14 { 15 Library* pLib=new Application(); 16 //下面的pLib->Run();由于Run()不是虚函数,所以会根据pLib的类型去做调用 17 //那么即调用基类Library中的Run()函数,但是在Run()中又调用了基类中和派生类中都有的虚函数 18 //Step2()和Step4(),此时会根据pLib指向的类型去做调用,即调用派生类中实现的Step2()和Step4() 19 pLib->Run(); 20 21 delete pLib; 22 } 23 }
上面的方法即实现了晚绑定,具体的实现思路如下:“不要你调用我,我去调用你---不要miain()函数去调用库函数中的方法,用库函数中的方法去调用子类中的方法”
a)模板方法定义:
定义一个操作中的算法骨架(即实现方法2中的Run()函数),而将一些步骤(如实现方法2中的Step2()和Step4()的实现)延迟在子类中实现。模板方法使得子类可以不改变一个算法的结构即可重写(override)该算法的某些特性步骤。
算法骨架即Run()函数中实现中即稳定、也有变化。稳定是指算法流程不会发生改变、变化是指Step2()和Step4()是虚函数,在应用开发人员那里是会发生变化的。所以稳定、不发生变化的函数写成普通函数、发生变化的要写成虚函数或纯虚函数。
b)什么时候使用模板方法?
(1)假如Step1()、Step2()、Step3()、Step4()、Step5()都是不稳定的,那么所有的设计模式都不适合,设计模式的假定条件是必须有稳定点,如果没有稳定点,设计模式就无意义了。
(2)假如Step1()、Step2()、Step3()、Step4()、Step5()都是稳定的,此时也不要用模板方法了。所有方法都是稳定的时候,设计模式也无意义了。
3、策略模式
场景是实现各国税法的计算实现
(1)使用一般的实现方法---该方法不利于扩展,即未来添加一个法国的税法计算法方法实现起来比较困难
该方法的核心使用了大量的if else语句
1 //一般实现方法 2 //实现不同国家税法的计算 3 enum TaxBase{ 4 CN_Tax, 5 US_Tax, 6 DE_Tax 7 }; 8 9 class SaleOrder{ 10 TaxBase tax; 11 public: 12 double CalculateTax(){ 13 //... 14 15 if(tax==CN_Tax){ //中国税法的计算实现 16 //CN****** 17 } 18 else if(tax==US_Tax){ //美国税法的计算实现 19 //US****** 20 } 21 else if(tax==DE_Tax){ //德国税法的计算实现 22 //DE****** 23 } 24 25 //... 26 } 27 }; 28 29 /*但是此时要添加法国的税法的计算实现,那么需要在第三行中添加一个FR_Tax,在第23行下添加法国税法的计算方法 30 *但此时就违背了开闭原则,因为做了这些更改之后,此时软件就得重新编译、重新测试、重新部署。 31 *所以尽可能的使用扩展的方式---使用策略设计模式 32 *而且在一个代码中去补代码,是很不合适的 33 */
/*但是此时要添加法国的税法的计算实现,那么需要在第三行中添加一个FR_Tax,在第23行下添加法国税法的计算方法
*但此时就违背了开闭原则,因为做了这些更改之后,此时软件就得重新编译、重新测试、重新部署。
*所以尽可能的使用扩展的方式---使用策略设计模式
*而且在一个代码中去补代码,是很不合适的
*/
(2)使用策略模式实现
该方法的核心是使用了类继承、虚函数和多态
1 //使用策略模式 2 class TaxStrategy{ 3 public: 4 virtual double Calculate(const Context& contex)=0; 5 virtual ~TaxStrategy(){} 6 }; 7 8 //这个类在工作中是要单独的放一个文件中的---中国的税法实现 9 class CNTax : public TaxStrategy{ 10 11 virtual double Calculate(Context& contex){ 12 //****** 13 } 14 }; 15 16 //这个类在工作中是要单独的放一个文件中的---美国的税法实现 17 class USTax : public TaxStrategy{ 18 19 virtual double Calculate(Context& contex){ 20 //****** 21 } 22 }; 23 24 //这个类在工作中是要单独的放一个文件中的---德国的税法实现 25 class DETax : public TaxStrategy{ 26 27 virtual double Calculate(Context& contex){ 28 //****** 29 } 30 }; 31 32 class SaleOrder{ 33 private: 34 TaxStrategy* strategy; //多态指针 35 public: 36 SaleOrder(StrategyFactory* strategyFactory){ 37 this->strategy=strategyFactory->NewStrategy();//这里使用了工厂模式,strategyFactory->NewStrategy()可能会返回中国、美国或德国的对象 38 } 39 40 ~SaleOrder(){ 41 delete this->strategy; 42 } 43 44 double CaculateTax(){ 45 //... 46 Context context(); //算法上下文参数 47 double val=strategy->Calculate(contex); //多态调用,运行时决策 48 //... 49 } 50 }; 51 52 //假设此时需要加法国税法的计算方法,此时只需要在第30行以下加如下代码即可 53 class FR_Tax : public TaxStrategy{ 54 55 virtual double Calculate(const Context& context){ 56 //****** 57 } 58 }; 59 //假如第37行的strategyFactory->NewStrategy()可以返回一个法国税法计算方法的对象 60 //那么此时可以实现法国的税法计算方法,即SaleOrder类不需要做改变,既遵循了开放封闭原则 61 62 /* 63 *策略模式定义: 64 *定义一些列算法,把他们一个个封装起来,并且使他们可以相互替换(变化的部分)。该模式使得算法可独立于它 65 *的客户程序(稳定)而变化(扩展,使用子类继承父类的方法去实现) 66 67 *什么时候用到策略模式? 68 *比如出现下面的代码的时候: 69 *if(...){ //这种是分而治之的方法 70 * 71 *} 72 *else if(...){ 73 * 74 *} 75 *那有没有可能出现第三种情况呢?此时可以使用策略模式,如果绝对没有可能出现第三种情况(如性别),那可以使用分而治之的方法 76 *看到很多else if的时候就应该嗅到策略模式的味道了 77 78 *为什么说使用策略模式可以提高代码性能? 79 *这是由于如果使用很多的if else语句,那么就表明运行一次只有一个else if条件被选中,那么其他的else if代码也是要占用 80 *内存空间的,加入内存空间很小,此时代码就会被放到虚拟内存中(即硬盘中),此时会降低代码执行效率。但是使用了策略模式 81 *之后,在执行第47行的时候,是在运行时做决策的,即在运行的时候决定是要将那段代码拷贝到内存,即减小了内存的使用率, 82 *提高了代码执行性能。 83 ***但是策略模式主要的出现主要是为了解决未来代码扩展性的问题的*** 84 */
a)策略模式定义:
定义一些列算法,把他们一个个封装起来,并且使他们可以相互替换(变化的部分)。该模式使得算法可独立于它的客户程序(稳定)而变化(扩展,使用子类继承父类的方法去实现)
b)什么时候用到策略模式?
比如出现下面的代码的时候:
1 if(...){ //这种是分而治之的方法 2 3 } 4 else if(...){ 5 6 }
那有没有可能出现第三种情况呢?此时可以使用策略模式,如果绝对没有可能出现第三种情况(如性别),那可以使用分而治之的方法
看到很多else if的时候就应该嗅到策略模式的味道了
c)为什么说使用策略模式可以提高代码性能?
这是由于如果使用很多的if else语句,那么就表明运行一次只有一个else if条件被选中,那么其他的else if代码也是要占用内存空间的,加入内存空间很小,此时代码就会被放到虚拟内存中(即硬盘中),此时会降低代码执行效率。但是使用了策略模式之后,在执行第47行的时候,是在运行时做决策的,即在运行的时候决定是要将那段代码拷贝到内存,即减小了内存的使用率,提高了代码执行性能。
***但是策略模式主要的出现主要是为了解决未来代码扩展性的问题的***
d)策略模式的结构
4、观察者模式
1)问题的引入
任务:实现文件分割,具体要分为文件分割类、窗口显式类。在文件分割类中实现了文件分割的具体实现,在窗口显式类中实现了比如按钮对应的槽函数等窗口函数。按照一般的设计思路,他们两个之间的关系是要在窗口类中的按钮对应的槽函数中创建一个文件分割类对象,然后由这个类对象调用该文件分割类下的文件分割方法。
下面是窗口类的实现伪代码:
1 //一般的实现方法---该文件是窗口提供了窗口显式的功能,注意以下指示伪代码,Form类可以认为是已有的 2 class MainForm : public Form{ 3 TextBox* txtFilePath; //保存待分割文件的文件路径 4 TextBox* txtFileNumber; //要将文件分解成多少个文件 5 6 public: 7 void Button1_Click(){ 8 string filePath = txtFilePath->getText(); //得到待分割文件的文件路径 9 int number = atoi(txtFileNumber->getText.c_str()); //得到要分割的文件的个数 10 11 FileSplitter splitter(filePath,number); //调用另一个类的构造函数,创建类对象 12 13 splitter.split(); //调用这个类下的分割文件的函数,进行文件的分割 14 } 15 };
下面是文件分割类的实现伪代码:
1 //分割文件实现的类 2 class FileSplitter{ 3 string m_filePath; 4 int m_fileNumber; 5 6 FileSplitter(const string & filePath,int fileNumber) : m_filePath(filePath),m_fileNumber(fileNumber){} 7 8 void split(){ 9 //1、读取大文件 10 11 //2、分批次向小文件中写入大文件中的数据 12 for(int i=0;i<m_fileNumber;++i){ 13 //... 14 } 15 } 16 };
现在问题来了:要在窗口中增加一个文件分割的进度条。
那么一般的设计思路是在窗口类中创建一个ProgressBar类对象,然后传递给窗口类中Button1对应的槽函数Button1_Clicked(),再传递给FileSplitter类的构造函数;然后同样的在文件分割类中也创建一个ProgressBar类对象,并且作为FileSplitter类中构造函数的一个形参,此时便可以在FileSplitter类中的split()函数使用这个ProgressBar类对象。具体实现如下:
2)一般的设计方法
下面是增加了进度条的窗口类的实现伪代码:
1 //增加显式分割文件进度的进度条 2 class MainForm : public Form{ 3 TextBox* txtFilePath; //保存待分割文件的文件路径 4 TextBox* txtFileNumber; //要将文件分解成多少个文件 5 6 ProgressBar* progressBar; //进度条类对象 7 8 public: 9 void Button1_Click(){ 10 string filePath = txtFilePath->getText(); //得到待分割文件的文件路径 11 int number = atoi(txtFileNumber->getText.c_str()); //得到要分割的文件的个数 12 13 //下面这句调用另一个类的构造函数,创建类对象,并将待分割的文件路径、分割数量、进度条对象传递给FileSplitter类中的split()函数 14 FileSplitter splitter(filePath,number,progressBar); 15 16 splitter.split(); //调用FileSplitter类下的分割文件的函数split(),进行文件的分割 17 } 18 };
下面是增加了进度条的文件分割类的实现伪代码:
1 //增加显式分割文件进度的进度条 2 class FileSplitter{ 3 string m_filePath; 4 int m_fileNumber; 5 6 ProgressBar* m_progressBar; //进度条类对象 7 8 //这里的形参progressBar对应的实参是从界面类MainForm中传递过来的,形成了耦合,所以观察者模式就是要在这里做改进 9 FileSplitter(const string & filePath,int fileNumber,ProgressBar* progressBar) : 10 m_filePath(filePath),m_fileNumber(fileNumber),m_progressBar(progressBar){ 11 12 } 13 14 void split(){ 15 //1、读取大文件 16 17 //2、分批次向小文件中写入大文件中的数据 18 for(int i=0;i<m_fileNumber;++i){ 19 //...进行文件分割 20 if(m_progressBar != nullptr){ 21 m_progressBar->setValue((i+1)/m_fileNumber); //更新进度条的值 22 } 23 } 24 } 25 };
但是上面的实现方法在编译的时候,MainForm类和FileSplitter类必须在一块编译,少了谁都不可以,且下次如果不使用进度条了的话,换成一个table现实百分比,还得再次修改。
但是上面的设计方法违背了以下原则:
1)依赖倒置原则:高层模块不能依赖底层模块,二者都应该依赖抽象,抽象不能依赖实现细节,实现细节应该依赖抽象。
上面第六行ProgressBar* progressBar;即是一个实现细节,可能这次需要使用这种进度条,那下次可能就不再使用这样的进度条了,因此
这个进度条对象progressBar是非常容易变的,但是在编译的时候还依赖于这个progressBar,所以形成了高层依赖底层(细节、变化)
2)ProgressBar* progressBar;实际上起到了一个通知的作用,可以使用一个IProgress类代替,在该类中实现一个虚函数
3)使用观察者模式的设计方法
下面是增加了进度条且使用观察者模式的窗口类的实现伪代码:
1 //使用观察者模式 2 class MainForm : public Form,IProgress{ 3 TextBox* txtFilePath; //保存待分割文件的文件路径 4 TextBox* txtFileNumber; //要将文件分解成多少个文件 5 6 ProgressBar* progressBar; //进度条类对象,起到了通知的作用,通知FileSplitter类下的split()函数对进度条进行使用并更新 7 8 public: 9 void Button1_Click(){ 10 string filePath = txtFilePath->getText(); //得到待分割文件的文件路径 11 int number = atoi(txtFileNumber->getText.c_str()); //得到要分割的文件的个数 12 13 //本来要传的是IProgress对象指针,但是在这里的this是MainForm对象的指针. 14 //相当于父类指针(形参IProgress对象指针)指向了派生类MainForm的类对象指针(这里的this) 15 //参数传递的时候类似于IProgress* iprogress=this; 而this是派生类MainForm类对象指针,iprogress是基类类对象指针,所以这样是可以的 16 FileSplitter splitter(filePath,number,this); 17 18 splitter.split(); //调用FileSplitter类下的分割文件的函数split(),进行文件的分割 19 } 20 21 virtual void DoProgress(float value){ 22 progressBar->setValue(value); 23 } 24 25 26 };
下面是增加了进度条且使用观察者模式的文件分割类的实现伪代码:
1 //使用观察者模式,此时FileSplitter类就不依赖于MainForm类,可以与任何窗口显式类相互搭配 2 //IProgress表示了一种抽象的通知控件 3 class IProgress{ 4 public: 5 virtual void DoProgress(float value)=0; 6 virtual ~IProgress(){} 7 }; 8 9 class FileSplitter{ 10 string m_filePath; 11 int m_fileNumber; 12 13 //ProgressBar* m_progressBar; //进度条类对象 14 IProgress* m_iprogress; 15 16 FileSplitter(const string & filePath,int fileNumber,IProgress* iprogress) : //这里的iprogress是一个基类指针,会形成多态调用 17 m_filePath(filePath), 18 m_fileNumber(fileNumber), 19 m_iprogress(iprogress){ //这里的iprogress指向的是派生类MainForm的类对象,所以下面会调用派生类中实现的DoProgress() 20 21 } 22 23 void split(){ 24 //1、读取大文件 25 26 //2、分批次向小文件中写入大文件中的数据 27 for(int i=0;i<m_fileNumber;++i){ 28 //...进行文件分割 29 if(m_iprogress != nullptr){ 30 float progressValue=m_fileNumber; //即将一个整数赋值给一个浮点数 31 progressValue=(i+1)/progressValue; 32 m_iprogress->DoProgress(progressValue); //更新进度条的值,多态调用 33 } 34 } 35 } 36 };
对文件分割类的实现伪代码的进一步改进:
1 //进一步的改进 2 //使用观察者模式,此时FileSplitter类就不依赖于MainForm类,可以与任何窗口显式类相互搭配 3 //IProgress表示了一种抽象的通知控件 4 class IProgress{ 5 public: 6 virtual void DoProgress(float value)=0; 7 virtual ~IProgress(){} 8 }; 9 10 class FileSplitter{ 11 string m_filePath; 12 int m_fileNumber; 13 14 //ProgressBar* m_progressBar; //进度条类对象 15 IProgress* m_iprogress; 16 17 FileSplitter(const string & filePath,int fileNumber,IProgress* iprogress) : //这里的iprogress是一个基类指针,会形成多态调用 18 m_filePath(filePath), 19 m_fileNumber(fileNumber), 20 m_iprogress(iprogress){ //这里的iprogress指向的是派生类MainForm的类对象,所以下面会调用派生类中实现的DoProgress() 21 22 } 23 24 void split(){ 25 //1、读取大文件 26 27 //2、分批次向小文件中写入大文件中的数据 28 for(int i=0;i<m_fileNumber;++i){ 29 //...进行文件分割 30 31 float progressValue=m_fileNumber; //即将一个整数赋值给一个浮点数 32 progressValue=(i+1)/progressValue; 33 onProgress(progressValue); 34 } 35 } 36 37 protected: 38 void onProgress(float value){ 39 if(m_iprogress != nullptr){ 40 m_iprogress->DoProgress(progressValue); //更新进度条的值,多态调用 41 } 42 } 43 };
此时FileSplitter类就不依赖于MainForm类,可以与任何窗口显式类相互搭配
4)多观察者模式
上面的ProgressBar* progressBar;只相当于一个观察者,假如要支持多个观察者该怎么办呢?
使用vector
比如此时不仅需要在界面中有进度条显式的功能,还要增加打印点的功能
下面是增加了进度条、打印点且使用多观察者模式的窗口类的实现伪代码:
1 class MainForm : public Form,IProgress{ 2 TextBox* txtFilePath; //保存待分割文件的文件路径 3 TextBox* txtFileNumber; //要将文件分解成多少个文件 4 5 ProgressBar* progressBar; //第一个观察者进度条类对象,起到了通知的作用,通知FileSplitter类下的split()函数对进度条进行使用并更新 6 ConsoleNotifier cn; //第二个观察者,起到了不断输出点的作用,只不过这个观察者的类是自己写的,并重写了基类IProgress中的DoProgress()方法 7 8 public: 9 void Button1_Click(){ 10 string filePath = txtFilePath->getText(); //得到待分割文件的文件路径 11 int number = atoi(txtFileNumber->getText.c_str()); //得到要分割的文件的个数 12 13 FileSplitter splitter(filePath,number); 14 15 splitter.addIProgress(this); //将第一个观察者放进FileSplitter类中的m_iprogressVector中---订阅通知,可以自主决定是否订阅 16 splitter.addIProgress(&cn); //将第二个观察者放进FileSplitter类中的m_iprogressVector中---订阅通知,可以自主决定是否订阅 17 18 splitter.split(); //调用FileSplitter类下的分割文件的函数split(),进行文件的分割 19 20 splitter.removeIProgress(&cn); //某个时刻不需要第二个观察者了 21 } 22 23 virtual void DoProgress(float value){ 24 progressBar->setValue(value); 25 } 26 }; 27 28 class ConsoleNotifier : public IProgress{ 29 public: 30 virtual void DoProgress(float value){ 31 std::cout<<"."; 32 } 33 }; 34 35 36 /* 37 1)观察者模式定义 38 定义对象间一种一对多(这个多即是变化的)的依赖关系,一遍当一个对象的状态发生改变时,所有依赖它的对象都得到通知并自动更新。 39 2)使用观察者模式,使得我们可以独立的改变目标与观察者,从而是二者的依赖关系达到松耦合。 40 即,在MainForm类中可以随便添加观察者(添加观察者类,如ConsoleNotifier类)或者是可以在MainForm类中随便的使用addIProgress()方法向m_iprogressVector 41 中添加观察者,但是在FileSpliter类中的addIProgress()、removeIProgress()、onProgress()都是不需要改变的。 42 3)模板发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)可以自动传播 43 发送通知,即在FileSplitter类中的split()函数中调用onProgress()函数的时候,在onProgress()中观察者指针调用DoProgress()的时候,是根据多态来决定到底是 44 使用的哪一个观察者的。 45 4)写一下该程序的执行逻辑: 46 首先用户点击了按钮1--->执行MainForm类中的Button1_Click()--->添加观察者,并调用FileSplitter类中的split()---> 47 调用通知观察者的函数onProgress()--->根据基类IProgress指针指向的观察者对象,调用不同的观察者中重写的DoProgress() 48 */
下面是增加了进度条、打印点且使用多观察者模式的文件分割类的实现伪代码:
1 //支持多多个观察者 2 class IProgress{ 3 public: 4 virtual void DoProgress(float value)=0; 5 virtual ~IProgress(){} 6 }; 7 8 class FileSplitter{ 9 string m_filePath; 10 int m_fileNumber; 11 12 //ProgressBar* m_progressBar; //进度条类对象 13 vector<IProgress*> m_iprogressVector; //观察者模式的核心,是一个多态指针,IProgress是一个基类,观察者(如ConsoleNotifier)是派生类,然后让基类指针指向不同的派生类类对象 14 15 FileSplitter(const string & filePath,int fileNumber) : //这里的iprogress是一个基类指针,会形成多态调用 16 m_filePath(filePath), 17 m_fileNumber(fileNumber){ //这里的iprogress指向的是派生类MainForm的类对象,所以下面会调用派生类中实现的DoProgress() 18 19 } 20 21 void addIProgress(IProgress* iprogress){ 22 m_iprogressVector.push_back(iprogress); 23 } 24 25 void removeIProgress(IProgress* iprogress){ 26 m_iprogressVector.remove(iprogress); 27 } 28 29 void split(){ 30 //1、读取大文件 31 32 //2、分批次向小文件中写入大文件中的数据 33 for(int i=0;i<m_fileNumber;++i){ 34 //...进行文件分割 35 36 float progressValue=m_fileNumber; //即将一个整数赋值给一个浮点数 37 progressValue=(i+1)/progressValue; 38 onProgress(progressValue); //给观察者发送通知 39 } 40 } 41 42 protected: 43 void onProgress(float value){ 44 vector<IProgress*>::iterator itor=m_iprogressVector.begin(); 45 vector<IProgress*>::iterator end=m_iprogressVector.end(); 46 47 while(itor != end){ 48 itor->DoProgress(value); //更新多个观察者的值,多态调用,根据itor指向的观察者对象,调用不同的观察者中重写的DoProgress() 49 itor++; 50 } 51 } 52 };
1)观察者模式定义
定义对象间一种一对多(这个多即是变化的)的依赖关系,一遍当一个对象的状态发生改变时,所有依赖它的对象都得到通知并自动更新。
2)使用观察者模式,使得我们可以独立的改变目标与观察者,从而是二者的依赖关系达到松耦合。
即,在MainForm类中可以随便添加观察者(添加观察者类,如ConsoleNotifier类)或者是可以在MainForm类中随便的使用addIProgress()方法向m_iprogressVector中添加观察者,但是在FileSpliter类中的addIProgress()、removeIProgress()、onProgress()都是不需要改变的。
3)模板发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)可以自动传播
发送通知,即在FileSplitter类中的split()函数中调用onProgress()函数的时候,在onProgress()中观察者指针调用DoProgress()的时候,是根据多态来决定到底是使用的哪一个观察者的。
4)写一下多观察者模式程序的执行逻辑:
首先用户点击了按钮1--->执行MainForm类中的Button1_Click()--->添加观察者,并调用FileSplitter类中的split()--->调用通知观察者的函数onProgress()--->根据基类IProgress指针指向的观察者对象,调用不同的观察者中重写的DoProgress()
5)多观察者模式的结构图
5、装饰模式---组合优于继承的典型体现
1)问题的引入---解决代码冗余的问题
假如要设计一个流操作,那么可以设计一个Stream做为基类,在还类下实现流的读操作、写操作、查找操作,全部设置为纯虚函数,不需要实现。然后设计文件FileStream类流继承Stream,并重写这三个虚函数;同理对于网络流NetworkStream类、内存流MemoryStream类。然后设计CryptoFileStream类为文件流FileStream类添加加密操作,同理网络流NetworkStream类、内存流MemoryStream类,也设计加密操作,框架如下:
实现的伪代码入下:
1 //业务操作 2 class Stream{ 3 public: 4 virtual char Read(int number)=0; 5 virtual void Seek(int position)=0; 6 virtual void Write(char data)=0; 7 8 virtual ~Stream(){} 9 }; 10 11 //主体类---文件流 12 class FileStream: public Stream{ 13 public: 14 virtual char Read(int number){ 15 //读文件流 16 } 17 virtual void Seek(int position){ 18 //定位文件流 19 } 20 virtual void Write(char data){ 21 //写文件流 22 } 23 24 }; 25 //主体类---网络流 26 class NetworkStream :public Stream{ 27 public: 28 virtual char Read(int number){ 29 //读网络流 30 } 31 virtual void Seek(int position){ 32 //定位网络流 33 } 34 virtual void Write(char data){ 35 //写网络流 36 } 37 38 }; 39 //主体类---内存流 40 class MemoryStream :public Stream{ 41 public: 42 virtual char Read(int number){ 43 //读内存流 44 } 45 virtual void Seek(int position){ 46 //定位内存流 47 } 48 virtual void Write(char data){ 49 //写内存流 50 } 51 52 }; 53 54 //扩展操作---为文件流加密 55 class CryptoFileStream :public FileStream{ 56 public: 57 virtual char Read(int number){ 58 59 //额外的加密操作... 60 FileStream::Read(number);//读文件流 61 62 } 63 virtual void Seek(int position){ 64 //额外的加密操作... 65 FileStream::Seek(position);//定位文件流 66 //额外的加密操作... 67 } 68 virtual void Write(byte data){ 69 //额外的加密操作... 70 FileStream::Write(data);//写文件流 71 //额外的加密操作... 72 } 73 }; 74 //扩展操作---为网络流加密 75 class CryptoNetworkStream : public NetworkStream{ 76 public: 77 virtual char Read(int number){ 78 79 //额外的加密操作... 80 NetworkStream::Read(number);//读网络流 81 } 82 virtual void Seek(int position){ 83 //额外的加密操作... 84 NetworkStream::Seek(position);//定位网络流 85 //额外的加密操作... 86 } 87 virtual void Write(byte data){ 88 //额外的加密操作... 89 NetworkStream::Write(data);//写网络流 90 //额外的加密操作... 91 } 92 }; 93 //扩展操作---为内存流加密 94 class CryptoMemoryStream : public MemoryStream{ 95 public: 96 virtual char Read(int number){ 97 98 //额外的加密操作... 99 MemoryStream::Read(number);//读内存流 100 } 101 virtual void Seek(int position){ 102 //额外的加密操作... 103 MemoryStream::Seek(position);//定位内存流 104 //额外的加密操作... 105 } 106 virtual void Write(byte data){ 107 //额外的加密操作... 108 MemoryStream::Write(data);//写内存流 109 //额外的加密操作... 110 } 111 }; 112 113 114 void Process(){ 115 116 //编译时装配 117 CryptoFileStream *fs1 = new CryptoFileStream(); 118 119 BufferedFileStream *fs2 = new BufferedFileStream(); 120 121 CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream(); 122 123 } 124 125 /* 126 上面的代码存在的问题: 127 1)不管是在CryptoFileStream类中,还是在CryptoNetworkStream类中等,所有的"额外的加密操作"对应的代码都是一样的,所以上面的代码存在着大量的代码冗余; 128 2)不管是在CryptoFileStream类中,还是在CryptoNetworkStream类中等,所有的"额外的加缓冲操作"对应的代码都是一样的,也存在代码冗余 129 */ 130 131 132 /* 133 装饰模式要解决的问题: 134 在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。 135 136 */
上面的代码存在的问题:
1)不管是在CryptoFileStream类中,还是在CryptoNetworkStream类中等,所有的"额外的加密操作"对应的代码都是一样的,所以上面的代码存在着大量的代码冗余;
2)不管是在CryptoFileStream类中,还是在CryptoNetworkStream类中等,所有的"额外的加缓冲操作"对应的代码都是一样的,也存在代码冗余
解决方法见下面的初步解决方法
2)初步的解决方法
步骤一:用类之间的组合替代继承,即现在CryptoFileStream类不继承FileStream类了,而是在CryptoFileStream类下创建FileStream类对象,然后让FileStream类对象去调用FileStream类中的方法;同理对于CryptoNetworkStream类和CryptoMemoryStream类,实现如下:
1 //业务操作 2 class Stream{ 3 public: 4 virtual char Read(int number)=0; 5 virtual void Seek(int position)=0; 6 virtual void Write(char data)=0; 7 8 virtual ~Stream(){} 9 }; 10 11 //主体类---文件流 12 class FileStream: public Stream{ 13 public: 14 virtual char Read(int number){ 15 //读文件流 16 } 17 virtual void Seek(int position){ 18 //定位文件流 19 } 20 virtual void Write(char data){ 21 //写文件流 22 } 23 24 }; 25 //主体类---网络流 26 class NetworkStream :public Stream{ 27 public: 28 virtual char Read(int number){ 29 //读网络流 30 } 31 virtual void Seek(int position){ 32 //定位网络流 33 } 34 virtual void Write(char data){ 35 //写网络流 36 } 37 38 }; 39 //主体类---内存流 40 class MemoryStream :public Stream{ 41 public: 42 virtual char Read(int number){ 43 //读内存流 44 } 45 virtual void Seek(int position){ 46 //定位内存流 47 } 48 virtual void Write(char data){ 49 //写内存流 50 } 51 52 }; 53 54 //扩展操作---为文件流加密 55 class CryptoFileStream{ 56 FileStream* stream; //改01:现在CryptoFileStream不继承FileStream了,而是让在CryptoFileStream类下创建一个FileStream类对象指针 57 public: 58 virtual char Read(int number){ 59 60 //额外的加密操作... 61 //FileStream::Read(number);//读文件流 62 stream->Read(number); //改02:使用stream去调用 FileStream类下的Read()方法 63 64 } 65 virtual void Seek(int position){ 66 //额外的加密操作... 67 //FileStream::Seek(position);//定位文件流 68 sream->Seek(position); //改03:使用stream去调用 FileStream类下的Seek()方法 69 //额外的加密操作... 70 } 71 virtual void Write(byte data){ 72 //额外的加密操作... 73 //FileStream::Write(data);//写文件流 74 sream->Write(data); //改04:使用stream去调用 FileStream类下的Write()方法 75 //额外的加密操作... 76 } 77 }; 78 //扩展操作---为网络流加密 79 class CryptoNetworkStream{ 80 NetworkStream* stream; 81 public: 82 virtual char Read(int number){ 83 84 //额外的加密操作... 85 //NetworkStream::Read(number);//读网络流 86 stream->Read(number); 87 } 88 virtual void Seek(int position){ 89 //额外的加密操作... 90 //NetworkStream::Seek(position);//定位网络流 91 stream->Seek(position); 92 //额外的加密操作... 93 } 94 virtual void Write(byte data){ 95 //额外的加密操作... 96 //NetworkStream::Write(data);//写网络流 97 stream->Write(data); 98 //额外的加密操作... 99 } 100 }; 101 //扩展操作---为内存流加密 102 class CryptoMemoryStream{ 103 MemoryStream* stream; 104 public: 105 virtual char Read(int number){ 106 107 //额外的加密操作... 108 //MemoryStream::Read(number);//读内存流 109 stream->Read(number); 110 } 111 virtual void Seek(int position){ 112 //额外的加密操作... 113 //MemoryStream::Seek(position);//定位内存流 114 stream->Seek(position); 115 //额外的加密操作... 116 } 117 virtual void Write(byte data){ 118 //额外的加密操作... 119 //MemoryStream::Write(data);//写内存流 120 stream->Write(data); 121 //额外的加密操作... 122 } 123 }; 124 125 126 void Process(){ 127 128 //编译时装配 129 CryptoFileStream *fs1 = new CryptoFileStream(); 130 131 BufferedFileStream *fs2 = new BufferedFileStream(); 132 133 CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream(); 134 135 }
步骤二:我们可以发现在CryptoFileStream类中使用FileStream* stream;的方法新建了一个FileStream类对象指针(第56行)、在CryptoNetworkStream类中使用NetworkStream* stream;的方法新建了一个NetworkStream类对象指针(第80行)、在CryptoMemoryStream类中使用MemoryStream* stream;的方法新建了一个MemoryStream类对象指针(第103行),把他们写一块,即:
1 class CryptoFileStream{ 2 FileStream* stream; 3 public: 4 ... 5 }; 6 7 class CryptoNetworkStream{ 8 NetworkStream* stream; 9 public: 10 ... 11 }; 12 13 class CryptoMemoryStream{ 14 MemoryStream* stream; 15 public: 16 ... 17 };
那不就可以使用FileStream、NetworkStream、MemoryStream这三个类的基类去创建stream指针,在赋值的时候将new FileStream()赋给stream、或者将new NetworkStream()赋给stream、new MemoryStream()赋给stream,而且还实现了运行时编译,即把编译时期的东西变成了运行时编译,于是可以做下面的更改,即将上面的代码替换为下面的形式:
1 class CryptoFileStream{ 2 Stream* stream; //使用的时候把new FileStream()赋给stream即可 3 public: 4 ... 5 }; 6 7 class CryptoNetworkStream{ 8 Stream* stream; //使用的时候把new NetworkStream()赋值给stream即可 9 public: 10 ... 11 }; 12 13 class CryptoMemoryStream{ 14 Stream* stream; //使用的时候把new MemoryStream()赋值给stream即可 15 public: 16 ... 17 };
完整的代码如下:
1 //业务操作 2 class Stream{ 3 public: 4 virtual char Read(int number)=0; 5 virtual void Seek(int position)=0; 6 virtual void Write(char data)=0; 7 8 virtual ~Stream(){} 9 }; 10 11 //主体类---文件流 12 class FileStream: public Stream{ 13 public: 14 virtual char Read(int number){ 15 //读文件流 16 } 17 virtual void Seek(int position){ 18 //定位文件流 19 } 20 virtual void Write(char data){ 21 //写文件流 22 } 23 24 }; 25 //主体类---网络流 26 class NetworkStream :public Stream{ 27 public: 28 virtual char Read(int number){ 29 //读网络流 30 } 31 virtual void Seek(int position){ 32 //定位网络流 33 } 34 virtual void Write(char data){ 35 //写网络流 36 } 37 38 }; 39 //主体类---内存流 40 class MemoryStream :public Stream{ 41 public: 42 virtual char Read(int number){ 43 //读内存流 44 } 45 virtual void Seek(int position){ 46 //定位内存流 47 } 48 virtual void Write(char data){ 49 //写内存流 50 } 51 52 }; 53 54 //扩展操作---为文件流加密 55 class CryptoFileStream{ 56 Stream* stream; //在使用的时候将new FileStream()赋值给stream 57 public: 58 virtual char Read(int number){ 59 60 //额外的加密操作... 61 //FileStream::Read(number);//读文件流 62 stream->Read(number); //改02:使用stream去调用 FileStream类下的Read()方法 63 64 } 65 virtual void Seek(int position){ 66 //额外的加密操作... 67 //FileStream::Seek(position);//定位文件流 68 sream->Seek(position); //改03:使用stream去调用 FileStream类下的Seek()方法 69 //额外的加密操作... 70 } 71 virtual void Write(byte data){ 72 //额外的加密操作... 73 //FileStream::Write(data);//写文件流 74 sream->Write(data); //改04:使用stream去调用 FileStream类下的Write()方法 75 //额外的加密操作... 76 } 77 }; 78 //扩展操作---为网络流加密 79 class CryptoNetworkStream{ 80 Stream* stream; //在使用的时候将new NetworkStream()赋值给stream 81 public: 82 virtual char Read(int number){ 83 84 //额外的加密操作... 85 //NetworkStream::Read(number);//读网络流 86 stream->Read(number); 87 } 88 virtual void Seek(int position){ 89 //额外的加密操作... 90 //NetworkStream::Seek(position);//定位网络流 91 stream->Seek(position); 92 //额外的加密操作... 93 } 94 virtual void Write(byte data){ 95 //额外的加密操作... 96 //NetworkStream::Write(data);//写网络流 97 stream->Write(data); 98 //额外的加密操作... 99 } 100 }; 101 //扩展操作---为内存流加密 102 class CryptoMemoryStream{ 103 Stream* stream; //在使用的时候将new MemoryStream()赋值给stream 104 public: 105 virtual char Read(int number){ 106 107 //额外的加密操作... 108 //MemoryStream::Read(number);//读内存流 109 stream->Read(number); 110 } 111 virtual void Seek(int position){ 112 //额外的加密操作... 113 //MemoryStream::Seek(position);//定位内存流 114 stream->Seek(position); 115 //额外的加密操作... 116 } 117 virtual void Write(byte data){ 118 //额外的加密操作... 119 //MemoryStream::Write(data);//写内存流 120 stream->Write(data); 121 //额外的加密操作... 122 } 123 }; 124 125 126 void Process(){ 127 128 //编译时装配 129 CryptoFileStream *fs1 = new CryptoFileStream(); 130 131 BufferedFileStream *fs2 = new BufferedFileStream(); 132 133 CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream(); 134 135 }
步骤三:此时发现CryptoFileStream类、CryptoNetworkStream类和CryptoMemoryStream类,这三个类中的内容是一样的,不一样的地方在于,运行时期给stream赋的值不一样,还有类名字不一样。即编译时一样(共用代码)、运行时不一样(用多态支持变化),此时发现这三个类完全可以用一个类来实现,即将CryptoFileStream类、CryptoNetworkStream类和CryptoMemoryStream类使用一个CryptoStream来实现
1 //业务操作 2 class Stream{ 3 public: 4 virtual char Read(int number)=0; 5 virtual void Seek(int position)=0; 6 virtual void Write(char data)=0; 7 8 virtual ~Stream(){} 9 }; 10 11 //主体类---文件流 12 class FileStream: public Stream{ 13 public: 14 virtual char Read(int number){ 15 //读文件流 16 } 17 virtual void Seek(int position){ 18 //定位文件流 19 } 20 virtual void Write(char data){ 21 //写文件流 22 } 23 24 }; 25 //主体类---网络流 26 class NetworkStream :public Stream{ 27 public: 28 virtual char Read(int number){ 29 //读网络流 30 } 31 virtual void Seek(int position){ 32 //定位网络流 33 } 34 virtual void Write(char data){ 35 //写网络流 36 } 37 38 }; 39 //主体类---内存流 40 class MemoryStream :public Stream{ 41 public: 42 virtual char Read(int number){ 43 //读内存流 44 } 45 virtual void Seek(int position){ 46 //定位内存流 47 } 48 virtual void Write(char data){ 49 //写内存流 50 } 51 52 }; 53 54 //扩展操作---为文件流加密 55 class CryptoStream{ 56 Stream* stream; //在使用的时候将new FileStream()或new NetworkStream()或new MemorykStream()赋值给stream 57 public: 58 virtual char Read(int number){ 59 60 //额外的加密操作... 61 //FileStream::Read(number);//读文件流 62 stream->Read(number); //改02:使用stream去调用 FileStream类下的Read()方法 63 64 } 65 virtual void Seek(int position){ 66 //额外的加密操作... 67 //FileStream::Seek(position);//定位文件流 68 sream->Seek(position); //改03:使用stream去调用 FileStream类下的Seek()方法 69 //额外的加密操作... 70 } 71 virtual void Write(byte data){ 72 //额外的加密操作... 73 //FileStream::Write(data);//写文件流 74 sream->Write(data); //改04:使用stream去调用 FileStream类下的Write()方法 75 //额外的加密操作... 76 } 77 }; 78 79 80 void Process(){ 81 82 //编译时装配 83 CryptoFileStream *fs1 = new CryptoFileStream(); 84 85 BufferedFileStream *fs2 = new BufferedFileStream(); 86 87 CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream(); 88 89 }
步骤四:但是此时CryptoStream类中的Read()、Seek()、Write()都是虚函数,那虚函数从何而来,是从Stream中继承而来的,并且只有继承了Stream类,才可以实现多态,所以CryptoStream类还要继承Stream类:
1 //业务操作 2 class Stream{ 3 public: 4 virtual char Read(int number)=0; 5 virtual void Seek(int position)=0; 6 virtual void Write(char data)=0; 7 8 virtual ~Stream(){} 9 }; 10 11 //主体类---文件流 12 class FileStream: public Stream{ 13 public: 14 virtual char Read(int number){ 15 //读文件流 16 } 17 virtual void Seek(int position){ 18 //定位文件流 19 } 20 virtual void Write(char data){ 21 //写文件流 22 } 23 24 }; 25 //主体类---网络流 26 class NetworkStream :public Stream{ 27 public: 28 virtual char Read(int number){ 29 //读网络流 30 } 31 virtual void Seek(int position){ 32 //定位网络流 33 } 34 virtual void Write(char data){ 35 //写网络流 36 } 37 38 }; 39 //主体类---内存流 40 class MemoryStream :public Stream{ 41 public: 42 virtual char Read(int number){ 43 //读内存流 44 } 45 virtual void Seek(int position){ 46 //定位内存流 47 } 48 virtual void Write(char data){ 49 //写内存流 50 } 51 52 }; 53 54 //扩展操作---为文件流、网络流、内存流加密 55 class CryptoStream : public Stream{ 56 Stream* stream; //在使用的时候将new FileStream()或new NetworkStream()或new MemorykStream()赋值给stream 57 public: 58 virtual char Read(int number){ 59 60 //额外的加密操作... 61 //FileStream::Read(number);//读文件流 62 stream->Read(number); //改02:使用stream去调用 FileStream类下的Read()方法 63 64 } 65 virtual void Seek(int position){ 66 //额外的加密操作... 67 //FileStream::Seek(position);//定位文件流 68 sream->Seek(position); //改03:使用stream去调用 FileStream类下的Seek()方法 69 //额外的加密操作... 70 } 71 virtual void Write(byte data){ 72 //额外的加密操作... 73 //FileStream::Write(data);//写文件流 74 sream->Write(data); //改04:使用stream去调用 FileStream类下的Write()方法 75 //额外的加密操作... 76 } 77 }; 78 79 80 void Process(){ 81 82 //编译时装配 83 CryptoFileStream *fs1 = new CryptoFileStream(); 84 85 86 CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream(); 87 88 }
步骤五:书写使用函数Process(),由于在Process()中要为CryptoStream类中的stream赋值,所以要在CryptoStream类中写一个CryptoStream(Stream* stm);函数,该函数为外界给stream赋值提供接口,如在87行使用s1为stream赋值
1 //业务操作 2 class Stream{ 3 public: 4 virtual char Read(int number)=0; 5 virtual void Seek(int position)=0; 6 virtual void Write(char data)=0; 7 8 virtual ~Stream(){} 9 }; 10 11 //主体类---文件流 12 class FileStream: public Stream{ 13 public: 14 virtual char Read(int number){ 15 //读文件流 16 } 17 virtual void Seek(int position){ 18 //定位文件流 19 } 20 virtual void Write(char data){ 21 //写文件流 22 } 23 24 }; 25 //主体类---网络流 26 class NetworkStream :public Stream{ 27 public: 28 virtual char Read(int number){ 29 //读网络流 30 } 31 virtual void Seek(int position){ 32 //定位网络流 33 } 34 virtual void Write(char data){ 35 //写网络流 36 } 37 38 }; 39 //主体类---内存流 40 class MemoryStream :public Stream{ 41 public: 42 virtual char Read(int number){ 43 //读内存流 44 } 45 virtual void Seek(int position){ 46 //定位内存流 47 } 48 virtual void Write(char data){ 49 //写内存流 50 } 51 52 }; 53 54 //扩展操作---为文件流、网络流、内存流加密 55 class CryptoStream : public Stream{ 56 Stream* stream; //在使用的时候将new FileStream()或new NetworkStream()或new MemorykStream()赋值给stream 57 public: 58 59 CryptoStream(Stream* stm) : stream(stm){} //为外界给stream赋值提供接口,如在87行使用s1为stream赋值 60 61 virtual char Read(int number){ 62 63 //额外的加密操作... 64 //FileStream::Read(number);//读文件流 65 stream->Read(number); //改02:使用stream去调用 FileStream类下的Read()方法 66 67 } 68 virtual void Seek(int position){ 69 //额外的加密操作... 70 //FileStream::Seek(position);//定位文件流 71 sream->Seek(position); //改03:使用stream去调用 FileStream类下的Seek()方法 72 //额外的加密操作... 73 } 74 virtual void Write(byte data){ 75 //额外的加密操作... 76 //FileStream::Write(data);//写文件流 77 sream->Write(data); //改04:使用stream去调用 FileStream类下的Write()方法 78 //额外的加密操作... 79 } 80 }; 81 82 83 void Process(){ 84 85 //运行时编译 86 FileStream* s1 = new FileStream(); //创建一个文件流对象指针 87 CryptoStream* s2 = new CryptoStream(s1); //为s1文件流加密,此时就相当于对CryptoStream类中的stream赋值为new FileStream(),因此在CryptoStream类中需要一个CryptoStream(Stream* stm)函数 88 }
步骤六:再写一个为文件流、网络流、内存流加缓存的操作,与是可以模仿上面CryptoStream类的书写方法,在写一个BufferedStream类,内容和CryptoStream类类似,要重写Read()、Seek()、Write()方法,只不过需要在这些方法中加入加缓存的代码
1 //业务操作 2 class Stream{ 3 public: 4 virtual char Read(int number)=0; 5 virtual void Seek(int position)=0; 6 virtual void Write(char data)=0; 7 8 virtual ~Stream(){} 9 }; 10 11 //主体类---文件流 12 class FileStream: public Stream{ 13 public: 14 virtual char Read(int number){ 15 //读文件流 16 } 17 virtual void Seek(int position){ 18 //定位文件流 19 } 20 virtual void Write(char data){ 21 //写文件流 22 } 23 24 }; 25 //主体类---网络流 26 class NetworkStream :public Stream{ 27 public: 28 virtual char Read(int number){ 29 //读网络流 30 } 31 virtual void Seek(int position){ 32 //定位网络流 33 } 34 virtual void Write(char data){ 35 //写网络流 36 } 37 38 }; 39 //主体类---内存流 40 class MemoryStream :public Stream{ 41 public: 42 virtual char Read(int number){ 43 //读内存流 44 } 45 virtual void Seek(int position){ 46 //定位内存流 47 } 48 virtual void Write(char data){ 49 //写内存流 50 } 51 52 }; 53 54 //扩展操作---为文件流、网络流、内存流加密 55 class CryptoStream : public Stream{ 56 Stream* stream; //在使用的时候将new FileStream()或new NetworkStream()或new MemorykStream()赋值给stream 57 public: 58 59 CryptoStream(Stream* stm) : stream(stm){} //为外界给stream赋值提供接口,如在87行使用s1为stream赋值 60 61 virtual char Read(int number){ 62 63 //额外的加密操作... 64 stream->Read(number); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下Read()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 65 66 } 67 virtual void Seek(int position){ 68 //额外的加密操作... 69 sream->Seek(position); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下的Seek()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 70 //额外的加密操作... 71 } 72 virtual void Write(byte data){ 73 //额外的加密操作... 74 sream->Write(data); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下的Write()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 75 //额外的加密操作... 76 } 77 }; 78 79 //扩展操作---为文件流、网络流、内存流加缓存 80 class BufferedStream : public{ 81 Stream* stream; //在使用的时候将new FileStream()或new NetworkStream()或new MemorykStream()赋值给stream 82 public: 83 BufferedStream(Stream* stm) : stream(stm){} //为外界给stream赋值提供接口,如在87行使用s1为stream赋值 84 85 virtual char Read(int number){ 86 87 //额外的加缓存操作... 88 stream->Read(number); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下的Read()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 89 90 } 91 virtual void Seek(int position){ 92 //额外的加缓存操作... 93 //FileStream::Seek(position);//定位文件流 94 sream->Seek(position); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下的Seek()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 95 //额外的加缓存操作... 96 } 97 virtual void Write(byte data){ 98 //额外的加缓存操作... 99 //FileStream::Write(data);//写文件流 100 sream->Write(data); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下的Write()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 101 //额外的加缓存操作... 102 } 103 }; 104 105 106 void Process(){ 107 108 //运行时编译 109 FileStream* s1 = new FileStream(); //创建一个文件流对象指针 110 CryptoStream* s2 = new CryptoStream(s1); //为s1文件流加密,此时就相当于对CryptoStream类中的stream赋值为new FileStream(),因此在CryptoStream类中需要一个CryptoStream(Stream* stm)函数 111 BufferedStream* s3 = new BufferedStream(s1); //为s1文件流加缓存,此时调用BufferedStream(Stream* stm)函数将s1复制给BufferedStream类中的stream 112 113 BufferedStream* s4 = new BufferedStream(s2); //对s1文件既加密有加缓存,BufferedStream类中的stream的类型是Stream,而Stream是一个基类;s2是CryptoStream类型的,且CryptoStream继承了Stream,所以这样调用时合法的 114 }
此时我们可以发现在CryptoStream类中和BufferedStream类中都有stream这个基类的类对象指针,此时我们可以创建一个中间类DecoratorStream类,该类继承了Stream类,并让ryptoStream类和BufferedStream类继承DecoratorStream类,具体实现见下面。
3)使用装饰模式
添加中间类DecoratorStream类的版本
1 //业务操作 2 class Stream{ 3 public: 4 virtual char Read(int number)=0; 5 virtual void Seek(int position)=0; 6 virtual void Write(char data)=0; 7 8 virtual ~Stream(){} 9 }; 10 11 //主体类---文件流 12 class FileStream: public Stream{ 13 public: 14 virtual char Read(int number){ 15 //读文件流 16 } 17 virtual void Seek(int position){ 18 //定位文件流 19 } 20 virtual void Write(char data){ 21 //写文件流 22 } 23 24 }; 25 //主体类---网络流 26 class NetworkStream :public Stream{ 27 public: 28 virtual char Read(int number){ 29 //读网络流 30 } 31 virtual void Seek(int position){ 32 //定位网络流 33 } 34 virtual void Write(char data){ 35 //写网络流 36 } 37 38 }; 39 //主体类---内存流 40 class MemoryStream :public Stream{ 41 public: 42 virtual char Read(int number){ 43 //读内存流 44 } 45 virtual void Seek(int position){ 46 //定位内存流 47 } 48 virtual void Write(char data){ 49 //写内存流 50 } 51 52 }; 53 54 class DecoratorStream : public Stream{ 55 protected: 56 Stream* stream; //在使用的时候将new FileStream()或new NetworkStream()或new MemorykStream()赋值给stream 57 DecoratorStream(Stream* stm) : stream(stm){} 58 }; 59 60 //扩展操作---为文件流、网络流、内存流加密 61 class CryptoStream : public DecoratorStream{ 62 63 public: 64 65 CryptoStream(Stream* stm) : stream(stm){} //为外界给stream赋值提供接口,如在87行使用s1为stream赋值 66 67 virtual char Read(int number){ 68 69 //额外的加密操作... 70 stream->Read(number); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下Read()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 71 72 } 73 virtual void Seek(int position){ 74 //额外的加密操作... 75 sream->Seek(position); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下的Seek()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 76 //额外的加密操作... 77 } 78 virtual void Write(byte data){ 79 //额外的加密操作... 80 sream->Write(data); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下的Write()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 81 //额外的加密操作... 82 } 83 }; 84 85 //扩展操作---为文件流、网络流、内存流加缓存 86 class BufferedStream : public DecoratorStream{ 87 88 public: 89 BufferedStream(Stream* stm) : stream(stm){} //为外界给stream赋值提供接口,如在87行使用s1为stream赋值 90 91 virtual char Read(int number){ 92 93 //额外的加缓存操作... 94 stream->Read(number); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下的Read()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 95 96 } 97 virtual void Seek(int position){ 98 //额外的加缓存操作... 99 //FileStream::Seek(position);//定位文件流 100 sream->Seek(position); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下的Seek()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 101 //额外的加缓存操作... 102 } 103 virtual void Write(byte data){ 104 //额外的加缓存操作... 105 //FileStream::Write(data);//写文件流 106 sream->Write(data); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下的Write()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 107 //额外的加缓存操作... 108 } 109 }; 110 111 112 void Process(){ 113 114 //运行时编译 115 FileStream* s1 = new FileStream(); //创建一个文件流对象指针 116 CryptoStream* s2 = new CryptoStream(s1); //为s1文件流加密,此时就相当于对CryptoStream类中的stream赋值为new FileStream(),因此在CryptoStream类中需要一个CryptoStream(Stream* stm)函数 117 BufferedStream* s3 = new BufferedStream(s1); //为s1文件流加缓存,此时调用BufferedStream(Stream* stm)函数将s1复制给BufferedStream类中的stream 118 119 BufferedStream* s4 = new BufferedStream(s2); //对s1文件既加密有加缓存,BufferedStream类中的stream的类型是Stream,而Stream是一个基类;s2是CryptoStream类型的,且CryptoStream继承了Stream,所以这样调用时合法的 120 }
上面代码的继承关系:
总结:
1)当代码中出现即继承又组合的方式的时候,99%可能就是装饰模式了
1 class DecoratorStream : public Stream{ //继承 2 protected: 3 Stream* stream; //组合,DecoratorStream类和Stream类组合 4 //... 5 };
2)实际上CryptoFileStream和FileStream并不是继承关系,而是扩展关系,所以第一个版本中使用继承时不对的
3)在某些情况下,我们可能会“过度的实验继承来扩展对象的功能”,由于机会才能为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
解释:
(a)静态特质:如第一个版本中的下面的代码:
1 //扩展操作---为文件流加密 2 class CryptoFileStream :public FileStream{ 3 public: 4 virtual char Read(int number){ 5 6 //额外的加密操作... 7 FileStream::Read(number);//读文件流---静态特质,定死的,永远调用的是FileStream中的Read() 8 9 } 10 virtual void Seek(int position){ 11 //额外的加密操作... 12 FileStream::Seek(position);//定位文件流---静态特质 13 //额外的加密操作... 14 } 15 virtual void Write(byte data){ 16 //额外的加密操作... 17 FileStream::Write(data);//写文件流---静态特质 18 //额外的加密操作... 19 } 20 };
解决方法:
1 class CryptoStream : public DecoratorStream{ 2 Stream* stream; //在使用的时候将new FileStream()或new NetworkStream()或new MemorykStream()赋值给stream 3 public: 4 5 CryptoStream(Stream* stm) : stream(stm){} //为外界给stream赋值提供接口,如在87行使用s1为stream赋值 6 7 virtual char Read(int number){ 8 9 //额外的加密操作... 10 stream->Read(number); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下Read()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 11 12 } 13 virtual void Seek(int position){ 14 //额外的加密操作... 15 sream->Seek(position); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下的Seek()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 16 //额外的加密操作... 17 } 18 virtual void Write(byte data){ 19 //额外的加密操作... 20 sream->Write(data); //使用stream去调用FileStream类或NetworkStream或MemorykStream类下的Write()方法,具体调用哪个类下的方法要看stream指向的是哪个类的类对象了 21 //额外的加密操作... 22 } 23 };
6、桥模式
01)桥模式和装饰模式是类似的,所以这里就直接写问题的解决步骤了
加入要实现一个平台Messager,该平台下支持手机平台和PC平台,且Messager中的部分函数在PC和手机下函数的写法是不一样的,而且PC平台下面又分了基本业务和完美版业务。
桥模式和装饰模式的不同之处在于Mesaager中即有手机平台和PC平台需要覆盖的纯虚函数,又有不需要二者覆盖的纯虚函数
实现代码如下:
1 class Messager{ 2 public: 3 virtual void Login(string username, string password)=0; 4 virtual void SendMessage(string message)=0; 5 virtual void SendPicture(Image image)=0; 6 7 //下面的这些函数在不同的平台下面实现方式是不一样的 8 virtual void PlaySound()=0; 9 virtual void DrawShape()=0; 10 virtual void WriteText()=0; 11 virtual void Connect()=0; 12 13 virtual ~Messager(){} 14 }; 15 16 17 //平台实现 18 class PCMessagerBase : public Messager{ 19 public: 20 21 virtual void PlaySound(){ 22 //********** 23 } 24 virtual void DrawShape(){ 25 //********** 26 } 27 virtual void WriteText(){ 28 //********** 29 } 30 virtual void Connect(){ 31 //********** 32 } 33 }; 34 35 class MobileMessagerBase : public Messager{ 36 public: 37 38 virtual void PlaySound(){ 39 //========== 40 } 41 virtual void DrawShape(){ 42 //========== 43 } 44 virtual void WriteText(){ 45 //========== 46 } 47 virtual void Connect(){ 48 //========== 49 } 50 }; 51 52 53 54 //PC业务抽象---基本版 55 class PCMessagerLite : public PCMessagerBase { 56 public: 57 58 virtual void Login(string username, string password){ 59 60 PCMessagerBase::Connect(); //连网 61 //........ 62 } 63 virtual void SendMessage(string message){ 64 65 PCMessagerBase::WriteText(); 66 //........ 67 } 68 virtual void SendPicture(Image image){ 69 70 PCMessagerBase::DrawShape(); 71 //........ 72 } 73 }; 74 75 76 //PC业务抽象---完美版 77 class PCMessagerPerfect : public PCMessagerBase { 78 public: 79 80 virtual void Login(string username, string password){ 81 82 PCMessagerBase::PlaySound(); //连网的时候播放声音 83 //******** 84 PCMessagerBase::Connect(); 85 //........ 86 } 87 virtual void SendMessage(string message){ 88 89 PCMessagerBase::PlaySound(); //发送文字的时候播放声音 90 //******** 91 PCMessagerBase::WriteText(); 92 //........ 93 } 94 virtual void SendPicture(Image image){ 95 96 PCMessagerBase::PlaySound(); //绘制图形的时候播放声音 97 //******** 98 PCMessagerBase::DrawShape(); 99 //........ 100 } 101 }; 102 103 //手机业务抽象---基本版 104 class MobileMessagerLite : public MobileMessagerBase { 105 public: 106 107 virtual void Login(string username, string password){ 108 109 MobileMessagerBase::Connect(); 110 //........ 111 } 112 virtual void SendMessage(string message){ 113 114 MobileMessagerBase::WriteText(); 115 //........ 116 } 117 virtual void SendPicture(Image image){ 118 119 MobileMessagerBase::DrawShape(); 120 //........ 121 } 122 }; 123 124 //手机业务抽象---完美版 125 class MobileMessagerPerfect : public MobileMessagerBase { 126 public: 127 128 virtual void Login(string username, string password){ 129 130 MobileMessagerBase::PlaySound(); 131 //******** 132 MobileMessagerBase::Connect(); 133 //........ 134 } 135 virtual void SendMessage(string message){ 136 137 MobileMessagerBase::PlaySound(); 138 //******** 139 MobileMessagerBase::WriteText(); 140 //........ 141 } 142 virtual void SendPicture(Image image){ 143 144 MobileMessagerBase::PlaySound(); 145 //******** 146 MobileMessagerBase::DrawShape(); 147 //........ 148 } 149 }; 150 151 152 void Process(){ 153 //编译时装配 154 Messager *m = 155 new MobileMessagerPerfect(); 156 }
继承框架如下:
问题一及解决方法:
我们把PCMessagerLite和MobileMessagerLite这两个类单独拿出来,发现PCMessagerLite和MobileMessagerLite中调用的函数都是一样的,不一样的地方在于是使用PCMessagerBase中的函数还是使用MobileMessagerBase中的函数。且此时PCMessagerLite继承了PCMessagerBase,那么完全可以使用装饰模式,在PCMessagerLite类下创建一个PCMessagerBase类对象,然后使用该类对象对调用这个类下的函数
1 //PC业务抽象---基本版 2 class PCMessagerLite : public PCMessagerBase { 3 public: 4 5 virtual void Login(string username, string password){ 6 7 PCMessagerBase::Connect(); //连网 8 //........ 9 } 10 virtual void SendMessage(string message){ 11 12 PCMessagerBase::WriteText(); 13 //........ 14 } 15 virtual void SendPicture(Image image){ 16 17 PCMessagerBase::DrawShape(); 18 //........ 19 } 20 }; 21 22 //手机业务抽象---基本版 23 class MobileMessagerLite : public MobileMessagerBase { 24 public: 25 26 virtual void Login(string username, string password){ 27 28 MobileMessagerBase::Connect(); 29 //........ 30 } 31 virtual void SendMessage(string message){ 32 33 MobileMessagerBase::WriteText(); 34 //........ 35 } 36 virtual void SendPicture(Image image){ 37 38 MobileMessagerBase::DrawShape(); 39 //........ 40 } 41 };
使用装饰模式,在PCMessagerLite类下创建一个PCMessagerBase类对象,然后使用该类对象对调用这个类下的函数
使用装饰模式,在MobileMessagerLite类下创建一个MobileMessagerBase类对象,然后使用该类对象对调用这个类下的函数
1 //PC业务抽象---基本版 2 class PCMessagerLite { 3 PCMessagerBase* messager; 4 public: 5 6 virtual void Login(string username, string password){ 7 8 messager->Connect(); //连网 9 //........ 10 } 11 virtual void SendMessage(string message){ 12 13 messager->WriteText(); 14 //........ 15 } 16 virtual void SendPicture(Image image){ 17 18 messager->DrawShape(); 19 //........ 20 } 21 }; 22 23 //手机业务抽象---基本版 24 class MobileMessagerLite { 25 MobileMessagerBase* messager; 26 public: 27 28 virtual void Login(string username, string password){ 29 30 messager->Connect(); 31 //........ 32 } 33 virtual void SendMessage(string message){ 34 35 messager->WriteText(); 36 //........ 37 } 38 virtual void SendPicture(Image image){ 39 40 messager->DrawShape(); 41 //........ 42 } 43 };
问题二及解决方法:
此时就可以使用基类Mesaager创建messager指针,并将new PCMessagerLite()或者是new MobileMessagerLite()赋值给messager。且必须继承Messager,才可以实现多态,如下的代码:
1 //PC业务抽象---基本版 2 class PCMessagerLite : public Messager { 3 Messager* messager; // = new PCMessagerBase() 4 public: 5 6 virtual void Login(string username, string password){ 7 8 messager->Connect(); //连网 9 //........ 10 } 11 virtual void SendMessage(string message){ 12 13 messager->WriteText(); 14 //........ 15 } 16 virtual void SendPicture(Image image){ 17 18 messager->DrawShape(); 19 //........ 20 } 21 }; 22 23 //手机业务抽象---基本版 24 class MobileMessagerLite : public Messager { 25 Messager* messager; // = new MobileMessagerBase() 26 public: 27 28 virtual void Login(string username, string password){ 29 30 messager->Connect(); 31 //........ 32 } 33 virtual void SendMessage(string message){ 34 35 messager->WriteText(); 36 //........ 37 } 38 virtual void SendPicture(Image image){ 39 40 messager->DrawShape(); 41 //........ 42 } 43 };
此时两个类一样,合并为一个:
1 class MessagerLite : public Messager { 2 Messager* messager; //赋值为new PCMessagerBase()或new MobileMessagerBase()就可以调用PCMessagerBase类和MobileMessagerBase类中不同的方法 3 public: 4 5 virtual void Login(string username, string password){ 6 7 messager->Connect(); 8 //........ 9 } 10 virtual void SendMessage(string message){ 11 12 messager->WriteText(); 13 //........ 14 } 15 virtual void SendPicture(Image image){ 16 17 messager->DrawShape(); 18 //........ 19 } 20 };
同理,对于PCMessagerPerfect类和MobileMessagerPerfect类:
1 class PCMessagerPerfect : public PCMessagerBase { 2 public: 3 4 virtual void Login(string username, string password){ 5 6 PCMessagerBase::PlaySound(); 7 //******** 8 PCMessagerBase::Connect(); 9 //........ 10 } 11 virtual void SendMessage(string message){ 12 13 PCMessagerBase::PlaySound(); 14 //******** 15 PCMessagerBase::WriteText(); 16 //........ 17 } 18 virtual void SendPicture(Image image){ 19 20 PCMessagerBase::PlaySound(); 21 //******** 22 PCMessagerBase::DrawShape(); 23 //........ 24 } 25 }; 26 27 class MobileMessagerPerfect : public MobileMessagerBase { 28 public: 29 30 virtual void Login(string username, string password){ 31 32 MobileMessagerBase::PlaySound(); 33 //******** 34 MobileMessagerBase::Connect(); 35 //........ 36 } 37 virtual void SendMessage(string message){ 38 39 MobileMessagerBase::PlaySound(); 40 //******** 41 MobileMessagerBase::WriteText(); 42 //........ 43 } 44 virtual void SendPicture(Image image){ 45 46 MobileMessagerBase::PlaySound(); 47 //******** 48 MobileMessagerBase::DrawShape(); 49 //........ 50 } 51 };
可以修改如下:
1 //完美版 2 class MessagerPerfect : public Messager { 3 Messager* messager; //赋值为new PCMessagerBase()或new MobileMessagerBase()就可以调用PCMessagerBase和MobileMessagerBase类中不同的方法 4 public: 5 6 virtual void Login(string username, string password){ 7 8 messager->PlaySound(); //连网的时候播放声音 9 //******** 10 messager->Connect(); 11 //........ 12 } 13 virtual void SendMessage(string message){ 14 15 messager->PlaySound(); //发送文字的时候播放声音 16 //******** 17 messager->WriteText(); 18 //........ 19 } 20 virtual void SendPicture(Image image){ 21 22 messager->PlaySound(); //绘制图形的时候播放声音 23 //******** 24 messager->DrawShape(); 25 //........ 26 } 27 };
完整的代码如下:
1 class Messager{ 2 public: 3 virtual void Login(string username, string password)=0; 4 virtual void SendMessage(string message)=0; 5 virtual void SendPicture(Image image)=0; 6 7 //下面的这些函数在不同的平台下面实现方式是不一样的 8 virtual void PlaySound()=0; 9 virtual void DrawShape()=0; 10 virtual void WriteText()=0; 11 virtual void Connect()=0; 12 13 virtual ~Messager(){} 14 }; 15 16 17 //平台实现 18 class PCMessagerBase : public Messager{ 19 public: 20 21 virtual void PlaySound(){ 22 //********** 23 } 24 virtual void DrawShape(){ 25 //********** 26 } 27 virtual void WriteText(){ 28 //********** 29 } 30 virtual void Connect(){ 31 //********** 32 } 33 }; 34 35 class MobileMessagerBase : public Messager{ 36 public: 37 38 virtual void PlaySound(){ 39 //========== 40 } 41 virtual void DrawShape(){ 42 //========== 43 } 44 virtual void WriteText(){ 45 //========== 46 } 47 virtual void Connect(){ 48 //========== 49 } 50 }; 51 52 53 //基本版 54 class MessagerLite : public Messager { 55 Messager* messager; //赋值为new PCMessagerBase()或new MobileMessagerBase()就可以调用PCMessagerBase和MobileMessagerBase类中不同的方法 56 public: 57 58 virtual void Login(string username, string password){ 59 60 messager->Connect(); 61 //........ 62 } 63 virtual void SendMessage(string message){ 64 65 messager->WriteText(); 66 //........ 67 } 68 virtual void SendPicture(Image image){ 69 70 messager->DrawShape(); 71 //........ 72 } 73 }; 74 75 76 //完美版 77 class MessagerPerfect : public Messager { 78 Messager* messager; //赋值为new PCMessagerBase()或new MobileMessagerBase()就可以调用PCMessagerBase和MobileMessagerBase类中不同的方法 79 public: 80 81 virtual void Login(string username, string password){ 82 83 messager->PlaySound(); //连网的时候播放声音 84 //******** 85 messager->Connect(); 86 //........ 87 } 88 virtual void SendMessage(string message){ 89 90 messager->PlaySound(); //发送文字的时候播放声音 91 //******** 92 messager->WriteText(); 93 //........ 94 } 95 virtual void SendPicture(Image image){ 96 97 messager->PlaySound(); //绘制图形的时候播放声音 98 //******** 99 messager->DrawShape(); 100 //........ 101 } 102 };
但是,上面的代码bug,问题如下:
此时PCMessagerBase将Messager类中的所有纯虚函数都继承过去了,但是只重写了部分的纯虚函数,因此PCMessagerBase还是一个纯虚基类,在第55行和第77行使用new PCMessagerBase()是错误的!!
修改方法如下:将Messager类拆分,拆分方法如下:
1 class Messager{ 2 public: 3 virtual void Login(string username, string password)=0; //MessagerLite和MessagerPerfect重写,归为一个类 4 virtual void SendMessage(string message)=0; //MessagerLite和MessagerPerfect重写,归为一个类 5 virtual void SendPicture(Image image)=0; //MessagerLite和MessagerPerfect重写,归为一个类 6 7 //下面的这些函数在不同的平台下面实现方式是不一样的 8 virtual void PlaySound()=0; //PCMessagerBase和MobileMessagerBase,归为一个类 9 virtual void DrawShape()=0; //PCMessagerBase和MobileMessagerBase,归为一个类 10 virtual void WriteText()=0; //PCMessagerBase和MobileMessagerBase,归为一个类 11 virtual void Connect()=0; //PCMessagerBase和MobileMessagerBase,归为一个类 12 13 virtual ~Messager(){} 14 };
PCMessagerBase和MobileMessagerBase重写的是MessagerImp中的函数,所以PCMessagerBase和MobileMessagerBase需要继承的是MessagerImp;
MessagerLite和MessagerPerfect重写的是Messager中的函数,所以MessagerLite和MessagerPerfect需要继承的是Messager;
在MessagerLite和MessagerPerfect类中需要创建的是MessagerImp指针,用来接收new PCMessagerBase和new MobileMessagerBase的赋值。
1 class Messager{ 2 public: 3 virtual void Login(string username, string password)=0; 4 virtual void SendMessage(string message)=0; 5 virtual void SendPicture(Image image)=0; 6 7 virtual ~Messager(){} 8 }; 9 10 class MessagerImp{ 11 public: 12 virtual void PlaySound()=0; 13 virtual void DrawShape()=0; 14 virtual void WriteText()=0; 15 virtual void Connect()=0; 16 17 virtual ~MessagerImp(){} 18 }; 19 20 //平台实现 21 class PCMessagerBase : public MessagerImp{ //PCMessagerBase重写的是MessagerImp中的函数,所以这里要继承的是MessagerImp 22 public: 23 24 virtual void PlaySound(){ 25 //********** 26 } 27 virtual void DrawShape(){ 28 //********** 29 } 30 virtual void WriteText(){ 31 //********** 32 } 33 virtual void Connect(){ 34 //********** 35 } 36 }; 37 38 class MobileMessagerBase : public Messager{ //MobileMessagerBase重写的是MessagerImp中的函数,所以这里要继承的是MessagerImp 39 public: 40 41 virtual void PlaySound(){ 42 //========== 43 } 44 virtual void DrawShape(){ 45 //========== 46 } 47 virtual void WriteText(){ 48 //========== 49 } 50 virtual void Connect(){ 51 //========== 52 } 53 }; 54 55 56 //基本版 57 class MessagerLite : public Messager { //MessagerLite重写的是Messager中的函数,所以要继承的是Messager 58 MessagerImp* messagerImp; //赋值为new PCMessagerBase()或new MobileMessagerBase()就可以调用PCMessagerBase和MobileMessagerBase类中不同的方法 59 public: 60 61 virtual void Login(string username, string password){ 62 63 messagerImp->Connect(); 64 //........ 65 } 66 virtual void SendMessage(string message){ 67 68 messagerImp->WriteText(); 69 //........ 70 } 71 virtual void SendPicture(Image image){ 72 73 messagerImp->DrawShape(); 74 //........ 75 } 76 }; 77 78 79 //完美版 80 class MessagerPerfect : public Messager { //MessagerLite重写的是Messager中的函数,所以要继承的是Messager 81 MessagerImp* messagerImp; //赋值为new PCMessagerBase()或new MobileMessagerBase()就可以调用PCMessagerBase和MobileMessagerBase类中不同的方法 82 public: 83 84 virtual void Login(string username, string password){ 85 86 messagerImp->PlaySound(); //连网的时候播放声音 87 //******** 88 messagerImp->Connect(); 89 //........ 90 } 91 virtual void SendMessage(string message){ 92 93 messagerImp->PlaySound(); //发送文字的时候播放声音 94 //******** 95 messagerImp->WriteText(); 96 //........ 97 } 98 virtual void SendPicture(Image image){ 99 100 messagerImp->PlaySound(); //绘制图形的时候播放声音 101 //******** 102 messagerImp->DrawShape(); 103 //........ 104 } 105 };
此时我们发现在MessagerLite和MessagerPerfect类中都有MessagerImp* messagerImp; 这一句话,且二者都继承了Messager,那么就可以将这一句话提到类Messager类中去,如下:
删除MessagerLite类中的MessagerImp* messagerImp; 第58行
删除MessagerPerfect类中的MessagerImp* messagerImp; 第81行
在Messager类中添加一个保护成员:MessagerImp* messagerImp;,并添加响应的构造函数
1 class Messager{ 2 protected: 3 MessagerImp* messagerImp; 4 public: 5 Messager(MessagerImp* tmp): messagerImp(tmp); 6 7 virtual void Login(string username, string password)=0; 8 virtual void SendMessage(string message)=0; 9 virtual void SendPicture(Image image)=0; 10 11 virtual ~Messager(){} 12 }; 13 14 class MessagerImp{ 15 public: 16 virtual void PlaySound()=0; 17 virtual void DrawShape()=0; 18 virtual void WriteText()=0; 19 virtual void Connect()=0; 20 21 virtual ~MessagerImp(){} 22 }; 23 24 //平台实现 25 class PCMessagerBase : public MessagerImp{ //PCMessagerBase重写的是MessagerImp中的函数,所以这里要继承的是MessagerImp 26 public: 27 28 virtual void PlaySound(){ 29 //********** 30 } 31 virtual void DrawShape(){ 32 //********** 33 } 34 virtual void WriteText(){ 35 //********** 36 } 37 virtual void Connect(){ 38 //********** 39 } 40 }; 41 42 class MobileMessagerBase : public Messager{ //MobileMessagerBase重写的是MessagerImp中的函数,所以这里要继承的是MessagerImp 43 public: 44 45 virtual void PlaySound(){ 46 //========== 47 } 48 virtual void DrawShape(){ 49 //========== 50 } 51 virtual void WriteText(){ 52 //========== 53 } 54 virtual void Connect(){ 55 //========== 56 } 57 }; 58 59 60 //基本版 61 class MessagerLite : public Messager { //MessagerLite重写的是Messager中的函数,所以要继承的是Messager 62 63 public: 64 65 virtual void Login(string username, string password){ 66 67 messagerImp->Connect(); 68 //........ 69 } 70 virtual void SendMessage(string message){ 71 72 messagerImp->WriteText(); 73 //........ 74 } 75 virtual void SendPicture(Image image){ 76 77 messagerImp->DrawShape(); 78 //........ 79 } 80 }; 81 82 83 //完美版 84 class MessagerPerfect : public Messager { //MessagerLite重写的是Messager中的函数,所以要继承的是Messager 85 86 public: 87 88 virtual void Login(string username, string password){ 89 90 messagerImp->PlaySound(); //连网的时候播放声音 91 //******** 92 messagerImp->Connect(); 93 //........ 94 } 95 virtual void SendMessage(string message){ 96 97 messagerImp->PlaySound(); //发送文字的时候播放声音 98 //******** 99 messagerImp->WriteText(); 100 //........ 101 } 102 virtual void SendPicture(Image image){ 103 104 messagerImp->PlaySound(); //绘制图形的时候播放声音 105 //******** 106 messagerImp->DrawShape(); 107 //........ 108 } 109 }; 110 111 112 113 void Process(){ 114 //编译时装配 115 MessagerImp *mImp = new PCMessagerBase(); 116 Messager *m = new Messager(mImp); 117 m->Login(); //这里是无法区分到底是调用MessagerLite类中的Login()还是调用MessagerPerfect()中的Login(),这个只能是不同的机器上配置不同的类了 118 m->SendMessage(); //只能区分调用的是PC平台下的PlaySound()等函数还是Mobile平台下的函数 119 }
桥模式结构
总结
如果有三个变化的维度,那么就这三个部分单独的拿出来,写成三个类,用三个抽象指针指向他们即可。
7、避免使用new +具体类创建类对象的方法---工厂方法(Factory Method)
对于以下代码:
1 //FileSpliter.cpp 2 class FileSpliter{ 3 public: 4 void split(){ 5 //... 6 } 7 }; 8 9 //MainForm.cpp 10 class MainForm { 11 public: 12 void Button_Click(){ 13 FileSpliter* spliter = new FileSpliter(); //这里使用的具体类是FileSpliter,不支持未来的变化,要使用抽象类,而不应该使用具体的类 14 spliter->split(); 15 } 16 };
问题一:假如以后要支持二进制文件的分割、txt文件的分割,那么就要创建对应的类,此时在MainForm.cpp中的第14行就得修改成对应的类,即只要一发生变化,那么就需要改第14行,而且第14行等号的左边和右边都是使用的具体类FileSplitter创建类对象,这就不符合倒置依赖原则了,在等号两边最好都使用抽象类,因此要创建一个抽象类,ISplitter,在第14行可以使用抽象类创建spliter对象,并且让每个文件分割类都继承这个类,并重写抽象类ISplitter中的文件分割方法split()方法如下:
1 class ISplitter{ 2 public: 3 virtual void split()=0; 4 virtual ~ISplitter(){} 5 }; 6 7 class FileSpliter : public ISplitter{ 8 public: 9 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 10 //... 11 } 12 }; 13 class BinarySplitter : public ISplitter{ 14 public: 15 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 16 //... 17 } 18 }; 19 20 class TxtSplitter: public ISplitter{ 21 public: 22 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 23 //... 24 } 25 }; 26 27 class PictureSplitter: public ISplitter{ 28 public: 29 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 30 //... 31 } 32 }; 33 34 class VideoSplitter: public ISplitter{ 35 public: 36 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 37 //... 38 } 39 }; 40 41 //MainForm.cpp 42 class MainForm { 43 public: 44 void Button_Click(){ 45 ISplitter* spliter = new FileSpliter(); //使用抽象类ISplitter创建类对象spliter,但是此时等号右边还是用到了具体类FileSpliter,还得继续改进 46 spliter->split(); 47 } 48 };
问题二:此时只是解决了FileSpliter* spliter = new FileSpliter();将等号左边的FileSpliter抽象化为ISplitter,那么等号右边的 new FileSpliter(filePath,number)还是具体类,只要有一处是具体类,那么就不满足依赖倒置原则。那么如何返回这个抽象类ISplitter类对象呢?那么解决方法是绕开new导致的紧耦合(不使用new了),解决的方法是在具体类中使用返回值的方法,给抽象类类对象赋值:
1 //Splitter.cpp 2 class SplitterFactory{ 3 public: 4 ISplitter* CreatSplitter(){ 5 //... 6 return new BinarySplitter(); 7 } 8 }; 9 10 class ISplitter{ 11 public: 12 virtual void split()=0; 13 virtual ~ISplitter(){} 14 }; 15 16 //次数省略了FileSpliter、BinarySpliter等类的实现 17 18 //MainForm.cpp 19 class MainForm { 20 public: 21 22 void Button_Click(){ 23 24 SplitterFactory factory; //创建工厂类的类对象 25 26 ISpliter* spliter = factory.CreatSplitter(); //使用SplitterFactory类中的CreatSplitter()函数返回值给ISpliter抽象类类对象spliter赋值 27 spliter->split(); 28 } 29 };
但是此时MainForm.cpp仍然间接的依赖了BinarySplitter类,此时仍然不满足条件,解决方法是将SplitterFactory类中的CreatSplitter()函数写为纯虚函数,既然是纯虚函数,那么就没必要在SplitterFactory类中立马做实现,此时可以在各自类中重写SplitterFactory类中的CreatSplitter()函数,在各自的类中CreatSplitter()函数中使用new返回各类的类对象。Splitter.cpp总体代码如下:
1 class ISplitter{ 2 public: 3 virtual void split()=0; 4 virtual ~ISplitter(){} 5 }; 6 7 class SplitterFactory{ 8 public: 9 virtual ISplitter* CreatSplitter()=0; //BinarySplitter类、TxtSplitter类都有自己的返回自己类对象的方法,需要在他们各自类中重写这个方法即可 10 virtual ~SplitterFactory(){} 11 }; 12 13 class FileSpliter : public ISplitter,SplitterFactory{ //继承两个抽象类 14 public: 15 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 16 //... 17 } 18 virtual ISplitter* CreatSplitter(){ //重写抽象类SplitterFactory中的CreatSplitter()方法 19 //... 20 return new FileSpliter(); //以这样的方式返回FileSpliter的类对象,使用的是FileSpliter类的默认构造函数 21 } 22 }; 23 class BinarySplitter : public ISplitter,SplitterFactory{ //继承两个抽象类 24 public: 25 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 26 //... 27 } 28 virtual ISplitter* CreatSplitter(){ //重写抽象类SplitterFactory中的CreatSplitter()方法 29 //... 30 return new BinarySplitter(); //以这样的方式返回BinarySplitter的类对象,使用的是BinarySplitter类的默认构造函数 31 } 32 }; 33 34 class TxtSplitter : public ISplitter,SplitterFactory{ //继承两个抽象类 35 public: 36 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 37 //... 38 } 39 virtual ISplitter* CreatSplitter(){ //重写抽象类SplitterFactory中的CreatSplitter()方法 40 //... 41 return new TxtSplitter(); //以这样的方式返回TxtSplitter的类对象,使用的是TxtSplitter类的默认构造函数 42 } 43 }; 44 45 class PictureSplitter : public ISplitter,SplitterFactory{ //继承两个抽象类 46 public: 47 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 48 //... 49 } 50 virtual ISplitter* CreatSplitter(){ //重写抽象类SplitterFactory中的CreatSplitter()方法 51 //... 52 return new PictureSplitter(); //以这样的方式返回PictureSplitter的类对象,使用的是PictureSplitter类的默认构造函数 53 } 54 }; 55 56 class VideoSplitter : public ISplitter,SplitterFactory{ //继承两个抽象类 57 public: 58 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 59 //... 60 } 61 virtual ISplitter* CreatSplitter(){ //重写抽象类SplitterFactory中的CreatSplitter()方法 62 //... 63 return new VideoSplitter(); //以这样的方式返回VideoSplitter的类对象,使用的是VideoSplitter类的默认构造函数 64 } 65 }; 66 67 //MainForm.cpp 68 class MainForm { 69 public: 70 71 void Button_Click(){ 72 73 SplitterFactory* factory; //创建工厂类的类对象 74 75 ISpliter* spliter = factory->CreatSplitter(); //使用SplitterFactory类中的CreatSplitter()函数返回值给ISpliter抽象类类对象spliter赋值 76 spliter->split(); 77 } 78 };
问题三:关于MainForm.cpp的修改
此时再去修改MainForm.cpp,因为Button_Click()是要被反复执行的,所以创建工厂类对象SplitterFactory* factory;这句话不应该放在Button_Click()函数里
修改方法如下:
1 class MainForm { 2 private: 3 SplitterFactory* factory; //创建工厂类的类对象 4 public: 5 MainForm(SplitterFactory* f) : factory(f){ //通过外界调用MainForm构造函数的时候,传入SplitterFactory抽象类的类对象给factory赋值 6 7 } 8 9 void Button_Click(){ 10 11 ISpliter* spliter = factory->CreatSplitter(); //使用SplitterFactory类中的CreatSplitter()函数返回值给ISpliter抽象类类对象spliter赋值 12 spliter->split(); 13 } 14 }; 15 //main.cpp 16 int main(){ 17 FileSpliter* f = new FileSpliter(); 18 MainForm M(f); 19 delete f; 20 //等带按钮时间触发Button_Click()被执行 21 return 0; 22 }
上面MainForm.cpp中main函数中的代码将MainForm中的私有变量factory指向了FileSpliter类对象,然后在Button_Click()中的factory->CreatSplitter()则调用的是FileSpliter类中的CreatSplitter()函数中使用new返回各类的类对象。自然CreatSplitter()函数返回值就是FileSpliter类对象,然后将CreatSplitter()返回的FileSpliter类对象赋值给抽象类ISpliter类对象spliter,由于spliter指向的是FileSpliter类对象,所以在调用split()函数的时候当然调用的就是FileSpliter类中的split()函数
此时MainForm只依赖Splitter和SplitterFactory两个抽象类,而和BinarySplitter、TxtSplitter等隔离开来。
1、工厂方法的定义:
定义一个用于创建对象的接口(SplitterFactory类中的CreatSplitter()方法),让子类去决定实例化哪一个类。工厂方法使得一个类的实例化得到延迟到子类,目的是实现结构,手段是使用虚函数。
2、注意:在讲课的过程中,老师是将split()纯虚函数和CreatSplitter()单独放在两个类中的,这样是为了防止出现某个类继承了有这两个纯虚函数的类,但是只重写了他们中的一个,从而导致使用new 类名出错的问题
8、避免使用new +具体类创建类对象的方法---抽象工厂(Abstract Factory)
对于以下代码:
1 //数据访问层---需要写一些列的对象 2 class EmployeeDAO{ 3 public: 4 vector<EmployeeDO> GetEmployees(){ 5 SqlConnection* connection = //创建链接对象 6 new SqlConnection(); 7 connection->ConnectionString = "..."; 8 9 SqlCommand* command = //创建命令对象 10 new SqlCommand(); 11 command->CommandText="..."; 12 command->SetConnection(connection); 13 14 SqlDataReader* reader = command->ExecuteReader(); //创建读对象 15 while (reader->Read()){ 16 17 } 18 19 } 20 };
问题一:有可能这次项目使用的是Sql,但是下次项目使用的可能就是mysql或者是Oracle了,所以上面的EmployeeDAO类不适用于面临多种数据库的变化,所以需要做一系列的抽象类:
1 //数据库访问的有关的基类 2 class IDBConnection{ 3 public: 4 virtual void ConnectionString(string s)=0; 5 }; 6 7 class IDBCommand{ 8 virtual void SetConnection()=0; 9 }; 10 11 class IDBDataReader{ 12 virtual void ExecuteReader()=0; 13 }; 14 15 //针对sql 16 class SqlConnection : public IDBConnection{ //重写IDBConnection类中的ConnectionString()方法,此处省略 17 18 }; 19 class SqlCommand : public IDBCommand{ //重写IDBCommand类中的SetConnection()方法,此处省略 20 21 }; 22 class SqlDataReader : public IDBDataReader{ //重写IDBDataReader类中的ExecuteReader()方法,此处省略 23 24 }; 25 26 //针对Oracle 27 class OracleConnection : public IDBConnection{ //重写IDBConnection类中的ConnectionString()方法,此处省略 28 29 }; 30 class OracleCommand : public IDBCommand{ //重写IDBCommand类中的SetConnection()方法,此处省略 31 32 }; 33 class OracleDataReader : public IDBDataReader{ 34 35 }; 36 37 class EmployeeDAO{ 38 public: 39 vector<EmployeeDO> GetEmployees(){ 40 IDBConnection* connection = //创建链接对象 41 new SqlConnection(); 42 connection->ConnectionString("..."); 43 44 IDBCommand* command = //创建命令对象 45 new SqlCommand(); 46 command->CommandText("..."); 47 command->SetConnection(connection); 48 49 IDBDataReader* reader = command->ExecuteReader(); //创建读对象 50 while (reader->Read()){ 51 52 } 53 54 } 55 };
问题二:再修改等号右边的new SqlConnection(),方法同工厂模式,还是再添加一个抽象基类,在改基类中有一个方法,改方法的返回值在各个具体的类中去实现
1 //数据库访问的有关的基类---作为抽象基类 2 class IDBConnection{ 3 public: 4 virtual void ConnectionString(string s)=0; 5 }; 6 7 class IDBCommand{ 8 virtual void SetConnection()=0; 9 }; 10 11 class IDBDataReader{ 12 virtual void ExecuteReader()=0; 13 }; 14 15 //工厂类---作为抽象基类 16 class IDBConnectionFactory{ 17 public: 18 virtual IDBConnection* CreatDBConnection()=0; //返回一个IDBConnection类对象 19 }; 20 21 class IDBCommandFactory{ 22 public: 23 virtual IDBCommand* CreatDBCommand()=0; //返回一个IDBCommand类对象 24 }; 25 26 class IDBDataReaderFactory{ 27 public: 28 virtual IDBDataReader* CreatDBDataReader()=0; //返回一个IDBDataReader类对象 29 }; 30 31 /****************************************针对sql****************************************/ 32 //针对sql---具体类---SqlConnection 33 class SqlConnection : public IDBConnection{ 34 public: 35 virtual void ConnectionString(string s){ //重写抽象基类IDBConnection中的ConnectionString()方法 36 37 } 38 }; 39 //针对sql---具体工厂---SqlConnectionFactory 40 class SqlConnectionFactory : public IDBConnectionFactory{ 41 public: 42 virtual IDBConnection* CreatDBConnection(){ //重写抽象工厂基类IDBConnectionFactory中的CreatDBConnection()方法 43 //... 44 return new SqlConnection(); 45 } 46 }; 47 //针对sql---具体类---SqlCommand 48 class SqlCommand : public IDBCommand{ 49 public: 50 virtual void SetConnection(){ //重写抽象基类IDBCommand中的SetConnection()方法 51 52 } 53 }; 54 //针对sql---具体工厂---SqlCommandFactory 55 class SqlCommandFactory : public IDBCommandFactory{ 56 virtual IDBCommand* CreatDBCommand(){ 57 //... 58 return new SqlCommand(); 59 } 60 }; 61 //针对sql---具体类---SqlDataReader 62 class SqlDataReader : public IDBDataReader{ 63 public: 64 virtual void ExecuteReader(){ //重写抽象基类IDBDataReader中的ExecuteReader()方法 65 66 } 67 }; 68 //针对sql---具体工厂---SqlDataReaderFactory 69 class SqlDataReaderFactory : public IDBDataReaderFactory{ 70 virtual IDBDataReader* CreatDBDataReader(){ 71 //... 72 return new SqlDataReader(); 73 } 74 }; 75 76 /****************************************针对oracel****************************************/ 77 //针对Oracle---具体类---OracleConnection 78 class OracleConnection : public IDBConnection{ 79 virtual void ConnectionString(string s){ //重写抽象基类IDBConnection中的ConnectionString()方法 80 81 } 82 }; 83 //针对Oracle---具体工厂---OracleConnectionFactory 84 class OracleConnectionFactory : public IDBConnectionFactory{ 85 virtual IDBConnection* CreatDBConnection(){ //重写抽象工厂基类IDBConnectionFactory中的CreatDBConnection()方法 86 //... 87 return new OracleConnection(); 88 } 89 }; 90 //针对Oracle---具体类---OracleCommand 91 class OracleCommand : public IDBCommand{ 92 93 }; 94 //针对Oracle---具体工厂---OracleCommandFactory 95 class OracleCommandFactory : public IDBCommandFactory{ 96 virtual IDBCommand* CreatDBCommand(){ 97 //... 98 return new OracleCommand(); 99 } 100 }; 101 //针对Oracle---具体类---OracleDataReader 102 class OracleDataReader : public IDBDataReader{ 103 104 }; 105 //针对Oracle---具体工厂---OracleDataReaderFactory 106 class OracleDataReaderFactory : public IDBDataReaderFactory{ 107 virtual IDBDataReader* CreatDBDataReader(){ 108 //... 109 return new OracleDataReader(); 110 } 111 }; 112 113 class EmployeeDAO{ 114 IDBConnectionFactory* dbConnectionFactory; //为构造函数传入参数的时候是要传入的是SqlConnectionFactory类对象或OracleConnectionFactory类对象,因为这两个类继承自IDBConnectionFactory 115 IDBCommandFactory* dbCommandFactory; 116 IDBDataReaderFactory* dbDataReaderFactory; 117 118 public: 119 EmployeeDAO(IDBConnectionFactory* connection,IDBCommandFactory* command,IDBDataReaderFactory* dataRead) : 120 dbConnectionFactory(connection),dbCommandFactory(command),dbDataReaderFactory(dataRead){} 121 122 vector<EmployeeDO> GetEmployees(){ 123 IDBConnection* connection = //创建链接对象 124 dbConnectionFactory->CreatDBConnection(); 125 connection->ConnectionString("..."); 126 127 IDBCommand* command = //创建命令对象 128 dbCommandFactory->CreatDBCommand(); 129 command->SetConnection(connection); 130 131 IDBDataReader* reader = command->ExecuteReader(); //创建读对象 132 while (reader->Read()){ 133 134 } 135 136 } 137 };
问题三:但是在EmployeeDAO类中,还是存在问题的,问题如下:
在调用EmployeeDAO类构造函数的时候,假如给EmployeeDAO类构造函数传入的第一个参数是针对sql的SqlConnectionFactory,第二个参数传入的是针对oracle的OracleCommandFactory,第三个参数传入的是针对oracle的OracleDataReaderFactory类对象,此时就会报错的。解决方法是将IDBConnectionFactory、IDBCommandFactory和IDBDataReaderFactory这三个工厂合并为一个工厂,具体代码如下:
即把下面的三个类合并成一个抽象工厂类
1 //工厂类---作为抽象基类 2 class IDBConnectionFactory{ 3 public: 4 virtual IDBConnection* CreatDBConnection()=0; //返回一个IDBConnection类对象 5 }; 6 7 class IDBCommandFactory{ 8 public: 9 virtual IDBCommand* CreatDBCommand()=0; //返回一个IDBCommand类对象 10 }; 11 12 class IDBDataReaderFactory{ 13 public: 14 virtual IDBDataReader* CreatDBDataReader()=0; //返回一个IDBDataReader类对象 15 };
替换为:
1 class IDBFactory{ 2 public: 3 virtual IDBConnection* CreatDBConnection()=0; //返回一个IDBConnection类对象 如IDBFactory类中的CreatDBConnection()、CreatDBCommand()和CreatDBDataReader()这三个虚函数 4 virtual IDBCommand* CreatDBCommand()=0; //返回一个IDBCommand类对象 5 virtual IDBDataReader* CreatDBDataReader()=0; //返回一个IDBDataReader类对象 6 };
同理将下面的代码:
1 //针对sql---具体工厂---SqlConnectionFactory 2 class SqlConnectionFactory : public IDBConnectionFactory{ 3 public: 4 virtual IDBConnection* CreatDBConnection(){ //重写抽象工厂基类IDBConnectionFactory中的CreatDBConnection()方法 5 //... 6 return new SqlConnection(); 7 } 8 }; 9 //针对sql---具体工厂---SqlCommandFactory 10 class SqlCommandFactory : public IDBCommandFactory{ 11 virtual IDBCommand* CreatDBCommand(){ 12 //... 13 return new SqlCommand(); 14 } 15 }; 16 //针对sql---具体工厂---SqlDataReaderFactory 17 class SqlDataReaderFactory : public IDBDataReaderFactory{ 18 virtual IDBDataReader* CreatDBDataReader(){ 19 //... 20 return new SqlDataReader(); 21 } 22 };
替换为:
1 class SqlDBFactory : public IDBFactory{ 2 public: 3 virtual IDBConnection* CreatDBConnection(){ //重写抽象工厂基类IDBConnectionFactory中的CreatDBConnection()方法 4 //... 5 return new SqlConnection(); 6 } 7 virtual IDBCommand* CreatDBCommand(){ 8 //... 9 return new SqlCommand(); 10 } 11 virtual IDBDataReader* CreatDBDataReader(){ 12 //... 13 return new SqlDataReader(); 14 } 15 };
此时在EmployeeDAO类中的代码可以修改为:
1 class EmployeeDAO{ 2 IDBFactory* dbFactory; 3 4 public: 5 EmployeeDAO(IDBFactory* DBFactory) : dbFactory(DBFactory){} //传入的可以是SqlDBFactory类对象或OracleDBFactory类对象,因为SqlDBFactory类和OracleDBFactory类的基类都是IDBFactory 6 7 vector<EmployeeDO> GetEmployees(){ 8 IDBConnection* connection = //创建链接对象 9 dbFactory->CreatDBConnection(); 10 connection->ConnectionString("..."); 11 12 IDBCommand* command = //创建命令对象 13 dbFactory->CreatDBCommand(); 14 command->SetConnection(connection); 15 16 IDBDataReader* reader = command->ExecuteReader(); //创建读对象 17 while (reader->Read()){ 18 19 } 20 21 } 22 };
整体代码修改如下;
1 //数据库访问的有关的基类---作为抽象基类 2 class IDBConnection{ 3 public: 4 virtual void ConnectionString(string s)=0; 5 }; 6 7 class IDBCommand{ 8 virtual void SetConnection()=0; 9 }; 10 11 class IDBDataReader{ 12 virtual void ExecuteReader()=0; 13 }; 14 15 //工厂类---作为抽象基类 16 class IDBFactory{ 17 public: 18 virtual IDBConnection* CreatDBConnection()=0; //返回一个IDBConnection类对象 19 virtual IDBCommand* CreatDBCommand()=0; //返回一个IDBCommand类对象 20 virtual IDBDataReader* CreatDBDataReader()=0; //返回一个IDBDataReader类对象 21 }; 22 23 /****************************************针对sql****************************************/ 24 //针对sql---具体类---SqlConnection 25 class SqlConnection : public IDBConnection{ 26 public: 27 virtual void ConnectionString(string s){ //重写抽象基类IDBConnection中的ConnectionString()方法 28 29 } 30 }; 31 //针对sql---具体类---SqlCommand 32 class SqlCommand : public IDBCommand{ 33 public: 34 virtual void SetConnection(){ //重写抽象基类IDBCommand中的SetConnection()方法 35 36 } 37 }; 38 //针对sql---具体类---SqlDataReader 39 class SqlDataReader : public IDBDataReader{ 40 public: 41 virtual void ExecuteReader(){ //重写抽象基类IDBDataReader中的ExecuteReader()方法 42 43 } 44 }; 45 //针对sql---具体工厂 46 class SqlDBFactory : public IDBFactory{ 47 public: 48 virtual IDBConnection* CreatDBConnection(){ //重写抽象工厂基类IDBFactory中的CreatDBConnection()方法 49 //... 50 return new SqlConnection(); 51 } 52 virtual IDBCommand* CreatDBCommand(){ 53 //... 54 return new SqlCommand(); 55 } 56 virtual IDBDataReader* CreatDBDataReader(){ 57 //... 58 return new SqlDataReader(); 59 } 60 }; 61 62 /****************************************针对oracel****************************************/ 63 //针对Oracle---具体类---OracleConnection 64 class OracleConnection : public IDBConnection{ 65 virtual void ConnectionString(string s){ //重写抽象基类IDBConnection中的ConnectionString()方法 66 67 } 68 }; 69 //针对Oracle---具体类---OracleCommand 70 class OracleCommand : public IDBCommand{ 71 virtual void SetConnection(){ //重写抽象基类IDBCommand中的SetConnection()方法 72 73 } 74 }; 75 //针对Oracle---具体类---OracleDataReader 76 class OracleDataReader : public IDBDataReader{ 77 virtual void ExecuteReader(){ //重写抽象基类IDBDataReader中的ExecuteReader()方法 78 79 } 80 }; 81 //针对Oracle---具体工厂 82 class OracleDBFactory : public IDBFactory{ 83 virtual IDBConnection* CreatDBConnection(){ //重写抽象工厂基类IDBFactory中的CreatDBConnection()方法 84 //... 85 return new OracleConnection(); 86 } 87 virtual IDBCommand* CreatDBCommand(){ //重写抽象工厂基类IDBFactory中的CreatDBCommand()方法 88 //... 89 return new OracleCommand(); 90 } 91 virtual IDBDataReader* CreatDBDataReader(){ //重写抽象工厂基类IDBFactory中的CreatDBDataReader()方法 92 //... 93 return new OracleDataReader(); 94 } 95 }; 96 97 class EmployeeDAO{ 98 IDBFactory* dbFactory; 99 100 public: 101 EmployeeDAO(IDBFactory* DBFactory) : dbFactory(DBFactory){} //传入的可以是SqlDBFactory类对象或OracleDBFactory类对象,因为SqlDBFactory类和OracleDBFactory类的基类都是IDBFactory 102 103 vector<EmployeeDO> GetEmployees(){ 104 IDBConnection* connection = //创建链接对象 105 dbFactory->CreatDBConnection(); 106 connection->ConnectionString("..."); 107 108 IDBCommand* command = //创建命令对象 109 dbFactory->CreatDBCommand(); 110 command->SetConnection(connection); 111 112 IDBDataReader* reader = command->ExecuteReader(); //创建读对象 113 while (reader->Read()){ 114 115 } 116 117 } 118 };
但是纯虚基类工厂IDBFactory中的三个虚函数,谁继承了IDBFactory类,那么在该类下就必须完整的重写IDBFactory中的三个纯虚函数,否则使用该类名去new一个类对象的时候是会报错的
使用抽象工厂的动机
抽象工厂结构图
抽象工厂要点总结
9、避免使用new +具体类创建类对象的方法---原型模式(Prototype)
避免对象创建过程中所导致的紧耦合(依赖具体类),有如下方法
1)Factory Method---工厂方法
2)Abstract Factory---抽象工厂
3)Prototype---原型模式
4)Builder
使用较多的是前两种方法
1、对于前几节提到的如下代码:
1 class ISplitter{ 2 public: 3 virtual void split()=0; 4 virtual ~ISplitter(){} 5 }; 6 7 class ISplitter{ 8 public: 9 virtual ISplitter* CreatSplitter()=0; //BinarySplitter类、TxtSplitter类都有自己的返回自己类对象的方法,需要在他们各自类中重写这个方法即可 10 virtual ~SplitterFactory(){} 11 };
使用原型模式,合并为一个类:
1 class ISplitter{ 2 public: 3 virtual void split()=0; 4 virtual ISplitter* clone()=0; //通过克隆自己来创建对象 5 6 virtual ~ISplitter(){} 7 };
2、对于其余的类,修改方法如下:
1 class BinarySplitter : public ISplitter{ //继承两个抽象类 2 public: 3 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 4 //... 5 } 6 virtual ISplitter* clone(){ //重写抽象类SplitterFactory中的CreatSplitter()方法 7 //... 8 return new BinarySplitter(*this); //调用BinarySplitter类的拷贝构造函数 9 } 10 };
3、对于对于MainForm,修改方法如下:
1 class MainForm { 2 private: 3 ISplitter* prototype; //创建原型对象,克隆用,不可以直接拿它去调用函数 4 public: 5 MainForm(ISplitter* f) : prototype(f){ //通过外界调用MainForm构造函数的时候,传入SplitterFactory抽象类的类对象给factory赋值 6 7 } 8 9 void Button_Click(){ 10 ISplitter* splitter = prototype->clone(); //克隆prototype得到一个新对象 11 12 spliter->split(); 13 } 14 };
使用原型模式对工厂方法(Factory Method)完整的修改方法如下:
1 class ISplitter{ 2 public: 3 virtual void split()=0; 4 virtual ISplitter* clone()=0; //BinarySplitter类、TxtSplitter类都有自己的返回自己类对象的方法,需要在他们各自类中重写这个方法即可 5 6 virtual ~ISplitter(){} 7 }; 8 9 class FileSpliter : public ISplitter{ 10 public: 11 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 12 //... 13 } 14 virtual ISplitter* clone(){ //重写抽象类ISplitter中的clone()方法 15 //... 16 return new FileSpliter(*this); //需要在FileSpliter类中有一个拷贝构造函数,且是深复制 17 } 18 }; 19 class BinarySplitter : public ISplitter{ 20 public: 21 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 22 //... 23 } 24 virtual ISplitter* clone(){ //重写抽象类ISplitter中的clone()方法 25 //... 26 return new BinarySplitter(*this); //需要在BinarySplitter类中有一个拷贝构造函数,且是深复制 27 } 28 }; 29 30 class TxtSplitter : public ISplitter{ 31 public: 32 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 33 //... 34 } 35 virtual ISplitter* clone(){ //重写抽象类ISplitter中的clone()方法 36 //... 37 return new TxtSplitter(*this); //需要在TxtSplitter类中有一个拷贝构造函数,且是深复制 38 } 39 }; 40 41 class PictureSplitter : public ISplitter{ 42 public: 43 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 44 //... 45 } 46 virtual ISplitter* clone(){ //重写抽象类ISplitter中的clone()方法 47 //... 48 return new PictureSplitter(*this); //需要在PictureSplitter类中有一个拷贝构造函数,且是深复制 49 } 50 }; 51 52 class VideoSplitter : public ISplitter{ 53 public: 54 virtual void split(){ //重写抽象类ISplitter中的文件具体的分割方法 55 //... 56 } 57 virtual ISplitter* clone(){ //重写抽象类ISplitter中的clone()方法 58 //... 59 return new VideoSplitter(*this); //需要在VideoSplitter类中有一个拷贝构造函数,且是深复制 60 } 61 }; 62 63 class MainForm { 64 private: 65 ISplitter* prototype; //创建原型对象,克隆用,不可以直接拿它去调用函数 66 public: 67 MainForm(ISplitter* f) : prototype(f){ //由于FileSpliter、BinarySplitter等的基类是ISplitter,所以传入的参数可以是 68 //FileSpliter、BinarySplitter等类对象赋值给prototype,当prototype调用clone()方法的时候, 69 } //就会根据指向的对象的类型去调用对应的clone()方法 70 71 void Button_Click(){ 72 ISplitter* splitter = prototype->clone(); //克隆prototype得到一个新对象 73 74 spliter->split(); 75 } 76 };
原型模式的优点:
假如一个对象结构比较复杂,用工厂方法就会写的很复杂,甚至在某些状态下都写不正确;
原型模式好处在于传入的原型是啥,克隆出来的对象的结构就和原对象的结构是一样的,避免了用工厂方法实现创建对象比较复杂的问题
什么时候使用原型模式?
工厂方法、抽象工厂、原型模式都是解决new+具体类名的问题。区别在于:
如果创建对象非常简单,几步就可以创建完成,那么就使用工厂方法;
如果创建对象非常繁琐,需要考虑对象很复杂的中间状态,且希望保留这个中间状态,那么就使用原型模式。
原型模式结构图
10、避免使用new +具体类创建类对象的方法---建造者模式(生成器模式)(Builder)
Builder模式也叫建造者模式或者生成器模式,是由GoF提出的23种设计模式中的一种。Builder模式是一种对象创建型模式之一,用来隐藏复合对象的创建过程,它把复合对象的创建过程加以抽象,通过子类继承和重载的方式,动态地创建具有复合属性的对象。
对象的创建:Builder模式是为对象的创建而设计的模式- 创建的是一个复合对象:被创建的对象为一个具有复合属性的复合对象- 关注对象创建的各部分的创建过程:不同的工厂(这里指builder生成器)对产品属性有不同的创建方法
使用Builder模式创建一个造房子的方式:
1 //Builder模式 2 class House{ 3 public: 4 void Init(){ //以下创建房子的步骤是否可以放在House类的构造函数中?也就是在构造函数中是否可以调用虚函数?结果是不可以 5 this->BuildPart1(); //原因在于我们的目的在于调用子类的BuildPart1()这些虚函数,但是在创建子类对象之前先调用父类的构造函数的, 6 for(int i=0;i<4;++i){ //这就会导致出现一个情况:子类的构造函数还没有被调用,此时子类重写父类的虚函数就已经被调用了,这就违背了对象的基本伦理 7 this->BuildPart2(); //对象的基本伦理:你得先生下来,再去使用 8 } //如果真的要把这些步骤放在House的构造函数中的话,那么使用的是静态绑定,它一直调用的是父类House中的虚函数,但是在父类House中并未实现这些虚函数 9 bool flag = this->BuildPart3(); 10 if(flag){ 11 this->BuildPart4(); 12 } 13 this->BuildPart5(); 14 } 15 16 string setFloor(string f){ 17 this->m_floor = f; 18 } 19 string setWall(string w){ 20 this->m_wall = w; 21 } 22 string setDoor(string d){ 23 this->m_door = d; 24 } 25 string setWindow(string window){ 26 this->m_window=window; 27 } 28 string setLight(string light){ 29 this->m_light=light; 30 } 31 32 virtual ~House(){} 33 protected: 34 virtual void BuildPart1()=0; 35 virtual void BuildPart2()=0; 36 virtual void BuildPart3()=0; 37 virtual void BuildPart4()=0; 38 virtual void BuildPart5()=0; 39 private: 40 string m_floor; //House的基本 41 string m_wall; 42 string m_door; 43 string m_window; 44 string m_light; 45 }; 46 47 class StoneHouse: public House{ 48 protected: //由于下面的这些创建房子的步骤,单独的对外提供这些步骤是无意义的,所以此时将他们声明为保护类型的 49 virtual void BuildPart1(){ 50 51 } 52 virtual void BuildPart2(){ 53 54 } 55 virtual void BuildPart3(){ 56 57 } 58 virtual void BuildPart4(){ 59 60 } 61 virtual void BuildPart5(){ 62 63 } 64 public: 65 StoneHouse(){} 66 virtual ~StoneHouse(){} 67 }; 68 69 int main(){ 70 House* pHouse = new StoneHouse(); 71 pHouse->Init(); //调用Init()函数,完成石头房子的创建 72 73 return 0; 74 }
对于以上代码的改进:
在某些场景下,对象可能过于复杂,除了实现House类中的Init()中的这些步骤之外,还有很多其他的如setFloor()等这样的步骤,此时可以做一拆分:将Init()放在HouseDirector类内,实现各个已加工好了的部件的组装;将BuildPart1()等纯虚函数放在HouseBuilder内,将HouseBuilder作为一个纯虚基类,然后让一些具体的房子创建继承HouseBuilder
1 /*########################对象的表示类House########################*/ 2 class House{ 3 public: 4 string setFloor(string f){ 5 this->m_floor = f; 6 } 7 string setWall(string w){ 8 this->m_wall = w; 9 } 10 string setDoor(string d){ 11 this->m_door = d; 12 } 13 string setWindow(string window){ 14 this->m_window=window; 15 } 16 string setLight(string light){ 17 this->m_light=light; 18 } 19 20 21 virtual ~House(){} 22 private: 23 string m_floor; //House的基本 24 string m_wall; 25 string m_door; 26 string m_window; 27 string m_light; 28 }; 29 /*########################对象的构建类HouseBuilder########################*/ 30 class HouseBuilder{ 31 public: 32 HouseBuilder(House* h) : pHouse(h){} 33 34 House* getResult(){ //让外界可以拿到这个pHouse这个保护类型的指针 35 return pHouse; 36 } 37 protected: 38 House* pHouse; //由于是在HouseBuilder类的内部使用pHouse指针,所以把它声明为保护类型的 39 virtual void BuildPart1()=0; 40 virtual void BuildPart2()=0; 41 virtual void BuildPart3()=0; 42 virtual void BuildPart4()=0; 43 virtual void BuildPart5()=0; 44 45 }; 46 47 class StoneHouse: public HouseBuilder{ 48 public: 49 StoneHouse(House* h) : HouseBuilder(h){} 50 virtual ~StoneHouse(){} 51 protected: //由于下面的这些创建房子的步骤,单独的对外提供这些步骤是无意义的,所以此时将他们声明为保护类型的 52 virtual void BuildPart1(){ 53 pHouse->setFloor("stone house"); //使用基类HouseBuilder中的保护成员pHouse 54 } 55 virtual void BuildPart2(){ 56 pHouse->setWall("stone wall"); 57 } 58 virtual void BuildPart3(){ 59 pHouse->setDoor("stone door"); 60 } 61 virtual void BuildPart4(){ 62 pHouse->setWindow("stone window"); 63 } 64 virtual void BuildPart5(){ 65 pHouse->setLight("stone light"); 66 } 67 }; 68 69 /*########################对已经成品的零部件进行组装类HouseDirector########################*/ 70 class HouseDirector{ 71 public: 72 HouseBuilder* pHouse; 73 HouseDirector(HouseBuilder* p){ 74 this->pHouse = p; 75 } 76 void Construct(){ 77 pHouse->BuildPart1(); 78 for(int i=0;i<4;++i){ 79 pHouse->BuildPart2(); 80 } 81 bool flag = pHouse->BuildPart3(); 82 if(flag){ 83 pHouse->BuildPart4(); 84 } 85 pHouse->BuildPart5(); 86 } 87 }; 88 89 int main(){ 90 /**客户直接造房子**/ 91 House* pHouse = new House(); 92 pHouse->setFloor("client floor"); 93 for(int i=0;i<4;++i){ 94 pHouse->setWall("client wall"); 95 } 96 bool flag = pHouse->setDoor("client door"); 97 if(flag){ 98 pHouse->setWindow("client window"); 99 } 100 pHouse->setLight("client light"); 101 102 delete pHouse; 103 104 /**指挥者指挥工程队造房子**/ 105 House* holder = new House("floor","wall","door","window","light"); //指定房主需要的装饰,需要在House类中创建对应的构造函数 106 HouseBuilder* builder = new StoneHouse(holder); //基类指针builder指向派生类StoneHouse的类对象 107 HouseDirector* director = new HouseDirector(builder); //指定工程队 108 director->Construct(); //开始造房子 109 110 delete holder; 111 delete builder; 112 delete director; 113 114 return 0; 115 }
1)、适用情况:
一个对象的构建比较复杂,将一个对象的构建(?)和对象的表示(?)进行分离。
创建者模式和工厂模式的区别
2)、Factory模式中:
1、有一个抽象的工厂。
2、实现一个具体的工厂---汽车工厂。
3、工厂生产汽车A,得到汽车产品A。
4、工厂生产汽车B,得到汽车产品B。
这样做,实现了购买者和生产线的隔离。强调的是结果。
3)、Builder模式:
1、引擎工厂生产引擎产品,得到汽车部件A。
2、轮胎工厂生产轮子产品,得到汽车部件B。
3、底盘工厂生产车身产品,得到汽车部件C。
4、将这些部件放到一起,形成刚好能够组装成一辆汽车的整体。
5、将这个整体送到汽车组装工厂,得到一个汽车产品。
这样做,目的是为了实现复杂对象生产线和其部件的解耦。强调的是过程
4)、两者的区别在于:
Factory模式不考虑对象的组装过程,而直接生成一个我想要的对象。
Builder模式先一个个的创建对象的每一个部件,再统一组装成一个对象。
Factory模式所解决的问题是,工厂生产产品。
而Builder模式所解决的问题是工厂控制产品生成器组装各个部件的过程,然后从产品生成器中得到产品。
Builder模式不是很常用。模式本身就是一种思想。知道了就可以了。
Builder模式参考博客:https://blog.csdn.net/qq_15054345/article/details/88068622