23种设计模式
发表于:2025-03-23 | 分类: 计算机
字数统计: 10.9k | 阅读时长: 44分钟 | 阅读量:

23种设计模式

前言

文章来源:design-patterns-for-humans

设计模式是解决重复问题的解决方案;是处理特定问题的指南。它们不是你可以插入应用程序并等待奇迹发生的类、包或库。相反,它们是关于在某些情况下处理特定问题的指南。

维基百科对它们的描述如下:

在软件工程中,软件设计模式是在软件设计的特定上下文中针对常见问题的通用可重用解决方案。它不是可以直接转换为源代码或机器代码的成品设计。它是关于如何解决问题的描述或模板,可在许多不同的情况下使用。

⚠️ 注意事项

  • 设计模式不是你所有问题的万能药。
  • 不要试图强行使用它们;如果这样做,坏事就会发生。
  • 请记住,设计模式是解决问题的方案,而不是寻找问题的方案;所以不要想太多。
  • 如果以正确的方式在正确的地方使用它们,它们可能会成为救星;否则可能会导致一团糟的代码。

创建型设计模式

用简单的话说

创建型模式专注于如何实例化一个对象或一组相关对象。

维基百科表示:

在软件工程中,创建型设计模式是处理对象创建机制的设计模式,试图以适合具体情况的方式创建对象。对象创建的基本形式可能会导致设计问题或增加设计的复杂性。创建型设计模式通过某种方式控制对象的创建来解决这个问题。

🏠 简单工厂

现实世界的例子

想象一下,你在建造一座房子,需要门。你可以穿上木工服,带上木头、胶水、钉子和所有制作门所需的工具,然后开始在你的房子里制作门,或者你可以简单地打电话给工厂,让他们把做好的门送到你家,这样你就不需要了解制作门的知识,也不需要处理制作过程中带来的混乱。

用简单的话说

简单工厂简单地为客户生成一个实例,而不向客户暴露任何实例化逻辑。

维基百科称

在面向对象编程(OOP)中,工厂是用于创建其他对象的对象 —— 从形式上讲,工厂是一个函数或方法,它从某个方法调用中返回不同原型或类的对象,该方法调用被假定为 “new”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from abc import ABC, abstractmethod


class Door(ABC):
@abstractmethod
def get_width(self) -> float:
pass

@abstractmethod
def get_height(self) -> float:
pass


class WoodenDoor(Door):
def __init__(self, width: float, height: float):
self.width = width
self.height = height

def get_width(self) -> float:
return self.width

def get_height(self) -> float:
return self.height

然后我们有我们的门厂,它制作门并将其返回。

1
2
3
4
class DoorFactory:
@staticmethod
def make_door(width: float, height: float) -> Door:
return WoodenDoor(width, height)

然后它可以被用作

1
2
3
4
5
6
7
## Make me a door of 100x200
door = DoorFactory.make_door(100, 200)
print(f'Width: {door.get_width()}')
print(f'Height: {door.get_height()}')

## Make me a door of 50x100
door2 = DoorFactory.make_door(50, 100)

何时使用?

当创建一个对象不仅仅是几个赋值操作,还涉及一些逻辑时,将其放在一个专门的工厂中而不是在各处重复相同的代码。

🏭 工厂方法

现实世界示例

考虑招聘经理的情况。一个人不可能为每个职位进行面试。她必须根据职位空缺决定并将面试步骤委托给不同的人。

用简单的话说

它提供了一种将实例化逻辑委托给子类的方式。

维基百科表示

在基于类的编程中,工厂方法模式是一种创建型模式,它使用工厂方法来处理创建对象的问题,而无需指定要创建的对象的确切类。这是通过调用工厂方法来创建对象实现的 —— 要么在接口中指定并由子类实现,要么在基类中实现并可由派生类选择性地重写 —— 而不是通过调用构造函数。

编程示例

以我们上面的招聘经理例子为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from abc import ABC, abstractmethod


class Interviewer(ABC):
@abstractmethod
def ask_questions(self):
pass


class Developer(Interviewer):
def ask_questions(self):
print('Asking about design patterns!')


class CommunityExecutive(Interviewer):
def ask_questions(self):
print('Asking about community building')

面试官

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class HiringManager(ABC):
@abstractmethod
def make_interviewer(self):
pass

def take_interview(self):
interviewer = self.make_interviewer()
interviewer.ask_questions()


class DevelopmentManager(HiringManager):
def make_interviewer(self):
return Developer()


class MarketingManager(HiringManager):
def make_interviewer(self):
return CommunityExecutive()

然后它可以被用作

1
2
3
4
dev_manager = DevelopmentManager ()
dev_manager.take_interview () ## 输出: Asking about design patterns
marketing_manager = MarketingManager ()
marketing_manager.take_interview () ## 输出: Asking about community building

何时使用?

当类中有一些通用处理,但所需的子类在运行时动态决定时,这是有用的。换句话说,当客户端不知道它可能需要哪个具体的子类时。

🔨 抽象工厂

现实世界的例子

扩展我们之前简单工厂的门例。根据你的需求,你可能从木门店买到木门,从铁店买到铁门,或者从相关商店买到PVC门。此外,你可能需要不同专业的人安装门,比如木匠装木门,焊工装铁门等。正如你所见,现在门之间存在依赖关系,木门需要木匠,铁门需要焊工等。

用简单的话说

一个工厂的工厂;一个将个体和相关/依赖的工厂分组在一起的工厂,而不指定它们的具体类。

维基百科上说

抽象工厂模式提供了一种方法来封装一组具有共同主题的单个工厂,而无需指定它们的具体类。

编程示例

将上述门例子进行转换。首先,我们有我们的Door接口及其一些实现

1
2
3
4
5
6
7
8
9
10
11
12
13
class Door:
def get_description(self):
pass


class WoodenDoor(Door):
def get_description(self):
print('I am a wooden door')


class IronDoor(Door):
def get_description(self):
print('I am an iron door')

然后我们为每种门类型配备了一些适配专家。

1
2
3
4
5
6
7
8
9
10
11
12
13
class DoorFittingExpert:
def get_description(self):
pass


class Welder(DoorFittingExpert):
def get_description(self):
print('I can only fit iron doors')


class Carpenter(DoorFittingExpert):
def get_description(self):
print('I can only fit wooden doors')

现在我们已经拥有了抽象工厂,它允许我们创建一系列相关对象。例如,木质门工厂会创建一个木质门和一个木质门安装专家,而铁质门工厂则会创建一个铁质门和一个铁质门安装专家。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DoorFactory:
def make_door(self):
pass

def make_fitting_expert(self):
pass


class WoodenDoorFactory(DoorFactory):
def make_door(self):
return WoodenDoor()

def make_fitting_expert(self):
return Carpenter()


class IronDoorFactory(DoorFactory):
def make_door(self):
return IronDoor()

def make_fitting_expert(self):
return Welder()

然后它可以被用作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## 创建 WoodenDoorFactory 实例
wooden_factory = WoodenDoorFactory()

door = wooden_factory.make_door()
expert = wooden_factory.make_fitting_expert()

door.get_description() ## 输出: I am a wooden door
expert.get_description() ## 输出: I can only fit wooden doors

## 同样适用于 IronFactory
iron_factory = IronDoorFactory()

door = iron_factory.make_door()
expert = iron_factory.make_fitting_expert()

door.get_description() ## 输出: I am an iron door
expert.get_description() ## 输出: I can only fit iron doors

正如你所见,木门工厂封装了木工木门,同样,铁门工厂也封装了铁门焊工。因此,这有助于我们确保为每个创建的门分配正确的装配专家。

何时使用?

当存在相互关联的依赖关系且涉及不那么简单的创建逻辑时

👷构建者

现实世界的例子

想象你在Hardee’s点了一份特定的套餐,比如“大Hardee”,他们毫无问题地递给你;这是简单工厂模式的一个例子。但有些情况下,创建逻辑可能涉及更多步骤。例如,你想要一个定制的Subway套餐,你有多个选择来制作你的汉堡,例如你想要哪种面包?你喜欢什么类型的酱料?你想要什么奶酪?等等。在这种情况下,构建者模式就派上用场了。

用简单的话来说

允许你创建对象的不同风格,同时避免构造函数污染。当对象可能有几种风格时很有用。或者在对象的创建过程中涉及很多步骤时也很有用。

维基百科说

构建者模式是一种对象创建的软件设计模式,旨在找到解决伸缩构造函数反模式的解决方案。

话虽如此,让我补充一下什么是伸缩构造函数反模式。在某个时候,我们都见过下面这样的构造函数:

1
2
3
public function __construct($size, $cheese = true, $pepperoni = true, $tomato = false, $lettuce = true)
{
}

正如你所看到的,构造函数的参数数量很快就会变得难以控制,理解参数的排列可能会变得困难。而且,如果你将来想要添加更多选项,这个参数列表可能会不断增长。这被称为望远镜构造函数反模式。

编程示例

一个更合理的替代方案是使用建造者模式。首先,我们有我们想要制作的汉堡

1
2
3
4
5
6
7
class Burger:
def __init__(self, builder):
self.size = builder.size
self.cheese = builder.cheese
self.pepperoni = builder.pepperoni
self.lettuce = builder.lettuce
self.tomato = builder.tomato

然后我们有生成器

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
class BurgerBuilder:
def __init__(self, size):
self.size = size
self.cheese = False
self.pepperoni = False
self.lettuce = False
self.tomato = False

def add_pepperoni(self):
self.pepperoni = True
return self

def add_lettuce(self):
self.lettuce = True
return self

def add_cheese(self):
self.cheese = True
return self

def add_tomato(self):
self.tomato = True
return self

def build(self):
return Burger(self)

然后它可以被使用如下:

1
burger = BurgerBuilder(14).add_pepperoni().add_lettuce().add_tomato().build()

何时使用?

当对象可能有多种变体,并且为了避免构造函数的伸缩问题时。与工厂模式的关键区别在于:工厂模式在创建过程是一步到位的情况下使用,而构建器模式在创建过程是多步进行的情况下使用。

🐑 原型模式

现实世界的例子

还记得多莉吗?那只被克隆的羊!我们不深入细节,但这里的关键是关于克隆

用简单的话来说

通过克隆现有对象来创建新对象。

维基百科说

原型模式是软件开发中的一种创建型设计模式。它用于在要创建的对象类型由一个原型实例决定时,该原型被克隆以产生新对象的情况。

简而言之,它允许你创建一个现有对象的副本,并根据你的需求进行修改,而不是从头开始创建一个对象并进行设置的麻烦。

编程示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import copy


class Sheep:
def __init__(self, name, age=0, category='Mountain Sheep'):
self.name = name
self.age = age
self.category = category

def clone_sheep(self):
return copy.deepcopy(self)

def set_name(self, name):
self.name = name

def get_name(self):
return self.name

def set_category(self, category):
self.category = category

def get_category(self):
return self.category

然后可以像下面这样进行克隆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
## 第一个克隆示例
original_sheep = Sheep("Dolly", 5)
cloned_sheep = original_sheep.clone_sheep()
cloned_sheep.name = "Cloned Dolly"

print(f"Original Sheep: {original_sheep.name}")
print(f"Cloned Sheep: {cloned_sheep.name}")

## 第二个克隆示例
original = Sheep('Jolly')
print(original.get_name())
print(original.get_category())

## 克隆并修改所需内容
cloned = original.clone_sheep()
cloned.set_name('Dolly')
print(cloned.get_name())
print(cloned.get_category())

同时,您可以使用魔术方法 __clone 来修改克隆行为。

何时使用?

当需要一个与现有对象相似的对象时,或者创建新对象的成本相对于克隆而言过高时。

💍 单例模式

现实世界示例

一个国家一次只能有一个总统。无论何时需要履行职责,都必须让同一个总统行动。这里的总统就是一个单例。

用通俗易懂的话说

确保一个特定类只创建一个对象。

维基百科说

在软件工程中,单例模式是一种软件设计模式,它限制一个类的实例化,使其只有一个对象。这在系统中需要精确地一个对象来协调动作时非常有用。

实际上,单例模式被认为是一种反模式,应该避免过度使用。它并非本质上就是坏的,可能有一些有效的用例,但使用时应当谨慎,因为它会在您的应用程序中引入全局状态,并且一个地方的改变可能会影响其他区域,调试起来可能会变得相当困难。关于它们的另一个坏处是,它们会使您的代码紧密耦合,而且模拟单例可能会很困难。

编程示例

要创建一个单例,请将构造函数设为私有,禁用克隆,禁用扩展,并创建一个静态变量来存放实例。

1
2
3
4
5
6
7
class President:
_instance = None

def __new__(cls):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance

然后为了使用

1
2
3
4
president1 = President()
president2 = President()

print(president1 is president2)

结构型设计模式

用简单的话说

结构模式主要关注对象的组成,或者换句话说,实体如何能够相互使用。另一种解释是,它们有助于回答“如何构建软件组件?”的问题。

维基百科说

在软件工程中,结构设计模式是通过识别实现实体之间关系的简单方法来简化设计的模式。

🔌 适配器

现实世界的例子

假设你的记忆卡里有一些图片,需要将它们传输到你的电脑上。为了传输这些图片,你需要一种与电脑端口兼容的适配器,这样你就可以将记忆卡连接到电脑上。在这种情况下,读卡器就是一个适配器。
另一个例子是著名的电源适配器;三脚插头不能直接插入两孔插座,它需要使用一个电源适配器使其与两孔插座兼容。
又比如一位翻译员,将一个人说的话翻译成另一个人能听懂的语言。

用简单的话说

适配器模式允许你用一个适配器包装原本不兼容的对象,使它与另一个类兼容。

维基百科说

在软件工程中,适配器模式是一种软件设计模式,它允许现有类的接口被用作另一个接口。它常用于使现有类与其他类一起工作,而无需修改它们的源代码。

编程示例

考虑一个游戏中有一个猎人,他捕猎狮子。

首先我们有一个Lion接口,所有类型的狮子都必须实现这个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from abc import ABC, abstractmethod


class Lion(ABC):
@abstractmethod
def roar(self):
pass


class AfricanLion(Lion):
def roar(self):
pass


class AsianLion(Lion):
def roar(self):
pass

并且猎人期望任何实现了 Lion 接口的实现都能够捕猎。

1
2
3
class Hunter:
def hunt(self, lion):
lion.roar()

现在假设我们需要在游戏中添加一个WildDog,这样猎人也可以捕捉它。但是我们不能直接这样做,因为狗有一个不同的接口。为了使它与我们的猎人兼容,我们将不得不创建一个适配器,使其兼容。

1
2
3
4
5
6
7
8
9
10
11
class WildDog:
def bark(self):
pass


class WildDogAdapter(Lion):
def __init__(self, dog):
self.dog = dog

def roar(self):
self.dog.bark()

现在,我们可以使用 WildDogAdapter 在我们的游戏中使用 WildDog

1
2
3
4
5
wild_dog = WildDog()
wild_dog_adapter = WildDogAdapter(wild_dog)

hunter = Hunter()
hunter.hunt(wild_dog_adapter)

🚡 桥接模式

现实世界示例

假设你有一个包含不同页面的网站,并且你需要允许用户更改主题。你会怎么做?为每个主题创建多个页面副本,还是只创建单独的主题并根据用户的偏好加载它们?桥接模式允许你采用第二种方法,即:

有桥接模式和无桥接模式

用简单的话说

桥接模式是关于优先选择组合而非继承的。实现细节从一个层次结构转移到具有单独层次结构的其他对象中。

维基百科说

桥接模式是软件工程中使用的一种设计模式,旨在“将抽象与其实现解耦,以便两者可以独立变化”

编程示例

将上面的WebPage示例进行转换。这里我们有WebPage层次结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class WebPage:
def __init__(self, theme):
self.theme = theme

def get_content(self):
pass


class About(WebPage):
def get_content(self):
return f"About page in {self.theme.get_color()}"


class Careers(WebPage):
def get_content(self):
return f"Careers page in {self.theme.get_color()}"

以及独立的主题层次结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Theme:
def get_color(self):
pass


class DarkTheme(Theme):
def get_color(self):
return 'Dark Black'


class LightTheme(Theme):
def get_color(self):
return 'Off white'


class AquaTheme(Theme):
def get_color(self):
return 'Light blue'

以及这两个层次结构

1
2
3
4
5
6
dark_theme = DarkTheme()
about = About(dark_theme)
careers = Careers(dark_theme)

print(about.get_content())
print(careers.get_content())

🌿 组合模式

现实世界的例子

每个组织都是由员工组成的。每位员工都有相同的特征,例如有薪水、有一些职责、可能向某人汇报工作、可能有一些下属等。

用简单的语言来说

组合模式允许客户端以统一的方式处理单个对象。

维基百科这样说

在软件工程中,组合模式是一种分割设计模式。组合模式描述了一组对象应该以与单个对象实例相同的方式处理。组合的目的是将对象“组合”成树状结构,以表示部分-整体的层次关系。实现组合模式可以让客户端以统一的方式处理单个对象和组合。

编程示例

以上面的员工为例。这里我们有不同的员工类型

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
from abc import ABC, abstractmethod


class Employee(ABC):
def __init__(self, name: str, salary: float):
self.name = name
self.salary = salary

@abstractmethod
def get_name(self) -> str:
pass

@abstractmethod
def set_salary(self, salary: float):
pass

@abstractmethod
def get_salary(self) -> float:
pass

@abstractmethod
def get_roles(self) -> list:
pass


class Developer(Employee):
def __init__(self, name: str, salary: float):
super().__init__(name, salary)
self.roles = []

def get_name(self) -> str:
return self.name

def set_salary(self, salary: float):
self.salary = salary

def get_salary(self) -> float:
return self.salary

def get_roles(self) -> list:
return self.roles


class Designer(Employee):
def __init__(self, name: str, salary: float):
super().__init__(name, salary)
self.roles = []

def get_name(self) -> str:
return self.name

def set_salary(self, salary: float):
self.salary = salary

def get_salary(self) -> float:
return self.salary

def get_roles(self) -> list:
return self.roles

然后我们有一个由几种不同类型的员工组成的组织。

1
2
3
4
5
6
7
8
9
10
11
12
class Organization:
def __init__(self):
self.employees = []

def add_employee(self, employee: Employee):
self.employees.append(employee)

def get_net_salaries(self) -> float:
net_salary = 0
for employee in self.employees:
net_salary += employee.get_salary()
return net_salary

然后它可以被用作

1
2
3
4
5
6
7
8
9
john = Developer('John Doe', 12000)
jane = Designer('Jane Doe', 15000)

## Add them to organization
organization = Organization()
organization.add_employee(john)
organization.add_employee(jane)

print(f"Net salaries: {organization.get_net_salaries()}") ## Net Salaries: 27000

☕ 装饰器

真实世界示例

想象你经营一家汽车服务店,提供多种服务。现在你要如何计算收费账单呢?你选择一个服务项目,然后动态地添加提供的服务价格,直到得到最终费用。在这里,每种服务类型就是一个装饰器。

用简单的话说

装饰器模式允许你在运行时通过将对象包装在装饰器类的对象中来动态改变对象的行为。

维基百科说

在面向对象编程中,装饰器模式是一种设计模式,它允许向单个对象添加行为,无论是静态还是动态添加,而不影响同一类其他对象的行为。装饰器模式通常有助于遵循单一职责原则,因为它允许功能在不同的关注点之间划分。

程序示例

以咖啡为例。首先我们有一个简单的实现咖啡接口的咖啡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from abc import ABC, abstractmethod


class Coffee(ABC):
@abstractmethod
def get_cost(self):
pass

@abstractmethod
def get_description(self):
pass


class SimpleCoffee(Coffee):
def get_cost(self):
return 10

def get_description(self):
return 'Simple coffee'

我们希望使代码具有可扩展性,以便在需要时能够修改它。让我们制作一些附加功能(装饰器)。

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
class MilkCoffee(Coffee):
def __init__(self, coffee):
self.coffee = coffee

def get_cost(self):
return self.coffee.get_cost() + 2

def get_description(self):
return f"{self.coffee.get_description()}, milk"


class WhipCoffee(Coffee):
def __init__(self, coffee):
self.coffee = coffee

def get_cost(self):
return self.coffee.get_cost() + 5

def get_description(self):
return f"{self.coffee.get_description()}, whip"


class VanillaCoffee(Coffee):
def __init__(self, coffee):
self.coffee = coffee

def get_cost(self):
return self.coffee.get_cost() + 3

def get_description(self):
return f"{self.coffee.get_description()}, vanilla"

让我们现在制作一杯咖啡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
some_coffee = SimpleCoffee()
print(some_coffee.get_cost())
print(some_coffee.get_description())

some_coffee = MilkCoffee(some_coffee)
print(some_coffee.get_cost())
print(some_coffee.get_description())

some_coffee = WhipCoffee(some_coffee)
print(some_coffee.get_cost())
print(some_coffee.get_description())

some_coffee = VanillaCoffee(some_coffee)
print(some_coffee.get_cost())
print(some_coffee.get_description())

📦 外观模式

现实世界的例子

你如何打开电脑?“按下电源按钮”,你会这么说!这是因为你使用的是电脑外部提供的简单接口,而内部实际上需要做很多工作才能实现这一点。这种对复杂子系统的简单接口就是外观模式。

用简单的话说

外观模式为复杂的子系统提供了一个简化的接口。

维基百科上说

外观是一个对象,它为更大的代码体(如类库)提供了一个简化的接口。

编程示例

以上面的电脑例子为例。这里我们有电脑类

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
class Computer:
def get_electric_shock(self):
print("Ouch!")

def make_sound(self):
print("Beep beep!")

def show_loading_screen(self):
print("Loading..")

def bam(self):
print("Ready to be used!")

def close_everything(self):
print("Bup bup bup buzzzz!")

def sooth(self):
print("Zzzzz")

def pull_current(self):
print("Haaah!")


class ComputerFacade:
def __init__(self, computer):
self.computer = computer

def turn_on(self):
self.computer.get_electric_shock()
self.computer.make_sound()
self.computer.show_loading_screen()
self.computer.bam()

def turn_off(self):
self.computer.close_everything()
self.computer.pull_current()
self.computer.sooth()

现在来使用这个外观

1
2
3
4
computer = Computer()
facade = ComputerFacade(computer)
facade.turn_on() ## 输出: Ouch! Beep beep! Loading.. Ready to be used!
facade.turn_off() ## 输出: Bup bup bup buzzzz! Haaah! Zzzzz

🍃 享元模式

现实世界的例子

你有没有在街边小摊买过新鲜的茶?他们通常会泡比你要求的更多的茶,然后把剩下的留给其他顾客,以节省资源,比如煤气等。享元模式就是关于共享的。

用简单的话来说

它通过尽可能多地与相似对象共享,来最小化内存使用或计算开销。

维基百科说

在计算机编程中,享元是一种软件设计模式。享元是一个通过与其他类似对象尽可能多地共享数据来最小化内存使用的对象;它是一种在简单的重复表示会占用不可接受的内存量时使用大量对象的方法。

编程示例

将上面的茶例转换为代码。首先,我们有茶类型和制茶者

1
2
3
4
5
6
7
8
9
10
11
12
class KarakTea:
pass


class TeaMaker:
def __init__(self):
self.available_tea = {}

def make(self, preference):
if preference not in self.available_tea:
self.available_tea[preference] = KarakTea()
return self.available_tea[preference]

然后我们有 TeaShop,它接受订单并提供服务。

1
2
3
4
5
6
7
8
9
10
11
class TeaShop:
def __init__(self, tea_maker):
self.orders = {}
self.tea_maker = tea_maker

def take_order(self, tea_type, table):
self.orders[table] = self.tea_maker.make(tea_type)

def serve(self):
for table, tea in self.orders.items():
print(f"Serving tea to table## {table}")

可以按如下方式使用

1
2
3
4
5
6
7
8
tea_maker = TeaMaker()
shop = TeaShop(tea_maker)

shop.take_order('less sugar', 1)
shop.take_order('more milk', 2)
shop.take_order('without sugar', 5)

shop.serve()

🎱 代理

现实世界示例

你有没有用过门禁卡通过一扇门?打开那扇门有多种选择,即可以使用门禁卡或按下一个绕过安全的按钮。这扇门的主要功能是开启,但它上面添加了一个代理以增加一些功能。让我用下面的代码示例更好地解释它。

用通俗易懂的话来说

使用代理模式,一个类代表另一个类的功能。

维基百科说

最广义的代理是一个作为其他事物的接口的类。代理是一个包装器或代理对象,由客户端调用以访问幕后的真实服务对象。使用代理可以简单地转发给真实对象,也可以提供额外的逻辑。在代理中可以提供额外功能,例如当对真实对象的操作资源密集时进行缓存,或在调用真实对象操作之前检查前提条件。

编程示例

以上取自我们的安全门示例。首先我们有门接口和门的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from abc import ABC, abstractmethod


class Door(ABC):
@abstractmethod
def open(self):
pass

@abstractmethod
def close(self):
pass


class LabDoor(Door):
def open(self):
print("Opening lab door")

def close(self):
print("Closing the lab door"

然后我们有一个代理来保护任何我们想要保护的门。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SecuredDoor(Door):
def __init__(self, door):
self.door = door

def open(self, password):
if self.authenticate(password):
self.door.open()
else:
print("Big no! It ain't possible.")

def authenticate(self, password):
return password == '$ecr@t'

def close(self):
self.door.close()

以下是如何使用它

1
2
3
4
door = SecuredDoor(LabDoor())
door.open('invalid') ## Big no! It ain't possible.
door.open('$ecr@t') ## Opening lab door
door.close() ## Closing lab door

行为型设计模式

用简单的语言来说

它是关于对象间责任分配的。与结构模式不同之处在于,它们不仅指定了结构,还概述了对象间消息传递/通信的模式。换句话说,它们帮助回答”如何在软件组件中运行行为?”

维基百科说

在软件工程中,行为设计模式是识别对象之间常见通信模式并实现这些模式的设计模式。通过这样做,这些模式增加了执行此通信的灵活性。

🔗 职责链

现实世界的例子

例如,你的账户中设置了三种支付方式(A、B和C),每种都有不同的金额。A有100美元,B有300美元,C有1000美元,支付偏好选择顺序是A然后B然后C。你试图购买价值210美元的东西。使用职责链模式,首先会检查账户A是否可以进行购买,如果可以则完成购买并中断链。如果不可以,请求将转发给账户B检查金额,如果可以则中断链,否则请求将继续转发直到找到合适的处理者。在这里,A、B和C是链的链接,整个现象就是职责链。

用简单的语言来说

它帮助构建一个对象链。请求从一端进入,从一个对象传递到另一个对象,直到找到合适的处理者。

维基百科说

在面向对象设计中,职责链模式是由命令对象源和一系列处理对象组成的设计模式。每个处理对象包含定义它可以处理的命令对象类型的逻辑;其余的被传递到链中的下一个处理对象。

编程示例

将上述账户示例转换为代码。首先我们有一个基础账户,它包含将账户串联在一起的逻辑以及一些账户

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
from abc import ABC, abstractmethod


class Account(ABC):
def __init__(self):
self.successor = None

def set_next(self, account):
self.successor = account

def pay(self, amount_to_pay):
if self.can_pay(amount_to_pay):
print(f'Paid {amount_to_pay} using {self.__class__.__name__}')
elif self.successor:
print(f'Cannot pay using {self.__class__.__name__}. Proceeding ..')
self.successor.pay(amount_to_pay)
else:
raise Exception('None of the accounts have enough balance')

@abstractmethod
def can_pay(self, amount):
pass


class Bank(Account):
def __init__(self, balance):
super().__init__()
self.balance = balance

def can_pay(self, amount):
return self.balance >= amount


class Paypal(Account):
def __init__(self, balance):
super().__init__()
self.balance = balance

def can_pay(self, amount):
return self.balance >= amount


class Bitcoin(Account):
def __init__(self, balance):
super().__init__()
self.balance = balance

def can_pay(self, amount):
return self.balance >= amount

现在让我们使用上面定义的链接(即银行、Paypal、比特币)来准备链条。

1
2
3
4
5
6
7
8
9
10
## 构建账户链
bank = Bank(100)
paypal = Paypal(200)
bitcoin = Bitcoin(300)

bank.set_next(paypal)
paypal.set_next(bitcoin)

## 尝试使用银行账户支付
bank.pay(259)

👮 命令模式

现实世界的例子

一个通用的例子是你在餐厅点餐。你(即 Client)让服务员(即 Invoker)带来一些食物(即 Command),服务员只是将请求转发给厨师(即 Receiver),而厨师知道要做什么以及如何烹饪。
另一个例子是你(即 Client)使用遥控器(Invoker)打开(即 Command)电视(即 Receiver)。

用通俗的话来说

允许你把动作封装在对象中。这个模式背后的关键思想是提供一种解耦客户端和接收者的方法。

维基百科说

在面向对象编程中,命令模式是一种行为设计模式,其中一个对象被用来封装执行动作或在将来某个时间触发事件所需的所有信息。这些信息包括方法名、拥有该方法的对象以及方法参数的值。

程序示例

首先,我们有一个接收者,它实现了可以执行的每一个动作的实现

1
2
3
4
5
6
7
8
## Receiver
class Bulb:
def turn_on(self):
print("Bulb has been lit")

def turn_off(self):
print("Darkness!")

然后我们有一个接口,每个命令都将实现这个接口,然后我们有一组命令。

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
## Command interface
class Command:
def execute(self):
pass

def undo(self):
pass

def redo(self):
pass


## Command
class TurnOn(Command):
def __init__(self, bulb):
self.bulb = bulb

def execute(self):
self.bulb.turn_on()

def undo(self):
self.bulb.turn_off()

def redo(self):
self.execute()


class TurnOff(Command):
def __init__(self, bulb):
self.bulb = bulb

def execute(self):
self.bulb.turn_off()

def undo(self):
self.bulb.turn_on()

def redo(self):
self.execute()

然后我们有一个 Invoker,客户端将通过它来处理任何命令。

1
2
3
4
## Invoker
class RemoteControl:
def submit(self, command):
command.execute()

最后,让我们看看如何在我们的客户端中使用它。

1
2
3
4
5
6
7
8
bulb = Bulb()

turn_on = TurnOn(bulb)
turn_off = TurnOff(bulb)

remote = RemoteControl()
remote.submit(turn_on)
remote.submit(turn_off)

命令模式也可以用来实施基于事务的系统。在这种系统中,你一执行命令就会维护命令的历史记录。如果最终的命令成功执行,一切顺利;否则,只需遍历历史记录,对所有已执行的命令继续执行undo操作。

➿ 迭代器

现实世界的例子

一个老式收音机将是迭代器的一个很好例子,用户可以从某个频道开始,然后使用“下一个”或“上一个”按钮浏览相应的频道。或者以MP3播放器或电视机为例,你可以按“下一个”和“上一个”按钮来浏览连续的频道,换句话说,它们都提供了一个接口来迭代浏览相应的频道、歌曲或广播电台。

用简单的语言来说

它提供了一种访问对象元素的方式,而不暴露底层的表示。

维基百科说

在面向对象编程中,迭代器模式是一种设计模式,其中使用迭代器来遍历容器并访问容器的元素。迭代器模式将算法与容器解耦;在某些情况下,算法必然是特定于容器的,因此无法解耦。

程序示例

1
2
3
4
5
6
class RadioStation:
def __init__(self, frequency):
self.frequency = frequency

def get_frequency(self):
return self.frequency

然后我们有我们的迭代器

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
class StationList:
def __init__(self):
self.stations = []
self.counter = 0

def add_station(self, station):
self.stations.append(station)

def remove_station(self, to_remove):
to_remove_frequency = to_remove.get_frequency()
self.stations = [station for station in self.stations if station.get_frequency() != to_remove_frequency]

def __len__(self):
return len(self.stations)

def __iter__(self):
self.counter = 0
return self

def __next__(self):
if self.counter < len(self.stations):
result = self.stations[self.counter]
self.counter += 1
return result
raise StopIteration

然后它可以被用作

1
2
3
4
5
6
7
8
9
10
11
station_list = StationList()

station_list.add_station(RadioStation(89))
station_list.add_station(RadioStation(101))
station_list.add_station(RadioStation(102))
station_list.add_station(RadioStation(103.2))

for station in station_list:
print(station.get_frequency())

station_list.remove_station(RadioStation(89)) ## Will remove station 89

👽 中介者模式

现实世界的例子

一个通用的例子是,当你通过手机与某人交谈时,有一个网络提供商坐在你和对方之间,你们的对话通过它传递而不是直接发送。在这种情况下,网络提供商就是中介者。

用简单的语言来说

中介者模式添加了一个第三方对象(称为中介者)来控制两个对象(称为同事)之间的交互。它有助于减少相互通信的类之间的耦合。因为现在它们不需要了解对方的实现细节。

维基百科说

在软件工程中,中介者模式定义了一个封装一组对象如何交互的对象。这种模式被认为是一种行为模式,因为它可以改变程序的运行行为。

编程示例

下面是一个简单的聊天室(即中介者)示例,用户(即同事)相互发送消息。

首先,我们有中介者,即聊天室

1
2
3
4
5
6
7
8
9
10
11
12
13
from datetime import datetime


class ChatRoomMediator:
def show_message(self, user, message):
pass


class ChatRoom(ChatRoomMediator):
def show_message(self, user, message):
time_str = datetime.now().strftime('%b %d, %y %H:%M')
sender = user.get_name()
print(f'{time_str} [{sender}]: {message}')

然后是我们的用户,即同事。

1
2
3
4
5
6
7
8
9
10
class User:
def __init__(self, name, chat_mediator):
self.name = name
self.chat_mediator = chat_mediator

def get_name(self):
return self.name

def send(self, message):
self.chat_mediator.show_message(self, message)

现在使用它

1
2
3
4
5
6
7
mediator = ChatRoom()

john = User('John Doe', mediator)
jane = User('Jane Doe', mediator)

john.send('Hi there!')
jane.send('Hey!')

💾 备忘录模式

现实世界示例

以计算器为例(即发起者),每当你进行一些计算时,最后一次计算结果会被保存在内存中(即备忘录),这样你就可以通过某些操作按钮(即管理者)返回到它并可能恢复它。

用简单的话说

备忘录模式是关于捕获和存储对象的当前状态,以便以后可以平滑地恢复它。

维基百科说

备忘录模式是一种软件设计模式,它提供了将对象恢复到其先前状态的能力(通过回滚撤销)。

当你需要提供某种撤销功能时,通常很有用。

编程示例

让我们以一个文本编辑器为例,该编辑器不时保存状态,如果你愿意,你可以恢复这些状态。

首先,我们有一个备忘录对象,它能够持有编辑器的状态

1
2
3
4
5
6
7
## Memento class to hold the state of the editor
class EditorMemento:
def __init__(self, content):
self.content = content

def get_content(self):
return self.content

然后我们有我们的编辑器,即发起者,它将使用备忘录对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Editor:
def __init__(self):
self.content = ''

def type(self, words):
self.content = f"{self.content} {words}"

def get_content(self):
return self.content

def save(self):
return EditorMemento(self.content)

def restore(self, memento):
self.content = memento.get_content()

然后它就可以用作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
editor = Editor()

## Type some stuff
editor.type('This is the first sentence.')
editor.type('This is second.')

## Save the state to restore to : This is the first sentence. This is second.
saved = editor.save()

## Type some more
editor.type('And this is third.')

## Output: Content before Saving
print(editor.get_content()) ## This is the first sentence. This is second. And this is third.

## Restoring to last saved state
editor.restore(saved)

print(editor.get_content()) ## This is the first sentence. This is second.

😎 观察者

现实世界的例子

一个很好的例子是求职者,他们订阅了一些招聘网站,每当有匹配的工作机会时,他们就会收到通知。

用简单的话说

定义了对象之间的依赖关系,以便当一个对象改变其状态时,所有依赖它的对象都会收到通知。

维基百科说

观察者模式是一种软件设计模式,其中一个对象,称为主题,维护一个依赖它的对象的列表,称为观察者,并通过调用它们的方法之一自动通知它们任何状态变化。

编程示例

将我们上面的例子进行转换。首先,我们有需要被通知的求职者关于职位发布的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class JobPost:
def __init__(self, title):
self.title = title

def get_title(self):
return self.title


class JobSeeker:
def __init__(self, name):
self.name = name

def on_job_posted(self, job):
print(f'Hi {self.name}! New job posted: {job.get_title()}')

然后我们有了工作招聘信息,求职者会订阅这些信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
class EmploymentAgency:
def __init__(self):
self.observers = []

def notify(self, job_posting):
for observer in self.observers:
observer.on_job_posted(job_posting)

def attach(self, observer):
self.observers.append(observer)

def add_job(self, job_posting):
self.notify(job_posting)

然后它可以被用作

1
2
3
4
5
6
7
8
9
10
11
## 创建订阅者
john_doe = JobSeeker('John Doe')
jane_doe = JobSeeker('Jane Doe')

## 创建发布者并添加订阅者
job_postings = EmploymentAgency()
job_postings.attach(john_doe)
job_postings.attach(jane_doe)

## 添加新职位并查看订阅者是否收到通知
job_postings.add_job(JobPost('Software Engineer'))

🏃 访问者

现实世界的例子

想象有人去迪拜旅游。他们只需要一种方式(即签证)进入迪拜。到达后,他们可以自行前往迪拜的任何地方参观,无需请求许可或为了访问某个地方而做额外的工作;只需告诉他们一个地方,他们就可以去参观。访问者模式让你能够做到这一点,它帮助你增加可参观的地方,使他们能够在不进行额外工作的情况下尽可能多地游览。

用简单的话来说

访问者模式允许你向对象添加更多操作,而无需修改它们。

维基百科说

在面向对象的编程和软件工程中,访问者设计模式是一种将算法与它所操作的对象结构分离的方法。这种分离的一个实际结果是能够在不修改现有对象结构的情况下向其中添加新的操作。这是遵循开放/封闭原则的一种方式。

程序示例

让我们以一个动物园模拟为例,我们有多种不同的动物,我们需要让它们发出声音。让我们使用访问者模式来翻译这个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
## Visitee
class Animal:
def accept(self, operation):
pass


## Visitor
class AnimalOperation:
def visit_monkey(self, monkey):
pass

def visit_lion(self, lion):
pass

def visit_dolphin(self, dolphin):
pass

然后我们有动物的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Monkey(Animal):
def shout(self):
print('Ooh oo aa aa!')

def accept(self, operation):
operation.visit_monkey(self)


class Lion(Animal):
def roar(self):
print('Roaaar!')

def accept(self, operation):
operation.visit_lion(self)


class Dolphin(Animal):
def speak(self):
print('Tuut tuttu tuutt!')

def accept(self, operation):
operation.visit_dolphin(self)

让我们实现我们的访问者

1
2
3
4
5
6
7
8
9
class Speak(AnimalOperation):
def visit_monkey(self, monkey):
monkey.shout()

def visit_lion(self, lion):
lion.roar()

def visit_dolphin(self, dolphin):
dolphin.speak()

然后,它可以被用作

1
2
3
4
5
6
7
8
9
monkey = Monkey()
lion = Lion()
dolphin = Dolphin()

speak = Speak()

monkey.accept(speak) ## Ooh oo aa aa!
lion.accept(speak) ## Roaaar!
dolphin.accept(speak) ## Tuut tutt tuutt!

我们本可以通过为动物建立一个继承层次结构来简单地做到这一点,但那样的话每当我们需要给动物添加新的动作时,就必须修改动物类。但现在我们不需要这样做了。例如,假设有人要求我们给动物添加跳跃行为,我们只需通过创建一个新的访问者即可轻松实现这一点。

1
2
3
4
5
6
7
8
9
class Jump(AnimalOperation):
def visit_monkey(self, monkey):
print('Jumped 20 feet high! on to the tree!')

def visit_lion(self, lion):
print('Jumped 7 feet! Back on the ground!')

def visit_dolphin(self, dolphin):
print('Walked on water a little and disappeared')

以及用法

1
2
3
4
5
6
7
8
9
10
jump = Jump()

monkey.accept(speak)
monkey.accept(jump)

lion.accept(speak)
lion.accept(jump)

dolphin.accept(speak)
dolphin.accept(jump)

💡 策略

现实世界的例子

考虑排序的例子,我们实现了冒泡排序,但随着数据量的增长,冒泡排序变得非常慢。为了解决这个问题,我们实现了快速排序。但现在,尽管快速排序算法在处理大数据时表现更好,但对于小数据集来说却非常慢。为了应对这一问题,我们实施了一种策略,即对于小数据集使用冒泡排序,而对于大数据集则使用快速排序。

用简单的话说

策略模式允许你根据情况切换算法或策略。

维基百科说

在计算机编程中,策略模式(也称为政策模式)是一种行为型软件设计模式,它允许在运行时选择算法的行为。

编程示例

将上面的例子翻译成代码。首先,我们有我们的策略接口和不同的策略实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from abc import ABC, abstractmethod


class SortStrategy(ABC):
@abstractmethod
def sort(self, dataset):
pass


class BubbleSortStrategy(SortStrategy):
def sort(self, dataset):
print("Sorting using bubble sort")
return dataset


class QuickSortStrategy(SortStrategy):
def sort(self, dataset):
print("Sorting using quick sort")
return dataset

然后我们有一个客户端,它将使用任何策略

1
2
3
4
5
6
7
8
9
10
class Sorter:
def __init__(self, sorter_small, sorter_big):
self.sorter_small = sorter_small
self.sorter_big = sorter_big

def sort(self, dataset):
if len(dataset) > 5:
return self.sorter_big.sort(dataset)
else:
return self.sorter_small.sort(dataset)

并且这个可以被用作

1
2
3
4
5
6
7
small_dataset = [1, 3, 4, 2]
big_dataset = [1, 4, 3, 2, 8, 10, 5, 6, 9, 7]

sorter = Sorter(BubbleSortStrategy(), QuickSortStrategy())

sorter.sort(small_dataset)
sorter.sort(big_dataset)

💢 状态

现实世界的例子

想象你正在使用某个绘图应用程序,你选择了画笔进行绘制。现在,画笔会根据所选颜色改变其行为,即如果你选择了红色,它会以红色绘制;如果是蓝色,则以蓝色绘制等。

用简单的话说

它允许你在状态变化时改变类的行为。

维基百科说

状态模式是一种行为软件设计模式,它以一种面向对象的方式实现了状态机。通过状态模式,每个单独的状态被实现为状态模式接口的派生类,并且通过调用由该模式的超类定义的方法来实现状态转换。
状态模式可以解释为一种策略模式,它能够通过在模式接口中定义的方法调用来切换当前的策略。

编程示例

让我们以手机为例。首先,我们有我们的状态接口和一些状态实现

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
class PhoneState:
def pick_up(self):
pass

def hang_up(self):
pass

def dial(self):
pass


class PhoneStateIdle(PhoneState):
def pick_up(self):
return PhoneStatePickedUp()

def hang_up(self):
raise Exception("already idle")

def dial(self):
raise Exception("unable to dial in idle state")


class PhoneStatePickedUp(PhoneState):
def pick_up(self):
raise Exception("already picked up")

def hang_up(self):
return PhoneStateIdle()

def dial(self):
return PhoneStateCalling()


class PhoneStateCalling(PhoneState):
def pick_up(self):
raise Exception("already picked up")

def hang_up(self):
return PhoneStateIdle()

def dial(self):
raise Exception("already dialing")

然后我们有一个Phone类,它在不同的行为调用中改变状态。

1
2
3
4
5
6
7
8
9
10
11
12
class Phone:
def __init__(self):
self.state = PhoneStateIdle()

def pick_up(self):
self.state = self.state.pick_up()

def hang_up(self):
self.state = self.state.hang_up()

def dial(self):
self.state = self.state.dial()

然后,它可以像下面这样使用,并会调用相关的状态方法:

1
2
3
phone = Phone()
phone.pick_up()
phone.dial()

📒 模板方法

现实世界示例

假设我们要建造一座房子。建造的步骤可能如下:

  • 准备房屋基础
  • 建造墙壁
  • 添加屋顶
  • 添加其他楼层

这些步骤的顺序不能改变,也就是说你不能在建墙之前先建屋顶等,但每个步骤都可以修改,例如墙壁可以用木头、聚酯或石头制作。

用简单的话来说

模板方法定义了执行某个算法的骨架,但将那些步骤的实现推迟到子类中。

维基百科说

在软件工程中,模板方法模式是一种行为设计模式,它在操作中定义了一个算法的程序骨架,并将一些步骤推迟给子类。它允许重新定义算法的某些步骤而不改变算法的结构。

编程示例

想象我们有一个构建工具,帮助我们进行测试、代码检查、构建、生成构建报告(即代码覆盖率报告、代码检查报告等)以及在测试服务器上部署我们的应用程序。

首先,我们有一个基础类,它指定了构建算法的骨架:

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
from abc import ABC, abstractmethod


class Builder(ABC):
def build(self):
self.test()
self.lint()
self.assemble()
self.deploy()

@abstractmethod
def test(self):
pass

@abstractmethod
def lint(self):
pass

@abstractmethod
def assemble(self):
pass

@abstractmethod
def deploy(self):
pass

然后我们可以有自己的实现

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
class AndroidBuilder(Builder):
def test(self):
print('Running android tests')

def lint(self):
print('Linting the android code')

def assemble(self):
print('Assembling the android build')

def deploy(self):
print('Deploying android build to server')


class IosBuilder(Builder):
def test(self):
print('Running ios tests')

def lint(self):
print('Linting the ios code')

def assemble(self):
print('Assembling the ios build')

def deploy(self):
print('Deploying ios build to server')

然后它可以被用作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
android_builder = AndroidBuilder()
android_builder.build()

## Output:
## Running android tests
## Linting the android code
## Assembling the android build
## Deploying android build to server

ios_builder = IosBuilder()
ios_builder.build()

## Output:
## Running ios tests
## Linting the ios code
## Assembling the ios build
## Deploying ios build to server
下一篇:
Linux黑马笔记