3.创建型模式

3.创建型模式

3.1 简单工厂模式

简单工厂模式并不属于GoF的23种设计模式。

那么为什么我要用工厂模式呢?请看下面的一段程序。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//水果类
class Fruit
{
public:
	Fruit(string name)
    {
		m_name = name;
	}
	void showFruitName()
    {
		if (m_name.compare("apple") == 0)
        {
			cout << "我是苹果" << endl;
		}
		else if (m_name.compare("banana")==0)
        {
			cout << "我是香蕉" << endl;
		}
		else if (m_name.compare("pear") == 0)
        {
			cout << "我是鸭梨" << endl;
		}
	}
private:
	string m_name;
};

int main()
{
	Fruit* apple = new Fruit("apple");
	Fruit* banana = new Fruit("banana");
	Fruit* pear = new Fruit("pear");

	apple->showFruitName();
	banana->showFruitName();
	pear->showFruitName();

	system("pause");
	return EXIT_SUCCESS;
}

  不难看出,Fruit类是一个“巨大的”类,在该类的设计中存在如下几个问题:

   (1) 在Fruit类中包含很多“if…else…”代码块,整个类的代码相当冗长,代码越长,阅读难度、维护难度和测试难度也越大;而且大量条件语句的存在还将影响系统的性能,程序在执行过程中需要做大量的条件判断。

  (2) Fruit类的职责过重,它负责初始化和显示所有的水果对象,将各种水果对象的初始化代码和显示代码集中在一个类中实现,违反了“单一职责原则”,不利于类的重用和维护;

  (3) 当需要增加新类型的水果时,必须修改Fruit类的源代码,违反了“开闭原则”

3.1.1模式中的角色和职责

工厂(Factory)角色:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。

抽象产品(AbstractProduct)角色:简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。

具体产品(Concrete Product)角色:简单工厂模式所创建的具体实例对象。

(1)需求背景

传统创建对象的方式存在痛点:

  1. 客户端需自行创建对象: 自己在业务函数 / 类里 new 对象(比如new Anew B
  2. 对象创建过程复杂:若类的初始化要 “读文件→解析文本→创建对象→设置属性”,每次用这个类都得重复做这些步骤,很麻烦;
  3. 实际需求是 “只用不创建”:不想创建,只想拿来用,这是引入简单工厂的核心诉求。

(2)核心逻辑

工厂类(class Factory)创建方法(CreateObject ())具体类(class A、class B 等)

  1. 客户端给工厂传参数(图片标注 “传入参数,让工厂知道应该创建什么类型对象”);
  2. 工厂通过参数判断,执行对应类的创建过程(包括复杂的初始化步骤);
  3. 工厂把创建好的对象给客户端,客户端直接用对象(不用自己 new)。

(3)优缺点

①优点
  1. 客户端和具体类解耦:客户端不直接和具体类打交道,只跟工厂打交道;
  2. 不用关心复杂创建过程:对象创建的麻烦步骤(读文件、解析等)不用自己管,工厂帮着做。
②缺点
  1. 不符合开闭原则:新增类(比如再加个类)得修改工厂的源代码,增加新功能是通过修改源代码实现,不符合开闭原则;
  2. 工厂类职责过重:工厂要管所有类的创建,干的事太多,这个类职责过重,出问题会影响很多用这个工厂的模块。

(4)总结

  简单工厂模式是 “让工厂帮着创建对象” 的方案,解决了自己创建对象的耦合和麻烦,但因为 “改代码才能加新类”“工厂管太多事” 的缺陷,没成为标准设计模式;它适合产品类型少、不用频繁新增类的简单场景。

3.1.2 简单工厂模式案例

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

//抽象水果
class AbstractFruit
{
public:
    virtual void ShowName() = 0;
};

//苹果
class Apple : public AbstractFruit 
{
public:
    virtual void ShowName()
    {
        cout << "我是苹果!" << endl;
    }
};

//香蕉
class Banana : public AbstractFruit
{
public:
    virtual void ShowName()
    {
        cout << "我是香蕉!" << endl;
    }
};

//鸭梨
class Pear : public AbstractFruit
{
public:
    virtual void ShowName()
    {
        cout << "我是鸭梨!" << endl;
    }
};

//水果工厂
class FruitFactory
{
public:
    static AbstractFruit* CreateFruit(string flag)
    {
        if (flag == "apple") { return new Apple; }
        else if (flag == "banana") { return new Banana; }
        else if (flag == "pear") { return new Pear; }
        else { return NULL; }
    }
};

void test01()
{
    FruitFactory* factory = new FruitFactory;
    AbstractFruit* fruit = factory->CreateFruit("apple");
    fruit->ShowName();
    delete fruit;

    fruit = factory->CreateFruit("banana");
    fruit->ShowName();
    delete fruit;

    fruit = factory->CreateFruit("pear");
    fruit->ShowName();
    delete fruit;

    delete factory;
}

int main()
{
    test01();
    return 0;
}

(1)核心主题

  讲解简单工厂模式(Simple Factory Pattern) 的核心思想与代码实现,该模式属于创建型设计模式,核心目标是解耦对象的创建与使用。

(2)简单工厂模式核心思想

  1. 封装对象创建逻辑:将具体对象的创建过程集中到一个 “工厂类” 中,而非让客户端直接通过new创建对象;
  2. 依赖抽象而非具体:工厂类返回 “抽象产品类” 的指针,客户端仅需与抽象产品类、工厂类交互,无需直接依赖具体产品类;
  3. 简化客户端使用:客户端只需向工厂类传入指定标识(如字符串),即可获取所需对象,无需关心对象创建的细节(如初始化、属性设置等)。

(3)代码实现步骤(分模块拆解)

①定义抽象产品类(AbstractFruit)
  • 作用:作为所有具体产品类的基类,定义统一的接口,保证多态特性,使工厂类能返回统一类型的指针;

  • 代码实现:

    class AbstractFruit
    {
    public:
        // 纯虚函数,定义统一接口,子类必须实现
        virtual void ShowName() = 0;
    };
    
  • 重点:返回值必须是抽象指针,不能返回具体类型,否则无法创建多种数据类型的对象。

②定义具体产品类(Apple/Banana/Pear)
  • 作用:继承抽象产品类,实现抽象接口,封装具体产品的行为;

  • 代码实现(以苹果为例,香蕉、鸭梨逻辑一致):

    class Apple : public AbstractFruit 
    {
    public:
        // 实现抽象接口,输出具体产品名称
        virtual void ShowName()
        {
            cout << "我是苹果!" << endl;
        }
    };
    
  • 重点:具体水果类继承抽象水果类,必须实现 ShowName 方法,每个具体类仅需关注自身的行为。

③定义工厂类(FruitFactory)
  • 作用:封装对象创建逻辑,提供静态方法供客户端调用;

  • 核心设计:

    • 静态方法CreateFruit:无需创建工厂实例即可调用,降低使用成本;
    • 入参flag:通过字符串标识指定要创建的产品类型;
    • 返回值:抽象产品类指针(AbstractFruit*),保证多态;
  • 代码实现:

    class FruitFactory
    {
    public:
        static AbstractFruit* CreateFruit(string flag)
        {
            // 根据标识创建对应具体产品对象
            if (flag == "apple") { return new Apple; }
            else if (flag == "banana") { return new Banana; }
            else if (flag == "pear") { return new Pear; }
            else { return NULL; } // 无匹配类型返回空
        }
    };
    
  • 重点:工厂类是核心,所有对象的创建都由工厂完成,客户端只需告诉工厂要什么,不用自己 new。

(4)客户端调用与内存管理

①调用逻辑(test01 函数)
void test01()
{
    FruitFactory* factory = new FruitFactory;
    // 1. 获取苹果对象并使用
    AbstractFruit* fruit = factory->CreateFruit("apple");
    fruit->ShowName(); // 多态调用Apple::ShowName
    delete fruit;      // 释放苹果对象
    
    // 2. 获取香蕉对象并使用(复用抽象指针)
    fruit = factory->CreateFruit("banana");
    fruit->ShowName(); // 多态调用Banana::ShowName
    delete fruit;
    
    // 3. 获取鸭梨对象并使用
    fruit = factory->CreateFruit("pear");
    fruit->ShowName(); // 多态调用Pear::ShowName
    delete fruit;

    delete factory; // 释放工厂对象
}
②内存管理要点
  • 动态创建的对象(水果、工厂)必须手动delete,避免内存泄漏;
  • 抽象指针可复用,每次获取新对象前需释放上一个对象的内存;
  • 讲课重点:“用完对象一定要 delete,不然会有内存泄漏问题”。

(5)核心要点总结

  1. 抽象产品类是基础:保证多态特性,使工厂能返回统一的抽象指针,支持扩展多种具体产品;
  2. 工厂类封装创建逻辑:是解耦的核心,所有具体对象的创建都由工厂负责;
  3. 客户端 “面向抽象编程”:仅通过抽象指针调用方法,无需关注具体产品类型;   
  4. 内存管理不可忽视:动态创建的对象必须手动释放,避免内存泄漏;
  5. 扩展说明:新增水果类型时,只需新增具体水果类,并在工厂类的CreateFruit方法中添加对应的if-else判断,客户端无需修改。

3.1.3 简单工厂模式的优缺点

优点:

  1. 解耦:客户端仅依赖抽象产品类和工厂类,与具体产品类(Apple/Banana/Pear)解耦,修改具体产品类不影响客户端;
  2. 简化创建:对象创建逻辑集中在工厂类,若具体产品创建逻辑复杂(如读文件、设置属性),只需在工厂内修改,客户端无需感知;
  3. 易用性:客户端只需传入标识即可获取对象,代码更简洁(“拿来即用,不用关心创建过程”)。

缺点:

  1. 对工厂类职责过重,一旦不能工作,系统受到影响。
  2. 增加系统中类的个数,复杂度和理解度增加。
  3. 违反“开闭原则”,添加新产品需要修改工厂逻辑,工厂越来越复杂。

3.2 工厂方法模式

  工厂方法(Factory Method)模式的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。

  工厂方法模式是简单工厂模式的衍生,解决了许多简单工厂模式的问题。首先完全实现‘开-闭 原则’,实现了可扩展。

3.2.1 工厂方法模式中的角色与职责

抽象工厂(Abstract Factory)角色:工厂方法模式的核心,任何工厂类都必须实现这个接口。

工厂(Concrete Factory)角色:具体工厂类是抽象工厂的一个实现,负责实例化产品对象。

抽象产品(Abstract Product)角色:工厂方法模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。

具体产品(Concrete Product)角色:工厂方法模式所创建的具体实例对象。

简单工厂模式 + “开闭原则” = 工厂方法模式

(1)模式背景:解决简单工厂的痛点

简单工厂模式中,工厂类集中负责所有产品的创建,新增产品时需修改工厂代码,因此不符合 “开闭原则”(对扩展开放、对修改关闭)

工厂方法模式的核心目标是让 “工厂” 也符合开闭原则。

(2)核心思路

将工厂抽象化:定义抽象工厂类,为每个具体产品配套一个具体工厂类(具体工厂继承抽象工厂),由具体工厂单独负责创建对应的具体产品。

(3)结构组成

组件类型 作用 代码示例
抽象产品 定义产品的公共接口 AbstractFruit(含ShowName纯虚函数)
具体产品 实现抽象产品的具体类 AppleBananaPear
抽象工厂 定义工厂的创建接口 AbstractFruitFactory(含CreateFruit纯虚函数)
具体工厂 继承抽象工厂,创建对应产品 AppleFactory(创建Apple)、BananaFactory(创建Banana

(4)优缺点

优点
  • 符合开闭原则:新增产品只需加 “具体产品类 + 对应具体工厂类”,无需修改旧代码;
  • 实现创建与使用分离:客户端通过工厂获取产品,无需直接创建产品对象;
  • 扩展性好:新增产品的逻辑独立,不影响原有代码。
缺点
  • 类数量成倍增加:每新增 1 个产品,需配套新增 1 个工厂类,类膨胀会增加维护成本
  • 判断逻辑转移到客户端:简单工厂的判断在工厂内,工厂方法需客户端自行选择具体工厂,增加了客户端复杂度;
  • 抽象复杂度提升:需同时维护 “产品抽象层” 和 “工厂抽象层”,理解成本更高。

(5)代码实现说明(修正讲课中的笔误)

// 02 工厂方法模式.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
using namespace std;

//抽象水果
class AbstractFruit
{
public:
    virtual void ShowName() = 0;
};

//苹果
class Apple : public AbstractFruit
{
public:
    virtual void ShowName()
    {
        cout << "我是苹果!" << endl;
    }
};

//香蕉
class Banana : public AbstractFruit
{
public:
    virtual void ShowName()
    {
        cout << "我是香蕉!" << endl;
    }
};

//鸭梨
class Pear : public AbstractFruit
{
public:
    virtual void ShowName()
    {
        cout << "我是鸭梨!" << endl;
    }
};

//抽象工厂
class AbstractFruitFactory
{
public:
    virtual AbstractFruit* CreateFruit() = 0;
};

//苹果工厂
class AppleFactory : public AbstractFruitFactory
{
public:
    virtual AbstractFruit* CreateFruit()
    {
        return new Apple;
    }
};

//香蕉工厂
class BananaFactory : public AbstractFruitFactory
{
public:
    virtual AbstractFruit* CreateFruit()
    {
        return new Banana;
    }
};

//鸭梨工厂
class PearFactory : public AbstractFruitFactory
{
public:
    virtual AbstractFruit* CreateFruit()
    {
        return new Pear;
    }
};

void test01()
{
    AbstractFruitFactory* factory = NULL;
    AbstractFruit* fruit = NULL;

    //创建一个苹果工厂
    factory = new AppleFactory;
    fruit = factory->CreateFruit();
    fruit->ShowName();

    delete fruit;
    delete factory;

    //创建一个香蕉工厂
    factory = new BananaFactory;
    fruit = factory->CreateFruit();
    fruit->ShowName();

    delete fruit;
    delete factory;

    //创建一个鸭梨工厂
    factory = new PearFactory;
    fruit = factory->CreateFruit();
    fruit->ShowName();

    delete fruit;
    delete factory;
}

int main()
{
    test01();
    return 0;
}

讲课代码中抽象工厂类名存在笔误(应是AbstractFruitFactory而非AbstractFactory),正确结构如下:

①抽象产品类
class AbstractFruit { // 定义水果公共接口
public:
    virtual void ShowName() = 0; // 纯虚函数,规范产品行为
};
②具体产品类
//苹果
class Apple : public AbstractFruit
{
public:
    virtual void ShowName()
    {
        cout << "我是苹果!" << endl;
    }
};

//香蕉
class Banana : public AbstractFruit
{
public:
    virtual void ShowName()
    {
        cout << "我是香蕉!" << endl;
    }
};

//鸭梨
class Pear : public AbstractFruit
{
public:
    virtual void ShowName()
    {
        cout << "我是鸭梨!" << endl;
    }
};
③抽象工厂类
class AbstractFruitFactory { // 定义工厂的创建接口
public:
    virtual AbstractFruit* CreateFruit() = 0; // 纯虚函数,规范工厂行为
};
④具体工厂类
class AppleFactory : public AbstractFruitFactory { // 苹果工厂(创建Apple)
public:
    AbstractFruit* CreateFruit() override {
        return new Apple; // 负责创建对应产品
    }
};
// 同理:BananaFactory、PearFactory类(略)
⑤客户端使用(test01 函数)
void test01() 
{
    AbstractFruitFactory* factory = nullptr;
    AbstractFruit* fruit = nullptr;

    // 创建苹果:通过AppleFactory获取产品
    factory = new AppleFactory;
    fruit = factory->CreateFruit();
    fruit->ShowName(); 
    // 释放资源
    delete fruit;
    delete factory;

    // 创建香蕉、鸭梨:仅更换具体工厂,逻辑一致(略)
}

(6)核心特点

工厂方法模式可理解为 “简单工厂模式 + 开闭原则”,通过分散创建责任遵守开闭原则,但代价是类数量膨胀。

3.2.2 适用场景

  1. 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
  2. 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。

3.3 抽象工厂模式

  工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。此时,我们可以考虑将一些相关的产品组成一个“产品族,由同一个工厂来统一生产,这就是我们本文将要学习的抽象工厂模式的基本思想。

1.当我们想添加一个新产品的时候,比如葡萄,虽然不用修改代码,但是我们需要添加大量的类,而且还需要添加相对的工厂。(系统开销,维护成本)

2.如果我们使用同一地域的水果(日本,日本,日本),那么我们需要分别创建具体的工厂,如果选择出现失误,将会造成混乱,虽然可以加一些约束,但是代码实现变得复杂。

3.3.1模式中的角色和

抽象工厂(Abstract Factory)角色:它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。

具体工厂(Concrete Factory)角色:它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。

抽象产品(Abstract Product)角色:它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。

具体产品(Concrete Product)角色:它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法。

3.3.2抽象工厂模式案例

// 03 抽象工厂模式.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#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
{
public:
	virtual AbstractApple* CreateApple() { return new ChinaApple; }
	virtual AbstractBanana* CreateBanana() { return new ChinaBanana; }
	virtual AbstractPear* CreatePear() { return new ChinaPear; }
};

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

//日本工厂
class JapanFactory : public AbstractFactory
{
public:
	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;

	// 1. 创建“中国工厂”(选择产品族)
	factory = new ChinaFactory;
	// 2. 生产中国产品族的产品
	apple = factory->CreateApple();
	banana = factory->CreateBanana();
	pear = factory->CreatePear();
	// 3. 使用产品
	apple->ShowName();   // 输出:中国苹果!
	banana->ShowName();  // 输出:中国香蕉!
	pear->ShowName();    // 输出:中国鸭梨!

	// 4. 释放资源
	delete pear;
	delete banana;
	delete apple;
	delete factory;
}


int main()
{
	test01();
	return 0;
}

(1)工厂方法模式的缺点

工厂方法模式中,每新增一个具体产品,需对应新增一个工厂类

例如:若要生产 “中国 / 美国 / 倭国” 的 “苹果 / 香蕉 / 鸭梨” 共 9 个产品,需创建 9 个工厂类,会导致系统中类的数量急剧增加,冗余度高。

(2)抽象工厂模式的引入

为解决工厂方法的类冗余问题,抽象工厂模式针对 “产品族” 设计

  • 同一产地 / 厂商、功能不同的产品(如中国苹果、中国香蕉、中国鸭梨) 归为一个 “产品族”;
  • 一个具体工厂负责生产整个产品族的所有产品(如 “中国工厂” 可生产中国苹果、中国香蕉、中国鸭梨)。

由此,原本 9 个工厂可简化为 3 个(中国、美国、倭国工厂),大幅减少类数量。

(3)核心概念

概念 定义 示例
产品族 同一产地 / 厂商、功能不同的产品集合 中国苹果 + 中国香蕉 + 中国鸭梨
产品等级结构 功能相同、产地 / 厂商不同的产品集合(即工厂方法模式的 “产品等级”) 中国苹果 + 美国苹果 + 倭国苹果

(4)抽象工厂模式的开闭原则表现

  • 支持新增产品族(符合开闭原则)

    若新增 “菲律宾产品族”,只需新增PhilippinesFactory类,以及对应的PhilippinesApplePhilippinesBanana等具体产品类,无需修改原有代码。

  • 不支持新增产品等级(不符合开闭原则)

    若新增 “西瓜” 这一产品等级,需修改抽象工厂类(如AbstracFactory需增加CreateWatermelon纯虚函数),同时所有具体工厂(中国、美国、倭国工厂)都要新增该方法的实现,违反 “对扩展开放、对修改关闭” 的原则。

(5)代码实现步骤

抽象工厂模式的代码分为 “抽象产品层”“具体产品层”“抽象工厂层”“具体工厂层” 四个模块:

①抽象产品类(定义产品的公共接口)

对每个 “产品等级” 抽象出基类,包含纯虚函数(统一接口):

// 抽象苹果(产品等级:苹果)
class AbstractApple
{
public:
	virtual void ShowName() = 0;// 纯虚函数,定义产品行为
};

// 抽象香蕉(产品等级:香蕉)
class AbstractBanana
{
public:
	virtual void ShowName() = 0;
};

// 抽象鸭梨(产品等级:鸭梨)
class AbstractPear
{
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 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 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
{
public:
	virtual AbstractApple* CreateApple() { return new ChinaApple; }
	virtual AbstractBanana* CreateBanana() { return new ChinaBanana; }
	virtual AbstractPear* CreatePear() { return new ChinaPear; }
};

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

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

(6)代码测试示例

void test01()
{
	AbstractFactory* factory = NULL;
	AbstractApple* apple = NULL;
	AbstractBanana* banana = NULL;
	AbstractPear* pear = NULL;

	// 1. 创建“中国工厂”(选择产品族)
	factory = new ChinaFactory;
	// 2. 生产中国产品族的产品
	apple = factory->CreateApple();
	banana = factory->CreateBanana();
	pear = factory->CreatePear();
	// 3. 使用产品
	apple->ShowName();   // 输出:中国苹果!
	banana->ShowName();  // 输出:中国香蕉!
	pear->ShowName();    // 输出:中国鸭梨!

	// 4. 释放资源
	delete pear;
	delete banana;
	delete apple;
	delete factory;
}

3.3.3抽象工厂模式的优缺点

优点:

  • 拥有工厂方法模式的优点
  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
  • 增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。

缺点:

  • 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。

3.3.4适用场景

  • 系统中有多于一个的产品族。而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。
  • 产品等级结构稳定。设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。

3.4 单例模式

  单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

3.4.1单例模式中的角色和职责

Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。

如何构建单例:

一是单例模式的类只提供私有的构造函数,

二是类定义中含有一个该类的静态私有对象,

三是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。

(1)单例模式的基本概念

单例模式是一种设计模式,核心目标是:一个类在整个系统中仅能创建一个实例对象,且禁止外部用户自行创建该类的对象,实例的创建和访问完全由类自身控制。

  • 核心要求:全局仅存在一个该类的实例,不允许出现多个同类型对象(如 A1、A2 同时存在则违反单例原则);
  • 核心逻辑:限制外部创建对象的能力,由类自身管理唯一实例的创建和访问。

(2)单例模式的应用场景

基于 “全局唯一 + 减少创建开销” 的核心优势,常见应用场景:

  1. 系统级唯一窗口:如操作系统的任务管理器(无论多少次打开,仅一个窗口对象);
  2. 游戏开发:场景切换管理(Director 单例类)、动画管理(全局唯一控制类);
  3. 数据库操作:数据库连接对象(避免频繁创建 / 销毁连接的性能开销,全局仅一个连接对象);
  4. 业务逻辑:如 “户主” 等天然唯一的业务实体建模。

(3)单例模式的核心实现步骤

要实现单例类,需从 “禁止外部创建 + 类内创建 + 对外提供访问接口” 三个维度设计,核心三步:

步骤 实现逻辑 底层原理
1 构造函数私有化(private 外部无法通过new创建对象(new会调用构造函数,私有化后外部无法访问)
2 类内部定义静态私有的当前类指针 用于存储全局唯一的实例(类内部可访问私有构造函数,可创建实例)
3 提供静态公有的对外接口(如getInstance 外部无法创建对象,只能通过 “类名::接口名” 调用获取唯一实例

(4)单例模式的两种实现方式

单例模式分为 懒汉式饿汉式,核心区别在于唯一实例的创建时机

①懒汉式(Lazy Singleton)
  1. 核心特点

    • 延迟加载:实例在第一次调用 getInstance 时才会创建,不调用则不创建,节省内存。
    • 线程不安全:多线程环境下,若多个线程同时调用 getInstance,可能突破 if 判断,创建多个实例。
  2. 代码实现

    class Singleton_lazy
    {
    private:
        // 步骤1:构造函数私有化,禁止外部new
        Singleton_lazy() { cout << "我是懒汉构造!" << endl; }
        // 步骤2:静态私有指针,存储唯一实例(类外初始化)
        static Singleton_lazy* pSingleton;
    public:
        // 步骤3:静态公有接口,获取实例
        static Singleton_lazy* getInstance()
        {
            // 仅第一次调用时创建实例
            if (pSingleton == NULL) 
            { 
                pSingleton = new Singleton_lazy; 
            }
            return pSingleton;
        }
    };
    // 类外初始化静态指针为NULL(C++语法要求)
    Singleton_lazy* Singleton_lazy::pSingleton = NULL;
    
②饿汉式(Hungry Singleton)
  1. 核心特点

    • 提前加载:程序启动(类加载阶段)时,就完成实例的创建,无需延迟判断。
    • 天然线程安全:实例在程序运行初期就已创建,多线程调用 getInstance 不会产生冲突。
  2. 代码实现

    class Singleton_hungry
    {
    private:
        // 步骤1:构造函数私有化(加打印验证创建时机)
        Singleton_hungry() { cout << "我是饿汉构造!" << endl; }
        // 步骤2:静态私有指针,存储唯一实例
        static Singleton_hungry* pSingleton;
    public:
        // 步骤3:静态公有接口,直接返回实例
        static Singleton_hungry* getInstance() { return pSingleton; }
        // 补充:单例对象释放接口(主动回收内存)
        static void freeSpace()
        {
            if (pSingleton != NULL) 
            { 
                delete pSingleton; 
                pSingleton = NULL; // 避免野指针
            }
        }
    };
    // 类外直接初始化,程序启动时就创建实例
    Singleton_hungry* Singleton_hungry::pSingleton = new Singleton_hungry;
    
关键验证

饿汉式构造函数的打印会在main函数执行前输出(类外初始化在程序启动阶段执行),而懒汉式仅调用getInstance时才触发构造。

③懒汉式 vs 饿汉式 对比
对比维度 懒汉式 饿汉式
实例创建时机 第一次调用getInstance 程序启动 / 类加载时
加载策略 懒加载(按需创建) 预加载(主动创建)
代码逻辑 需要判空(if (NULL) 无需判空,直接返回
线程安全 非线程安全(需加锁) 天然线程安全(提前创建)
内存开销 启动时无开销 启动时即占用内存

(5)代码实现注意事项

  1. 构造函数必须私有化:外部无法通过newA a等方式创建对象,是单例的基础;
  2. 存储实例的指针必须是staticgetInstance是静态方法,只能访问静态成员;
  3. getInstance必须是static:外部无法创建类对象,只能通过 “类名::接口名” 调用;
  4. 静态成员变量必须类外初始化:C++ 语法规定,静态成员不属于某个对象,需在类外初始化;
  5. 单例对象释放:默认由操作系统在程序结束时回收,如需主动释放,可提供静态释放接口(如freeSpace),释放后需将指针置NULL避免野指针;
  6. 避免递归创建:示例中class A的构造函数写A() { a = new A; }是错误的(递归创建导致栈溢出),正确方式是在getInstance中创建实例。

(6)验证单例有效性

通过对比多个指针是否指向同一内存地址验证单例:

void test01()
{
    // 懒汉式验证
    Singleton_lazy* p1 = Singleton_lazy::getInstance();
    Singleton_lazy* p2 = Singleton_lazy::getInstance();
    if (p1 == p2) { cout << "懒汉式:两个指针指向同一块内存,是单例!" << endl; }

    // 饿汉式验证
    Singleton_hungry* p3 = Singleton_hungry::getInstance();
    Singleton_hungry* p4 = Singleton_hungry::getInstance();
    if (p3 == p4) { cout << "饿汉式:两个指针指向同一块内存,是单例!" << endl; }
}

执行结果:两个指针均指向同一内存,验证单例实现成功。

(7)单例对象的内存释放问题

①不推荐方案:手动提供释放函数(freeSpace)
A.实现思路

在类中增加静态释放函数,判断实例指针非空时delete释放:

static void freeSpace()
{
    if (pSingleton != NULL){ delete pSingleton; }
}

外部可通过Singleton_hungry::freeSpace()调用释放。

B.核心弊端

单例对象是全局唯一的,若任意位置误调用该函数,会导致后续获取实例的操作全部出错(实例已销毁),系统无法正常运行,严禁提供此类函数

②推荐方案:无需手动释放(核心结论)
  • 单例对象仅创建一次,存储在堆区;
  • 程序结束时,操作系统会自动回收进程占用的所有内存(包括堆区的单例对象);
  • 单例仅占一份内存,不会因重复创建导致内存溢出,因此无需手动释放。
③特殊需求方案:嵌套回收类(Garbo)自动释放(多此一举)
A.实现思路
  • 在单例类内部定义嵌套类Garbo(垃圾回收类),仅实现析构函数,在析构中释放单例实例;
  • 在单例类中定义静态的Garbo对象garbo
  • 程序结束时,静态对象会自动调用析构函数,Garbo的析构函数执行时释放单例实例。
B.代码实现
class Singleton_hungry
{
    // ... 其他代码 ...
    // 嵌套回收类(俗称“垃圾工人”)
    class Garbo
    {
        ~Garbo()
        {
            // 析构时释放单例实例
            if (pSingleton != NULL){ delete pSingleton; }
        }
    };
    // 静态回收对象,程序结束时自动析构
    static Garbo garbo;
};
// 静态成员类外初始化(C++语法要求)
Singleton_hungry::Garbo Singleton_hungry::garbo;
C.说明

该方式虽能实现自动释放,但本质是 “多此一举”—— 因为程序结束时系统已自动回收内存,仅适用于 “必须显式释放单例” 的特殊场景。

D.完整代码:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;


class A
{
private:
	A() { a = new A; }
public:
	static A* getInstace() { return a; }

private:
	static A* a;
};

A* A::a = NULL;

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

//单例 分为懒汉式 
class Singleton_lazy
{
private:
	Singleton_lazy() { cout << "我是懒汉构造!" << endl; }
public:
	static Singleton_lazy* getInstance()
	{
		if (pSingleton == NULL) { pSingleton = new Singleton_lazy; }
		return pSingleton;
	}
private:
	static Singleton_lazy* pSingleton;
};
//类外初始化
Singleton_lazy* Singleton_lazy::pSingleton = NULL;

//饿汉式
class Singleton_hungry
{
private:
	// 步骤1:构造函数私有化(加打印验证创建时机)
	Singleton_hungry() { cout << "我是饿汉构造!" << endl; }
	// 步骤2:静态私有指针,存储唯一实例
	static Singleton_hungry* pSingleton;
	static Garbo garbo;
public:
	// 步骤3:静态公有接口,直接返回实例
	static Singleton_hungry* getInstance() { return pSingleton; }
#if 0
	// 补充:单例对象释放接口(主动回收内存)
	static void freeSpace()
	{
		if (pSingleton != NULL)
		{
			delete pSingleton;
			pSingleton = NULL; // 避免野指针
		}
	}
#endif
	class Garbo
	{
		~Garbo()
		{
			if (pSingleton != NULL) { delete pSingleton; }
		}
	};
};
// 类外直接初始化,程序启动时就创建实例
Singleton_hungry* Singleton_hungry::pSingleton = new Singleton_hungry;


void test01()
{

	Singleton_lazy* p1 = Singleton_lazy::getInstance();
	Singleton_lazy* p2 = Singleton_lazy::getInstance();

	if (p1 == p2) { cout << "两个指针指向同一块内存空间,是单例!" << endl; }
	else { cout << "不是单例模式!" << endl; }

	Singleton_hungry* p3 = Singleton_hungry::getInstance();
	Singleton_hungry* p4 = Singleton_hungry::getInstance();

	if (p3 == p4) { cout << "两个指针指向同一块内存空间,是单例!" << endl; }
	else { cout << "不是单例模式!" << endl; }
}

//单例对象释放问题

void test02() {}


int main(void)
{
	test01();

	return 0;
}

3.4.2单例碰到多线程

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<Windows.h>
using namespace std;

/* 懒汉式 */
class Chairman_lazy{
private:
	Chairman_lazy(){}
public:
	static Chairman_lazy* getInstance(){
		if (s_singleton == NULL){
			//Sleep(1000); //等到1000秒
			s_singleton = new Chairman_lazy;
		}
		return s_singleton;
	}
private:
	static Chairman_lazy* s_singleton;
};

Chairman_lazy* Chairman_lazy::s_singleton = NULL;


/* 饿汉式 */
class Chairman_hangry{
private:
	Chairman_hangry(){}
public:
	static Chairman_hangry* getInstance(){
		return s_singleton;
	}
private:
	static Chairman_hangry* s_singleton;
};

//初始化
Chairman_hangry* Chairman_hangry::s_singleton = new Chairman_hangry;

DWORD WINAPI MyThread_hangry(LPVOID lpThreadParameter){
	
	Chairman_hangry* chairman = Chairman_hangry::getInstance();
	cout << "单例对象地址:" << (int*)chairman << endl;

	return 0;
}

//饿汉式单例碰到多线程测试
void test01(){

	HANDLE handler[10];
	for (int i = 0; i < 10;i++){
		handler[i] = CreateThread(NULL, NULL, MyThread_hangry, NULL, NULL, NULL);
	}

}

DWORD WINAPI MyThread_lazy(LPVOID lpThreadParameter){

	Chairman_lazy* chairman = Chairman_lazy::getInstance();
	cout << "单例对象地址:" << (int*)chairman << endl;

	return 0;
}

//懒汉式单例碰到多线程
void test02(){

	HANDLE handler[10];
	for (int i = 0; i < 10; i++){
		handler[i] = CreateThread(NULL, NULL, MyThread_lazy, NULL, NULL, NULL);
	}

}

int main(){

	//test01();
	test02();

	system("pause");
	return EXIT_SUCCESS;
}

Test01函数执行结果(饿汉式单例模式):

Test02函数执行结果(懒汉式单例模式):

练习:用单例模式,模拟公司员工使用打印机场景,打印机可以打印员工要输出的内容,并且可以累积打印机使用次数

思考:当单例模式中的懒汉模式遇见多线程,改怎么办?

(1)课前铺垫:单例模式的创建方式

本节课以单例模式为切入点引入多线程问题,首先回顾单例模式的两种核心创建方式:

  • 懒汉式:按需创建实例,使用时才初始化;
  • 饿汉式:类加载时就完成实例创建,提前初始化。

(2)多线程基础概念

①单线程执行特征

单线程程序中,代码按 “从上到下逐个执行” 的顺序运行。例如包含test01()test02()两个函数的程序,会先完整执行test01(),再执行test02(),执行顺序完全确定。

②多线程执行特征

多线程程序中,不同线程中的函数会 “几乎同时执行”,执行顺序无固定规律:

  • 若将test01()放入线程 A、test02()放入线程 B,两个函数不再按先后顺序执行;
  • 即使两个函数仅执行简单的print("hello world"),也无法确定test01()test02()哪个先执行完成。

(3)多线程核心问题:竞争资源(线程安全问题)

①案例分析(全局变量操作)

以全局变量int a = 10为例,线程 A 的test01()和线程 B 的test02()同时操作该变量:

  • 线程 A(test01):执行a = a + 10
  • 线程 B(test02):执行if (a == 10) { a = a - 10; }
②执行结果的不确定性

由于两个线程几乎同时操作变量a,最终结果无法确定,典型场景:

  • 场景 1:线程 B 先执行,a=10满足条件,执行a-10a=0;线程 A 再执行a+10,最终a=10
  • 场景 2:线程 A 先执行,a=10+10=20;线程 B 判断a≠10,不执行减法,最终a=20
  • 场景 3:线程 A 执行a+10的过程中,线程 B 抢先执行减法,导致结果混乱。
③核心概念:竞争资源

多线程环境下,被多个线程同时操作的共享变量(如上例中的a)称为竞争资源(也叫共享资源),多个线程会 “争抢” 对该资源的操作权,导致变量值的最终结果不可控。

(4)解决思路:加锁机制

①加锁的核心原理

通过 “锁” 限制竞争资源的访问规则,保证同一时间只有一个线程能操作该资源,流程如下:

  1. 线程 A 先访问变量a时,为a加锁(相当于 “关上门”),独占操作权限;
  2. 线程 B 此时尝试访问a,发现已加锁,只能在 “门口等待”;
  3. 线程 A 完成对a的操作后,释放锁(“打开门”)并退出;
  4. 线程 B 检测到锁已释放,进入操作a,并重新加锁,防止其他线程抢占。
②锁机制的注意事项:死锁

若线程操作完竞争资源后未释放锁(“出门没开门”),会导致其他线程永远无法获取锁,这种现象称为死锁,是多线程开发中需重点避免的问题。

(5)编程实践建议

实际开发中,多线程场景下应尽量避免使用全局变量:

  • 全局变量易成为竞争资源,导致值的不确定性;
  • 若必须使用共享变量,需通过加锁机制保证线程安全(后续多线程课程会详细讲解加锁的具体实现)。

(5)核心总结

  1. 单线程执行顺序确定,多线程执行几乎同时,顺序不可控;
  2. 多线程共享变量(竞争资源)的并发操作会导致结果不确定;
  3. 加锁是解决竞争资源问题的核心思路,需注意避免死锁;
  4. 多线程场景应减少全局变量使用,降低线程安全风险。

(6)懒汉式单例的多线程安全问题

①懒汉式特点

实例采用延迟创建:只有调用 getInstance() 方法时,才会创建类的实例。

②多线程下的风险场景

当两个线程(如线程 A、线程 B)几乎同时调用 getInstance() 时:

  • 两个线程会同时执行 “实例是否为空” 的判断;
  • 由于线程并发执行,两者都会认为 “实例为空”,进而都执行 new 操作,最终创建出多个实例

这违背了单例 “唯一实例” 的核心原则,因此懒汉式单例是线程不安全的

(7)饿汉式单例的多线程安全性

①饿汉式特点

实例采用提前创建:在 main 函数执行前(程序启动阶段),就已经完成了类实例的创建。

②多线程下的表现

当多线程启动并调用 getInstance() 时,实例已经是 “预先创建好的唯一实例”,线程仅会直接返回该实例 —— 无论多少个线程调用,拿到的都是同一个对象。

因此饿汉式单例是线程安全的

(8)懒汉式 vs 饿汉式(多线程场景对比)

单例类型 实例创建时机 多线程安全性 优缺点
懒汉式 调用getInstance 不安全 延迟创建省资源,但需手动加锁解锁(实现麻烦)
饿汉式 程序启动前 安全 线程安全无需额外处理,但提前创建会占用资源

(9)实操建议

  • 优先选择饿汉式单例:线程安全且实现简单,无需手动处理多线程同步问题;
  • 若要使用懒汉式:需自行添加 “加锁 / 解锁” 逻辑保证线程安全,操作相对繁琐。

3.4.3 单例模式的优缺点

优点:

  • 单例模式提供了对唯一实例的受控访问。
  • 节约系统资源。由于在系统内存中只存在一个对象。

缺点:

  • 扩展略难。单例模式中没有抽象层。
  • 单例类的职责过重。

3.4.4适用场景

  • 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
  • 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

参考资料来源:黑马程序员

posted @ 2025-12-20 10:32  CodeMagicianT  阅读(1)  评论(0)    收藏  举报