此系列为设计模式的艺术这本书的总结,本篇首先对最简单的创建型模式进行总结。
概述
创建型模式(Creational Pattern)关注对象的创建过程,是一类最常用的设计模式,在软件开发应用非常广泛。创建型模式将对象的创建和使用分离,在使用对象时无须关心对象的创建细节,从而降低系统的耦合度,让设计方案更易于修改和扩展。每个创建型模式都通过采用不同的解决方案来回答三个问题:创建什么(What)、由谁创建(Who)和何时创建(When)。
创建型模式包含GoF设计模式的5种(单例模式、工厂方法模式、抽象工厂模式、原型模式、建造者模式)以及非GoF模式的简单工厂模式。
单例模式
单例模式用于创建那些在软件系统中独一无二的对象,是一个简单但很实用的设计模式。
单例模式(Singleton Pattern)的定义如下:
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。
单例模式有三个要点:
- 某个类只能有一个实例;
- 它必须自行创建这个实例;
- 它必须自行向整个系统提供这个实例
单例模式按照实例化对象的时机可分为饿汉式单例模式和懒汉式单例模式
饿汉式单例模式
饿汉很害怕自己挨饿,所以会提前把食物准备好,等到需要吃的时候直接拿就好了,也就是饿汉式单例模式会在类加载的时候就实例化对象,后续使用时直接取这个对象。
饿汉式在python中可以使用模块导入或者元类的方式来实现
使用模块
当一个模块(py文件)被导入时,首先会执行这个模块的代码,然后将这个模块的名称空间加载到内存。当这个模块第二次再被导入时,不会再执行该文件,而是直接在内存中找。导入模块的行为和单例模式是一致的,即只有第一次会实例化,后续都是去取这个对象。
# singleton_module.py
class Singleton:
def __init__(self):
pass
singleton = Singleton() # 载入模块时实例化
import singleton_module # 导入模块使用单例对象
my_singleton = singleton_module.singleton
重写类的new或者init方法
class Singleton:
_instance = None
def __new__(cls):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
在类初始化的时候就将实例准备好,并且保证唯一
使用装饰器
def singleton(cls):
instances = {}
def get_instance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return get_instance
@singleton
class MySingleton:
def __init__(self):
self.value = 0
# 创建单例对象
my_singleton = MySingleton()
singleton是一个返回get_instance函数的装饰器,get_instance会返回被装饰的类的唯一一个实例,当类被调用时,实际上是调用get_instance方法。
使用元类
元类是 Python 中高级的概念,它可以用来动态地创建类。我们可以定义一个元类,使其在创建类时自动创建单例对象。
class SingletonMeta(type):
_instance = None # 保存实例
def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class Singleton(metaclass=SingletonMeta): # 将单例类的元类设置为SingletonMeta
def __init__(self):
pass
my_singleton = Singleton() # 类调用会先执行__call__方法,尝试从单例类的_instance中获取对象,如果没有则初始化
懒汉式单例模式
懒汉只有在需要吃东西的时候才会去准备食物,所以懒汉式单例模式指的是类一开始并不实例化对象,等到要用的时候才会去实例化。
使用类方法
基于线程安全的双重检查锁定(Double-Checked Locking)方式
class Singleton:
__instance = None # 私有类属性
@staticmethod
def get_instance():
if Singleton.__instance is None:
# 加锁
with threading.Lock():
if Singleton.__instance is None:
Singleton.__instance = Singleton()
return Singleton.__instance
s = Singleton()
# ......其他操作
多例类
import pymysql
class ConnectionPool:
__instances = []
def __init__(self, max_connections=3):
self.max_connections = max_connections
self.connections = []
self.initialize_pool()
ConnectionPool.__instances.append(self)
def initialize_pool(self):
for _ in range(self.max_connections):
self.connections.append(pymysql.connect(
host='localhost',
user='username',
password='password',
database='mydatabase',
charset='utf8mb4'
))
def get_connection(self):
return self.connections.pop()
def release_connection(self, conn):
self.connections.append(conn)
@classmethod
def get_instance(cls, max_connections=3):
for instance in cls.__instances:
if instance.max_connections == max_connections:
return instance
return cls(max_connections=max_connections)
# 获取一个最大连接数为3的连接池实例
pool = ConnectionPool.get_instance(max_connections=3)
# 从连接池中获取一个连接对象
conn = pool.get_connection()
# 执行查询操作
cur = conn.cursor()
cur.execute("SELECT * FROM mytable")
results = cur.fetchall()
print(results)
cur.close()
# 将连接对象释放回连接池
pool.release_connection(conn)
练习
使用饿汉式单例、带双重检查锁定机制的懒汉模式实现负载均衡器LoadBalancer
from random import choice
class LoadBalancer:
_instance = None
def __new__(cls):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self) -> None:
self.server_list = []
def add_server(self, server):
if server not in self.server_list:
self.server_list.append(server)
def remover_server(self, server):
self.server_list.remove(server)
def get_server(self):
if not self.server_list:
raise Exception("server list is None, please add server first.")
return choice(self.server_list)
balancer1 = LoadBalancer()
balancer2 = LoadBalancer()
print(balancer1==balancer2)
balancer1.add_server("127.0.0.1")
balancer1.add_server("128.1.2.1")
balancer1.add_server("129.1.2.1")
balancer1.add_server("110.2.2.1")
balancer1.remover_server("110.2.2.1")
print(balancer1.get_server())
print(balancer1.get_server())
print(balancer1.get_server())
print(balancer1.get_server())
print(balancer1.get_server())
print(balancer1.get_server())
import threading
class LazyLoadBalancer:
_instance = None
@staticmethod
def get_instance():
if LazyLoadBalancer._instance is None:
# 加锁
with threading.Lock():
if LazyLoadBalancer._instance is None:
LazyLoadBalancer._instance = LazyLoadBalancer()
return LazyLoadBalancer._instance
def __init__(self) -> None:
self.server_list = []
def add_server(self, server):
if server not in self.server_list:
self.server_list.append(server)
def remover_server(self, server):
self.server_list.remove(server)
def get_server(self):
if not self.server_list:
raise Exception("server list is None, please add server first.")
return choice(self.server_list)
lazy_balancer1 = LazyLoadBalancer.get_instance()
lazy_balancer2 = LazyLoadBalancer.get_instance()
print(lazy_balancer1==lazy_balancer2)
lazy_balancer1.add_server("127.0.0.1")
lazy_balancer1.add_server("128.1.2.1")
lazy_balancer1.add_server("129.1.2.1")
lazy_balancer1.add_server("110.2.2.1")
lazy_balancer1.remover_server("110.2.2.1")
print(lazy_balancer1.get_server())
print(lazy_balancer1.get_server())
print(lazy_balancer1.get_server())
print(lazy_balancer1.get_server())
print(lazy_balancer1.get_server())
print(lazy_balancer1.get_server())
简单工厂模式
定义
通常所说的工厂模式是指工厂方法模式,它也是使用频率最高的工厂模式,简单工厂模式是工厂方法模式的“小弟”,它不属于GoF 23种设计模式,但在软件开发中应用也较为频繁,通常将它作为学习其他工厂模式的入门。此外,工厂方法模式还有一位“大哥”——抽象工厂模式,这3种工厂模式各具特色,难度也逐个加大,在软件开发中都得到了广泛应用,成为面向对象软件中常用的对象创建工具。
简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。
简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。
模式的角色
职责分离
与一个对象相关的职责通常有3类:对象本身所具有的职责、创建对象的职责和使用对象的职责。
对象本身的职责,即对象自身所具有的一些数据和行为,可通过一些公开的方法来实现其职责。
对象的创建职责指的是构造函数及初始化函数的实现
使用对象的职责是在使用一个已经创建好的类时,需要遵循该类的接口规范、使用正确的参数和方法调用方式,以及正确处理异常等使用细节
为了实现单一职责原则,工厂模式会将对象的创建及使用职责分离,工厂类负责对象的创建及初始化,而对象的使用职责则是客户端实现,只与对象本身所具有的职责则由具体的产品类去完成。
优缺点
优点
- 工厂类包含必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例。客户端可以免除直接创建产品对象的职责,而仅仅“消费”产品。简单工厂模式实现了对象创建和使用的分离。
- 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可。对于一些复杂的类名,通过简单工厂模式可以在一定程度减少使用者的记忆量。
- 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
缺点
- 由于工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响。
- 使用简单工厂模式势必会增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度。
- 系统扩展困难。一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
- 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。
适用场景
- 工厂类负责创建的对象比较少。由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
- 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。
例子
使用简单工厂模式设计一个可以创建不同几何形状(如圆形、方形和三角形等)的绘图工具,每个几何图形都具有绘制draw()和擦除erase()两个方法,要求在绘制不支持的几何图形时,提示一个UnSupportedShapeException。
# 使用简单工厂模式设计一个可以创建不同几何形状(如圆形、方形和三角形等)的绘图工具
# 每个几何图形都具有绘制draw()和擦除erase()两个方法
# 要求在绘制不支持的几何图形时,提示一个UnSupportedShapeException。
from abc import ABCMeta, abstractmethod
class Shape(metaclass=ABCMeta):
@abstractmethod
def draw(self):
pass
@abstractmethod
def erase(self):
pass
class UnSupportedShapeException(Exception):
pass
class ShapeFactory:
def add_shape(self, shape_name: str) -> Shape:
if shape_name == "circle":
shape = Circle()
elif shape_name == "square":
shape = Square()
elif shape_name == "triangle":
shape = Triangle()
else:
raise UnSupportedShapeException("不支持该图形的绘制")
return shape
class Circle(ShapeFactory):
def draw(self):
print("draw a circle")
def erase(self):
print("erase a circle")
class Square(ShapeFactory):
def draw(self):
print("draw a square")
def erase(self):
print("erase a square")
class Triangle(ShapeFactory):
def draw(self):
print("draw a triangle")
def erase(self):
print("erase a triangle")
def client_code():
shape_name = "triangle"
shape_factorry = ShapeFactory()
shape = shape_factorry.add_shape(shape_name)
shape.draw()
shape.erase()
shape_name = "pentagon"
shape = shape_factorry.add_shape(shape_name)
if __name__ == "__main__":
client_code()
工厂模式
简单工厂模式有一个缺点在于工厂类的职责过重,需要根据入参来生成各类的具体产品,包含了各种具体产品的创建及初始化逻辑,并且当产品种类增加时,工厂类不可避免地需要增加判断逻辑,并且加入新产品的创建及初始化代码,违背了开闭原则。因此需要进行改进,本章介绍简单工厂模式改进后的工厂方法模式,简称工厂模式。
定义
工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。工厂方法模式是一种类创建型模式。
定义一个用于创建对象的接口指的是抽象工厂类的创建产品类方法,而子类是具体工厂类,它可以生成指定的产品类,工厂方法模式让具体的工厂类来生成对应的产品,而不再是由单一一个工厂类去完成所有产品的创建,工厂和产品一一对应,从而减少了工厂类的工作。
当我们增加新的产品时,也需要新增工厂类,但不再修改原有的代码(客户端需要修改,但也可通过配置文件的方式避免),从而满足开闭原则。
模式的角色
与简单工厂模式相比,工厂方法模式最重要的区别是引入了抽象工厂角色。抽象工厂可以是接口,也可以是抽象类或者具体类。
在抽象工厂中声明了工厂方法但并未实现工厂方法,具体产品对象的创建由其子类负责。
客户端针对抽象工厂编程,可在运行时再指定具体工厂类。具体工厂类实现了工厂方法,不同的具体工厂可以创建不同的具体产品。在实际使用时,具体工厂类在实现工厂方法时除了创建具体产品对象之外,还可以负责产品对象的初始化工作以及一些资源和环境配置工作,例如连接数据库、创建文件等。在客户端代码中,只需关心工厂类即可。不同的具体工厂可以创建不同的产品
优缺点
优点
- 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节。用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名
- 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,正是因为所有的具体工厂类都具有同一抽象父类
- 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合开闭原则
缺点
- 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度。
适用场景
- 客户端不知道其所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
- 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
例子
使用工厂方法模式设计一个程序来读取各种不同类型的图片格式,针对每种图片格式都设计一个图片读取器。例如,GIF图片读取器用于读取GIF格式的图片,JPG图片读取器用于读取JPG格式的图片。需充分考虑系统的灵活性和可扩展性。
from abc import ABCMeta, abstractmethod
class PictureReader(metaclass=ABCMeta):
@abstractmethod
def load(self):
pass
class PictureReaderFactory(metaclass=ABCMeta):
@abstractmethod
def create_reader(self):
pass
class GifReader(PictureReader):
def load(self):
print("read a gif picture.")
class GifReaderFactory(PictureReaderFactory):
def create_reader(self):
return GifReader()
class JpgReader(PictureReader):
def load(self):
print("read a jpg picture.")
class JpgReaderFactory(PictureReaderFactory):
def create_reader(self):
return GifReader()
class UnSupportedShapeException(Exception):
pass
def client_code(pic_type):
if pic_type == "gif":
reader_factory = GifReaderFactory()
elif pic_type == "jpg":
reader_factory = JpgReaderFactory()
else:
raise UnSupportedShapeException("unsupported type picture!")
reader = reader_factory.create_reader()
reader.load()
if __name__ == "__main__":
client_code("jpg")
client_code("gif")
client_code("jpeg")
抽象工厂模式
工厂模式中,一个具体工厂类对应一个具体产品类,但是当我们的产品是配套的时候,这种模式就存在一个弊端了,那就是工厂类会随着产品类不断增加。比如机器人模型,我们有不同的款式,它们都由手、脚、躯干和头部组成,如果我们使用工厂模式,那么每个款式我们需要创建4个产品类,同时工厂类也需要相应创建4个,这样每增加一个款式,就需要增加8个类。
但因为这些配套产品其实是成套去创建的,所以我们也可以选择将它们放到一个工厂去生产,这样就可以减少工厂的数量,不管一套产品有多少组件,都只需要新建一个工厂类。
这种设计模式称为抽象工厂模式。
定义
产品等级结构和产品族
产品等级结构:即产品的继承结构,例如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。例如海尔电器工厂生产海尔电视机和海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中,海尔电视机、海尔电冰箱构成了一个产品族。
模式定义
抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,它是一种对象创建型模式。
模式的角色
- AbstractFactory(抽象工厂):它声明了一组用于创建一族产品的方法,每个方法对应一种产品。
- ConcreteFactory(具体工厂):它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每种产品都位于某个产品等级结构中。
- AbstractProduct(抽象产品):它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
- ConcreteProduct(具体产品):它定义具体工厂生产的具体产品对象,实现在抽象产品接口中声明的业务方法
优缺点
优点
- 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易,所有的具体工厂都实现了在抽象工厂中声明的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
- 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
- 增加新的产品族很方便,无须修改已有系统,符合开闭原则
缺点
增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了开闭原则
适用场景
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是很重要的,用户无须关心对象的创建过程,将对象的创建和使用解耦。
- 系统中有多于一个的产品族,而每次只使用其中某一个产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。
- 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。同一个产品族中的产品可以是没有任何关系的对象,但是它们都具有一些共同的约束。例如同一操作系统下的按钮和文本框,按钮与文本框之间没有直接关系,但它们都是属于某一操作系统的,此时具有一个共同的约束条件:操作系统的类型。
- 产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。
例子
Sunny软件公司欲推出一款新的手机游戏软件,该软件能够支持iOS和Android等多个智能手机操作系统平台。针对不同的手机操作系统,该游戏软件提供了不同的游戏操作控制(OperationController)类和游戏界面控制(InterfaceController)类,并提供相应的工厂类来封装这些类的初始化过程。该软件要求具有较好的扩展性以支持新的操作系统平台,为了满足上述需求,试采用抽象工厂模式对其进行设计
from abc import ABCMeta, abstractmethod
class OperationController(metaclass=ABCMeta):
@abstractmethod
def run(self):
pass
class InterfaceController(metaclass=ABCMeta):
@abstractmethod
def move(self):
pass
class IosOperationController(OperationController):
def run(self):
print("operate player to run in ios system.")
class IosInterfaceController(InterfaceController):
def move(self):
print("control interface to move in ios system.")
class AndroidOperationController(OperationController):
def run(self):
print("operate player to run in android system.")
class AndroidInterfaceController(InterfaceController):
def move(self):
print("control interface to move in android system.")
class ControllerFactory(metaclass=ABCMeta):
@abstractmethod
def create_operation(self):
pass
@abstractmethod
def create_interface(self):
pass
class IosControllerFactory(ControllerFactory):
def create_operation(self):
return IosOperationController()
def create_interface(self):
return IosInterfaceController()
class AndroidControllerFactory(ControllerFactory):
def create_operation(self):
return AndroidOperationController()
def create_interface(self):
return AndroidInterfaceController()
class UnSupportedShapeException(Exception):
pass
def client_code(system):
if system == "ios":
controller_factory = IosControllerFactory()
elif system == "android":
controller_factory = AndroidControllerFactory()
else:
raise UnSupportedShapeException("unsupported system!")
operator_controller = controller_factory.create_operation()
operator_controller.run()
interface_controller = controller_factory.create_interface()
interface_controller.move()
if __name__ == "__main__":
client_code("ios")
client_code("android")
client_code("sony")
原型模式
定义
原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过克隆这些原型创建新的对象。原型模式是一种对象创建型模式。
原型模式的工作原理很简单:将一个原型对象传给要发动创建的对象,这个要发动创建的对象通过请求原型对象克隆自己来实现创建过程。由于在软件系统中经常会遇到需要创建多个相同或者相似对象的情况,因此原型模式在真实开发中的使用频率还是非常高的。原型模式是一种“另类”的创建型模式,创建克隆对象的工厂就是原型类自身,工厂方法由克隆方法来实现。
需要注意的是,通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址。通常,对克隆所产生的对象进行的修改不会对原型对象造成任何影响,每个克隆对象都是相互独立的。通过不同的方式对克隆对象进行修改以后,可以得到一系列相似但不完全相同的对象。
模式的角色
带原型管理器的原型模式
原型管理器(Prototype Manager)是将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。在原型管理器中针对抽象原型类进行编程,以便扩展。
代码
import copy
class Prototype:
def __init__(self):
self._name = 'Prototype'
def clone(self):
return copy.deepcopy(self)
class ConcretePrototype(Prototype):
def __init__(self):
super().__init__()
if __name__ == '__main__':
prototype = ConcretePrototype()
prototype_copy = prototype.clone()
# 带管理器的原型类
import copy
class Prototype:
def __init__(self):
self._objects = {}
def register_object(self, name, obj):
self._objects[name] = obj
def unregister_object(self, name):
del self._objects[name]
def clone(self, name, **kwargs):
obj = copy.deepcopy(self._objects.get(name))
obj.__dict__.update(kwargs)
return obj
class MyClass:
def __init__(self, name):
self.name = name
def __str__(self):
return f"MyClass name={self.name}"
if __name__ == '__main__':
prototype = Prototype()
prototype.register_object('my_class', MyClass('prototype'))
obj = prototype.clone('my_class')
print(obj)
obj2 = prototype.clone('my_class', name='new name')
print(obj2)
优缺点
优点
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
- 扩展性较好。由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少具体原型类对原有系统都没有任何影响。
- 原型模式提供了简化的创建结构。工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样。原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
- 可以使用深克隆的方式保存对象的状态。使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用,例如恢复到某一历史状态,可辅助实现撤销操作。
缺点
- 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部。当对已有的类进行改造时,需要修改源代码,违背了开闭原则。
- 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。
适用场景
- 创建新对象成本较大(例如初始化需要占用较长的时间,占用太多的CPU资源或网络资源)。新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
- 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式
- 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态。通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
例子
Sunny软件公司在某销售管理系统中设计并实现了一个客户类Customer,其中包含一个名为客户地址的成员变量,客户地址的类型为Address。用浅克隆和深克隆分别实现Customer对象的复制,并比较这两种克隆方式的异同。
import copy
class Address:
def __init__(self, addr: str):
self.value = addr
class Prototype:
def clone(self):
return copy.copy(self)
def deep_clone(self):
return copy.deepcopy(self)
class Customer(Prototype):
def __init__(self, addr: Address):
self.address = addr
if __name__ == "__main__":
first_customer = Customer(Address("stress1"))
copy_customer = first_customer.clone()
decopy_customer = first_customer.deep_clone()
print(first_customer.address == copy_customer.address)
print(first_customer.address == decopy_customer.address)
first_customer.address.value = "stree2"
print(
first_customer.address.value,
copy_customer.address.value,
decopy_customer.address.value,
)
浅复制 | 深复制 | |
---|---|---|
不可变类型(整型、字符串、元组) | 复制对象的指针,指向原来的对象 | 复制对象的指针,指向原来的对象 |
可变类型(数组、字典、集合、对象) | 复制对象的指针,指向原来的对象 | 开辟新空间,存储容器对象,并且将指针指向新的容器对象 |
建造者模式
建造者模式又称为生成器模式,它是一种较为复杂、使用频率也相对较低的创建型模式。建造者模式向客户端返回的不是一个简单的产品,而是一个由多个部件组成的复杂产品。
定义
建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。
建造者模式是较为复杂的创建型模式,它将客户端与包含多个组成部分(或部件)的复杂对象的创建过程分离,客户端只需要知道所需的建造者类型即可。建造者模式关注如何一步一步地创建一个复杂对象,不同的具体建造者定义了不同的创建过程,且具体建造者相互独立,增加新的建造者非常方便,无须修改已有代码,系统具有较好的扩展性。
模式的角色
优缺点
优点
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 每个具体建造者都相对独立,而与其他具体建造者无关。因此,可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。由于指挥者类针对抽象建造者编程,增加新的具体建造者无须修改原有类库的代码,系统扩展方便,符合开闭原则
- 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
缺点
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似。如果产品之间的差异性很大,例如很多组成部分都不相同,就不适合使用建造者模式,因此其使用范围受到一定的限制
- 如果产品的内部结构复杂且多变,可能会需要定义很多具体建造者类来实现这种变化,这就导致系统变得很庞大,增加系统的理解难度和运行成本。
适用场景
在以下情况下可以考虑使用建造者模式:
- 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量。
- 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
- 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中。
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
例子
Sunny软件公司欲开发一个视频播放软件。为了方便用户使用,该播放软件提供多种界面显示模式,例如完整模式、精简模式、记忆模式、网络模式等。在不同的显示模式下主界面的组成元素有所差异。例如,在完整模式下将显示菜单、播放列表、主窗口、控制条等,在精简模式下只显示主窗口和控制条,而在记忆模式下将显示主窗口、控制条、收藏列表等。试使用建造者模式设计该软件。
from abc import ABCMeta, abstractmethod
class DisplayMode:
def __init__(self) -> None:
self.menu = None
self.play_list = None
self.main_window = None
self.control_line = None
self.favorite_list = None
def set_menu(self, menu: str):
self.menu = menu
def set_play_list(self, play_list: list):
self.play_list = play_list
def set_main_window(self, main_window: str):
self.main_window = main_window
def set_control_line(self, control_line: str):
self.control_line = control_line
def set_favorite_list(self, favorite_list: list):
self.favorite_list = favorite_list
def __str__(self) -> str:
display = "in this mode,"
if self.menu:
display = f"{display} the menu is {self.menu}"
if self.play_list:
display = f"{display}, the play_list is {self.play_list}"
if self.main_window:
display = f"{display}, the main_window is {self.main_window}"
if self.control_line:
display = f"{display}, the control_line is {self.control_line}"
if self.favorite_list:
display = f"{display}, the favorite_list is {self.favorite_list}"
return display
class DisplayModeBuilder(metaclass=ABCMeta):
def __init__(self) -> None:
self.display_mode = DisplayMode()
def build_menu(self, menu: str):
self.display_mode.set_menu(menu)
def build_play_list(self, play_list: list):
self.display_mode.set_play_list(play_list)
def build_main_window(self, main_window: str):
self.display_mode.set_main_window(main_window)
def build_control_line(self, control_line: str):
self.display_mode.set_control_line(control_line)
def build_favorite_list(self, favorite_list: list):
self.display_mode.set_favorite_list(favorite_list)
@abstractmethod
def is_set_menu(self):
pass
@abstractmethod
def is_set_play_list(self):
pass
@abstractmethod
def is_set_main_window(self):
pass
@abstractmethod
def is_set_control_line(self):
pass
@abstractmethod
def is_set_favorite_list(self):
pass
# 完整模式下将显示菜单、播放列表、主窗口、控制条等,
# 在精简模式下只显示主窗口和控制条,
# 记忆模式下将显示主窗口、控制条、收藏列表等。
class CompleteDisplayModeBuilder(DisplayModeBuilder):
def is_set_menu(self):
return True
def is_set_play_list(self):
return True
def is_set_control_line(self):
return True
def is_set_main_window(self):
return True
def is_set_favorite_list(self):
return False
class SimpleDisplayModeBuilder(DisplayModeBuilder):
def is_set_menu(self):
return False
def is_set_play_list(self):
return False
def is_set_main_window(self):
return True
def is_set_control_line(self):
return True
def is_set_favorite_list(self):
return False
class MemoryDisplayModeBuilder(DisplayModeBuilder):
def is_set_menu(self):
return False
def is_set_play_list(self):
return False
def is_set_main_window(self):
return True
def is_set_control_line(self):
return True
def is_set_favorite_list(self):
return True
class Director:
def __init__(self, builder: DisplayModeBuilder) -> None:
self.builder = builder
def constuct(
self,
control_line: str = None,
menu: str = None,
play_list: list = None,
favor_list: list = None,
main_window: str = None,
) -> DisplayMode:
if self.builder.is_set_control_line():
self.builder.build_control_line(control_line)
if self.builder.is_set_main_window():
self.builder.build_main_window(main_window)
if self.builder.is_set_menu():
self.builder.build_menu(menu)
if self.builder.is_set_play_list():
self.builder.build_play_list(play_list)
if self.builder.is_set_favorite_list():
self.builder.build_favorite_list(favor_list)
return self.builder.display_mode
def reset_builder(self, builder: DisplayModeBuilder):
self.builder = builder
if __name__ == "__main__":
display_mode_params = dict(
control_line="forward/backupward_line",
main_window="a big main window",
menu="file/edit/select/open/",
play_list=["a1", "a2", "b1", "b2"],
favor_list=["f1", "f2"],
)
simple_builder = SimpleDisplayModeBuilder()
director = Director(simple_builder)
print(director.constuct(**display_mode_params))
complete_builder = CompleteDisplayModeBuilder()
director.reset_builder(complete_builder)
print(director.constuct(**display_mode_params))
memory_builder = MemoryDisplayModeBuilder()
director.reset_builder(memory_builder)
print(director.constuct(**display_mode_params))