创建者模式 ######################################## 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 简单工厂模式 **************************************** 简单工厂模式将创建对象的任务移交给工厂,通过产品接口对产品进行约束。 .. uml:: @startuml class PhoneFactory abstract class Phone Phone<|..MiPhone Phone <|..ApplePhone PhoneFactory <..Phone @enduml 我们通过 ``Phone`` 对产品接口进行约束,然后通过 ``PhoneFactory`` 去获取对象。通过这种方式,以后我们再拓展产品时只需要 ``继承接口类`` 、 ``在工厂类里添加条件分支`` 。而客户也只需要知道产品的名称就行,产品对应的类名可以根据需求任意更改。 简单工厂模式做到了: - 可复用:新增的产品可以直接使用已有接口。要使用产品,只需要导入工厂类即可 - 松耦合:新增的产品不会对已有产品的代码造成任何影响。 简单工厂模式解决了在实现上面两个优点的情况下 ``工厂类实例化哪一个产品`` 的工作。 .. code-block:: cpp 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** 三种不同的协议。 - 获取日志储存器:可选择 **本地硬盘**, **系统事件**, **远程服务器** 等。 工厂模式 **************************************** 简单工厂虽然解决了如何实例化的问题,但是每次增加一个产品都需要修改原有代码,很明显 **违背了开闭原则**。而工厂模式不再设立大工厂,而是对工厂创建对象的接口进行约束,让那个工厂接口的子类决定实例化哪个类。工厂方法使一个类的实例化推迟到工厂子类中。 .. uml:: @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 .. code-block:: cpp #include "SimpleFactory.h" class Factory{ public: virtual Phone* makePhone() = 0; }; class MiFactory : public Factory{ public: Phone * makePhone() override{ cout<<"Make Mi Phone"<makePhone(); auto appleFactory = new AppleFactory; auto apple = appleFactory->makePhone(); 通过工厂模式,我们现在不仅可以生产 ``Phone``,还可以生产 ``Clothes`` 等产品。但是你必须要知道你需要的产品是哪个工厂生产的。但是,虽然通过 ``Factory`` 对创建产品的接口进行了限制,``一个工厂负责一个产品`` 会造成大量的类冗余。 相比而言,工厂模式遵循了开闭原则,但是客户端需要独立判读实例化哪一个工厂,等于是将简单工厂的逻辑判断移交给客户端。现在,当你需要拓展产品时,你需要实例化产品及对应工厂,而客户端则自由判断是否需要使用这个产品。 抽象工厂模式 **************************************** 抽象工厂是对工厂模式的进一步抽象,提出了 “工厂的工厂” 这概念。获取产品时,首先从超级工厂(即:工厂的工厂)获取工厂,然后从简单工厂获取产品。这样,只要你知道产品的类别就行了: .. uml:: @startuml abstract BigFactory{ + PhoneFactory* createPhone + ClothesFactory* createClothes } BigFactory <.. ClothesFactory BigFactory <.. PhoneFactory ClothesFactory <..Clothes Clothes <|..Jacket Clothes <|..Trouser PhoneFactory <.. Phone Phone <|.. MiPhone Phone <|.. ApplePhone @enduml .. code-block:: cpp #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"); 相比于工厂模式,抽象工厂模式可以出产多种产品,当增加商品时,需要添加商品类并修改对应的产品工厂。当添加新类型的产品时,还需要修改抽象工厂,一共三个类。 抽象工厂模式具有多种变体,对于拥有 **反射** 的语言来说,可以将抽象工厂改装地更简单。 单例模式 **************************************** 单例模式的优缺点: 优点: #. 单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例 #. 例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。 #. 供了对唯一实例的受控访问。 #. 于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。 #. 许可变数目的实例。 #. 免对共享资源的多重占用。 缺点: #. 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。 #. 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。 #. 单例类的职责过重,在一定程度上违背了“单一职责原则”。 #. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。 单例模式用于创建一个系统中唯一的对象,例如打印机、全局内存池。单例模式确保当你通过任意方法获取唯一对象时,得到的对象都是一样的。 根据对象创建的时机,又可以把单例模式分为懒汉式和饿汉式。其中,懒汉式在有方法访问唯一对象时才会创建对象,而饿汉式则在程序加载时就创建对象: .. code-block:: cpp // 懒汉式 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; .. note:: - 使用静态对象和懒汉式需要注意线程安全问题 - Java 中可以使用双检锁的形式保证线程安全性,这是因为 Java5 修改了 volatile 的语义,但是 C++ 中并不是安全的。但是 C++ 11 保证静态对象是线程安全的 建造者模式 **************************************** 建造者模式将一个复杂对象分解为多个简单对象,其适用于复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定. .. uml:: @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 .. code-block:: cpp struct Computer{ string os; string cpu; friend ostream& operator<<(ostream& stdos, Computer&comp); }; ostream &operator<<(ostream &stdos, Computer &comp) { stdos<buildCPU(cpu); builder->buildOS(); return builder->getComputer(); } }; // 使用方式 auto builder = new MibookBuilder; auto manager = new Manager(builder); auto computer = manager->build("Intel"); cout<`_ - `设计模式之建造者(Builder)模式 `_ - `建造者模式 `_ 原型模式 **************************************** 原型模式主要解决对象的拷贝问题。 C++ 和 Java 的原型模式略有不同,Java 主要用于减少构造函数执行的开销,其方式就是通过重写 ``Cloneable`` 接口实现深拷贝。而 C++ 在类型已知的情况下可以直接调用拷贝构造函数,因此,C++ 中原型模式的意义在于 ``拷贝类型未知的对象``。 .. note:: 对于一个对象的拷贝 - Java 需要先创建新对象,然后再逐一复制,这就导致中间多了构造函数的开销。通过重写 ``Cloneable`` 接口可以达到直接拷贝的目的,减少了构造函数执行的开销。 - C++ 可以通过重写拷贝构造函数达到深拷贝的目的。 那么何时类型未知呢?答案是当你通过基类指针操作子类对象时,而子类对象可能是非常多的,甚至可能不是你写的,这时候通过拷贝构造函数复制对象很明显是不显示的,因为构造函数是没有多态的,而且构造函数内虚函数也不生效。 .. code-block:: cpp 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<`_ - `设计模式之原型模式(Java代码实现) `_ - `原型模式 `_