设计模式(单例模式、工厂模式及适配器模式、装饰器模式)

news/2024/10/17 23:24:37/

目录

0 、设计模式简介

一、单例模式

二、工厂模式

三、适配器模式

四、装饰器模式 


0 、设计模式简介

设计模式可以分为以下三种: 

  1. 创建型模式:用来描述 “如何创建对象”,它的主要特点是 “将对象的创建和使用分离”。包括单例、原型、工厂方法抽象工厂和建造者 5 种模式。
  2. 结构型模式:用来描述如何将类或对象按照某种布局组成更大的结构。包括代理、适配器、桥接、装饰、外观、享元和组合 7 种模式。
  3. 行为型模式:用来识别对象之间的常用交流模式以及如何分配职责。包括模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录和解释器 11 种模式。

下面将介绍单例模式、工厂模式、适配器模式、组合模式。 

一、单例模式

单例模式的实现需要三个必要的条件

  1. 单例类的构造函数必须是私有的,这样才能将类的创建权控制在类的内部,从而使得类的外部不能创建类的实例。
  2. 单例类通过一个私有的静态变量来存储其唯一实例。
  3. 单例类通过提供一个公开的静态方法,使得外部使用者可以访问类的唯一实例。

注意:
因为单例类的构造函数是私有的,所以单例类不能被继承。

另外,实现单例类时,还需要考虑三个问题:

  • 创建单例对象时,是否线程安全。
  • 单例对象的创建,是否延时加载。
  • 获取单例对象时,是否需要加锁(锁会导致低性能)。

单例模式分为两种:饿汉方式和懒汉方式

饿汉方式:在类加载的时候就实例化对象,懒汉方式:只有在使用的时候才初始化对象。

饿汉式单例优缺点:

  • 优点:
    • 单例对象的创建是线程安全的;
    • 获取单例对象时不需要加锁。
    • 不用考虑线程安全问题,不需要加锁,执行效率较高
  • 缺点:
    • 单例对象的创建,不是延时加载。
    • 类加载的时候就初始化,不管后期用不用都占着空间,浪费了内存

懒汉式单例优缺点:

  • 优点:
    • 对象的创建是线程安全的。
    • 支持延时加载
  • 缺点:获取对象的操作被加上了锁,影响了并发度。
    • 如果单例对象需要频繁使用,那这个缺点就是无法接受的。
    • 如果单例对象不需要频繁使用,那这个缺点也无伤大雅。

使用场景: 

  • 饿汉式单例模式在类加载时就创建实例,适用于单例对象的创建频繁的场景,以及对于性能要求较高的场景。
  • 懒汉式单例模式在第一次被使用时创建实例,适用于单例对象的创建不是很频繁的场景。

饿汉方式代码:

class singleton{protected:singleton(){}private:static singleton* p;public:static singleton* initance();};singleton* singleton::p = new singleton;singleton* singleton::initance(){return p;}

懒汉方式代码:

class singleton
{
protected:singleton(){pthread_mutex_init(&mutex);}
private:static singleton* p;
public:static pthread_mutex_t mutex;static singleton* initance();
};pthread_mutex_t singleton::mutex;
singleton* singleton::p = NULL;
singleton* singleton::initance()
{if (p == NULL){pthread_mutex_lock(&mutex);if (p == NULL)p = new singleton();pthread_mutex_unlock(&mutex);}return p;
}

二、工厂模式

工厂模式分为简单工厂模式、工厂方法模式、抽象工厂模式。

简单工厂模式:提供一个工厂类,在这个工厂类中生产A、B、C类产品。

工厂方法模式:提供n个工厂类,都继承自同一个总工厂类,然后分别生产各自的产品。

抽象工厂模式:提供n个工厂类,都继承自同一个总工厂类,然后分别生产各自的产品,产品可以包含多种。也就是对于每一个工厂,有多条生产线。

1.简单工厂模式:

NiKeShoes、AdidasShoes、LiNingShoes为具体鞋子的类,分别是耐克、阿迪达斯和李宁鞋牌的鞋,它们都继承于Shoes抽象类。 

// 鞋子抽象类
class Shoes
{
public:virtual ~Shoes() {}virtual void Show() = 0;
};// 耐克鞋子
class NiKeShoes : public Shoes
{
public:void Show(){std::cout << "我是耐克球鞋,我的广告语:Just do it" << std::endl;}
};// 阿迪达斯鞋子
class AdidasShoes : public Shoes
{
public:void Show(){std::cout << "我是阿迪达斯球鞋,我的广告语:Impossible is nothing" << std::endl;}
};// 李宁鞋子
class LiNingShoes : public Shoes
{
public:void Show(){std::cout << "我是李宁球鞋,我的广告语:Everything is possible" << std::endl;}
};

ShoesFactory为工厂类,类里实现根据鞋子类型创建对应鞋子产品对象的CreateShoes(SHOES_TYPE type)函数。 

enum SHOES_TYPE
{NIKE,LINING,ADIDAS
};// 总鞋厂
class ShoesFactory
{
public:// 根据鞋子类型创建对应的鞋子对象Shoes *CreateShoes(SHOES_TYPE type){switch (type){case NIKE:return new NiKeShoes();break;case LINING:return new LiNingShoes();break;case ADIDAS:return new AdidasShoes();break;default:return NULL;break;}}
};

main函数,先是构造了工厂对象,后创建指定类型的具体鞋子产品对象,创建了具体鞋子产品的对象便可直接打印广告。因为采用的是`new`的方式创建了对象,用完了要通过`delete` 释放资源资源。

int main()
{// 构造工厂对象ShoesFactory shoesFactory;// 从鞋工厂对象创建阿迪达斯鞋对象Shoes *pNikeShoes = shoesFactory.CreateShoes(NIKE);if (pNikeShoes != NULL){// 耐克球鞋广告喊起pNikeShoes->Show();// 释放资源delete pNikeShoes;pNikeShoes = NULL;}// 从鞋工厂对象创建阿迪达斯鞋对象Shoes *pLiNingShoes = shoesFactory.CreateShoes(LINING);if (pLiNingShoes != NULL){// 李宁球鞋广告喊起pLiNingShoes->Show();// 释放资源delete pLiNingShoes;pLiNingShoes = NULL;}// 从鞋工厂对象创建阿迪达斯鞋对象Shoes *pAdidasShoes = shoesFactory.CreateShoes(ADIDAS);if (pAdidasShoes != NULL){// 阿迪达斯球鞋广告喊起pAdidasShoes->Show();// 释放资源delete pAdidasShoes;pAdidasShoes = NULL;}return 0;
}

输出结果:

2.工厂方法模式: (有的地方直接是抽象工厂模式,即对工厂也进行了抽象)

ShoesFactory抽象工厂类,提供了创建具体鞋子产品的纯虚函数。

NiKeProducer、AdidasProducer、LiNingProducer具体工厂类,继承持续工厂类,实现对应具体鞋子产品对象的创建。

// 总鞋厂
class ShoesFactory
{
public:virtual Shoes *CreateShoes() = 0;virtual ~ShoesFactory() {}
};// 耐克生产者/生产链
class NiKeProducer : public ShoesFactory
{
public:Shoes *CreateShoes(){return new NiKeShoes();}
};// 阿迪达斯生产者/生产链
class AdidasProducer : public ShoesFactory
{
public:Shoes *CreateShoes(){return new AdidasShoes();}
};// 李宁生产者/生产链
class LiNingProducer : public ShoesFactory
{
public:Shoes *CreateShoes(){return new LiNingShoes();}
};

main函数针对每种类型的鞋子,构造了每种类型的生产线,再由每个生产线生产出对应的鞋子。需注意的是具体工厂对象和具体产品对象,用完了需要通过delete释放资源。 

int main()
{// ================ 生产耐克流程 ==================== //// 鞋厂开设耐克生产线ShoesFactory *niKeProducer = new NiKeProducer();// 耐克生产线产出球鞋Shoes *nikeShoes = niKeProducer->CreateShoes();// 耐克球鞋广告喊起nikeShoes->Show();// 释放资源delete nikeShoes;delete niKeProducer;// ================ 生产阿迪达斯流程 ==================== //// 鞋厂开设阿迪达斯生产者ShoesFactory *adidasProducer = new AdidasProducer();// 阿迪达斯生产线产出球鞋Shoes *adidasShoes = adidasProducer->CreateShoes();// 阿迪达斯球鞋广喊起adidasShoes->Show();// 释放资源delete adidasShoes;delete adidasProducer;return 0;
}

 输出结果:

3.抽象工厂模式: 

鞋厂为了扩大了业务,不仅只生产鞋子,把运动品牌的衣服也一起生产了。

Clothe和Shoes,分别为衣服和鞋子的抽象产品类。

NiKeClothe和NiKeShoes,分别是耐克衣服和耐克衣服的具体产品类。

// 基类 衣服
class Clothe
{
public:virtual void Show() = 0;virtual ~Clothe() {}
};// 耐克衣服
class NiKeClothe : public Clothe
{
public:void Show(){std::cout << "我是耐克衣服,时尚我最在行!" << std::endl;}
};// 基类 鞋子
class Shoes
{
public:virtual void Show() = 0;virtual ~Shoes() {}
};// 耐克鞋子
class NiKeShoes : public Shoes
{
public:void Show(){std::cout << "我是耐克球鞋,让你酷起来!" << std::endl;}
};

Factory为抽象工厂,提供了创建鞋子CreateShoes()和衣服产品CreateClothe()对象的接口。

NiKeProducer为具体工厂,实现了创建耐克鞋子和耐克衣服的方式。

// 总厂
class Factory
{
public:virtual Shoes *CreateShoes() = 0;virtual Clothe *CreateClothe() = 0;virtual ~Factory() {}
};// 耐克生产者/生产链
class NiKeProducer : public Factory
{
public:Shoes *CreateShoes(){return new NiKeShoes();}Clothe *CreateClothe(){return new NiKeClothe();}
};

main函数,构造耐克工厂对象,通过耐克工厂对象再创建耐克产品族的衣服和鞋子对象。同样,对象不再使用时,需要手动释放资源。 

int main()
{// ================ 生产耐克流程 ==================== //// 鞋厂开设耐克生产线Factory *niKeProducer = new NiKeProducer();// 耐克生产线产出球鞋Shoes *nikeShoes = niKeProducer->CreateShoes();// 耐克生产线产出衣服Clothe *nikeClothe = niKeProducer->CreateClothe();// 耐克球鞋广告喊起nikeShoes->Show();// 耐克衣服广告喊起nikeClothe->Show();// 释放资源delete nikeShoes;delete nikeClothe;delete niKeProducer;return 0;
}

 输出结果:

三、适配器模式

适配器模式的作用是解决两个软件实体间的接口不兼容的问题。将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作。

适配器模式有两种实现方法,类适配器和对象适配器。类适配器以多继承方式实现。对象适配器以组合的方式实现,即适配器类中包含了适配者类对象。(调用了适配者类中的方法,即新的方法)

一共包含三个类:目标类、适配者类、适配器类。目标类中的接口和适配者类中的接口不兼容,可通过引入适配器类,在适配器类中,保留了原目标类的接口名,调用了适配者类接口中的功能,对功能重新进行了封装,然后通过目标类指针指向适配器类对象或者目标类引用引用适配器类对象就可调用新的功能,完成对该接口的功能完善。
 

应用场景:通过适配器完成USB与TypeC的对接。原先接口功能是USB接口,新的接口是TypeC接口,需要完成适配,即可以把USB接口当成TypeC接口来使用。(相当于给中间连了转换器)

类适配器代码:

/* Connect Usb port */
class CUsbDisk
{
public:virtual ~CUsbDisk() {}virtual void ConnectDevice(){cout << "Connect usb port." << endl;}
};/* Connect Type-C port */
class CTypeCInterface
{
public:virtual ~CTypeCInterface() {}void ConnectDevice(){cout << "Connect Type-C port." << endl;}
};/* Not only connect Usb port, but also connect Type-C port */
class CAdapter : public CUsbDisk, public CTypeCInterface
{
public:void ConnectDevice(){//调用了适配者类中的方法,即完善了功能,用旧的名字调用了新的功能。CTypeCInterface::ConnectDevice();}
};int main(int argc, char *argv[])
{//通过目标类指针指向适配器类对象,调用了新的方法(原先的接口及新的功能)CUsbDisk *theDisk = new CAdapter();theDisk->ConnectDevice();delete theDisk;return 0;
}

输出结果:

对象适配器模式:

/* Connect Usb port */
class CUsbDisk
{
public:virtual ~CUsbDisk() {}virtual void ConnectDevice(){cout << "Connect usb port." << endl;}
};/* Connect Type-C port */
class CTypeCInterface
{
public:virtual ~CTypeCInterface() {}void ConnectDevice(){cout << "Connect Type-C port." << endl;}
};/* Usb device connect phone */
class CAdapter : public CUsbDisk
{
public:CAdapter(){mpAdaptee = new CTypeCInterface();}~CAdapter(){if (NULL != mpAdaptee) {delete mpAdaptee;}}void ConnectDevice(){if (NULL != mpAdaptee) {mpAdaptee->ConnectDevice();} else {cout << "Adapter abnormal. Connect fail!" << endl;}}private://包含了适配者类对象,通过该对象调用了新的方法,重新封装了旧接口。CTypeCInterface *mpAdaptee;
};int main(int argc, char *argv[])
{CUsbDisk *theDisk = new CAdapter();theDisk->ConnectDevice();delete theDisk;return 0;
}

输出结果:

 或:

/*** The Target defines the domain-specific interface used by the client code.*/
class Target {public:virtual ~Target() = default;virtual std::string Request() const {return "Target: The default target's behavior.";}
};/*** The Adaptee contains some useful behavior, but its interface is incompatible* with the existing client code. The Adaptee needs some adaptation before the* client code can use it.*/
class Adaptee {public:std::string SpecificRequest() const {return ".eetpadA eht fo roivaheb laicepS";}
};/*** The Adapter makes the Adaptee's interface compatible with the Target's* interface using multiple inheritance.*/
class Adapter : public Target, public Adaptee {public:Adapter() {}std::string Request() const override {std::string to_reverse = SpecificRequest();std::reverse(to_reverse.begin(), to_reverse.end());return "Adapter: (TRANSLATED) " + to_reverse;}
};/*** The client code supports all classes that follow the Target interface.*/
void ClientCode(const Target *target) {std::cout << target->Request();
}int main() {std::cout << "Client: I can work just fine with the Target objects:\n";Target *target = new Target;ClientCode(target);std::cout << "\n\n";Adaptee *adaptee = new Adaptee;std::cout << "Client: The Adaptee class has a weird interface. See, I don't understand it:\n";std::cout << "Adaptee: " << adaptee->SpecificRequest();std::cout << "\n\n";std::cout << "Client: But I can work with it via the Adapter:\n";Adapter *adapter = new Adapter;ClientCode(adapter);std::cout << "\n";delete target;delete adaptee;delete adapter;return 0;
}

 结果如下:

Client: I can work just fine with the Target objects:
Target: The default target's behavior.Client: The Adaptee class has a weird interface. See, I don't understand it:
Adaptee: .eetpadA eht fo roivaheb laicepSClient: But I can work with it via the Adapter:
Adapter: (TRANSLATED) Special behavior of the Adaptee.s

四、装饰器模式 

装饰器模式是比较常用的一种设计模式,Python中就内置了对于装饰器的支持。

具体来说,装饰器模式是用来给对象增加某些特性或者对被装饰对象进行某些修改。

如上图所示,需要被装饰的对象在最上方,它自身可以有自己的实例,一般通过抽象类来实现(Java中也可以通过接口实现)。

右侧中间是一个装饰器类或者接口,其实内容与原对象基本一致,不过我们自定义的装饰器一般会继承这个装饰器基类。

最下层就是具体的装饰器了,可以看到,具体装饰器类中需要包含被装饰对象成员(也就是说,装饰器需要和被装饰对象有同样的子类),然后增加一些额外的操作。

下面的代码是一个买煎饼的例子,如我们生活中所见,可以选基础煎饼(鸡蛋煎饼,肉煎饼等),然后再额外加别的东西。

 代码如下:

#include<iostream>
#include<string>
using namespace std;class Pancake//基类
{
public:string description = "Basic Pancake";virtual string getDescription(){ return description; }virtual double cost() = 0;
};class CondimentDecorator :public Pancake//装饰器基类
{
public:string getDescrition();
};class MeatPancake :public Pancake//肉煎饼
{
public:MeatPancake(){ description = "MeatPancake"; }double cost(){ return 6; }
};
class EggPancake :public Pancake//鸡蛋煎饼
{
public:EggPancake(){ description = "EggPancake"; }double cost(){ return 5; }
};class Egg :public CondimentDecorator//额外加鸡蛋
{
public:Pancake* base;string getDescription(){ return base->getDescription() + ", Egg"; }Egg(Pancake* d){ base = d; }double cost(){ return base->cost() + 1.5; }
};
class Potato :public CondimentDecorator//额外加土豆
{
public:Pancake* base;string getDescription(){ return base->getDescription() + ", Potato"; }Potato(Pancake* d){ base = d; }double cost(){ return base->cost() + 1; }
};
class Bacon :public CondimentDecorator//额外加培根
{
public:Pancake* base;string getDescription(){ return base->getDescription() + ", Bacon"; }Bacon(Pancake* d){ base = d; }double cost(){ return base->cost() + 2; }
};int main()
{Pancake* pan = new EggPancake();pan = &Potato(pan);pan = &Bacon(pan);cout << pan->getDescription() << "  $ : " << pan->cost() << endl;system("pause");return 0;
}


http://www.ppmy.cn/news/1185676.html

相关文章

MATLAB算法实战应用案例精讲-【图像处理】机器视觉(基础篇)(六)

目录 前言 几个高频面试题目 工业相机与普通相机的差别 一、 工业相机与普通相机的区别

CSDN热榜分析4:生成词云图

文章目录 函数封装UI设计输出词云 热榜分析系列&#xff1a; CSDN热榜分析&#x1f525; UI界面&#x1f525; 领域热榜 函数封装 词云图的绘制功能早在最开始做热榜分析的时候就已经实现了&#xff0c;但需要依赖matplotlib来画图&#xff0c;而并没有直接导出功能&#x…

CSS宽度100%和宽度100vw之间有什么不同?

vw和vh分别代表视口宽度和视口高度。 使用width: 100vw代替的区别在于width: 100%&#xff0c;虽然100%将使元素适合所有可用空间&#xff0c;但视口宽度具有特定的度量&#xff0c;在这种情况下&#xff0c;可用屏幕的宽度 。 如果设置样式body { margin: 0 }&#xff0c;则1…

qt-C++笔记之带有倒计数显示的按钮,计时期间按钮锁定

qt-C笔记之带有倒计数显示的按钮&#xff0c;计时期间按钮锁定 code review! 文章目录 qt-C笔记之带有倒计数显示的按钮&#xff0c;计时期间按钮锁定1.运行2.main.cc3.main.pro 1.运行 2.main.cc 代码 #include <QApplication> #include <QPushButton> #includ…

webGL编程指南 第五章 MultiAttributeSize_interleaved.js

我会持续更新关于wegl的编程指南中的代码。 当前的代码不会使用书中的缩写&#xff0c;每一步都是会展开写。希望能给后来学习的一些帮助 git代码地址 &#xff1a;空 在上一章节中我们使用的是2个buffer&#xff0c;向着色器中传递数据&#xff0c;本章节中我们学习使用一个…

voice 和token 互相转

voice 和token 互相转 解释代码解释 这段代码实现了一个将音频数据转换为 token 列表,并将 token 列表转换回音频的转换过程。以下是代码的主要步骤: 导入所需的库,包括 paddle、numpy、tqdm 和 glob。 定义一个名为 read_and_gen_token 的函数,该函数接受一个音频文件名作…

网上的流量卡与实际不符,可能是这三种原因导致的!

很多朋友反映&#xff0c;自己在网上买的流量卡套餐内流量与实际情况不符&#xff0c;其实&#xff0c;这是一种比较常见的现象&#xff0c;今天&#xff0c;关于其中的原因&#xff0c;小编给大家介绍一下。 ​ 如果买的流量卡套餐内流量与实际不符&#xff0c;无非有三种原因…

AI助力隧道等洞体类场景下水泥基建缺陷检测,基于DeeplabV3Plus开发构建洞体场景下壁体建筑缺陷分割系统

随着智能化硬件的加持&#xff0c;越来越多的场景开始有AI的助力&#xff0c;诸如&#xff1a;道路自动巡检养护、隧道巡检、铁路质检等等&#xff0c;引入AI技术可以大幅降低人工工作量&#xff0c;提升质检的工作效率&#xff0c;在前面的文章中我们已经落地实践开发洞体类场…