3.3原型模式(代码见vs)
原型模式就是自我复制。原型模式的核心是一个clone()方法, 对于C++来说,其实就是拷贝构造函数,需要实现深拷贝。拷贝构造函数根据谁来拷贝才能实现自我复制呢?答案就是根据自己复制自己,即根据*this指针来复制。举例:打印后再复印,比直接打印500份省时省力气。
总结:
原型模式是一种不太常见的创建型模式,我们平时的工作中几乎不会遇到这种模式,但是在一些框架设计中会用到,这里我们只做了解即可。
应用场景:
1)当我们的对象类型不是开始就能确定的,而是在运行期确定的话,那么我们通过这个类型的对象克隆出一个新的对象比较容易一些;
2)有的时候,我们需要一个对象在某个状态下的副本,此时,我们使用原型模式是最好的选择;例如:一个对象,经过一段处理之后,其内部的状态发生了变化;这个时候,我们需要一个这个状态的副本,如果直接new一个新的对象的话,但是它的状态是不对的,此时,可以使用原型模式,将对象拷贝一个出来,这个对象就和此时状态下的对象是完全一致的了;
3)当我们处理一些比较简单的对象时,并且对象之间的区别很小,可能就几个属性不同而已,那么就可以使用原型模式来完成,省去了创建对象时的麻烦了;
4)有的时候,创建对象时,构造函数的参数很多,而自己又不完全的知道每个参数的意义,就可以使用原型模式来创建一个新的对象,不必去理会创建的过程。
原型模式练习
#include <iostream>
using namespace std;
//原型模式
//品牌抽象类
class Brand
{
public:
virtual Brand* clone() = 0;//这个克隆方法是原型模式最核心的方法,其实就是实现拷贝构造
virtual void show() = 0;
};
//具体品牌:美的
class Midea :public Brand
{
public:
virtual Brand* clone() { return new Midea(*this); }
virtual void show() { cout << "美的品牌" << endl; }
};
void test01()
{
Brand* md = new Midea();//这是原型对象
//接下来使用clone方法克隆更多的对象
Brand* md1 = md->clone();
Brand* md2 = md->clone();
md->show();
md1->show();
md2->show();
cout << md << "," << md1 << "," << md2 << endl;
delete md;
delete md1;
delete md2;
}
3.4适配器模式(代码见vs)
在不改变源代码的情况下,将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以兼容。如下图所示:汽车和轨道之间增加了一个适配的板车。
可以将适配器理解为我们日常用的电源适配器: 家庭电压为220V,而手机充电是5V左右,所以需要适配。如下图:目标抽象类是现有的类,是我们要调用的接口,但我们实际想执行的功能接口是在另一个类中的,称之为被适配类,在不改变现有类代码的情况下,如何能实现目标?答案就是再写一个适配器类。
适配器可以分为两种实现方式: 类适配器和对象适配器。
类适配需要适配器类继承需要适配的两个类:目标类和被适配类,然后重写目标类的方法,使之变成被适配类的方法,这样就完成了适配,也就是说我们通过适配器类调用了目标类被重写的方法,方法接口不变,但实际执行的是被适配类的方法。如果不适配,目标类和被适配类因为方法接口不兼容,是无法通过调用目标类的方法去执行被适配类的方法的。
对象适配器则是把被适配类作为适配器的属性,适配器类只需要继承目标类,重写目标类的方法,通过这个属性调用被适配类的方法。
1.类适配器
//类适配器,现有接口类,不能改变现有接口
class RobotTarget
{
public:
virtual void cry() = 0;
virtual void move() = 0;
};
//被适配类,想调用的接口,通过这个接口来拓展功能
class DogAdaptee
{
public:
void wang() { cout << "wang wang" << endl; }
void run() { cout << "run run" << endl; }
};
//适配器类
class DogAdapter :public RobotTarget, public DogAdaptee
{
public:
virtual void cry() { wang(); }//完成对原有接口cry的改造,调用的是wang方法
virtual void move() { run(); }
};
void test02()
{
//通过适配器对象来调用原有接口,实现新的功能
RobotTarget* adapter = new DogAdapter();
adapter->cry();
adapter->move();
delete adapter;
}
2.对象适配器
//对象适配器
class DogAdapter_obj :public RobotTarget
{
DogAdaptee* mdog;//被适配类作为类属性
public:
DogAdapter_obj(DogAdaptee* dog) :mdog(dog) {};
virtual void cry() { mdog->wang(); }//完成对原有接口cry的改造,调用的是wang方法
virtual void move() { mdog->run(); }
};
void test03()
{
DogAdaptee* dog = new DogAdaptee();
RobotTarget* adapter_obj = new DogAdapter_obj(dog);
adapter_obj->cry();
adapter_obj->move();
delete dog;
delete adapter_obj;
}
3.总结
在类适配器模式中,适配器与被适配者之间是继承关系。在对象适配器模式中,适配器与被适配者之间是关联关系;不论是类适配器还是对象适配器,适配器模式都将现有接口转化为客户类所期望的接口,实现了对现有类的复用(不用修改目标抽象类)。
推荐使用对象适配方式,因为耦合度低,符合合成复用原则。
4.适配器模式的应用场景:
在使用第三方组件时,可能需要将其接口适配为符合本系统需求的接口。
当一个系统的接口与外部系统的接口不兼容时,可以使用适配器模式进行适配。
在代码重构时,可能需要将已有代码的接口进行适配,以兼容新的接口要求。
在使用不同的类库或者框架时,可以使用适配器模式进行适配。
5.适配器模式练习
Printer和Banner类如下,分别用类适配器和对象适配器,完成Printer接口利用Banner的方法实现打印。Printer是目标类,Banner是被适配类。
//被适配类
class Banner
{
string item;
public:
Banner(string i) :item(i){}
void showWeak() { cout << "......" << item << "......" << endl; }
void showStrong() { cout << "******" << item << "******" << endl; }
};
//目标类
class Printer
{
public:
virtual void printWeak() {};
virtual void printStrong() {};
};
//类适配器
class ClassPrinter :public Banner, public Printer
{
public:
ClassPrinter(string item):Banner(item){}
virtual void printWeak() { showWeak(); }
virtual void printStrong() { showStrong(); }
};
void test04()
{
Printer* class_adapter = new ClassPrinter("我是类适配器");
class_adapter->printWeak();
class_adapter->printStrong();
delete class_adapter;
}
//对象适配器
class ObjPrinter :public Printer
{
Banner* banner;
public:
ObjPrinter(Banner* ba):banner(ba){}
virtual void printWeak() { banner->showWeak(); }
virtual void printStrong() { banner->showStrong(); }
};
void test05()
{
Banner* banner = new Banner("类适配器的Banner对象");
Printer* obj_adapter = new ObjPrinter(banner);
obj_adapter->printWeak();
obj_adapter->printStrong();
delete banner;
delete obj_adapter;
}
3.5桥接模式(代码见vs)
通过桥接模式,将一个对象的多个变化因素关联起来。
1.实例
比如这样一个项目:我们的目的是实例化一个带颜色的图形。其中包括两个因素:颜色和形状。
形状包括:圆形、长方形、三角形;
颜色包括:蓝色,黄色,橙色。
如下图:有两种方案,上面是继承方案,下面是桥接方案。继承方案就是让不同的具体图形继承抽象图形类,然后在每个具体图形类中实现具体的颜色,显然,这种方式造成了代码臃肿,每种颜色都要在每个图形中实现一遍。如果增加了新颜色,我们还要修改每个具体图形类的源代码。不满足开闭原则。
接下来看下面的桥接模式,将形状和颜色分离出来,都定义成抽象类和具体实现类,然后通过某个抽象类桥接另一个抽象类,让他们产生关系,这样就可以进行灵活的组合,
具体做法如下:
图形类抽象类作为桥接类,起到关联图形对象不同的变化因素的作用。桥接类作为抽象父类,把需要关联的变化因素作为属性,比如将颜色抽象类作为属性关联过来。具体的形状类继承图形抽象类,然后再跟具体的颜色关联起来,这样具体形状就跟具体颜色建立了联系。具体形状类的内部就可以使用不同的颜色(就是使用父类的属性)。可以实现任何颜色和任何形状的组合。
这样做的好处:后期如果我们需要扩展颜色或者形状,可以直接新增颜色的子类和形状的子类,不用修改源代码,只需要通过增加代码就可以实现。支持开闭原则。
//桥接模式
//颜色抽象类
class Color
{
public:
virtual void Paint() = 0;
};
//具体颜色:红色
class Red :public Color
{
public:
virtual void Paint() { cout << "红色的" << endl; }
};
//具体颜色:蓝色
class Blue :public Color
{
public:
virtual void Paint() { cout << "蓝色的" << endl; }
};
//形状抽象类
class Shape
{
protected:
Color* mcolor;//将颜色类桥接过来
public:
Shape(Color* color):mcolor(color){}
virtual void draw() = 0;
};
//具体形状:矩形
class Rectangle :public Shape
{
public:
Rectangle(Color* color) :Shape(color) {};//先构造父类,需要使用一个Color对象
virtual void draw() { cout << "形状是矩形,颜色是:"; mcolor->Paint(); }
};
//具体形状:圆形
class Circle :public Shape
{
public:
Circle(Color* color) :Shape(color) {};//先构造父类,需要使用一个Color对象
virtual void draw() { cout << "形状是圆形,颜色是:"; mcolor->Paint(); }
};
void test06()
{
//构造一个蓝色的矩形
Color* c = new Blue();
Shape* sh = new Rectangle(c);
sh->draw();
delete c;
delete sh;
//构造一个红色的圆形
Color* c1 = new Red();
Shape* sh1 = new Circle(c1);
sh1->draw();
delete c1;
delete sh1;
}
2. 桥接总结
常用的场景:
当我们考虑一个对象的多个因素可以动态变化的时候,考虑使用桥接模式,如上面例子中的支付方式和认证方式可能随着技术的发展不断变化,所以将他们分离出来,然后使用桥接模式联系起来,同时它们可以独立变化。可以在不修改源代码的情况下,增加新的支付方式。
优点:
抽象和实现的分离。
优秀的扩展能力,支持开闭原则。
实现细节对客户透明。(抽象与实现是如何组合的是透明的)
缺点:
客户必须知道选择哪一种类型的实现。
3.桥接模式练习
有个需求,点奶茶,奶茶容量:分为大,小杯。奶茶口味:可以添加牛奶,水果。用桥接模式实现,可以点不同容量不同口味组合的奶茶,同时方便后期更换杯子容量和口味。
//口味基类
class Taste
{
public:
virtual void addSomething() = 0;
};
//具体口味:牛奶
class MilkTaste :public Taste
{
virtual void addSomething() { cout << "加奶" << endl; }
};
//具体口味:水果
class FruitTaste :public Taste
{
virtual void addSomething() { cout << "加水果" << endl; }
};
//奶茶基类,桥接类
class Tea
{
protected:
Taste* taste;
public:
Tea(Taste* t) :taste(t) {};
virtual void orderTea(int count) = 0;
};
//小杯容量奶茶
class SmallTea :public Tea
{
public:
SmallTea(Taste* t) :Tea(t) {};
virtual void orderTea(int count)
{
cout << "下单" << count << "小杯" << endl;
taste->addSomething();
}
};
//大杯容量奶茶
class LargeTea :public Tea
{
public:
LargeTea(Taste* t) :Tea(t) {};
virtual void orderTea(int count)
{
cout << "下单" << count << "大杯" << endl;
taste->addSomething();
}
};
void test07()
{
//需要两大杯牛奶味奶茶
Taste* milk = new MilkTaste();
Tea* t = new LargeTea(milk);
t->orderTea(2);
delete milk;
delete t;
}
3.6外观模式(代码见vs)
外观模式又称为门面模式,它是一种对象结构型模式。外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观对象关联了子系统各个对象,作为自己的属性来使用它们的方法。
案例:
子系统:智能馒头机,功能有:和面,发酵,揉面,蒸;
界面:外观模式类,提供统一的接口。
//外观模式
//和面子系统
class MixDough
{
public:
void doMix() { cout << "水和面按照固定比例混合均匀,并加入发酵粉,搅拌15分钟。" << endl; }
};
//发酵子系统
class Ferment
{
public:
void doFerment() { cout << "在合适的温度下密封静置6小时" << endl; }
};
//揉面子系统
class Knead
{
public:
void doKnead() { cout << "加入面粉或者玉米粉,揉面5分钟,然后做出馒头的形状" << endl; }
};
//蒸锅子系统
class Steam
{
public:
void boilWater() { cout << "加水点火" << endl; }
void doSteam() { cout << "放入馒头,烧开后,蒸20分钟" << endl; }
};
//外观界面,让用户一键启动
class Facade
{
MixDough* mixDough;
Ferment* ferment;
Knead* knead;
Steam* steam;
public:
Facade()
{
mixDough = new MixDough();
ferment = new Ferment();
knead = new Knead();
steam = new Steam();
}
~Facade()
{
if (mixDough!=nullptr)
{
delete mixDough;
mixDough = nullptr;
}
if (ferment!=nullptr)
{
delete ferment;
ferment = nullptr;
}
if (knead!=nullptr)
{
delete knead;
knead = nullptr;
}
if (steam!=nullptr)
{
delete steam;
steam = nullptr;
}
}
void work()
{
//一键启动,完成各个子系统的调度
mixDough->doMix();
ferment->doFerment();
knead->doKnead();
steam->boilWater();
steam->doSteam();
cout << "馒头已蒸好!" << endl;
}
};
void test08()
{
Facade* f = new Facade();
f->work();
delete f;
}
总结:
优点:
根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象(相当于控制层,满足最少知道原则),它为子系统的访问提供了一个简单而单一的入口。
外观模式也是“迪米特法则”的体现,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度。
外观模式从很大程度上提高了客户端使用的便捷性,降低了系统的复杂度。使得客户端无须关心子系统的工作细节,通过外观角色即可调用相关功能。
使用场景:
当你要为一个复杂子系统提供一个简单接口时。
外观模式练习
使用外观模式,模拟代码编译的过程。
//语法检查子系统
class CSyntaxParser
{
public:
void syntaxParser() { cout << "开始语法检查..." << endl; }
};
//生成中间代码子系统
class CGenMidCode
{
public:
void genMidCode() { cout << "生成中间代码..." << endl; }
};
//生成汇编语言子系统
class CGenAssemblyCode
{
public:
void genAssemblyCode() { cout << "生成汇编语言..." << endl; }
};
//链接可执行程序子系统
class CLinkSystem
{
public:
void linkSystem() { cout << "链接生成可执行程序..." << endl; }
};
//界面类
class ComileFacade
{
CSyntaxParser* syntax;
CGenMidCode* midCode;
CGenAssemblyCode* assemblyCode;
CLinkSystem* link;
public:
ComileFacade()
{
syntax = new CSyntaxParser();
midCode = new CGenMidCode();
assemblyCode = new CGenAssemblyCode();
link = new CLinkSystem();
}
~ComileFacade()
{
if (syntax != nullptr)
{
delete syntax;
syntax = nullptr;
}
if (midCode != nullptr)
{
delete midCode;
midCode = nullptr;
}
if (assemblyCode != nullptr)
{
delete assemblyCode;
assemblyCode = nullptr;
}
if (link != nullptr)
{
delete link;
link = nullptr;
}
}
void compile()
{
syntax->syntaxParser();
midCode->genMidCode();
assemblyCode->genAssemblyCode();
link->linkSystem();
cout << "编译完成!" << endl;
}
};
void test09()
{
ComileFacade* f = new ComileFacade();
f->compile();
delete f;
}