软件设计模式-创建型
ssk-wh Lv4

创建型模式涉及到对象的创建和实例化。

以下是几种常见的创建型模式:

工厂方法模式:定义一个工厂接口,由子类来决定具体实例化哪个对象。

抽象工厂模式:提供一个创建一系列相关或相互依赖的对象的接口,而无需指定它们具体的类。

单例模式:保证一个类仅有一个实例,并提供一个全局访问点来访问这个唯一实例。

建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

原型模式:通过复制现有的实例来创建新的实例。

这些模式都关注于如何创建对象,并提供了不同的解决方案来满足不同的需求。

工厂模式

工厂模式是一种创建型设计模式,它提供了一种封装对象创建过程的方式,使得客户端无需关心具体对象是如何创建的,只需要调用工厂方法就可以得到所需的对象。在工厂模式中,有一个工厂类负责创建对象,并向客户端提供接口。

工厂模式可以分为两种:简单工厂模式和工厂方法模式。

简单工厂模式

简单工厂模式又称为静态工厂方法模式,它通过一个工厂类来创建对象。客户端只需要向工厂类传递一个指定的参数(例如一个字符串),工厂类就会根据这个参数来决定创建哪种具体对象。简单工厂模式违反了开放-封闭原则,因为如果需要添加新类型的产品,需要修改工厂类的代码。

(开放-封闭原则:开放-封闭原则(Open-Closed Principle, OCP)是指一个软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。也就是说,当需求变化时,应该尽量通过扩展原有代码来实现新的功能,而不是通过修改已有代码来实现。开放-封闭原则是面向对象设计的重要原则之一,它可以提高软件系统的可维护性、可扩展性和复用性,降低软件开发和维护的成本。)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Product {
public:
virtual ~Product() {}

virtual void operation() = 0;
};

class ConcreteProductA : public Product {
public:
void operation() {
std::cout << "ConcreteProductA operation" << std::endl;
}
};

class ConcreteProductB : public Product {
public:
void operation() {
std::cout << "ConcreteProductB operation" << std::endl;
}
};

class SimpleFactory {
public:
static Product* createProduct(const std::string& type) {
if (type == "A") {
return new ConcreteProductA();
} else if (type == "B") {
return new ConcreteProductB();
} else {
return nullptr;
}
}
};

int main() {
Product* productA = SimpleFactory::createProduct("A");
Product* productB = SimpleFactory::createProduct("B");

productA->operation(); // Output: ConcreteProductA operation
productB->operation(); // Output: ConcreteProductB operation

delete productA;
delete productB;

return 0;
}

工厂方法模式

工厂方法模式又称为多态性工厂模式,它定义了一个创建对象的接口,但是由子类来决定要实例化哪个类。工厂方法让类的实例化推迟到子类中进行。这样,客户端代码就不需要依赖于具体的产品类。工厂方法模式满足了开放-封闭原则,因为如果需要添加新类型的产品,只需要添加新的工厂子类即可,不需要修改已有的代码。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>
using namespace std;

// 抽象产品类
class Product {
public:
virtual void use() = 0;
};

// 具体产品类A
class ProductA : public Product {
public:
void use() override {
cout << "I'm product A." << endl;
}
};

// 具体产品类B
class ProductB : public Product {
public:
void use() override {
cout << "I'm product B." << endl;
}
};

// 抽象工厂类
class Factory {
public:
virtual Product* createProduct() = 0;
};

// 具体工厂类A
class FactoryA : public Factory {
public:
Product* createProduct() override {
return new ProductA();
}
};

// 具体工厂类B
class FactoryB : public Factory {
public:
Product* createProduct() override {
return new ProductB();
}
};

int main() {
// 创建工厂对象
Factory* factoryA = new FactoryA();
Factory* factoryB = new FactoryB();

// 生产产品A
Product* productA = factoryA->createProduct();
productA->use();

// 生产产品B
Product* productB = factoryB->createProduct();
productB->use();

// 释放资源
delete productA;
delete productB;
delete factoryA;
delete factoryB;

return 0;
}

在这个示例中,我们定义了抽象产品类Product,并实现了两个具体产品类ProductAProductB,这些产品类都实现了抽象产品类中定义的接口use。接下来,我们定义了抽象工厂类Factory,并实现了两个具体工厂类FactoryAFactoryB,这些工厂类都实现了抽象工厂类中定义的接口createProduct,用于创建具体产品类的对象。最后,我们在main函数中创建了具体工厂对象,并使用这些工厂对象创建了具体产品对象,并通过调用use方法来使用这些产品对象。在程序结束时,我们需要释放这些对象所占用的资源。

抽象工厂

抽象工厂模式是一种创建型设计模式,它可以提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。

场景

抽象工厂模式通常用于以下场景:

  1. 当需要创建一系列相互依赖或相互关联的对象时,可以使用抽象工厂模式。这些对象之间有一定的约束关系,因此必须一起创建,否则可能会导致系统出现错误。
  2. 当需要在不同操作系统或不同外观风格下创建一系列相关的对象时,可以使用抽象工厂模式。这些对象之间的差异比较大,因此需要使用不同的工厂来创建它们。
  3. 当希望提供一个产品族的接口,而不是单一产品的接口时,可以使用抽象工厂模式。产品族指的是一组相关的产品,这些产品具有共同的特点,例如相同的界面风格、相同的操作方式等等。

在抽象工厂模式中,抽象工厂类定义了一组用于创建产品对象的工厂方法,这些工厂方法分别负责创建不同类型的产品。具体工厂类实现了这些工厂方法,并创建了具体的产品对象。抽象产品类定义了产品的接口,具体产品类实现了产品的具体功能。

优点

使用抽象工厂模式可以将客户端代码与具体产品类的实现分离,从而提高系统的灵活性和可维护性。同时,抽象工厂模式也可以保证客户端代码始终引用抽象工厂和抽象产品类,而不用关心具体工厂和具体产品类的实现细节。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 抽象产品类 - 手机
class Phone {
public:
virtual void display() = 0;
};

// 具体产品类 - 华为手机
class HuaweiPhone : public Phone {
public:
void display() {
std::cout << "This is a Huawei phone." << std::endl;
}
};

// 具体产品类 - 小米手机
class XiaomiPhone : public Phone {
public:
void display() {
std::cout << "This is a Xiaomi phone." << std::endl;
}
};

// 抽象产品类 - 电脑
class Computer {
public:
virtual void display() = 0;
};

// 具体产品类 - 华为电脑
class HuaweiComputer : public Computer {
public:
void display() {
std::cout << "This is a Huawei computer." << std::endl;
}
};

// 具体产品类 - 小米电脑
class XiaomiComputer : public Computer {
public:
void display() {
std::cout << "This is a Xiaomi computer." << std::endl;
}
};

// 抽象工厂类
class AbstractFactory {
public:
virtual Phone* createPhone() = 0;
virtual Computer* createComputer() = 0;
};

// 具体工厂类 - 华为工厂
class HuaweiFactory : public AbstractFactory {
public:
Phone* createPhone() {
return new HuaweiPhone();
}
Computer* createComputer() {
return new HuaweiComputer();
}
};

// 具体工厂类 - 小米工厂
class XiaomiFactory : public AbstractFactory {
public:
Phone* createPhone() {
return new XiaomiPhone();
}
Computer* createComputer() {
return new XiaomiComputer();
}
};

// 客户端代码
int main() {
AbstractFactory* huaweiFactory = new HuaweiFactory();
Phone* huaweiPhone = huaweiFactory->createPhone();
Computer* huaweiComputer = huaweiFactory->createComputer();
huaweiPhone->display();
huaweiComputer->display();

AbstractFactory* xiaomiFactory = new XiaomiFactory();
Phone* xiaomiPhone = xiaomiFactory->createPhone();
Computer* xiaomiComputer = xiaomiFactory->createComputer();
xiaomiPhone->display();
xiaomiComputer->display();

return 0;
}

在这个示例中,AbstractFactory 是抽象工厂类,定义了创建手机和电脑的接口。HuaweiFactoryXiaomiFactory 是具体工厂类,分别创建华为品牌和小米品牌的手机和电脑。PhoneComputer 是抽象产品类,定义了手机和电脑的接口。HuaweiPhoneXiaomiPhoneHuaweiComputerXiaomiComputer 是具体产品类,实现了 PhoneComputer 的接口。

客户端代码只需要使用抽象工厂类和抽象产品类,不需要了解具体工厂类和具体产品类的实现细节。这样可以大大提高系统的

区别

简单工厂、工厂方法、抽象工厂都属于工厂模式,它们的主要区别如下:

  1. 简单工厂模式:由一个工厂类根据传入的参数决定创建哪一种产品类的实例。即通过一个简单的工厂类来创建产品,用户只需要传入所需的产品类型即可,不需要关心产品的具体实现细节。
  2. 工厂方法模式:定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。即每个产品类都有一个对应的工厂类,用户需要使用哪种产品,就选择对应的工厂类。工厂方法模式符合开放封闭原则,可以新增产品类而不需要修改原有的工厂类代码。
  3. 抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。即每个具体工厂类负责创建一整套产品,这些产品之间存在着相关性,需要一起使用。抽象工厂模式适用于产品族概念的场景,也符合开放封闭原则。

简单来说,三者的区别在于职责不同:简单工厂模式是由一个工厂类来负责创建产品;工厂方法模式是由每个产品类对应一个工厂类来创建产品;而抽象工厂模式则是由每个具体工厂类来创建一整套产品。

建造者模式

建造者模式是一种创建型设计模式,它的主要思想是将一个复杂对象的构建过程与其表示分离开来,使得同样的构建过程可以创建不同的表示。

建造者模式通常包含以下几个角色:

产品类(Product):要创建的复杂对象。

抽象建造者类(Builder):抽象出来的用于创建产品各个部分的方法,以及返回最终产品的方法。

具体建造者类(ConcreteBuilder):实现抽象建造者类中定义的方法,用于构建产品的各个部分。

指挥者类(Director):调用建造者类中定义的方法来构建产品,并返回最终的产品。

建造者模式的核心思想是将产品的构造过程分解为若干个部分,每个部分的构造细节由具体的建造者类来实现。指挥者类则按照一定的顺序来调用建造者类的方法,最终构建出完整的产品。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 产品类
class Computer {
public:
void setCPU(string cpu) { m_cpu = cpu; }
void setMemory(string memory) { m_memory = memory; }
void setHardDisk(string hardDisk) { m_hardDisk = hardDisk; }
void setOS(string os) { m_os = os; }
void show() {
cout << "CPU: " << m_cpu << endl;
cout << "Memory: " << m_memory << endl;
cout << "HardDisk: " << m_hardDisk << endl;
cout << "OS: " << m_os << endl;
}

private:
string m_cpu;
string m_memory;
string m_hardDisk;
string m_os;
};

// 抽象建造者类
class Builder {
public:
virtual ~Builder() {}
virtual void buildCPU() = 0;
virtual void buildMemory() = 0;
virtual void buildHardDisk() = 0;
virtual void buildOS() = 0;
virtual Computer* getResult() = 0;
};

// 具体建造者类
class ConcreteBuilder : public Builder {
public:
ConcreteBuilder() {
m_computer = new Computer();
}
~ConcreteBuilder() {
delete m_computer;
m_computer = nullptr;
}
void buildCPU() { m_computer->setCPU("Intel Core i5"); }
void buildMemory() { m_computer->setMemory("8GB DDR4"); }
void buildHardDisk() { m_computer->setHardDisk("512GB SSD"); }
void buildOS() { m_computer->setOS("Windows 10"); }
Computer* getResult() { return m_computer; }

private:
Computer* m_computer;
};

// 指挥者类
class Director {
public:
void create(Builder* builder) {
builder->buildCPU();
builder->buildMemory();
builder->buildHardDisk();
builder->buildOS();
}
};

// 客户端代码

场景

建造者模式通常适用于以下场景:

当一个对象有很多参数需要设置,或者对象的构造方法参数很多,而每个参数又有默认值或者可选值时,可以使用建造者模式。通过建造者模式,我们可以将对象的构造方法分解为多个部分,这样可以更加灵活地组合各个部分,来构造出不同的对象。

当需要创建的对象具有复杂的层次结构时,可以使用建造者模式。例如,一个电脑对象可能包含了多个部件,每个部件又有不同的子部件,这时候可以使用建造者模式,将构造复杂对象的过程分解成多个步骤,使得每个步骤都比较简单,易于维护。

当需要创建的对象的内部结构比较复杂,但是创建过程相对固定时,可以使用建造者模式。例如,一个包含了多个子节点的 XML 文档,创建过程比较固定,但是文档结构比较复杂,可以使用建造者模式将文档结构分解成多个部分,使得创建过程更加清晰、易于理解。

总之,建造者模式适用于需要创建复杂对象,且创建过程相对固定,但是需要通过不同的方式组合构造出不同的对象。

原型模式

原型模式(Prototype Pattern)是一种创建型设计模式,它通过复制现有对象来创建新对象。通俗地说,原型模式就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。

在原型模式中,我们需要创建一个实现了 Cloneable 接口的原型类,它具有一个 clone() 方法,该方法可以创建并返回当前对象的一个副本。在使用原型对象的时候,我们可以通过克隆的方式来创建新的对象,而不是重新创建一个新的对象。

使用原型模式可以避免创建大量的重复对象,提高对象的创建效率,同时也可以简化对象的创建过程。

优点

原型模式的优点包括:

可以在运行时动态生成新的对象,而不需要编写额外的代码。

可以避免创建大量的重复对象,提高对象的创建效率。

可以简化对象的创建过程,使得创建对象更加方便、灵活。

缺点

原型模式的缺点包括:

如果需要克隆的对象比较复杂,可能需要深度克隆,这样会增加程序的复杂度。

如果需要克隆的对象包含循环引用,克隆过程会陷入死循环,导致程序崩溃。

如果需要克隆的对象不支持序列化,克隆过程需要手动实现,增加程序的复杂度。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <string>

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

class Person : public Cloneable {
public:
Person(std::string name, int age) : name(name), age(age) {}

std::string getName() const { return name; }

int getAge() const { return age; }

virtual Cloneable* clone() const { return new Person(*this); }

private:
std::string name;
int age;
};

int main() {
Cloneable* p1 = new Person("Tom", 20);
Cloneable* p2 = p1->clone();

std::cout << "p1's name: " << dynamic_cast<Person*>(p1)->getName() << ", age: " << dynamic_cast<Person*>(p1)->getAge() << std::endl;
std::cout << "p2's name: " << dynamic_cast<Person*>(p2)->getName() << ", age: " << dynamic_cast<Person*>(p2)->getAge() << std::endl;

delete p1;
delete p2;

return 0;
}


在本示例中,我们先定义了一个 Cloneable 接口,它包含了 clone() 方法,用于创建对象的克隆。然后我们定义了一个 Person 类,它继承了 Cloneable 接口,并实现了 clone() 方法。在 main() 函数中,我们先创建了一个 Person 对象 p1,然后通过调用 clone() 方法来创建一个新的对象 p2,并输出了两个对象的信息。最后,我们释放了两个对象的内存。注意,在输出对象信息时,我们使用了 dynamic_cast 来将基类指针转换为派生类指针,以便能够调用派生类的方法。

单例模式

单例模式是一种创建型设计模式,它保证一个类只有一个实例,并提供全局访问点。

特点

在单例模式中,一个类只有一个实例,这个实例通常由该类自身创建,并且在整个程序运行期间都存在。单例模式的核心思想是通过限制该类的实例化过程,确保程序中只有一个该类的实例,从而避免了多个实例之间可能出现的数据竞争和冲突问题。

常见的实现方式是将该类的构造函数声明为私有的,这样外部就无法直接实例化该类,然后通过一个静态方法来控制该类的实例化过程,确保只有一个实例被创建并返回该实例的引用。

使用场景

单例模式在很多场景中都有应用,比如在日志记录器、数据库连接池、线程池、配置文件管理器等场景中,都需要确保该类只有一个实例,以保证数据的一致性和安全性。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Singleton {
public:
// 获取单例实例的方法
static Singleton& getInstance() {
static Singleton instance; // 静态局部变量,保证只初始化一次
return instance;
}

// 定义一个操作
void doSomething() {
// ...
}

private:
// 构造函数私有化,禁止外部创建实例
Singleton() {}

// 拷贝构造函数和赋值运算符重载函数私有化,禁止复制实例
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
};

在这个示例中,Singleton 类使用了私有化构造函数,以及私有化拷贝构造函数和赋值运算符重载函数,以防止在外部创建多个实例。同时,getInstance() 方法返回一个静态局部变量 instance,保证只初始化一次,并返回其引用。

调用示例

1
2
3
4
5
Singleton& s1 = Singleton::getInstance();
s1.doSomething();

Singleton& s2 = Singleton::getInstance();
s2.doSomething();

以上两次获取实例的调用都返回同一个实例,因此可以看作是单例模式的实现。

线程安全

在C++11标准中,静态局部变量的初始化是线程安全的,编译器会保证只有一个线程会执行初始化,其他线程会等待初始化完成后再使用该变量。

如果你使用的是较早的C++标准或者其他编程语言,需要手动添加线程安全措施,例如加锁(mutex)等。

线程不安全

懒汉式单例模式是线程不安全的,如果存在多线程的场景,需要注意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Singleton {
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton;
}
return instance;
}

void doSomething() {
// ...
}

private:
Singleton() {}

static Singleton* instance;
};

Singleton* Singleton::instance = nullptr;

懒汉式单例模式在第一次调用 getInstance() 方法时才会初始化实例,如果有多个线程同时调用 getInstance(),会导致多个线程同时检测到 instancenullptr,从而创建多个实例。

 Comments