设计模式 C++

参考博客:https://blog.csdn.net/weixin_45712636/article/details/124328504

参考视频:https://www.bilibili.com/video/BV1Np4y1z7BU?p=1&vd_source=fce372ba0aa0bf9ed76094e3192b7015

参考文档:https://bright-boy.gitee.io/technical-notes/#/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/index

1. 设计模式概述

1.1 设计模式产生背景

  设计模式最开始被用在建筑领域设计中,在1995年在《设计模式:可复用面向对象软件的基础》一书中正式提出23中设计模式。

1.2 软件设计模式概念

  设计模式是一套被反复使用、经过分类编目和代码设计经验的总结,描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。

1.3 设计模式的必要性

  正确使用设计模式具有以下优点:

  • 提高程序员的思维、编程和设计能力;
  • 程序设计标准化,编码工程化,提高开发效率。缩短开发周期;
  • 提高代码的可重用行、可读性、可靠性、灵活性和可维护性。

1.4  设计模式分类

  • 创建型模式

  用于描述“怎样创建对象”,特点是将对象的创建和使用分离。 单例、原型、工厂、抽象工厂、建造者模式 5种。

  • 结构型模式

  用于描述如何将类或者对象按某种布局组成更大的结构。代理、适配器、桥接、装饰、外观、享元、组合模式 7种。

  • 行为型模式

  用于描述类或者对象之间怎样互相协作完成单个对象无法单独完成的任务,以及怎样分配职责。模板、策略、命令、职责、状态、观察者、中介者、迭代器、访问者、备忘录、解释器共11种。

2. UML

  统一建模语言(Unified Modeling Language, UML),用来设计软件的可视化建模语言。

  UML从目标系统的不同角度出发,定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构建图、部署图。

2.1 类图

  类图(Class diagram),显示模型的静态结构,特别是模型中存在的类、类的内部结构以及与其他类的关系等。

2.1.1 类的表示方法

  类图中,类包含类名、属性(field)和方法(method),且用带有分割线的矩形来表示。比如下图表示的一个Employee类,包含name、age、address 3个属性和1个work()方法。

  表示可见性的三种符号:

  • + : public
  • -  : private
  • # : protected

  属性的表示方式:可见性  名称 : 类型 【=缺省值】

  方法的表示方式:可见性  名称(参数列表):【:返回类型】

2.1.2 类与类之间关系的表示方式

略。参考文档吧

1. 关联

  关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系。我们先介绍一般关联。

  关联又可以分为单向关联,双向关联,自关联。

  • 单向关联

  在UML类图中单向关联用一个带箭头的实线表示。上图表示每个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现。

  • 双向关联
  • 自关联
2. 聚合
3. 组合
4. 依赖
5. 继承
6. 实现

3.  设计原则

3.1 开闭原则

3.2 里氏代换原则

3.3 依赖倒转原则

3.4 接口隔离原则

3.5 迪米特法则

3.6 合成复用法则

4. 创建者模式

4.1 单例模式 Singleton

参考博客:https://blog.csdn.net/guyuealian/article/details/82012828

  一个类只创建一个唯一的对象,即一次创建多次使用。

  实现单例模式的步骤:
  1、构造函数私有化
  2、增加静态私有的当前类的指针变量
  3、提供静态对外接口,可以让用户获得单例对象

  单例设计模式分类两种:

  • ​ 饿汉式:类加载就会导致该单实例对象被创建

  还没有使用该单例对象,该单例对象就已经被加载到内存了,在对象过多时会造成内存浪费

  由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。

  • ​ 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

  解决了饿汉式内存浪费问题,但是线程不安全的,可以通过互斥量mutex.lock()和mutex.unlock()来解决

  在访问量较小时,采用懒汉实现。这是以时间换空间。

4.1.1 饿汉模式

//饿汉式    对象在程序执行时优先创建
//饿汉式是线程安全的
class SingletonHungry {
public:
    static SingletonHungry* getInstance() {
        return pSingleton;
    }

    static void freeSpace() {
        if (pSingleton != NULL) {
            delete pSingleton;
        }
    }
private:
    SingletonHungry() {}
    static SingletonHungry* pSingleton;
};
//以下语句将会在main函数运行前执行
SingletonHungry* SingletonHungry::pSingleton=new SingletonHungry;

4.1.2 懒汉模式

//懒汉式   对象的创建在第一次调用getInstance函数时创建
//懒汉式是线程不安全的
class SingletonLazy {
public:
    static SingletonLazy* getInstance() {
        if (pSingleton == NULL) {
            pSingleton = new SingletonLazy;
        }
        return pSingleton;
    }
private:
    SingletonLazy() {}
    static SingletonLazy* pSingleton;
};
//在类外面进行初始化
SingletonLazy* SingletonLazy::pSingleton=NULL;
void test01() {
    SingletonLazy* p1 = SingletonLazy::getInstance();
    SingletonLazy* p2 = SingletonLazy::getInstance();
    if (p1 == p2) {
        cout << "单例模式" << endl;
    }
    else {
        cout << "不是单例模式" << endl;
    }
    SingletonHungry* p3 = SingletonHungry::getInstance();
    SingletonHungry* p4 = SingletonHungry::getInstance();
    if (p3 == p4) {
        cout << "单例模式" << endl;
    }
    else {
        cout << "不是单例模式" << endl;
    }
}
int main()
{
    test01();
}

  上面这种简单的懒汉模式的单例模式,是线程不安全的,如果两个线程同时首次调用getInstance方法且同时检测到pSingleton是NULL值,则两个线程会同时构造一个实例给pSingleton,这是严重的错误!

  为了实现线程安全的单例模式,我们可以使用双检锁机制(DCLP,Double-Checked Locking Pattern)。以下是一个使用C++11标准的线程安全单例模式的示例:

#include <iostream>
#include <mutex>

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mtx;

    // 私有构造函数,防止通过构造函数创建对象
    Singleton() {}

public:
    // 获取单例实例的静态方法
    static Singleton* getInstance() {
        // 双检锁机制:第一次检查,避免不必要的加锁
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mtx); // 加锁
            // 第二次检查,确保只有一个线程创建实例
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return instance;
    }

    // 删除拷贝构造函数和赋值运算符,确保单例对象不能被复制
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 供析构函数释放单例资源
    ~Singleton() {
        // 释放资源
    }
};

// 静态成员初始化
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

  在上述代码中,我们使用静态成员instance来保存唯一的实例,并在需要时使用双检锁机制确保只有一个线程能够创建实例。std::mutex用于实现互斥锁来保护临界区,确保线程安全。

  虽然双检锁机制可以确保线程安全,但由于涉及到细微的内存模型问题,一些平台的编译器对其行为进行了优化,可能导致该模式无法正确工作。为了避免这种情况,C++11引入了std::call_once函数,它提供了一种更可靠的实现线程安全的单例模式的方法。以下是使用std::call_once的示例代码:

std::once_flag fg;
class Singleton
{
private:
    Singleton(){}
 
private:
    // 此类唯一对象
    static Singleton *instance;
public:
    static void newInstance()
    {
        instance = new Singleton();
        std::cout << "创建一个对象\n";
    }
    static Singleton* getInstance()
    {
        std::call_once(fg, newInstance);
        return instance;
    }
 
};
Singleton* Singleton::instance = NULL;

  还有一种是这样的:

#include <iostream>
#include <mutex>

class Singleton {
private:
    Singleton() {}

public:
    static Singleton& getInstance() {
        static Singleton instance; // 这里的局部静态变量保证了线程安全且只会初始化一次
        return instance;
    }

    // 防止拷贝构造和赋值操作
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

int main() {
    // 获取单例实例
    Singleton& singleton1 = Singleton::getInstance();
    Singleton& singleton2 = Singleton::getInstance();

    if (&singleton1 == &singleton2) {
        std::cout << "同一个实例" << std::endl;
    } else {
        std::cout << "不同的实例" << std::endl;
    }

    return 0;
}

  因为 std::call_once 已经被内部实现,不需要我们显式地调用它。使用局部静态变量的方式保证了线程安全和懒加载的单例实例,因为实例只会在第一次调用 getInstance() 时创建。

  这种方式不仅简洁,而且线程安全性由 C++ 标准库保证,因此更推荐使用它来实现线程安全的单例模式。

  std::call_once 不需要显式调用是因为它是一个 C++ 标准库提供的工具函数,内部已经实现了线程安全的一次性初始化操作。它的设计目的是在多线程环境下确保某个函数只被执行一次,而且能够正确地处理并发调用,不需要开发者手动管理锁或其他线程同步机制。

  std::call_once 需要一个 std::once_flag 对象作为参数,它是一个标志用于追踪 std::call_once 的调用状态。当 std::call_once 第一次被调用时,它会执行指定的函数,并将 std::once_flag 对象标记为已经调用。之后,如果有其他线程再次调用 std::call_once 并传递相同的 std::once_flag 对象,该函数将不会再次执行,因为标志已经被设置为已调用状态。

  这样就保证了在多线程环境下,只有第一个到达的线程执行函数,其他线程都会等待,并且不会重复执行相同的函数。

4.2 工厂模式

  在面向对象系统设计中经常可以遇到以下的两类问题:

  我们经常会抽象出一些类的公共接口以形成抽象基类或者接口。这样我们可以通过声明一个指向基类的指针来指向实际的子类实现,达到了多态的目的。所以就不得不在要用到子类的地方写new 对象。这样实体类的使用者必须知道实际的子类名称,以及会使程序的扩展性和维护变得越来越困难。
还有一种情况就是在父类中并不知道具体要实例化哪一个具体的子类。只能在父类中写方法调用,具体调用哪一个类的方法交给子类实现。
以上两个问题也就引出了 Factory 模式的两个最重要的功能:
  1)定义创建对象的接口,封装了对象的创建。
  2)使得具体化类的工作延迟到了子类中。
结构

  工厂方法模式的主要角色:

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

举例

  需求:设计一个咖啡店点餐系统。

  设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。

抽象工厂:

//Factory.h 
#ifndef _FACTORY_H_ 
#define _FACTORY_H_ 
class Product; 
class Factory 
{ 
public: 
    virtual ~Factory() = 0;
    virtual Product* CreateProduct() = 0; 
protected: 
    Factory(); 
private:
};
class ConcreteFactory:public Factory
{ 
public: 
    ~ConcreteFactory(); 
    ConcreteFactory(); 
    Product* CreateProduct(); 
protected: 
private: 
}; 
#endif //~_FACTORY_H_

具体工厂:

//Factory.cpp 
#include "Factory.h" 
#include "Product.h" 
#include <iostream> 
using namespace std; 
Factory::Factory() 
{ 
} 
Factory::~Factory() 
{
} 
ConcreteFactory::ConcreteFactory() 
{ 
    cout<<"ConcreteFactory....."<<endl; 
} 
ConcreteFactory::~ConcreteFactory() 
{ 
} 
    Product* ConcreteFactory::CreateProduct() 
{ 
return new ConcreteProduct(); 
} 

 抽象产品:

//Product.h 
#ifndef _PRODUCT_H_ 
#define _PRODUCT_H_ 
class Product 
{ 
public: 
    virtual ~Product() = 0; 
 
protected: 
    Product(); 
private: 
}; 
class ConcreteProduct:public Product 
{ 
public: 
    ~ConcreteProduct(); 
    ConcreteProduct(); 
 
protected: 
private: 
}; 
#endif //~_PRODUCT_H_

具体产品:

//Product.cpp 
#include "Product.h" 
#include <iostream> 
using namespace std; 
Product::Product() 
{ 
 
} 
Product::~Product() 
{ 
} 
ConcreteProduct::ConcreteProduct() 
{ 
    cout<<"ConcreteProduct...."<<endl; 
} 
ConcreteProduct::~ConcreteProduct() 
{ 
}

咖啡店类:

//main.cpp 
#include "Factory.h" 
#include "Product.h"
#include <iostream> 
using namespace std; 
int main(int argc,char* argv[])
{ 
    Factory* fac = new ConcreteFactory(); 
    Product* p = fac->CreateProduct(); 
     return 0;
} 

  从以上的编写的代码可以看到,要增加产品类时也要相应地增加工厂类,不需要修改工厂类的代码了 。

4.3 抽象工厂模式

  假如我们要买水果,水果的产地来自中国、日本、美国,每个国家的水果种类都可以分为苹果、香蕉、梨子。作为开发者,我们就不得不创建苹果类(香蕉和梨子类似),然后每种苹果都继承自苹果类。每上架一个国家的苹果我们都要实现一次苹果类,这样就会有成千上万的苹果类需要被创建,AbstractFactory 模式就是用来解决这类问题的:要创建一组相关或者相互依赖的对象。

//抽象工厂模式
#include <iostream>
using namespace std;

//苹果的抽象
class AbstractApple {
public:
    virtual void showName() = 0;
};

//中国苹果
class ChinaApple :public AbstractApple {
public:
    virtual void showName() {
        cout << "中国苹果" << endl;
    }
};

//美国苹果
class USAApple :public AbstractApple {
public:
    virtual void showName() {
        cout << "美国苹果" << endl;
    }
};

//日本苹果
class JapanApple :public AbstractApple {
public:
    virtual void showName() {
        cout << "日本苹果" << endl;
    }
};

//香蕉的抽象
class AbstractBanana {
public:
    virtual void showName() = 0;
};

//中国香蕉
class ChinaBanana :public AbstractBanana {
public:
    virtual void showName() {
        cout << "中国香蕉" << endl;
    }
};

//美国香蕉
class USABanana :public AbstractBanana {
public:
    virtual void showName() {
        cout << "美国香蕉" << endl;
    }
};

//日本香蕉
class JapanBanana :public AbstractBanana {
public:
    virtual void showName() {
        cout << "日本香蕉" << endl;
    }
};

//鸭梨的抽象
class AbstractPear {
public:
    virtual void showName() = 0;
};

//中国鸭梨
class ChinaPear :public AbstractPear {
public:
    virtual void showName() {
        cout << "中国鸭梨" << endl;
    }
};

//美国鸭梨
class USAPear :public AbstractPear {
public:
    virtual void showName() {
        cout << "美国鸭梨" << endl;
    }
};

//日本鸭梨
class JapanPear :public AbstractPear {
public:
    virtual void showName() {
        cout << "日本鸭梨" << endl;
    }
};

//抽象工厂  针对产品族
class AbstractFactory {
public:
    virtual AbstractApple* CreateApple() = 0;
    virtual AbstractBanana* CreateBanana() = 0;
    virtual AbstractPear* CreatePear() = 0;
};

//中国工厂
class ChinaFactory :public AbstractFactory {
    virtual AbstractApple* CreateApple() {
        return new ChinaApple;
    }
    virtual AbstractBanana* CreateBanana() {
        return new ChinaBanana;
    }
    virtual AbstractPear* CreatePear() {
        return new ChinaPear;
    }
};

//美国工厂
class USAFactory :public AbstractFactory {
    virtual AbstractApple* CreateApple() {
        return new USAApple;
    }
    virtual AbstractBanana* CreateBanana() {
        return new USABanana;
    }
    virtual AbstractPear* CreatePear() {
        return new USAPear;
    }
};

//日本工厂
class JapanFactory :public AbstractFactory {
    virtual AbstractApple* CreateApple() {
        return new JapanApple;
    }
    virtual AbstractBanana* CreateBanana() {
        return new JapanBanana;
    }
    virtual AbstractPear* CreatePear() {
        return new JapanPear;
    }
};

void test01() {
    AbstractFactory* factory = NULL;
    AbstractApple* apple = NULL;
    AbstractBanana* Banana = NULL;
    AbstractPear* Pear = NULL;

    //中国工厂
    factory = new ChinaFactory;
    apple = factory->CreateApple();
    Banana = factory->CreateBanana();
    Pear = factory->CreatePear();

    apple->showName();
    Banana->showName();
    Pear->showName();

    delete Pear;
    delete apple;
    delete Banana;
    delete factory;
}

int main()
{
    test01();
}

  AbstractFactory模式是为创建一组(有多类)相关或依赖的对象提供创建接口,而 Factory模式是为一类对象提供创建接口或延迟对象的创建到子类中实现。并且可以看到,AbstractFactory模式通常都是使用 Factory 模式实现。

4.4 原型模式

4.5 建造者模式

5. 结构模式

5.1 代理模式

5.2 适配器模式

5.3 装饰者模式

5.4 桥接模式

5.5 外观模式

5.6 组合模式

5.7 享元模式

6. 行为模式

6.1 模板模式

6.2 策略模式

6.3 命令模式

6.4 责任链模式

6.5 状态模式

6.6 观察者模式

6.7 中介模式

6.8 迭代器模式

6.9 访问者模式

6.10 备忘录模式

6.11 解释器模式

 

posted @ 2023-08-03 21:49  莫莫君不恋爱  阅读(31)  评论(0编辑  收藏  举报