设计模式---(简单工厂模式,工厂模式,抽象工程模式),单例模式,代理模式,装饰器
简单工厂模式
简单工厂模式并不属于GoF的23种设计模式。
那么为什么我要用工厂模式呢?请看下面的一段程序。
#include <iostream> using namespace std; class Fruit { public: Fruit(string name) { this-‐>name = name; if (name == "apple") { //代表苹果 } else if (name == "banana") { //2 代表⾹香蕉 } else if (name == "pear") { //3 代表鸭梨 } } void getName() { if (name == "apple") { cout << "我是苹果" << endl; } else if (name == "banana") { cout << "我是⾹香蕉" << endl; } else if (name == "pear") { cout << "我是鸭梨" << endl; } } private: int kind; string name; }; int main(void) { Fruit apple = Fruit("apple"); Fruit banana = Fruit("banana"); Fruit pear = Fruit("pear"); apple.getName(); banana.getName(); pear.getName(); return 0; }
不难看出,Fruit类是一个“巨大的”类,在该类的设计中存在如下几个问题:
(1) 在Fruit类中包含很多“if…else…”代码块,整个类的代码相当冗长,
代码越长,阅读难度、维护难度和测试难度也越大;而且大量条件语句的存在
还将影响系统的性能,程序在执行过程中需要做大量的条件判断。
(2) Fruit类的职责过重,它负责初始化和显示所有的水果对象,将各种水
果对象的初始化代码和显示代码集中在一个类中实现,违反了“单一职责原
则”,不利于类的重用和维护;
(3) 当需要增加新类型的水果时,必须修改Fruit类的源代码,违反了“开
闭原则”。
模式中的角色和职责
工厂(Factory)角色:简单工厂模式的核心,它负责实现创建所有实例
的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
抽象产品(AbstractProduct)角色:简单工厂模式所创建的所有对象的
父类,它负责描述所有实例所共有的公共接口。
具体产品(Concrete Product)角色:简单工厂模式所创建的具体实例
对象。
#include <iostream> using namespace std; /* 定义一个水果抽象类,供具体水果实现,和工厂使用 */ class Fruit { public: virtual void getName() = 0; }; class Apple :public Fruit { public : virtual void getName() { cout << "我是苹果" << endl; } }; class Banana : public Fruit { public: virtual void getName() { cout << "我是香蕉" << endl; } }; /* 现在添加一个鸭梨,那么只需要再创建一个鸭梨类 来实现抽象水果类即可。 */ ?20 class Pear :public Fruit { public: virtual void getName() { cout << "我是鸭梨" << endl; } }; class Factory { public : Fruit * createFruit(string name) { if (name == "apple") { return new Apple; } else if (name == "banana") { return new Banana; } /* 但是,当我们创建鸭梨的时候,Factory却变得越加庞大了 这样就不符合了开闭原则。 */ else if (name == "pear") { return new Pear; } } }; int main(void) { Factory * factory = new Factory; Fruit * fruit = NULL; /* 我们用工厂创建一个具体水果类,返回抽象类指针 那么这样就会发生多态。 此时抽象类指针指向了具体的子类,那么调用抽象方法 就会调用子类的实现方法。 */ fruit = factory-‐>createFruit("apple"); fruit-‐>getName(); delete fruit; fruit = NULL; fruit = factory-‐>createFruit("banana"); fruit-‐>getName(); delete fruit; fruit = NULL; fruit = factory-‐>createFruit("pear"); fruit-‐>getName(); delete fruit; fruit = NULL; delete factory; return 0; }
优点:
1. 实现了对象创建和使用的分离。
2. 不需要记住具体类名,记住参数即可,减少使用者记忆量。
缺点:
1. 对工厂类职责过重,一旦不能工作,系统受到影响。
2. 增加系统中类的个数,复杂度和理解度增加。
3. 违反“开闭原则”,添加新产品需要修改工厂逻辑,工厂越来越复杂。
适用场景
1. 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂
方法中的业务逻辑太过复杂。
2. 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。
工厂方法模式
工厂方法模式中的角色和职责
抽象工厂(Abstract Factory)角色:工厂方法模式的核心,任何工厂类
都必须实现这个接口。
工厂(Concrete Factory)角色:具体工厂类是抽象工厂的一个实现,
负责实例化产品对象。
抽象产品(Abstract Product)角色:工厂方法模式所创建的所有对象
的父类,它负责描述所有实例所共有的公共接口。
具体产品(Concrete Product)角色:工厂方法模式所创建的具体实例对象。
工厂方法模式的案例
#include <iostream> using namespace std; /* 创建一个水果抽象类,供具体的水果实现,和抽象工厂返回 */ class Fruit { public: virtual void getName() = 0; }; /* 创建一个工厂抽象类,供具体的水果工厂实现,并且返回抽象水果类 */ class AbstractFactory { public: virtual Fruit * createFruit() = 0; }; class Apple :public Fruit { public: virtual void getName() { cout << "我是苹果" << endl; } }; class Banana : public Fruit { public: virtual void getName() { cout << "我是⾹香蕉" << endl; } }; //实现了抽象工厂,并且返回⼀一个抽象水果指针 class AppleFactory :public AbstractFactory { public: virtual Fruit * createFruit() { return new Apple; } }; class BananaFactory : public AbstractFactory { public: virtual Fruit *createFruit() { return new Banana; } }; /* 此时,如果要想添加一种 鸭梨 那么 不需要再去修改其中任何一个类, 而只需要实现一个鸭梨工厂,和一个鸭梨具体类 */ class Pear : public Fruit { public: virtual void getName() { cout << "我是鸭梨" << endl; } }; class PearFactory : public AbstractFactory { public: virtual Fruit *createFruit() { return new Pear; } }; int main(void) { /* 根据依赖倒转原则, 是针对接⼝口的编程, 那么,何为针对接口? 就是 我们只需要 使用 抽象工厂类的指针,和抽象水果类的指针, 通过多态的特性,就可以搞定完成具体类的业务。 */ AbstractFactory *abFactory = NULL; Fruit *fruit = NULL; /* 抽象类指针,完成Apple 业务 */ abFactory = new AppleFactory; fruit = abFactory-‐>createFruit(); fruit-‐>getName(); delete abFactory; delete fruit; /* ?25 抽象类指针,完成Banana 业务 */ abFactory = new BananaFactory; fruit = abFactory-‐>createFruit(); fruit-‐>getName(); delete abFactory; delete fruit; /* 抽象类指针,完成Banana 业务 */ abFactory = new PearFactory; fruit = abFactory-‐>createFruit(); fruit-‐>getName(); delete abFactory; delete fruit; return 0; }
优点:
1. 不需要记住具体类名,甚至连具体参数都不用记忆。
2. 实现了对象创建和使用的分离。
3. 系统的可扩展性也就变得非常好,无需修改接口和原类。
缺点:
1. 增加系统中类的个数,复杂度和理解度增加。
2. 增加了系统的抽象性和理解难度。
适用场景
1. 客户端不知道它所需要的对象的类。
2. 抽象工厂类通过其子类来指定创建哪个对象。
抽象工厂模式
工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责
太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致
系统中存在大量的工厂类,势必会增加系统的开销。此时,我们可以考虑将一
些相关的产品组成一个“产品族”,由同一个工厂来统一生产,这就是我们本
文将要学习的抽象工厂模式的基本思想。
抽象工厂(Abstract Factory)角色:它声明了一组用于创建一族产品的
方法,每一个方法对应一种产品。
具体工厂(Concrete Factory)角色:它实现了在抽象工厂中声明的创
建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品
都位于某个产品等级结构中。
抽象产品(Abstract Product)角色:它为每种产品声明接口,在抽象
产品中声明了产品所具有的业务方法。
具体产品(Concrete Product)角色:它定义具体工厂生产的具体产品
对象,实现抽象产品接口中声明的业务方法。
抽象工厂模式案例
#include <iostream> using namespace std; //苹果抽象类,供具体产地苹果实现 class AbstractApple { public : virtual void getName() = 0; }; //香蕉抽象类,供具体产地香蕉实现 class AbstractBanana { public : virtual void getName() = 0; }; class ChinaApple : public AbstractApple { public: virtual void getName() { cout << "中国苹果" << endl; } }; class USAApple : public AbstractApple { public : virtual void getName() { cout << "美国苹果" << endl; } }; class ChinaBanana : public AbstractBanana { public : virtual void getName() { cout << "中国⾹香蕉" << endl; } }; class USABanana : public AbstractBanana { public: virtual void getName() { cout << "美国⾹香蕉" << endl; } }; //抽象的工厂类,供具体产品族的工厂实现 class AbstractFactory { public: virtual AbstractApple * createApple() = 0; virtual AbstractBanana * createBanana() = 0; }; class ChinaFactory : public AbstractFactory { public: ?30 virtual AbstractApple * createApple() { return new ChinaApple; } virtual AbstractBanana * createBanana() { return new ChinaBanana; } }; class USAFactory : public AbstractFactory { public: virtual AbstractApple * createApple() { return new USAApple; } virtual AbstractBanana * createBanana() { return new USABanana; } }; int main(void) { //现在准备 要一个 中国的苹果,中国的香蕉 AbstractApple *apple = NULL; AbstractBanana *banana = NULL; AbstractFactory *factory = NULL; factory = new ChinaFactory; //中国的苹果 apple = factory-‐>createApple(); //中国的香蕉 banana = factory-‐>createBanana(); apple-‐>getName(); banana-‐>getName(); delete apple; delete banana; delete factory; return 0; }
1. 拥有工厂方法模式的优点
2. 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端
始终只使用同一个产品族中的对象。
3 增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点:
1. 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需
要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”
适用场景
(1) 系统中有多于一个的产品族。而每次只使用其中某一产品族。可以通过
配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产
品族。
(2) 产品等级结构稳定。设计完成之后,不会向系统中增加新的产品等级结
构或者删除已有的产品等级结构。
单例模式
保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局
访问方法。
(GoF给出单例模式的定义)
三个要点:
一是某个类只能有一个实例;
二是它必须自行创建这个实例;
三是它必须自行向整个系统提供这个实例。
模式中的角色和职责
Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供
一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止
在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个
Singleton类型的静态对象,作为外部共享的唯一实例。
单例模式的使用步骤:
a) 构造函数私有化。
b) 提供一个全局的静态方法(全局访问点)来获取单例对象。
c) 在类中定义一个静态指针,指向本类的变量的静态变量指针 。
#include <iostream> using namespace std; /* 懒汉模式 */ class Singelton { public: /* 对外提供⼀一个全局静态方法 */ static Singelton * getInstance() { /* 懒汉式: 在调用全局静态方法,获取单例的时候, 再进行创建。懒汉式的单例创建在程序的执行中进行。 */ if (instance == NULL) {
//线程中加锁 instance = new Singelton; }
//解锁 m_count++; return instance; } int getCount() { return m_count; } private: /* 构造函数私有化 */ Singelton() { instance = NULL; m_count = 0; cout << "构造函数 singelton() 执行" << endl; } /* 在类中定义一个静态指针,指向本类的变量的静态变量指针 */ static Singelton *instance; static int m_count; }; /* 对静态变量的初始化,要放在类的外部, 即,放在全局位置上。 */ Singelton * Singelton::instance = NULL; int Singelton::m_count = 0; /* 饿汉模式 */ class Singelton2 { public: static Singelton2* getInstance() { m_count++; return instance; } int getCount() { return m_count; } private: Singelton2() { instance = NULL; m_count = 0; } static Singelton2 * instance; static int m_count; }; /* 饿汉式不是在全局静态方法中创建, 而是不管你创不创建实例,我在声明的 时候就创建出来, 饿汉式的单例,是在编译的时候就已经创建好了。 */ Singelton2 * Singelton2::instance = new Singelton2; int Singelton2::m_count = 0; int main(void) { Singelton * singer = Singelton::getInstance(); cout << singer-‐>getCount() << endl; Singelton * singer2 = Singelton::getInstance(); cout << singer2-‐>getCount() << endl; if (singer == singer2) { cout << "二者是同⼀一个实例" << endl; } else { cout << "二者不是同一个实例" << endl; } cout << "-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐ 以下 是 饿汉式 -‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐" << endl; Singelton2 * singer3 = Singelton2::getInstance(); cout << singer3-‐>getCount() << endl; Singelton2 * singer4 = Singelton2::getInstance(); cout << singer4-‐>getCount() << endl; if (singer3 == singer4) { cout << "⼆二者是同⼀一个实例" << endl; } else { cout << "⼆二者不是同⼀一个实例" << endl; } return 0; }
思考:
当单例模式中的懒汉模式遇见多线程,改怎么办?加锁
单例模式的优缺点
优点:
(1) 单例模式提供了对唯一实例的受控访问。
(2) 节约系统资源。由于在系统内存中只存在一个对象。
缺点:
(1) 扩展略难。单例模式中没有抽象层。
(2) 单例类的职责过重。
3.4.4 适用场景
(1) 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器
或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
(2) 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问
点,不能通过其他途径访问该实例。
代理模式
Proxy模式又叫做代理模式,是构造型的设计模式之一,它可以为其他对象
提供一种代理(Proxy)以控制对这个对象的访问。
所谓代理,是指具有与代理元(被代理的对象)具有相同的接口的类,
客户端必须通过代理与被代理的目标类交互,而代理一般在交互的过程中(交
互前后),进行某些特别的处理。
模式中的角色和职责
subject(抽象主题角色):真实主题与代理主题的共同接口。
RealSubject(真实主题角色):定义了代理角色所代表的真实对象。
Proxy(代理主题角色):含有对真实主题角色的引用,代理角色通常在
将客户端调用传递给真是主题对象之前或者之后执行某些操作,而不是单纯返
回真实的对象。
4.1.2 代理模式的案例
#include <iostream> #include <string> using namespace std; class Item { public: Item(string kind, bool fact) { this-‐>kind = kind; this-‐>fact = fact; } //物品类别 string kind; //是否真伪? bool fact; }; /* 抽象一种购物⽅方式,具有买功能。 */ class Shopping { public: virtual void buy(Item & item) = 0; }; /* 基于抽象类,实现一种具体的购物模式,去韩国购物 */ class KoreaShopping : public Shopping { public: virtual void buy(Item & item) { cout << "去韩国进⾏行了购物, 买了" << item.kind << endl; } }; /* 基于抽象类,实现⼀一种具体的购物模式,去美国购物 */ class USAShopping : public Shopping { public: virtual void buy(Item & item) { cout << "去美国进⾏行了购物, 买了" << item.kind << endl; } }; /* 基于抽象类,实现⼀一种具体的购物模式,去⾮非洲购物 */ class AfrikaShopping : public Shopping { public: virtual void buy(Item & item) { cout << "去非洲进行了购物, 买了" << item.kind << endl; } }; /* 有一个海外代购 代理,同样实现了购物模式, 而且增加了 办理护照 和 货物海关安检等 具体业务。 */ class OverseasProxy : public Shopping { public: OverseasProxy(Shopping * shopping) { this-‐>shopMode = shopping; } ~OverseasProxy() { delete this-‐>shopMode; } //调⽤用 真实 购物模式 virtual void buy(Item & item) { if (distinguish(item) == true) { this-‐>shopMode-‐>buy(item); check(item); } else { cout << "发现伪货["<<item.kind<<"],不能购买" << endl; } } //辨别物品真伪 bool distinguish(Item & item) { cout << "对物品[" <<item.kind<<"]辨别真伪. " << endl; return item.fact; } //海关安检 void check(Item &item) { cout << "通过海关安检,带回国内" << endl; } private: Shopping * shopMode; }; int main(void) { // 有⼏几种类型的物品 Item t1("化妆品", true); Item t2("CET4证书", false); Item t3("addass运动鞋", true); Shopping *shopping = NULL; OverseasProxy * proxy = NULL; //现在想要买这⼏几种物品 //1. 去韩国买 化妆品 proxy = new OverseasProxy(new KoreaShopping); proxy-‐>buy(t1); delete proxy; //2 去美国买CET4 证书 proxy = new OverseasProxy(new USAShopping); proxy-‐>buy(t2); delete proxy; //3 去⾮非洲买addass proxy = new OverseasProxy(new AfrikaShopping); proxy-‐>buy(t3); delete proxy; return 0; }
代理模式的种类
(1) 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一
个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另
一台主机中,远程代理又称为大使(Ambassador)。
(2) 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先
创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
(3) 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用
户提供不同级别的使用权限。
(4) 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空
间,以便多个客户端可以共享这些结果。
(5) 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供
一些额外的操作,例如将对象被调用的次数记录下来等。
代理模式的优缺点
优点:
(1) 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
(2) 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源
代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
缺点:
(1) 代理实现较为复杂。
适用场景
为其他对象提供一种代理以控制对这个对象的访问。
装饰模式
装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,
就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对
象结构型模式。
装饰模式中的角色和职责
Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声
明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理
未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
ConcreteComponent(具体构件):它是抽象构件类的子类,用于定
义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额
外的职责(方法)。
Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件
增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引
用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,
以达到装饰的目的。
ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向
构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在
抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
4.2.2 装饰器案例
#include <iostream> #include <string> using namespace std; //一个抽象的构件, 他是具体构件和抽象装饰类的父类。 class Phone { public: virtual void show() = 0; }; //具体的构件 class iPhone : public Phone { public: iPhone(string kind) { this-‐>kind = kind; } virtual void show() { cout << "秀出了 iPhone-‐"<<kind<<":" <<endl; } private: string kind; }; //具体的构件 class Mi :public Phone { public: Mi(string kind) { this-‐>kind = kind; } virtual void show() { cout << "秀出了 Mi-‐" << kind << ":" << endl; } private: string kind; }; //抽象的手机装饰器类,必须包含抽象的构件 class DecoratorPhone : public Phone { public: DecoratorPhone() { } DecoratorPhone(Phone *phone) { this-‐>phone = phone; } virtual void show()=0;private: Phone *phone; }; //具体的装饰器 class DecoratorPhoneMo :public DecoratorPhone { public: DecoratorPhoneMo(Phone *phone){ this-‐>phone = phone; } virtual void show() { this-‐>phone-‐>show(); AddMo(); } void AddMo() { cout << "装饰:手机贴膜" << endl; } private: Phone *phone; }; //具体的装饰器 class DecoratorPhoneTao :public DecoratorPhone { public: DecoratorPhoneTao(Phone *phone){ this-‐>phone = phone; } virtual void show() { this-‐>phone-‐>show(); AddTao(); } void AddTao() { cout << "装饰:⼿手机外套" << endl; } private: Phone *phone; }; int main(void) { Phone * phone = NULL; DecoratorPhone * hasMophone = NULL; DecoratorPhone * hasTaophone = NULL; DecoratorPhone * hasMoTaophone = NULL; //定义⼀一个iphone 6 plus phone = new iPhone("6 plus"); //给普通iphone 加上贴膜 hasMophone = new DecoratorPhoneMo(phone); //给普通iphone 加上⽪皮套 hasTaophone = new DecoratorPhoneTao(phone); hasMophone-‐>show(); hasTaophone-‐>show(); //给有⽪皮套的iphone 加上贴膜 hasMoTaophone = new DecoratorPhoneMo(hasTaophone); hasMoTaophone-‐>show(); delete hasTaophone; delete hasMophone; delete hasMoTaophone; delete phone; //定义⼀一个⼩小⽶米4s phone = new Mi("4s"); //给普通⼩小⽶米4s 加上贴膜 hasMophone = new DecoratorPhoneMo(phone); //给普通⼩小⽶米4s 加上⽪皮套 hasTaophone = new DecoratorPhoneTao(phone); hasMophone-‐>show(); hasTaophone-‐>show(); ?45 //给有贴膜的⼩小⽶米4s 加上⽪皮套 hasMoTaophone = new DecoratorPhoneTao(hasMophone); hasMoTaophone-‐>show(); delete hasTaophone; delete hasMophone; delete hasMoTaophone; delete phone; return 0; }
装饰模式的优缺点
优点:
(1) 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数
急剧增加。
(2) 可以通过一种动态的方式来扩展一个对象的功能,从而实现不同的行为。
(3) 可以对一个对象进行多次装饰。
(4) 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构
件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。
缺点:
(1) 使用装饰模式进行系统设计时将产生很多小对象,大量小对象的产生势必会
占用更多的系统资源,影响程序的性能。
适用场景
(1) 动态、透明的方式给单个对象添加职责。
(2) 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展
和维护时可以使用装饰模式。
装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对
对象的访问。换句话 说,用代理模式,代理类(proxy class)可以对它的客
户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个
代理类中创建一个对象的实例。并且,当我们使用装饰器模 式的时候,我们通
常的做法是将原始对象作为一个参数传给装饰者的构造器。