浅尝设计模式

设计模式

  • 实践

    1. 工厂模式

  • 假设你有一个披萨店,你可以使用工厂方法来制作不同种类的披萨。在这个情况下:

    • Product 就是披萨,它定义了披萨应该有的特性(比如有面团、酱料和配料)。
    • ConcreteProduct 是不同种类的披萨,比如芝士披萨或意大利辣香肠披萨。每个具体产品是不同类型的披萨。
    • Creator 是披萨店,它定义了如何制造披萨的方法,但不指定具体的披萨类型。
    • ConcreteCreator 是不同种类的披萨店,比如芝士披萨店或意大利辣香肠披萨店。每个具体创建者可以制造不同类型的披萨。

    当客户需要披萨时,他们不需要知道如何制作它,只需告诉披萨店(具体创建者),然后披萨店使用工厂方法制作适当类型的披萨。这种方式使代码更灵活,因为你可以轻松添加新类型的披萨,而无需修改客

    **工厂方法模式就像一个工厂,它负责生产各种产品,但不会亲自制作每个产品。相反,工厂定义了制造产品的方法,然后不同的工厂子类可以制造不同类型的产品。
    • 代码

      任务描述 : 输入不同国家 输出对应国家的电影

      任务分析 : 定义工厂类与产品类(也就是国家与电影), 电影的生产主体是国家,然而不同国家生产了不同的电影.所以在具体实现时,需要从上至下进行编写 : 先定义一个工厂类其实现生产电影的任务, 再针对不同国家定义不同的类对生产电影的任务进行重写, 最后调用不同的函数进行实现

      Creator.h:

#ifndef CREATOR_H_
#define CREATOR_H_

#include <memory>
#include "Product.h"

// 抽象工厂类 生产电影
class Factor
{
public:
  virtual std::shared_ptr<Movie> get_movie() = 0;
};

#endif
ConcreteCreator.h:
#ifndef CONCRETE_CREATOR_H_
#define CONCRETE_CREATOR_H_
​
#include <memory>
#include "Creator.h"
#include "ConcreteProduct.h"

// 具体不同的工厂类
class ChineseProducer : public Factory
{
public:
  // 创建一个派生类对象的智能指针,并将其返回给基类指针。
  // 具体说明 : 
  // 1. std::shared_ptr<Movie>:这部分表示要返回一个基类 Movie 的智能指针,其中 Movie 是一个基类,可能有派生类。
  // 2. get_movie() override:这是一个虚函数的覆盖,意味着在派生类中重新实现了基类的虚函数。
  // 3. { return std::make_shared<ChineseMovie>(); }:在这里,代码使用 std::make_shared 创建一个 ChineseMovie 类的智能指针,并将其作为 Movie 类的智能指针返回。
  // 4. 这是多态性的应用,因为派生类 ChineseMovie 的对象可以被当作基类 Movie 的对象来使用。
  std::shared_ptr<Movie> get_movie() override {return std::make_shared<ChineseMovie>(); }
}
class JapaneseProducer : public Factor
{
public:
  std::shared_ptr<Movie> get_movie() override
  {
    return std::make_shared<JapaneseMovie>();
  }
}
Product.h
#ifndef PRODUCT_H_
#define PEODUCT_H_

#include <string>

class Movie
{
public:
  virtual std::string get_a_movie() = 0;
}; 
 
 #endif

ConcreteProduct.h:
#ifndef  CONCRETE_PRODUCT_H_
#define  CONCRETE_PRODUCT_H_
​
#include <iostream>
#include <string>
#include "Product.h"

// 具体产品类
class ChineseMovie : public Movie
{
public:
  std::string get_a_movie() override
  {
    return "<< 让子弹飞 >>"
  }
};

class JapaneseMovie : public Movie {
 public:
    std::string get_a_movie() override {
        return "《千与千寻》";
    }
};

#endif
main.cpp **这里比较关键**
#include "ConcreteCreator.h"

int main()
{
    std::shared_ptr<Factory> factory;
    std::shared_ptr<Movie> product;
​
    // 这里假设从配置中读到的是Chinese(运行时决定的)
    std::string conf = "China";
​
    // 程序根据当前配置或环境选择创建者的类型
    if (conf == "China") {
        factory = std::make_shared<ChineseProducer>();
    } else if (conf == "Japan") {
        factory = std::make_shared<JapaneseProducer>();
    } else {
        std::cout << "error conf" << std::endl;
    }
​
    product = factory->get_movie();
    std::cout << "获取一部电影: " << product->get_a_movie() << std::endl;
}

  1. **生成器模式 : **

生成器模式就像在组装一辆汽车一样。汽车是一个复杂的对象,由引擎、车轮、座椅等部分组成。

  • 产品(Product)就是最终的汽车。
  • 抽象生成器(Builder)是汽车工厂,它定义了如何制造汽车的各个部分,如引擎、车轮、座椅等。
  • 具体生成器(Concrete Builder)是不同类型的汽车工厂,比如制造小轿车或越野车的工厂。每个工厂知道如何制造特定类型的汽车。
  • 指挥者(Director)是汽车销售商,它知道客户需要哪种汽车,并且协调汽车工厂来组装这种汽车。
  • 客户端(Client)是购车的人,他们告诉销售商他们需要什么类型的汽车,然后销售商使用合适的汽车工厂来制造汽车。

这种方式让你可以根据客户需求制造不同类型的汽车,而不需要了解每个部分的制造细节。生成器模式的好处在于构建复杂对象时能够保持代码的清晰性和可维护性。

  • 代码

    Product.h

#ifndef PRODUCT_H_
#define PRODUCT_H_

#include <string>
#include <iostream>

// 产品类 车
 class Car
 {
 public:
   Car() {}
   
   void set_car_tire(std::string t) {
        tire_ = t;
        std::cout << "set tire: " << tire_ << std::endl;
    }
    void set_car_steering_wheel(std::string sw) {
        steering_wheel_ = sw;
        std::cout << "set steering wheel: " << steering_wheel_ << std::endl;
    }
    void set_car_engine(std::string e) {
        engine_ = e;
        std::cout << "set engine: " << engine_ << std::endl;
    }
 // 属性
 private:
   std::string tire_;
   std::string steering_wheel_;
   std::string engine_;
 };
 
 #endif

Builder.h 抽象的汽车工厂 定义制造汽车属性的工厂 但不同的汽车品牌的制造要在具体生成器中进行构造
#ifndef BUILDER_H_
#define BUILDER_H_

#include "Product.h"

// 抽象建造者 只是定义了构造产品属性所需的方法
class CarBuilder
{
public:
  Car getCar()
  {
    return car_;
  }
protected:
  Car car_;
};
#endif
ConcreteBuilder.h
#ifndef CONCRETE_BUILDER_H_
#define CONCRETE_BUILDER_H_

#include "Builder.h"

// 具体建造者以及具体建造方法
class BenzBuilder : public CarBuilder {
 public:
    // 具体实现方法
    void buildTire() override {
        car_.set_car_tire("benz_tire");
    }
    void buildSteeringWheel() override {
        car_.set_car_steering_wheel("benz_steering_wheel");
    }
    void buildEngine() override {
        car_.set_car_engine("benz_engine");
    }
};
​
// 具体建造者 奥迪
class AudiBuilder : public CarBuilder {  // 继承了CarBuilder,并对其中的抽象建造方法进行重写
 public:
    // 具体实现方法
    void buildTire() override {
        car_.set_car_tire("audi_tire");
    }
    void buildSteeringWheel() override {
        car_.set_car_steering_wheel("audi_steering_wheel");
    }
    void buildEngine() override {
        car_.set_car_engine("audi_engine");
    }
};

#endif  // CONCRETE_BUILDER_H_

Director.h
#ifndef  DIRECTOR_H_
#define  DIRECTOR_H_
​
#include "Builder.h"
​
class Director {
 public:
    Director() : builder_(nullptr) {}
​
    void set_builder(CarBuilder *cb) {
        builder_ = cb;
    }
​
    // 组装汽车
    Car ConstructCar() {
        builder_->buildTire();
        builder_->buildSteeringWheel();
        builder_->buildEngine();
        return builder_->getCar();
    }
​
 private:
    CarBuilder* builder_;
};
​
#endif  // DIRECTOR_H_
main.cpp
#include "Director.h"
#include "ConcreteBuilder.h"
​
int main() {
    // 抽象建造者(一般是动态确定的)
    CarBuilder* builder;
    // 指挥者
    Director* director = new Director();
    // 产品
    Car car;
​
    // 建造奔驰
    std::cout << "==========construct benz car==========" << std::endl;
    builder = new BenzBuilder();
    director->set_builder(builder);
    car = director->ConstructCar();
    delete builder;
​
    // 建造奥迪
    std::cout << "==========construct audi car==========" << std::endl;
    builder = new AudiBuilder();
    director->set_builder(builder);
    car = director->ConstructCar();
    delete builder;
​
    std::cout << "==========done==========" << std::endl;
    delete director;
}

  1. **原型模式 : **原型模式就像复制粘贴一份文件一样。你有一个现有的对象,然后你可以复制它来创建一个全新的对象,而不需要从头开始重新创建。
  • 原型(Prototype)就是你要复制的对象,就像文件的原始版本。
  • 克隆方法(Clone Method)定义了如何创建一个原型的副本,就像复制和粘贴文件。
  • 客户端(Client)是使用原型模式的人,他们告诉原型对象要复制多少份,并得到克隆后的对象,就像复制文件后得到一个新文件。

原型模式适用于那些对象创建过程比较复杂,但你又需要创建多个相似对象的情况。它使得创建对象变得简单,只需复制一个现有对象,而不必重新构建

  • 代码

    Prototype.h

#infndef PROTOTYPE_H_
#define PROTOTYPE_H_

// 抽象原型类
class Object
{
 public:
   virtual Object* clone() = 0;
};

#endif
ConcretePrototype.h
#ifndef CONCRETE_PROTOTYPE_H_
#define CONCRETE_PROTOTYPE_H_
​
#include <iostream>
#include <string>
#include "Prototype.h"
​
class Attachment
{
public:
  // =========== 多做这一步其实是为了保护 传入的content的安全
  void set_content(std::string content)
  {
    content_ = content;
  }
  
  std::string get_content()
  {
    return content_;
  }
  // =========== 注意这里定义的content_是私有成员变量
private:
  std::string content_;
};

// 具体原型: 邮件类
class Email : public Object
{
public:
  Email() {}
  
  Email(std::string text, std::string attachment_content) : text_(text), attachment_(new Attachment())
  {
    attachment_->set_content(attachment_content);
  }
  
  ~Email()
  {
    if(attachment_ != nullptr)
    {
      delete attachment_;
      attachment_ nullptr;
    }
  }
  
  void dispaly()
  {
      std::cout << "------------查看邮件------------" << std::endl;
      std::cout << "正文: " << text_ << std::endl;
      std::cout << "邮件: " << attachment_->get_content() << std::endl;
      std::cout << "------------查看完毕------------" << std::endl;
  }
  
  // 深拷贝
  Email* clone() override
  {
    return new Email(this->text_, this->attachment_->get_content());
  }
  
  void changeText(std::string new_text)
  {
    text_ = new_text;
  }
  
  void changeAttachment(std::string content)
  {
    attachment_->set_content(content);
  }
  
private:
  std::string text_;
  Attachment *attachment_ = nullptr;
};

#endif  // CONCRETE_PROTOTYPE_H_

main.cpp
#include "ConcretePrototype.h"
​
#include <cstdio>

int main()
{
    Email* email = new Email("最初的文案", "最初的附件");
    Email* copy_email = email->clone();
    copy_email->changeText("新文案");
    copy_email->changeAttachment("新附件");
    std::cout << "original email:" << std::endl;
    email->display();
    std::cout << "copy email:" << std::endl;
    copy_email->display();
​
    delete email;
    delete copy_email;
}

  1. 单例模式** 封装过程中使用到的一些全局都需要用到的变量可以在其对应的类内添加互斥锁 实现线程安全
  • 构造函数私有:即单例模式只能在内部私有化
  • 实例对象static:保证全局只有一个
  • 外界通过GetInstance()获取实例对象

a. 线程安全的懒汉单例模式

  • 代码

    Singleton.h

#ifndef SINGLETON_H_
#define SINGLETON_H_
​
#include <iostream>
#include <string>
#include <mutex>

class Singleton
{
public:
  static Singleton* GetInstance()
  {
    if(instance_ == nullptr)
    {
      m_mutex_.lock();
      if(instance_ == nullptr)
      {
        instance_ = new Singleton();
      }
      m_mutex_.unlock();
    }
    return instance_;
  }
  
private:
  Singleton() {}
  static Singleton* instance_;
  static std::mutex m_mutex_;
}

#endif
// 初始化静态变量

Singleton.cpp
#include "Singleton.h"

// 静态变量instance初始化不要放在头文件中, 如果多个文件包含singleton.h会出现重复定义问题
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::m_mutex_;
main.cpp
#include <iostream>
#include "Singleton.h"

int main() {
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();
​
    std::cout << "s1地址: " << s1 << std::endl;
    std::cout << "s2地址: " << s2 << std::endl;  // 此时s1与s2地址相同,说明可以在不同的程序中访问相同对象
    return 0;
}

b. 饿汉单例模式: 用于确保一个类在程序中只有一个实例,并且这个实例在整个程序生命周期中一直存在。这种模式的特点是在类加载的时候就创建了单例实例,而不是在第一次使用时才创建。

就像一个“饿汉”,他很早就准备好了吃的,无论是否真正需要.

  • 代码
class Singleton
{
public:

  // 获取单例实例的静态方法
  static Singleton& getInstance()
  {
    // 在类加载时,这个实例就已经被创建
    static Singleton instance;
    return instance;
  }
  
  // 其他成员函数
private:
  // 将构造函数以及拷贝构造函数私有化,防止外部创建多个实例
  Singleton()
  {
  // 初始化单例
  }
  Singleton(const Singleton&) = delete;
  
}

c. Meyers' Singleton

  • 代码
#ifndef SINGLETON_H_
#define SINGLETON_H_
​
class Singleton {
 public:
    static Singleton& GetInstance() {                 // 静态成员变量 确保其只会被初始一次
        static Singleton instance;
        return instance;
    }
    Singleton(const Singleton&) = delete;             // 阻止了拷贝构造函数和拷贝赋值操作符的使用。
    Singleton& operator=(const Singleton&) = delete;  // 这样做是为了确保单例实例不会被复制或赋值,从而保持单例的独立性。
​
 private:
    Singleton() {}
};
​
#endif  // SINGLETON_H_
这个单例模式的实现确保了在程序中只能存在一个 `Singleton` 类的实例,提供了全局访问点 `GetInstance()` 来获取该实例。这对于需要管理全局资源或状态的情况非常有用,以确保只有一个实例来处理这些资源或状态。

若想在类外实现访问与修改其中的成员变量可以添加一个公共的成员函数

- 代码
class Singleton {
public:
    static Singleton& GetInstance() {
        static Singleton instance;
        return instance;
    }

    // 允许外部修改单例的内部状态
    void SetData(int newData) {
        data = newData;
    }

    // 允许外部获取单例的内部状态
    int GetData() const {
        return data;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() : data(0) {}

    int data;
};

但是上述代码有潜在的并发风险,可以加锁 改进如下

- 代码
#include <iostream>
#include <mutex>

class Singleton {
public:
    static Singleton& GetInstance() {
        static Singleton instance;
        return instance;
    }

    void SetData(int newData) {
        // 在修改前加锁
        std::lock_guard<std::mutex> lock(mutex_);
        data = newData;
    }

    int GetData() const {
        // 在访问前加锁
        std::lock_guard<std::mutex> lock(mutex_);
        return data;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() : data(0) {}

    int data;
    mutable std::mutex mutex_;  // 用于保护对data的访问的互斥锁
};

**上述优缺点 : **

优点:

- 解决了普通单例模式全局变量初始化依赖
- C++只能保证在同一个文件中声明的static遍历初始化顺序和其遍历声明的顺序一致,但是不能保证不同文件中static遍历的初始化顺序

缺点:

- 需要C++11支持(C++11保证static成员初始化的线程安全)
- 性能问题(同懒汉模式一样,每次调用`GetInstance()`方法时需要判断局部static变量是否已经初始化,如果没有初始化就会进行初始化,这个判断逻辑会消耗一点性能)

结构型模式 : 关注如何将类和对象组合在一起以形成更大的结构,而不涉及特定对象的行为

  1. 适配器模式 : 适配器模式允许你将一个类的接口转化为另一个类可以使用的接口,使不兼容的类能够一起工作
  • 代码
#include <iostream>

//旧系统的类,拥有不兼容的接口
class OldSystem
{
public:
   void doLegacyOperation() 
   {
      std::cout << "Legacy operation is performed." << std::endl;
   }
};

// 新系统的接口,需要适配旧系统
class NewSystem 
{
public:
    virtual void doNewOperation() = 0;
};

// 适配器类,将旧系统的接口适配到新系统
class Adapter : public NewSystem 
{
private:
    OldSystem* oldSystem;

public:
    Adapter(OldSystem* old) : oldSystem(old) {}
// : 标识着构造函数的初始化列表的开始。在初始化列表中,你可以指定如何初始化类的成员变量
// oldSystem(old) 是初始化列表的一部分
// 它表示将 Adapter 类的成员变量 oldSystem 初始化为传递给构造函数的 OldSystem 类的指针 old

    void doNewOperation() override 
    {
        std::cout << "Adapter is converting the call to the new system." << std::endl;
        oldSystem->doLegacyOperation();
    }
};

int main() 
{
    OldSystem old;
    Adapter adapter(&old);  // 通过适配器将旧系统适配到新系统

    // 使用新系统的接口
    adapter.doNewOperation();

    return 0;
}


  • 基础知识点

    • a类访问b类public变量

      在C++中,如果类A想访问类B的public成员变量,有几种方法:

      1. 继承关系:如果类A是类B的派生类,它可以访问类B的public成员变量。这是继承的基本概念,派生类继承了基类的公共成员。
      2. 友元函数:你可以在类A中声明一个友元函数,该函数可以访问类B的私有成员变量。这需要在类B中声明类A为友元。这允许类A中的特定函数访问类B的私有成员,但不是整个类A。
      3. 使用对象:如果你有类B的对象,你可以通过这个对象来访问类B的public成员变量。例如,如果你有一个类A的对象a和类B的对象b,你可以使用b.publicMember来访问类B的public成员。
注意第三种方法在实现过程中如果不使用智能指针的话,需要手动开辟与释放内存
#include <iostream>
#include <stdio.h>
class A
{
    public:
    std:: string a = "a";
    std:: string b = "b";
};
class B
{
    public:
    A* aa;
    std::string c = aa->a;
};

int main()
{
    B bb;
    std::cout << bb.c <<std::endl;
    return 0;
}
如果直接在B类中声明A类对象会报错,因为:
在类**B的成员变量**声明了一个**指向类A对象的指针**,但没有分配内存或初始化它。这会**导致在访问aa->a时出现未定义行为**(undefined behavior),因为指针aa没有指向有效的A对象。

**改进如下:**
#include <iostream>
#include <stdio.h>
class A
{
    public:
    std:: string a = "a";
    std:: string b = "b";
};
class B
{
    public:
    B() : aa(new A()) {}
    // 在类B的析构函数中释放 aa 指针指向的内存
    ~B() 
    {
        delete aa;
    }
    A* aa;
    std::string c = aa->a;
};

int main()
{
    B bb;
    std::cout << bb.c <<std::endl;
    return 0;
}

参考链接:如何学习设计模式? - 知乎 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值