创建型模式:主要实现单实例模式,简单工厂模式,工厂方法模式
结构型模式:主要实现适配器模式,桥接模式
创建型模式:
对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了软件的结构更加清晰,外界模块中的对象只需要知道他们共同的接口,而不清楚具体对象的实现细节,使整个系统的设计更加符合单一职责原则。
创建型模式在创建了什么,由谁创建,何时创建等方面都为软件设计者提供了尽可能答得灵活性。
- 简单工厂模式
模式结构:
简单工厂模式包含如下角色:
工厂角色
产品角色
具体产品角色
简单工厂模式属于类创建型模式,在简单工厂模式中根据自变量的不同产生不同的实例。
实现代码:
#include <iostream>
#include <memory>
using namespace std ;
//简单工厂模式
class fruit {
public :
fruit() {
}
virtual ~fruit() {};
virtual void operation() = 0;
} ;
class apple : public fruit{
public :
apple() {}
~apple() {}
void operation() {
cout << "我是一个苹果!" << endl ;
}
} ;
class grape : public fruit {
public :
grape() {
}
~grape() {
}
void operation() {
cout << "我是一个葡萄!" << endl ;
}
} ;
class factory {
public :
static shared_ptr<fruit> getPerson(int permission) {
if(permission == 0) {
//此处不能是实例化一个对象并返回,意思是例如 返回值改成person 这里boss bos; return bos
//只能以引用的形式返回
shared_ptr<apple>per =shared_ptr<apple>(new apple);
return per ;
}
else {
shared_ptr<grape>per =shared_ptr<grape>(new grape);
return per ;
}
}
} ;
int main() {
cout << "生产水果...." << endl ;
shared_ptr<fruit> bos = factory :: getPerson(0) ;
bos->operation() ;
shared_ptr<fruit> emp = factory :: getPerson(1) ;
emp->operation() ;
return 0;
}
其中苹果和葡萄属于水果的子类,在工厂类中根据传进来的参数不同,生产不同的水果。
简单工厂模式的优缺点:
工厂类号有必要的判断逻辑,可以决定在什么时候创建哪一个产品类,客户端可以免除直接创建工厂产品的对象的责任,而仅仅消费产品,简单工厂通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。
由于工厂类集成了所有产品的创建逻辑,一旦不能正常工作,整个系统都受到影响。
系统扩展困难,一旦添加产品就不得不修改工厂逻辑,在产品较多的时候,可能造成工厂逻辑过于复杂,不利于维护和扩展。
使用环境:
- 工厂方法模式
模式结构:
每个要生产的产品都由专门的工厂来生产。
工厂方法模式是简单工厂模式的进一步推广和抽象,由于使用了面向对象的多态性,工厂方法模式保持了简单工厂的优点,克服了缺点,在工厂方法模式中,核心的工厂类不在负责所有产品的创建,而是将具体创建的职责交给子类去做,这个核心类仅仅负责给出具体工厂必须实现的接口,而不是负责那个产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。
模式结构:
抽象产品
具体产品
抽象工厂
具体工厂
代码举例:
#include <iostream>
#include <memory>
using namespace std ;
//工厂方法模式
class tv ;
class changhong_tv ;
class haier_tv ;
class tv_factory ;
class changhong_factory ;
class tv {
public :
tv() {}
virtual ~tv() {} ;
virtual void play()= 0 ;
} ;
class haier_tv : public tv{
public :
haier_tv() {}
~haier_tv() {}
void play() {
cout << "我是海尔电视" << endl ;
}
} ;
class changhong_tv : public tv {
public :
changhong_tv() {}
~changhong_tv() {}
void play() {
cout << "我是长虹电视" << endl ;
}
} ;
class tv_factory {
public :
tv_factory() {}
virtual ~tv_factory() {}
virtual shared_ptr<tv> produce() = 0;
} ;
class haier_factory : public tv_factory {
public :
shared_ptr<tv> produce() {
cout << "生产海尔电视机" << endl ;
shared_ptr<haier_tv> haier = shared_ptr<haier_tv>(new haier_tv) ;
return haier ;
}
} ;
class changhong_factory : public tv_factory {
public :
shared_ptr<tv> produce() {
cout << "生产长虹电视机" << endl ;
shared_ptr<changhong_tv> ctv = shared_ptr<changhong_tv>(new changhong_tv) ;
return ctv ;
}
} ;
int main() {
shared_ptr<tv_factory>tf = shared_ptr<haier_factory>(new haier_factory) ;
shared_ptr<tv> htv = tf->produce() ;
htv->play() ;
tf = shared_ptr<changhong_factory>(new changhong_factory) ;
shared_ptr<tv> ctv = tf->produce() ;
ctv->play() ;
return 0;
}
工厂方法模式模式的优缺点:
在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无需关心创建细节,甚至无需知道具体产品类的类名。
基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,就正是因为所有的具体工厂类都具有同一抽象父类。
使用工厂方法模式的另一个优点是在系统中加入新产品时,无需修改抽象工厂和抽象产品提供的接口,无需修改客户端,也无需修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
- 单例模式
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。
一个更好的解决办法是让类自身负责保存它的惟一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。
单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式包含的角色只有一个,就是单例类——Singleton。单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法,该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。
饿汉式单例类在自己被加载时就将自己实例化。单从资源利用效率角度来讲,这个比懒汉式单例类稍差些。从速度和反应时间角度来讲,则比懒汉式单例类稍好些。
懒汉式单例类在实例化时,必须处理好在多个线程同时首次引用此类时的访问限制问题,特别是当单例类作为资源控制器,在实例化时必然涉及资源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次引用此类的机率变得较大,需要通过同步化机制进行控制。
#include <iostream>
#include <memory>
using namespace std ;
//懒汉模式
//单例类的责任过重,在一定程度上违背了单一职责原则
//因为单例类即充当了工厂角色,提供了工厂方法,同时又充当了
//产品角色,包含一些业务方法,将产品的创建和产品的本身功能融合
//在一起
class AA {
public :
static shared_ptr<AA> get_instance() {
if(aa == nullptr) {
aa = shared_ptr<AA>(new AA) ;
}
return aa ;
}
void print() {
cout<< "我就是单实例" << endl ;
}
~AA() {}
private :
AA() {}
static shared_ptr<AA> aa ;
} ;
shared_ptr<AA> AA :: aa = nullptr ;
int main()
{
shared_ptr<AA>aa = AA::get_instance() ;
aa->print() ;
return 0;
}
结构型模式
结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。
结构型模式可以分为类结构型模式和对象结构型模式:
类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。
- 适配器模式
在软件开发中采用类似于电源适配器的设计和编码技巧被称为适配器模式。
通常情况下,客户端可以通过目标类的接口访问它所提供的服务。有时,现有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是因为现有类中方法名与目标类中定义的方法名不一致等原因所导致的。
在这种情况下,现有的接口需要转化为客户类期望的接口,这样保证了对现有类的重用。如果不进行这样的转化,客户类就不能利用现有类所提供的功能,适配器模式可以完成这样的转化。
在适配器模式中可以定义一个包装类,包装不兼容接口的对象,这个包装类指的就是适配器(Adapter),它所包装的对象就是适配者(Adaptee),即被适配的类。
适配器提供客户类需要的接口,适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器可以使由于接口不兼容而不能交互的类可以一起工作。这就是适配器模式的模式动机。
适配器模式包含如下角色:
Target:目标抽象类
Adapter:适配器类
Adaptee:适配者类
Client:客户类
实现代码:
#pragma once
#include <iostream>
#include <memory>
using namespace std ;
class adaptee ;
class target ;
class adapter ;
class adaptee {
public :
adaptee() {
cout << "创建了适配器" << endl ;
}
~adaptee() {}
void specific_request() {
cout << "我是适配器接口" << endl ;
}
} ;
class target {
public :
target() {}
virtual ~target() {}
virtual void request() = 0;
} ;
class adapter :public target{
public :
adapter():adt(nullptr) {}
~adapter() {}
void set_adaptee(shared_ptr<adaptee>adt) { this->adt = adt;}
void request() ;
private :
shared_ptr<adaptee> adt ;
};
#include "shi_pei_qi.h"
void adapter::request() {
shared_ptr<adaptee>adt(new adaptee) ;
if(adt ==nullptr) {
cout << "还没注册适配器" << endl ;
return ;
}
adt->specific_request() ;
}
int main() {
//创建一个父类
shared_ptr<target>tar(new adapter) ;
tar->request() ;
return 0;
}
多个适配器和和适配类
#pragma once
#include <vector>
#include <iostream>
#include <memory>
#include <string>
using namespace std ;
//加密适配器
class NewCipher ;
class Caesar ;
class DataOperator ;
class CipherAdapter ;
class NewCipherAdapter ;
class Target {
} ;
class DataOperator {
public :
DataOperator() {}
virtual ~DataOperator() {}
virtual string doEncrypt(int key, string ps) {return "" ;};
void setPassword(string pass) { password = pass ;}
string getPassword() { return password ;}
private :
string password ;
} ;
//原来的加密适配器
class CipherAdapter : public DataOperator{
public :
void setCeasar(shared_ptr<Caesar>cs) {
cae = cs ;
}
CipherAdapter() {}
string doEncrypt(int key, string ps) ;
private :
shared_ptr<Caesar> cae ;
} ;
class Caesar {
public :
string doEncrypt(int key, string ps) {
cout << "旧的加密适配器" << endl ;
cout << key << " " << ps << endl ;
return to_string(key)+"------->" +ps ;
}
} ;
//新的加密适配器
class NewCipherAdapter : public DataOperator{
public :
NewCipherAdapter() {}
string doEncrypt(int key, string ps) ;
private :
shared_ptr<NewCipher> nc ;
} ;
class NewCipher {
public :
string doEncrypt(int key, string ps) {
cout << "新的适配器" << endl ;
cout << key << " " << ps << endl ;
return to_string(key)+"============>"+ps ;
}
} ;
//旧的加密适配器系统
string CipherAdapter:: doEncrypt(int key, string ps) {
cae = make_shared<Caesar>() ;
string keys = cae->doEncrypt(key, ps) ;
return keys ;
}
string NewCipherAdapter:: doEncrypt(int key, string ps) {
nc = make_shared<NewCipher>() ;
string keys = nc->doEncrypt(key, ps) ;
return keys ;
}
int main() {
shared_ptr<DataOperator> dd = make_shared<CipherAdapter>() ;
//为target类设置密码
dd->setPassword("hello") ;
//使用原来的适配器
string res = dd->doEncrypt(123, dd->getPassword()) ;
cout << "旧的加密结果!\n" << res << endl ;
dd = make_shared<NewCipherAdapter>() ;
dd->setPassword("hello") ;
res = dd->doEncrypt(123, dd->getPassword()) ;
cout << "新的加密结果!" << endl ;
cout << res << endl ;
return 0;
}
模式优缺点
类适配器模式还具有如下优点:
由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
类适配器模式的缺点如下:
Java不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。
在以下情况下可以使用适配器模式:
系统需要使用现有的类,而这些类的接口不符合系统的需要。
想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
- 桥接模式
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
代码实现:
#pragma once
#include <iostream>
#include <memory>
using namespace std ;
class RefinedAbstration ;
class Abstraction ;
class Implement ;
//接口
class Implement {
public :
Implement() {}
virtual ~Implement() {} ;
virtual void operationImpl() =0 ;
};
//实现接口A
class ConcretImplementorA : public Implement {
public :
void operationImpl() {
cout << "具体实现A" << endl ;
}
} ;
//实现接口B
class ConcretImplementorB : public Implement {
public :
void operationImpl() {
cout << "具体实现B" << endl ;
}
} ;
//抽象类
class Abstraction {
public :
Abstraction() {}
virtual ~Abstraction() {}
virtual void operation() = 0 ;
void getConcretImp(int flag) ;
protected :
shared_ptr<Implement> impl ;
};
//子类
class RefinedAbstration:public Abstraction{
public :
void operation() {
cout << "具体类调用具体实现的操作!" << endl ;
impl->operationImpl() ;
}
} ;
#include "bagage.h"
void Abstraction:: getConcretImp(int flag) {
if(flag == 1) {
impl = make_shared<ConcretImplementorA>() ;
}
else {
impl = make_shared<ConcretImplementorB>() ;
}
}
int main() {
shared_ptr<Abstraction> abs = make_shared<RefinedAbstration>() ;
abs->getConcretImp(1) ;
abs->operation() ;
abs->getConcretImp(2) ;
abs->operation() ;
}
多个画笔可以对应不同种颜色:
实例代码:
#pragma once
#include <iostream>
#include <memory>
using namespace std ;
class SmallPen ;
class BigPen ;
class MiddlePen ;
class Color ;
class Pen {
public :
Pen() {}
virtual ~Pen() {}
void getPenColor(int flag) ;
virtual void draw() = 0;
protected :
shared_ptr<Color>col ;
} ;
class SmallPen : public Pen {
public:
void draw() ;
} ;
class BigPen : public Pen {
public:
void draw() ;
};
class MiddlePen : public Pen {
public :
void draw() ;
};
class Color {
public :
Color() {}
virtual ~Color(){}
virtual void bepaint() = 0;
};
class Red : public Color {
public :
void bepaint() {
cout << "红笔" << endl ;
}
};
class Blue : public Color{
public :
void bepaint() {
cout << "蓝笔" << endl ;
}
} ;
class Black : public Color{
public :
void bepaint() {
cout << "黑笔" << endl ;
}
} ;
#include "mao_bi.h"
void Pen :: getPenColor(int flag) {
switch(flag) {
case 1:
col = make_shared<Red>() ;
break ;
case 2:
col = make_shared<Blue>() ;
break ;
default :
col = make_shared<Black>() ;
break ;
}
}
void SmallPen :: draw() {
cout << "小笔" << endl ;
col->bepaint() ;
}
void BigPen :: draw() {
cout << "粗笔" << endl ;
col->bepaint() ;
}
void MiddlePen:: draw() {
cout << "中性笔" << endl ;
col->bepaint() ;
}
int main() {
shared_ptr<Pen> pen = make_shared<SmallPen>() ;
pen->getPenColor(1) ;
pen->draw() ;
pen->getPenColor(1) ;
pen->draw() ;
shared_ptr<Pen> pen1 = make_shared<MiddlePen>() ;
pen1->getPenColor(1) ;
pen1->draw() ;
pen1->getPenColor(3) ;
pen1->draw() ;
shared_ptr<Pen> pen2 = make_shared<BigPen>() ;
pen2->getPenColor(2) ;
pen2->draw() ;
pen2->getPenColor(1) ;
pen2->draw() ;
return 0;
}
桥接模式的缺点
桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
模式适用环境
在以下情况下可以使用桥接模式:
如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。