创建者模式

这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

简单工厂模式

简单工厂模式将创建对象的任务移交给工厂,通过产品接口对产品进行约束。

@startuml
class PhoneFactory
abstract class Phone

Phone<|..MiPhone
Phone <|..ApplePhone

PhoneFactory <..Phone
@enduml

我们通过 Phone 对产品接口进行约束,然后通过 PhoneFactory 去获取对象。通过这种方式,以后我们再拓展产品时只需要 继承接口类在工厂类里添加条件分支 。而客户也只需要知道产品的名称就行,产品对应的类名可以根据需求任意更改。

简单工厂模式做到了:

  • 可复用:新增的产品可以直接使用已有接口。要使用产品,只需要导入工厂类即可

  • 松耦合:新增的产品不会对已有产品的代码造成任何影响。

简单工厂模式解决了在实现上面两个优点的情况下 工厂类实例化哪一个产品 的工作。

class Phone {
public:
   virtual ~Phone() = 0;
};

Phone::~Phone() {

}

class MiPhone : public Phone {
public:
   void *operator new(size_t size) {
      cout << "MiPhone" << endl;
      return malloc(size);
   }
};

class ApplePhone : public Phone {
public:
   void *operator new(size_t size) {
      cout << "Apple Phone" << endl;
      return malloc(size);
   }
};

class PhoneFactory {
public:
   Phone *createPhone(string const &name) {
      if (name == "Mi")
            return new MiPhone;
      else if (name == "Apple")
            return new ApplePhone;
      else return nullptr;
   }
};

// 使用
auto factory = new PhoneFactory;
auto mi = factory->createPhone("Mi");
auto apple = factory->createPhone("Apple");

使用场景:

  • 创建数据库连接时选择不同的数据库驱动。(例如:QSqlDatabase)。

  • 连接邮件服务器连接客户端:可选择 POP, IMAPHTTP 三种不同的协议。

  • 获取日志储存器:可选择 本地硬盘系统事件, 远程服务器 等。

工厂模式

简单工厂虽然解决了如何实例化的问题,但是每次增加一个产品都需要修改原有代码,很明显 违背了开闭原则。而工厂模式不再设立大工厂,而是对工厂创建对象的接口进行约束,让那个工厂接口的子类决定实例化哪个类。工厂方法使一个类的实例化推迟到工厂子类中。

@startuml

abstract class Factory{
   + Phone* makePhone()
}

class MiFactory{
   + Phone* makePhone()
}

class AppleFactory{
   + Phone* makePhone()
}

Factory <|.. MiFactory
Factory <|.. AppleFactory

MiFactory <.. Phone
AppleFactory <..Phone

Phone <|..Apple
Phone <|..Mi

@enduml

#include "SimpleFactory.h"

class Factory{
public:
   virtual Phone* makePhone() = 0;
};

class MiFactory : public Factory{
public:
   Phone * makePhone() override{
      cout<<"Make Mi Phone"<<endl;
      return new MiPhone;
   }
};

class AppleFactory: public Factory{
public:
   Phone * makePhone() override{
      cout<<"Make Apple Phone"<<endl;
      return new ApplePhone;
   }
};
// 使用

auto miFactory = new MiFactory;
auto miPhone = miFactory->makePhone();
auto appleFactory = new AppleFactory;
auto apple = appleFactory->makePhone();

通过工厂模式,我们现在不仅可以生产 Phone,还可以生产 Clothes 等产品。但是你必须要知道你需要的产品是哪个工厂生产的。但是,虽然通过 Factory 对创建产品的接口进行了限制,一个工厂负责一个产品 会造成大量的类冗余。

相比而言,工厂模式遵循了开闭原则,但是客户端需要独立判读实例化哪一个工厂,等于是将简单工厂的逻辑判断移交给客户端。现在,当你需要拓展产品时,你需要实例化产品及对应工厂,而客户端则自由判断是否需要使用这个产品。

抽象工厂模式

抽象工厂是对工厂模式的进一步抽象,提出了 “工厂的工厂” 这概念。获取产品时,首先从超级工厂(即:工厂的工厂)获取工厂,然后从简单工厂获取产品。这样,只要你知道产品的类别就行了:

@startuml

abstract BigFactory{
   + PhoneFactory* createPhone
   + ClothesFactory* createClothes
}

BigFactory <.. ClothesFactory
BigFactory <.. PhoneFactory


ClothesFactory <..Clothes
Clothes <|..Jacket
Clothes <|..Trouser

PhoneFactory <.. Phone
Phone <|.. MiPhone
Phone <|.. ApplePhone

@enduml

#include "SimpleFactory.h"

class Clothes {
public:
   virtual ~Clothes() = 0;
};

Clothes::~Clothes() {}

class Jacket : public Clothes {
public:
   void *operator new(size_t size) {
      cout << "Jacket" << endl;
      return malloc(size);
   }
};

class Trouser : public Clothes {
public:
   void *operator new(size_t size) {
      cout << "Trouser" << endl;
      return malloc(size);
   }
};

class ClothesFactory {
public:
   Clothes *getClothes(string const &clothes) {
      if (clothes == "Jacket") return new Jacket;
      else if (clothes == "Trouser") return new Trouser;
      else return nullptr;
   }
};


class BigFactory {
public:
   PhoneFactory *createPhone() {
      return new PhoneFactory;
   }

   ClothesFactory *createClothes() {
      return new ClothesFactory;
   }
};
// 使用
auto bigFactory = new BigFactory;
auto phoneFactory = bigFactory->createPhone();
auto miPhone= phoneFactory->getPhone("Mi");

auto clothesFactory = bigFactory->createClothes();
auto jacket = clothesFactory->getClothes("Jacket");

相比于工厂模式,抽象工厂模式可以出产多种产品,当增加商品时,需要添加商品类并修改对应的产品工厂。当添加新类型的产品时,还需要修改抽象工厂,一共三个类。

抽象工厂模式具有多种变体,对于拥有 反射 的语言来说,可以将抽象工厂改装地更简单。

单例模式

单例模式的优缺点:

优点:

  1. 单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例

  2. 例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。

  3. 供了对唯一实例的受控访问。

  4. 于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。

  5. 许可变数目的实例。

  6. 免对共享资源的多重占用。

缺点:
  1. 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。

  2. 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。

  3. 单例类的职责过重,在一定程度上违背了“单一职责原则”。

  4. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

单例模式用于创建一个系统中唯一的对象,例如打印机、全局内存池。单例模式确保当你通过任意方法获取唯一对象时,得到的对象都是一样的。

根据对象创建的时机,又可以把单例模式分为懒汉式和饿汉式。其中,懒汉式在有方法访问唯一对象时才会创建对象,而饿汉式则在程序加载时就创建对象:

 // 懒汉式
class LazySingleton {
   static LazySingleton *lazySingleton;

   LazySingleton() = default;

public:

   LazySingleton(const LazySingleton &) = delete;

   LazySingleton(const LazySingleton &&) = delete;

   ~LazySingleton() {
      if (lazySingleton)
            delete lazySingleton;
   }

   static LazySingleton *getInstance() {
      if (!lazySingleton) lazySingleton = new LazySingleton();
      return lazySingleton;
   }
};

LazySingleton *LazySingleton::lazySingleton = nullptr;

// 饿汉式
class HungrySingleton {
   static HungrySingleton *printer;

   HungrySingleton() = default;

public:
   HungrySingleton(HungrySingleton &) = delete;

   HungrySingleton(HungrySingleton &&) = delete;

   static HungrySingleton *getPrinter() {
      return printer;
   }
};

HungrySingleton *HungrySingleton::printer = new HungrySingleton;

备注

  • 使用静态对象和懒汉式需要注意线程安全问题

  • Java 中可以使用双检锁的形式保证线程安全性,这是因为 Java5 修改了 volatile 的语义,但是 C++ 中并不是安全的。但是 C++ 11 保证静态对象是线程安全的

建造者模式

建造者模式将一个复杂对象分解为多个简单对象,其适用于复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定.

@startuml

class Manager{
   - builder
   + Manager(Builder& builder)
   + Computer build(string& cpu)
}

abstract ComputerBuilder{
   + buildCPU()
   + buildOS()
}

class Computer{
   + os
   + cpu
}

Manager <.. ComputerBuilder

ComputerBuilder <|.. MibookBuilder
ComputerBuilder <|.. MacbookBuilder

MibookBuilder ..> Computer
MacbookBuilder ..> Computer

@enduml

struct Computer{
   string os;
   string cpu;
   friend ostream& operator<<(ostream& stdos, Computer&comp);
};

ostream &operator<<(ostream &stdos, Computer &comp) {
   stdos<<comp.os<<" "<<comp.cpu;
   return stdos;
}

class ComputerBuilder{
public:
   virtual void buildCPU(const string& cpu) = 0;
   virtual void buildOS() = 0;
   virtual Computer getComputer() = 0;
};

class MibookBuilder: public ComputerBuilder{
   Computer computer;
public:
   void buildCPU(const string &cpu) override{
      computer.cpu = cpu;
   }
   void buildOS() override{
      computer.os = "Windows";
   }
   Computer getComputer() override{
      return computer;
   }
};

class MacbookBuilder : public ComputerBuilder{
   Computer computer;
public:
   void buildCPU(const string &cpu) override{
      computer.cpu = cpu;
   }
   void buildOS() override{
      computer.os = "MacOS";
   }
   Computer getComputer() override{
      return computer;
   }
};

class Manager{
   ComputerBuilder* builder;
public:
   explicit Manager(ComputerBuilder* builder) : builder(builder){}
   Computer build(const string& cpu){
      builder->buildCPU(cpu);
      builder->buildOS();
      return builder->getComputer();
   }
};

// 使用方式
auto builder = new MibookBuilder;
auto manager = new Manager(builder);
auto computer = manager->build("Intel");
cout<<computer;

原型模式

原型模式主要解决对象的拷贝问题。

C++ 和 Java 的原型模式略有不同,Java 主要用于减少构造函数执行的开销,其方式就是通过重写 Cloneable 接口实现深拷贝。而 C++ 在类型已知的情况下可以直接调用拷贝构造函数,因此,C++ 中原型模式的意义在于 拷贝类型未知的对象

备注

对于一个对象的拷贝

  • Java 需要先创建新对象,然后再逐一复制,这就导致中间多了构造函数的开销。通过重写 Cloneable 接口可以达到直接拷贝的目的,减少了构造函数执行的开销。

  • C++ 可以通过重写拷贝构造函数达到深拷贝的目的。

那么何时类型未知呢?答案是当你通过基类指针操作子类对象时,而子类对象可能是非常多的,甚至可能不是你写的,这时候通过拷贝构造函数复制对象很明显是不显示的,因为构造函数是没有多态的,而且构造函数内虚函数也不生效。

class Cloneable{
public:
   virtual Cloneable* clone() = 0;
};

class Paper: public Cloneable{
   char* content = nullptr;
public:
   explicit Paper(const char* str){
      size_t len = strlen(str) + 1;
      content = (char*)malloc(len);
      strcpy_s(content, len, str);
   }
   Paper(const Paper& b){
      // 当持有 Paper 对象时,直接使用拷贝构造函数
      if(content) free(content);
      size_t len = strlen(b.content) + 1;
      content = (char*)malloc(len);
      strcpy_s(content, len, b.content);
   }
   Cloneable * clone() override{
      // 当持有 Cloneable 对象时,使用 clone() 进行拷贝
      Paper* duplicate = new Paper(*this);
      return duplicate;
   }
};
// 使用
Cloneable* paper = new Paper("Hello, world!");
Cloneable* paper1 = paper->clone();
cout<<boolalpha<<(paper == paper1);