创建者模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
简单工厂模式
简单工厂模式将创建对象的任务移交给工厂,通过产品接口对产品进行约束。
我们通过 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, IMAP, HTTP 三种不同的协议。
获取日志储存器:可选择 本地硬盘, 系统事件, 远程服务器 等。
工厂模式
简单工厂虽然解决了如何实例化的问题,但是每次增加一个产品都需要修改原有代码,很明显 违背了开闭原则。而工厂模式不再设立大工厂,而是对工厂创建对象的接口进行约束,让那个工厂接口的子类决定实例化哪个类。工厂方法使一个类的实例化推迟到工厂子类中。
#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 对创建产品的接口进行了限制,一个工厂负责一个产品 会造成大量的类冗余。
相比而言,工厂模式遵循了开闭原则,但是客户端需要独立判读实例化哪一个工厂,等于是将简单工厂的逻辑判断移交给客户端。现在,当你需要拓展产品时,你需要实例化产品及对应工厂,而客户端则自由判断是否需要使用这个产品。
抽象工厂模式
抽象工厂是对工厂模式的进一步抽象,提出了 “工厂的工厂” 这概念。获取产品时,首先从超级工厂(即:工厂的工厂)获取工厂,然后从简单工厂获取产品。这样,只要你知道产品的类别就行了:
#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");
相比于工厂模式,抽象工厂模式可以出产多种产品,当增加商品时,需要添加商品类并修改对应的产品工厂。当添加新类型的产品时,还需要修改抽象工厂,一共三个类。
抽象工厂模式具有多种变体,对于拥有 反射 的语言来说,可以将抽象工厂改装地更简单。
单例模式
单例模式的优缺点:
优点:
单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
供了对唯一实例的受控访问。
于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
许可变数目的实例。
免对共享资源的多重占用。
- 缺点:
不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
单例类的职责过重,在一定程度上违背了“单一职责原则”。
滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
单例模式用于创建一个系统中唯一的对象,例如打印机、全局内存池。单例模式确保当你通过任意方法获取唯一对象时,得到的对象都是一样的。
根据对象创建的时机,又可以把单例模式分为懒汉式和饿汉式。其中,懒汉式在有方法访问唯一对象时才会创建对象,而饿汉式则在程序加载时就创建对象:
// 懒汉式
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 保证静态对象是线程安全的
建造者模式
建造者模式将一个复杂对象分解为多个简单对象,其适用于复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定.
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);