- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
- 单例模式
1.简单工厂模式
简单工厂模式并不属于23种设计模式。
class Fruit
{
public:
Fruit(string kind){
this->kind = kind;
if(kind == "apple"){
//代表苹果
//苹果的初始化方式
}
else if(kind == "banana"){
//代表香蕉
//香蕉的初始化方式
}
}
void getName(){
if(this->kind == "apple"){
cout<<"我是苹果"<<endl;
}
else if(this->kind == "banana"){
cout<<"我是香蕉"<<endl;
}
}
private:
string kind;
};
int main(){
//创建一个苹果
Fruit *apple = new Fruit("apple");
apple->getName();
delete apple;
//main函数跟Fruit类的构造函数耦合度高,随着水果种类的增加 构造函数越来越复杂
return 0;
}
不难看出,Fruit类是一个“巨大的”类,在该类的设计中存在如下几个问题:
(1) 在Fruit类中包含很多“if…else…”代码块,整个类的代码相当冗长,代码越长,阅读难度、维护难度和测试难度也越大;而且大量条件语句的存在还将影响系统的性能,程序在执行过程中需要做大量的条件判断。
(2) Fruit类的职责过重,它负责初始化和显示所有的水果对象,将各种水果对象的初始化代码和显示代码集中在一个类中实现,违反了“单一职责原则”,不利于类的重用和维护;
(3) 当需要增加新类型的水果时,必须修改Fruit类的源代码,违反了“开闭原则”。
1.1模式中的角色和职责
工厂(Factory)角色:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
抽象产品(AbstractProduct)角色:简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
具体产品(Concrete Product)角色:简单工厂模式所创建的具体实例对象。
1.2简单工厂模式案例
2.pngclass 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;
}
};
//添加一个新产品 鸭梨
class Pear:public Fruit
{
public:
virtual void getName(){
cout<<"我是鸭梨"<<endl;
}
}
//工厂
class Factory
{
public:
Fruit * createFruit(string kind){
Fruit *f = NULL;
if(kind == "apple"){
f = new Apple;
}
else if(kind == "banana"){
f = new Banana;
}
//添加一个鸭梨 修改了工厂的方法 违背的开闭原则!
else if(kind == "pear"){
f= new Pear;
}
return f;
}
}
int main(void)
{
//人们是跟工厂打交道
Factory *factory = new Factory;//创建一个工厂
//给我来一个苹果
Fruit *apple = factory->createFruit("apple");
apple->getName();
Fruit *banana = factory->createFruit("banana");
banana->getName();
Fruit *pear = factory->createFruit("pear");
delete apple;
delete banana;
delete pear;
delete factory;
return 0;
}
1.3简单工厂模式的优缺点
优点:
- 实现了对象创建和使用的分离
- 不需要记住具体类名,记住参数即可,减少使用者记忆量
缺点:
- 对工厂类职责过重,一旦不能工作,系统受到影响。
- 增加系统中类的个数,复杂度和理解度增加
- 违反“开闭原则”,添加新产品需要修改工厂逻辑,工厂越来越复杂
1.4使用场景
- 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
- 客户端只知道传入工厂类的参数,对于如何创建对象并不关心
2.工厂方法模式
2.1工厂方法模式中的角色和职责
3.png抽象工厂(Abstract Factory)角色:工厂方法模式的核心,任何工厂类都必须实现这个接口。
工厂(Concrete Factory)角色:具体工厂类是抽象工厂的一个实现,负责实例化产品对象。
抽象产品(Abstract Product)角色:工厂方法模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
具体产品(Concrete Product)角色:工厂方法模式所创建的具体实例对象。
简单工厂模式 + "开闭原则" = 工厂方法模式
2.2工厂方法模式的案例
4.png#include <iostream>
#include <string>
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;
}
};
//添加一个梨产品
class Pear:public Fruit
{
public:
virtual void getName(){
cout<<"我是鸭梨"<<endl;
}
};
//抽象的工厂类------------------------------
class AbstractFactory
{
public:
virtual Fruit * createFruit() = 0;//抽象的工厂生产器
};
//苹果的工厂
class AppleFactory:public AbstractFactory
{
public:
virtual Fruit * createFruit(){
return new Apple;
}
};
//香蕉工厂
class BananaFactory:public AbstractFactory
{
public:
virtual Fruit * createFruit(){
return new Banana;
}
};
//新添加梨工厂
class PearFactory:public AbstractFactory
{
public:
virtual Fruit *createFruit(){
return new Pear;
}
};
int main(void)
{
//1.给我来一个香蕉的工厂
AbstractFactory *bananaFactory = new BananaFactory;
//2.给我一个水果
Fruit* banana = bananaFactory->createFruit();//只能够生成香蕉
banana->getName();
delete bananaFactory;
delete banana;
AbstractFactory *appleFactory = new AppleFactory;
Fruit *apple = appleFactory->createFruit();
apple->getName();
delete appleFactory;
delete apple;
AbstractFactory *pearFactory = new PearFactory;
Fruit *pear = pearFactory->createFruit();
pear->getName();
delete pear;
delete pearFactory;
return 0;
}
2.3工厂方法模式的优缺点
优点:
- 不需要记住具体类名,甚至连具体参数都不用记忆
- 实现了对象创建和使用的分离
- 系统的可扩展性也就变得非常好,无需修改接口和原类
缺点:
- 增加系统中类的个数,复杂度和理解度增加
- 增加了系统的抽象性和理解难度
2.5使用场景
- 客户端不知道它所需要的对象的类
- 抽象工厂类通过其子类来指定创建哪个对象
3.抽象工厂模式
工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。此时,我们可以考虑将一些相关的产品组成一个“产品族”,由同一个工厂来统一生产,这就是我们本文将要学习的抽象工厂模式的基本思想。
“工厂方法模式”实现多区域水果类图- 当我们想添加一个新产品的时候,比如葡萄,虽然不用修改代码,但是我们需要添加大量的类,而且还需要添加相对的工厂。(系统开销,维护成本)
- 如果我们使用同一地域的水果(日本🍎 ,日本🍌 ,日本🍐 ),那么我们需要分别创建具体的工厂,如果选择出现失误,将会造成混乱,虽然可以加一些约束,但是代码实现变得复杂。
3.1产品族与产品等级结构
13.2模式中的角色和职责
2- 抽象工厂(Abstract Factory)角色:它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。
- 具体工厂(Concrete Factory)角色:它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。
- 抽象产品(Abstract Product)角色:它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
- 具体产品(Concrete Product)角色:它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法。
3.3抽象工厂模式案例
3#include <iostream>
#include <string>
using namespace std;
//1.添加一个日本的工厂 和日本的苹果 和香蕉(添加一个产品族)
//针对产品族进行添加 符合开闭
//2.添加一个产品种类,鸭梨(添加一个产品等级结构)
//针对产品等级结构添加 不符合开闭
//3.考虑 1,2是否符合开闭原则
class Fruit
{
public:
virtual void getName() = 0;
};
class USAApple:public Fruit
{
public:
virtual void getName(){
cout<<"我是美国的苹果"<<endl;
}
};
class USABanana:public Fruit
{
public:
virtual void getName(){
cout<<"我是美国的香蕉"<<endl;
}
};
class USAPear:public Fruit
{
public:
virtual void getName(){
cout<<"我是美国的鸭梨"<<endl;
}
};
class CNApple:public Fruit
{
public:
virtual void getName(){
cout<<"我是中国的苹果"<<endl;
}
};
class CNBanana:public Fruit
{
public:
virtual void getName(){
cout<<"我是中国的香蕉"<<endl;
}
};
class CNPear:public Fruit
{
public:
virtual void getName(){
cout<<"我是中国的鸭梨"<<endl;
}
};
class JPApple:public Fruit
{
public:
virtual void getName(){
cout<<"我是日本的香蕉"<<endl;
}
};
class JPBanana:public Fruit
{
public:
virtual void getName(){
cout<<"我是日本的香蕉"<<endl;
}
};
class JPPear:public Fruit
{
public:
virtual void getName(){
cout<<"我是日本的鸭梨"<<endl;
}
};
//定义一个抽象的工厂,是面向产品族进行生产
class AbstractFactory
{
public:
virtual Fruit *createApple() = 0;
virtual Fruit *createBanana() = 0;
//添加一个鸭梨的生产器
virtual Fruit *createPear() = 0;
};
//美国的具体工厂
class USAFactory:public AbstractFactory
{
public:
virtual Fruit *createApple(){
return new USAApple;
}
virtual Fruit *createBanana(){
return new USABanana;
}
//添加一个美国的生产器
virtual Fruit *createPear(){
return new USAPear;
}
};
//中国的具体工厂
class CNFactory:public AbstractFactory
{
public:
virtual Fruit *createApple(){
return new CNApple;
}
virtual Fruit *createBanana(){
return new CNBanana;
}
virtual Fruit *createPear(){
return new CNPear;
}
};
//日本的具体工厂
class JPFactory:public AbstractFactory
{
public:
virtual Fruit *createApple(){
return new JPApple;
}
virtual Fruit *createBanana(){
return new JPBanana;
}
virtual Fruit *createPear(){
return new JPPear;
}
};
int main(void)
{
//想要一个美国的香蕉
//1.来一个美国的工厂
AbstractFactory *usaF = new USAFactory;
Fruit *usaBanana = usaF->createBanana();
//2.来一个香蕉
usaBanana->getName();
delete usaF;
delete usaBanana;
//想要一个中国的苹果
AbstractFactory *cnF = new CNFactory;
Fruit *cnApple = cnF->createApple();
cnApple->getName();
delete cnF;
delete cnApple;
return 0;
}
3.4抽象工厂模式的优缺点
优点:
- 拥有工厂方法模式的优点
- 当一个产品族中的多个对象呗设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象
- 增加新的产品族很方便,无需修改已有系统,符合“开闭原则”
缺点:
- 增加新的产品登记结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不变,违背了“开闭原则”。
3.5适用场景
- 系统中有多于一个的产品族。而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。
- 产品等级结构稳定。设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。
工厂三兄弟对比
简单工厂模式 + "开闭原则" = 工厂方法模式
工厂方法模式 + "产品族" = 抽象工厂方法模式简单工厂模式(规模较小的模型)
工厂方法模式(中等)
抽象工厂方法模式(复杂的产品等级和产品族)
4.单例模式
保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。
三个要点:
一是某个类只能有一个实例;
二是它必须自行创建这个实例;
三是它必须自行向整个系统提供这个实例。
4.1模式中的角色和职责
SingletonSingleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
单例模式的使用步骤:
- 构造函数私有化。
- 提供一个全局的静态方法(全局访问点)来获取单例对象。
- 在类中定义一个静态指针,指向本类的变量的静态变量指针。
#include <iostream>
using namespace std;
/*
一是某个类只能有一个实例;
二是它必须自行创建这个实例;
三是它必须自行向整个系统提供这个实例。
*/
/*
单例模式的使用步骤:
a)构造函数私有化。 //为了不让在类的外部再创建一个本类的实例
b)提供一个全局的静态方法(全局访问点)来获取单例对象。
c)在类中定义一个静态指针,指向本类的变量的静态变量指针。
*/
class Singleton
{
public:
static Singleton* getInstance(){
return instance;
}
private:
//不让类的外部再创建实例
Singleton(){};
static Singleton *instance;//指向本类位移实例的指针。 放在代码区(全局区)
};
/*
饿汉式——在编译期间就已经确定这个唯一的例子了
*/
Singleton* Singleton::instance = new Singleton;//类的内部
class Singleton2
{
public:
static Singleton2* getInstance(){
//加锁
if(instace == NULL){
instance = new Singleton2;
}
//释放锁
return instance;
}
private:
Singleton2(){}
static Singleton2 *instacne;
};
//懒汉式的初始化方式
Singleton2 *Singleton2::instance = NULL;
int main(void)
{
Singleton *s1 = Singleton::getInstance();
Singleton *s2 = Singleton::getInstance();
if(s1 == s2){
cout<<"s1 == s2"<<endl;
}
else{
cout<<"s1 != s2"<<endl;
}
Singleton2 *s3 = Singleton2::getInstance();
Singleton2 *s4 = Singleton2::getInstance();
if(s3 == s4){
cout<<"s3 == s4"<<endl;
}
else{
cout<<"s3 != s4"<<endl;
}
return 0;
}
4.2单例模式的应用
4.2.1单例模式的优点
- 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显
- 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永远驻留内存的方式来解决。
- 单例模式可以变对资源的多重启用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
4.2.2单例模式的缺点
- 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例、接口或对象类是不可能被实例化的。当然在特殊情况下,单例模式可以实现接口、被继承等,需要在系统开发环境中根据环境判断。
- 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象
- 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中
4.2.3单例模式的使用场景
在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”,可以采用单例模式,具体的场景如下:
- 要求生成唯一序列号的环境
- 在整个项目中需要一个共享访问点或共享数据,例如一个WEB页面上的计数器,可以不用把每次刷新纪录都纪录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的。
- 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)
4.3单例练习
用单例模式,模拟公司员工使用打印机场景,打印机可以打印员工要输出的内容,并且可以累积打印机使用次数
#include <iostream>
#include <string>
using namespace std;
class Printer
{
public:
static Printer* getInstance(){
return instance;
}
void print(string text){
cout<<"打印的内容是:"<<text<<endl;
count++;
}
static int getCount(){
return count;
}
private:
Printer(){};
static int count;
static Printer* instance;
//想回收堆内存也可以,一般不需要你回收!不回收也没什么影响,想回收有以下方法
//垃圾回收工
class Garbo{
private:
~Garbo(){
if(instance != NULL){
delete instance;
}
}
};
static Garbo garbo;
}
Printer* Printer::instance = new Printer;
int Printer::count = 0;
Printer::Garbo Printer::Garbo;
int main(void)
{
//员工1 获取
Printer *p1 = Printer::getInstance();//获取到唯一的打印机实例
p1->print("一份简历");
//员工2
Printer *p2 = Printer::getInstance();
p2->print("lol 皮肤");
//员工3
Printer *p3 = Printer::getInstance();
p3->print("离职申请");
int count = Printer::getCount();
cout<<"今天打印了"<<count<<"份文件"<<endl;
return 0;
}
网友评论