目录
一、什么是设计模式
设计模式并不是一种数据结构,而是一种解决特定软件设计问题的通用解决方案。它们是在软件开发过程中针对常见问题所形成的一系列经过验证的最佳实践。
数据结构是组织和存储数据的方式,它定义了数据之间的关系以及对数据的操作。而设计模式是一种描述在特定情境下如何解决问题的模板或指南,它关注的是软件设计的结构、组织和交互,而不是数据本身。
设计模式是面对各种问题进行提炼和抽象而形成的解决方案。这些设计方案是前人不断试验,考虑了封装性、复用性、效率、可修改、可移植等各种因素的高度总结。它不限于一种特定的语言,它是一种解决问题的思想和方法
二、为什么要有设计模式
问题解决方案的复用性: 在软件开发过程中,经常会遇到一些常见的设计问题,例如对象的创建与初始化、对象之间的通信、算法的选择等。设计模式提供了针对这些问题的通用解决方案,可以帮助开发人员避免重复造轮子,提高代码的复用性和可维护性。
提高代码质量: 使用设计模式可以使代码更加清晰、易于理解和维护。设计模式经过了长期的实践验证,是一种经过考验的最佳实践,可以帮助开发人员编写高质量的代码。
促进团队合作与沟通: 设计模式提供了一种通用的设计语言,可以帮助团队成员更好地沟通和理解彼此的代码。通过使用设计模式,团队成员可以更容易地理解代码的设计思路,降低沟通成本,促进团队合作。
降低软件维护成本: 软件的维护成本往往比开发成本更高。使用设计模式可以使软件更加灵活和易于扩展,从而降低了软件的维护成本。当需求发生变化时,通过使用设计模式,可以更快地进行代码修改和扩展。
提高开发效率: 设计模式提供了一套经过验证的解决方案,可以帮助开发人员更快地解决问题,从而提高开发效率。通过使用设计模式,可以减少开发过程中的试错时间,提高代码的稳定性和可靠性。
三、设计模式六大原则 📜
名称 英文名称 原则说明 单一职责原则 Single Responsibility Principle (SRP) 一个类应该只有一个引起变化的原因,即一个类应该只有一个职责。 开放-封闭原则 Open-Closed Principle (OCP) 软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改。 里氏替换原则 Liskov Substitution Principle (LSP) 所有引用基类的地方必须能够透明地使用其子类的对象,即子类必须能够替换掉父类,并且软件功能不受影响。 依赖倒置原则 Dependency Inversion Principle (DIP) 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于具体实现细节,具体实现细节应该依赖于抽象。 接口隔离原则 Interface Segregation Principle (ISP) 客户端不应该被迫依赖于它们不使用的接口。一个类对另一个类的依赖应该建立在最小的接口上。 迪米特法则(最少知识原则) Law of Demeter (LoD) 一个对象应该对其他对象有尽可能少的了解,不和陌生人说话,只和朋友说话。
四、设计模式分类
设计模式可以分为三个大类: 创建类设计模式、结构类设计模式、行为类设计模式
类型 设计模式 创建型 单例模式、工厂模式(简单工厂模式、抽象工厂模式)、建造者模式、原型模式 结构型 代理模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式、桥梁模式 行为型 策略模式、责任链模式、命令模式、中介者模式、模板模式、迭代器模式、访问者模式、观察者模式、解释器模式、备忘录模式、状态模式
设计模式也衍生出了很多的新的种类,不局限于这23种
五、创建型模式
1、简单工厂模式 🏭
简单工厂模式不是23中设计模式中的,但是必须要知道。
简单工厂模式不直接向客户端暴露对象创建的细节,而是通过一个工厂类来负责创建产品类的实例。
代码示例👋
class Product: def operation(self): pass class ConcreteProduct1(Product): def operation(self): return "ConcreteProduct1 operation" class ConcreteProduct2(Product): def operation(self): return "ConcreteProduct2 operation" class SimpleFactory: def create_product(self, product_type): if product_type == 1: return ConcreteProduct1() elif product_type == 2: return ConcreteProduct2() else: raise ValueError("Invalid product type") # 客户端代码 factory = SimpleFactory() product1 = factory.create_product(1) print(product1.operation()) # 输出: ConcreteProduct1 operation product2 = factory.create_product(2) print(product2.operation()) # 输出: ConcreteProduct2 operation
这里就是——根据传入参数,返回了对应的类
优点与缺点 ✨
优点
- 封装了对象创建的细节:客户端代码不需要直接负责创建对象,而是通过工厂类来创建,从而将对象创建的细节隐藏起来,降低了客户端与具体产品类的耦合度。
- 简化了客户端代码:客户端只需要关心所需产品的类型,而不需要了解如何创建这些产品,简化了客户端代码的复杂度。
- 易于扩展:如果需要新增产品,只需要在工厂类中添加相应的产品类型判断和创建逻辑即可,不需要修改客户端代码,符合开闭原则。
缺点
- 违反了开闭原则:每次新增产品都需要修改工厂类的代码,违反了开闭原则,对修改开放,对扩展关闭。
- 工厂类职责过重:随着产品类型的增多,工厂类的责任会越来越重,可能会导致类的臃肿,不利于维护和扩展。
- 不够灵活:由于工厂类负责创建所有产品,当产品类型过多或者产品创建逻辑复杂时,可能会导致工厂类变得臃肿且难以维护。
总的来说,简单工厂模式适用于对象类型较少且创建逻辑相对简单的场景,但在复杂的情况下可能不够灵活。
2、工厂方法模式 🏭
简单工厂模式只创建一个工厂类,当有新的产品时,需要修改工厂类代码。
而 工厂方法模式的每个具体产品对应一个具体的工厂类,不需要修改工厂类代码,并且同时也能满足隐藏对象创建的细节。
但是工厂方法模式也是有缺点的,就是 每增加一个具体产品类,就必须增加一个相应的具体方法。
代码示例👋
from abc import ABC, abstractmethod class Product(ABC): @abstractmethod def operation(self): pass class ConcreteProduct1(Product): def operation(self): return "ConcreteProduct1 operation" class ConcreteProduct2(Product): def operation(self): return "ConcreteProduct2 operation" class Factory(ABC): @abstractmethod def create_product(self): pass class ConcreteFactory1(Factory): def create_product(self): return ConcreteProduct1() class ConcreteFactory2(Factory): def create_product(self): return ConcreteProduct2() # 客户端代码 factory1 = ConcreteFactory1() product1 = factory1.create_product() print(product1.operation()) # 输出: ConcreteProduct1 operation factory2 = ConcreteFactory2() product2 = factory2.create_product() print(product2.operation()) # 输出: ConcreteProduct2 operation
优点与缺点 ✨
优点 缺点 1. 符合开闭原则:新增产品时,只需添加相应的具体工厂类和产品类,不需要修改已有代码,符合开闭原则。 1. 类数量增多:每个具体产品都需要对应一个具体工厂类,可能导致类的数量增多。 2. 更好的扩展性:每个具体产品都有对应的具体工厂类,新增产品时只需新增对应的工厂类,不会影响其他类的实现。 2. 复杂性增加:相比简单工厂模式,工厂方法模式引入了更多的类和接口,增加了系统的复杂度。 3. 降低了代码耦合度:客户端只需依赖抽象工厂和抽象产品类,不需要依赖具体产品类,降低了代码的耦合度。 3. 每个产品都需要一个对应的具体工厂类:如果产品数量较多,可能会导致具体工厂类的数量过多,不利于管理和维护。
3、抽象工厂模式 🏭
抽象工厂模式是一种创建型设计模式,它提供一个接口,用于创建相关或依赖对象的家族,而不需要指定具体类。这种模式与工厂方法模式相似,区别在于抽象工厂模式针对的是产品族,而工厂方法模式针对的是单一产品。
代码示例👋
from abc import ABC, abstractmethod # 抽象产品A class AbstractProductA(ABC): @abstractmethod def operation(self): pass # 具体产品A1 class ConcreteProductA1(AbstractProductA): def operation(self): return "ConcreteProductA1 operation" # 具体产品A2 class ConcreteProductA2(AbstractProductA): def operation(self): return "ConcreteProductA2 operation" # 抽象产品B class AbstractProductB(ABC): @abstractmethod def operation(self): pass # 具体产品B1 class ConcreteProductB1(AbstractProductB): def operation(self): return "ConcreteProductB1 operation" # 具体产品B2 class ConcreteProductB2(AbstractProductB): def operation(self): return "ConcreteProductB2 operation" # 抽象工厂 class AbstractFactory(ABC): @abstractmethod def create_product_a(self): pass @abstractmethod def create_product_b(self): pass # 具体工厂1 class ConcreteFactory1(AbstractFactory): def create_product_a(self): return ConcreteProductA1() def create_product_b(self): return ConcreteProductB1() # 具体工厂2 class ConcreteFactory2(AbstractFactory): def create_product_a(self): return ConcreteProductA2() def create_product_b(self): return ConcreteProductB2() # 客户端代码 factory1 = ConcreteFactory1() productA1 = factory1.create_product_a() productB1 = factory1.create_product_b() print(productA1.operation()) # 输出: ConcreteProductA1 operation print(productB1.operation()) # 输出: ConcreteProductB1 operation factory2 = ConcreteFactory2() productA2 = factory2.create_product_a() productB2 = factory2.create_product_b() print(productA2.operation()) # 输出: ConcreteProductA2 operation print(productB2.operation()) # 输出: ConcreteProductB2 operation
优点与缺点 ✨
优点 缺点 1. 封装性好:客户端无需知道具体产品的类名,只需关心抽象工厂和抽象产品的接口即可。 1. 灵活性差:新增产品族时,需要修改抽象工厂接口及所有的具体工厂类,违背了开闭原则。 2. 易于替换:由于客户端只依赖于抽象工厂和抽象产品,因此可以方便地替换具体工厂,实现不同产品族的切换。 2. 复杂性增加:随着产品族的增多,抽象工厂和具体工厂类的数量会增加,导致系统复杂度增加。 3. 产品族一致性:抽象工厂确保创建的产品属于同一族,保证了产品之间的兼容性。 3. 不易扩展:新增产品族时,需要修改抽象工厂及所有的具体工厂类,扩展性较差。 4. 符合单一职责原则:每个具体工厂类负责创建一个产品族,符合单一职责原则,降低了类的复杂度。 4. 增加系统复杂度:引入了更多的抽象层,增加了系统的理解和维护难度
4、3种工厂模式——总结 💎
模式 概述 优点 缺点 适用场景 抽象工厂模式 提供接口创建相关或依赖对象的家族,不需指定具体类 封装性好;易于替换;产品族一致性 灵活性差;复杂性增加 需要创建一系列相关或依赖对象的家族,确保产品兼容性的场景 简单工厂模式 通过工厂类创建对象,不需直接实例化对象 简单易用;降低耦合度 违反开闭原则;不利于扩展 根据参数创建不同类型对象,对象创建逻辑相对简单的场景 工厂方法模式 定义创建对象接口,由子类决定实例化的类 符合开闭原则;客户端只需知道抽象类 类的数量增加;客户端需了解具体工厂类 创建单一类型对象,将对象创建逻辑封装在子类中的场景
5、建造者模式 ⛏️
就是设计一个顺序来创建对象中的各个部分,这样可以方便我们控制这个复杂的对象
建造者模式是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
角色有抽象创建者、具体创建者、指挥者和产品。
展示一个简单的 Python 示例,演示如何使用建造者模式来构建房子。
对于这个房子——对他的构建会进行分离——抽离出三个对象【地基,楼房,房顶】
然后指挥者将会按照搭建房子的顺序进行创建——现有地基,再有楼房,最后有屋顶
毕竟搭建房子不可能先有屋顶吧!!!
代码示例👋
# 产品类 - 房子 class House: def __init__(self): self.foundation = None self.structure = None self.roof = None def __str__(self): return f"Foundation: {self.foundation}, Structure: {self.structure}, Roof: {self.roof}" # 抽象建造者类 class HouseBuilder: def __init__(self): self.house = House() def build_foundation(self): pass def build_structure(self): pass def build_roof(self): pass def get_house(self): return self.house # 具体建造者类 - 楼房建造者 class HighRiseHouseBuilder(HouseBuilder): def build_foundation(self): self.house.foundation = "High-rise foundation" def build_structure(self): self.house.structure = "High-rise structure" def build_roof(self): self.house.roof = "High-rise roof" # 指挥者类 class Director: def __init__(self, builder): self.builder = builder def construct_house(self): self.builder.build_foundation() self.builder.build_structure() self.builder.build_roof() # 客户端代码 builder = HighRiseHouseBuilder() director = Director(builder) director.construct_house() house = builder.get_house() print("High-rise house constructed:") print(house)
优缺点和适用场景 ✨
特点 描述 优点 1. 将复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
2. 可以控制产品的构建过程,灵活地组合各个部件。
3. 可以避免构造函数的参数过多,提高代码的可读性和可维护性。
缺点 1. 建造者模式的实现需要编写较多的代码,包括产品类、抽象建造者类、具体建造者类和指挥者类,增加了系统的复杂度。
2. 如果产品内部结构变化较大,可能需要修改建造者接口和具体建造者类,不够灵活。
适用场景 1. 需要创建复杂对象,其构建过程需要独立于其组成部分和表示。
2. 构建过程中的顺序和组件的组合需要灵活变化。
3. 需要避免构造函数参数过多、构造函数重载的情况。
5、单例模式
单例模式保证一个类只有一个实例,并提供一个访问它的全局访问点。
代码示例👋
class Singleton: _instance = None def __new__(cls): if not cls._instance: cls._instance = super(Singleton, cls).__new__(cls) return cls._instance # 测试 singleton1 = Singleton() singleton2 = Singleton() print("ID of singleton1:", id(singleton1)) print("ID of singleton2:", id(singleton2)) print("Are singleton1 and singleton2 the same instance?", singleton1 is singleton2)
优缺点和适用场景 ✨
特点 描述 优点 1. 确保一个类只有一个实例,节省系统资源。
2. 提供一个全局访问点,方便对该实例的访问和操作。
3. 对于频繁使用的对象,可以减少内存占用和提高性能。
缺点 1. 单例模式的实现可能会增加系统的复杂性。
2. 单例模式可能会造成单点故障,一旦该实例出现问题,整个系统可能受到影响。
3. 不适合多线程环境下的使用,需要考虑线程安全问题。
适用场景 1. 需要确保一个类只有一个实例,并且提供一个全局访问点。
2. 需要频繁使用的资源对象,避免多次创建和销毁实例,提高性能。
3. 需要对资源进行集中管理,如日志记录器、数据库连接池等。
6、原型模式
就是直接复制之前创建好的对象,简化操作
原型模式允许通过复制现有对象来创建新对象,而不是通过实例化类。
这对于创建对象的成本较高或者初始化过程较为复杂的情况非常有用。
代码示例👋
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, **attr): """克隆一个对象""" obj = copy.deepcopy(self._objects.get(name)) obj.__dict__.update(attr) return obj class Car: def __init__(self): self.make = "Unknown" self.model = "Unknown" self.year = "Unknown" def __str__(self): return f"{self.year} {self.make} {self.model}" # 创建原型 prototype = Prototype() # 创建一个原型汽车对象 car = Car() car.make = "Toyota" car.model = "Camry" car.year = 2022 # 注册原型汽车对象 prototype.register_object("Camry2022", car) # 克隆原型汽车对象 cloned_car = prototype.clone("Camry2022") print("Cloned Car:", cloned_car)
我们首先定义了一个 Prototype 类,它包含了一个字典 _objects 用于存储对象的原型。register_object 方法用于注册一个对象,unregister_object 方法用于注销一个对象,clone 方法用于克隆一个对象。
然后,我们定义了一个 Car 类作为示例对象,并创建了一个原型汽车对象,并将其注册到原型中。接着,我们使用原型模式克隆了该原型汽车对象,并打印了克隆后的对象。
优缺点和适用场景✨
特点 描述 优点 1. 对象的复制相比于新建对象更加高效,尤其是当对象的创建成本较高或者初始化过程较为复杂时。
2. 可以动态地添加或移除原型,灵活性高。
3. 可以简化对象的创建过程,提高代码复用性。
缺点 1. 需要注意深拷贝和浅拷贝的问题,确保克隆对象的完整性。
2. 部分编程语言的原型模式的实现可能较为复杂,需要仔细考虑。
3. 如果对象的状态包含对其他对象的引用,需要确保这些引用对象也能正确地被复制。
适用场景 1. 需要避免创建成本较高的对象,如数据库连接、线程等。
2. 需要动态地生成对象的情况,且对象类型不确定。
3. 对象的创建过程较为复杂,但是新对象和现有对象之间的区别较小。
4. 希望通过克隆来避免构造函数的初始化过程,提高性能和灵活性。
六、结构型模式(7种)
1、适配器模式 🔌 🔌 🔌
将一个类的接口转换成客户希望的另外一个接口,适配器使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
比如我之前的接口都是统一的,
后面,我使用了别人的SDK,接口方法名不一样了
我就得写一个适配器,在那个对象上面套一层,让他接口和我的接口风格统一
就类似我们平时使用的接口转换器:USB转TypeC、HMDI转HSV等等
代码示例👋
# 定义目标接口 class Target: def request(self): pass # 定义被适配者类 class Adaptee: def specific_request(self): return "Adaptee's specific request" # 定义适配器类 class Adapter(Target): def __init__(self, adaptee): self.adaptee = adaptee def request(self): return self.adaptee.specific_request() # 客户端代码 def client_code(target): print(target.request()) if __name__ == "__main__": # 创建被适配者对象 adaptee = Adaptee() # 创建适配器对象,将被适配者对象传入 adapter = Adapter(adaptee) # 将适配器传给客户端代码,客户端代码无需关心具体实现 client_code(adapter)
Target
是客户端期望的接口,Adaptee
是需要被适配的类,其具有一个与Target
不兼容的specific_request
方法。
Adapter
类继承了Target
接口,并在内部持有一个Adaptee
对象,在request
方法中调用Adaptee
的specific_request
方法,从而实现了适配。客户端代码只需要与
Target
接口打交道,无需关心具体的实现细节,即可调用适配器对象的request
方法。
优缺点和适用场景✨
特点 描述 优点 1. 提高代码复用性:可以重用现有的类,而不需要修改原有的代码,使得系统更易维护。
2. 提高系统灵活性:能够使原本由于接口不匹配而不能一起工作的类可以协同工作。
3. 增强了系统的可扩展性:可以在不改变原有代码的情况下对接口进行扩展。
缺点 1. 增加复杂性:适配器可能会增加代码的复杂性,特别是在适配器涉及到大量的逻辑时。
2. 过多适配器:如果系统中有大量的适配器,会增加系统的维护难度。
适用场景 1. 系统需要使用已有的类,而这些类的接口不符合系统的需要。
2. 想要创建一个可复用的类,该类可以与不相关或不可预见的类协同工作。
3. 在使用第三方提供的组件时,需要将自己的系统与其接口相匹配。
2、代理模式 💇♂ 💇♂
为其它对象提供一种代理以控制对这个对象的访问。角色有抽象实体、实体和代理。
代码示例👋
# 定义真实主题接口 class Subject: def request(self): pass # 定义真实主题类 class RealSubject(Subject): def request(self): print("RealSubject: Handling request") # 定义代理类 class Proxy(Subject): def __init__(self): self.real_subject = None def request(self): # 在真正需要时才创建真实主题对象 if not self.real_subject: self.real_subject = RealSubject() # 代理可以在调用真实主题前后执行额外的操作 print("Proxy: Logging request before calling RealSubject") self.real_subject.request() print("Proxy: Logging request after calling RealSubject") # 客户端代码 def client_code(subject): subject.request() if __name__ == "__main__": # 创建代理对象 proxy = Proxy() # 将代理传给客户端代码 client_code(proxy)
Subject
是真实主题和代理的共同接口,RealSubject
是真实的业务逻辑实现,Proxy
是代理类,用于控制对真实主题的访问。当客户端调用代理的
request
方法时,代理在调用真实主题之前和之后可以执行额外的操作,如日志记录等。这种方式使得代理可以对真实主题进行保护、延迟加载等操作。
优缺点和适用场景✨
特点 描述 优点 1. 代理可以在客户端和真实主题之间起到中介的作用,保护真实主题免受直接访问,提高安全性。
2. 代理可以延迟加载真实主题,提高系统性能。
3. 代理可以对客户端提供额外的服务,如缓存、日志记录、权限控制等。
缺点 1. 增加了系统复杂性:引入了代理类后,系统会有更多的类参与,增加了系统的复杂度。
2. 会降低系统性能:由于需要通过代理访问真实主题,可能会降低系统的性能。
3. 增加了开发和维护成本:需要额外编写和维护代理类。
适用场景 1. 远程代理:用于访问远程对象,如RPC调用。
2. 虚拟代理:用于延迟加载对象,减少系统启动时间和资源消耗。
3. 安全代理:用于控制真实主题的访问权限,保护真实主题不被非法访问。
4. 日志记录代理:用于记录方法调用日志等。
3、装饰器模式 ⚜️ ⚜️ ⚜️
装饰器模式允许动态地将新功能附加到对象上,扩展其行为,而无需修改其代码。
装饰器模式是继承方式的一个替代方案,可以轻量级的扩展被装饰对象的功能
代码示例👋
# 定义组件接口 class Component: def operation(self): pass # 定义具体组件类 class ConcreteComponent(Component): def operation(self): return "ConcreteComponent: operation()" # 定义装饰器基类 class Decorator(Component): def __init__(self, component): self._component = component def operation(self): return self._component.operation() # 定义具体装饰器类 class ConcreteDecoratorA(Decorator): def operation(self): return f"ConcreteDecoratorA: {self.operation_added()} " + self._component.operation() def operation_added(self): return "Added Behavior A" # 定义具体装饰器类 class ConcreteDecoratorB(Decorator): def operation(self): return f"ConcreteDecoratorB: {self.operation_added()} " + self._component.operation() def operation_added(self): return "Added Behavior B" # 客户端代码 def client_code(component): print(component.operation()) if __name__ == "__main__": # 创建具体组件对象 component = ConcreteComponent() print("Client: I get a simple component:") client_code(component) print("\n") # 用具体装饰器A装饰具体组件 decorator_a = ConcreteDecoratorA(component) print("Client: Now I get a decorated component:") client_code(decorator_a) print("\n") # 用具体装饰器B装饰具体组件 decorator_b = ConcreteDecoratorB(component) print("Client: Now I get another decorated component:") client_code(decorator_b) print("\n") # 用具体装饰器B装饰已经被装饰过的组件 decorator_b = ConcreteDecoratorB(decorator_a) print("Client: Now I get a decorated component with both A and B behaviors:") client_code(decorator_b)
优缺点和适用场景✨
优点 缺点 适用场景 1. 装饰器模式允许向对象动态添加新的功能,而无需更改其接口或现有代码。 1. 可能会导致类的数量增加,增加代码复杂度。 1. 当需要在不修改现有对象代码的情况下,动态地添加、移除或更改对象的行为时。 2. 可以通过组合不同的装饰器类来创建具有不同功能组合的对象。 2. 如果装饰器层级很深,可能会变得难以管理和理解。 2. 当希望在运行时动态地添加功能而不影响其他对象时。 3. 装饰器模式遵循开放-封闭原则,使得代码更容易扩展。 4. 可以避免使用子类来扩展对象功能,从而减少了类的数量。 5. 允许将多个装饰器叠加在一起,灵活组合功能。
4、门面模式(外观模式)🎭 🎭 🎭
就是再对外封装一层更加精致的接口,让系统调用那些更加的方便
门面模式(Facade Pattern)是一种结构型设计模式,提供了一个统一的接口,用于访问子系统中的一群接口。通过定义一个高层接口,简化了客户端与子系统之间的交互。
代码示例👋
# 子系统组件 class CPU: def freeze(self): print("CPU冻结") def jump(self, position): print(f"CPU跳转到地址 {position}") def execute(self): print("CPU执行指令") class Memory: def load(self, position, data): print(f"内存加载数据 '{data}' 到地址 {position}") class HardDrive: def read(self, lba, size): return f"从磁盘读取了 {size} 字节的数据" # 门面类 class ComputerFacade: def __init__(self): self.cpu = CPU() self.memory = Memory() self.hard_drive = HardDrive() def start(self): self.cpu.freeze() self.memory.load("BOOT_ADDRESS", self.hard_drive.read(BOOT_SECTOR, SECTOR_SIZE)) self.cpu.jump("BOOT_ADDRESS") self.cpu.execute() # 客户端代码 BOOT_SECTOR = 100 SECTOR_SIZE = 1024 computer_facade = ComputerFacade() computer_facade.start()
在这个示例中,ComputerFacade 作为门面类,封装了子系统组件 CPU、Memory 和 HardDrive 的复杂操作,提供了简化的接口 start 给客户端使用。客户端只需要与 ComputerFacade 交互,无需直接与子系统组件打交道,从而简化了客户端的操作。
优缺点和适用场景✨
优点 缺点 适用场景 1. 简化了客户端与子系统之间的交互,降低了耦合度。 1. 如果系统变得复杂,门面类可能会变得庞大。 1. 当需要简化复杂系统的接口并提供更简单的接口给客户端时。 2. 隐藏了子系统的复杂性,使得客户端更容易使用。 2. 不符合开闭原则,对修改关闭但对扩展开放。 2. 当希望将复杂系统划分为更小的模块并提供统一接口时。 3. 降低了客户端与子系统之间的依赖关系,提高了系统的灵活性。 4. 便于实现子系统的重构和更换,不影响客户端。
5、组合模式 🧩 🧩 🧩
将对象组合成树形结构以表示“”部分-整体“”的层次结构
适用场景:创建树形关系的对象(包含父子关系那种)【使用递归套用】
一般要创建3个东西:抽象组件、叶子组件、复合组件
将对象组合成树形结构以表示“部分-整体”的层次结构(特别是结构是递归的),
组合模式使得用户对单个对象和组合对象的使用具有一致性。
优点是定义了包含基本对象和组合对象的层次结构;
简化客户端代码,客户端可以一致地使用组合对象和单个对象;
更加容易增加新类型的组件。
代码示例👋
from abc import ABC, abstractmethod # 组件接口 class Component(ABC): @abstractmethod def operation(self): pass # 叶子节点 class Leaf(Component): def __init__(self, name): self.name = name def operation(self): print(f"Leaf {self.name} 执行操作") # 组合节点 class Composite(Component): def __init__(self, name): self.name = name self.children = [] def add(self, component): self.children.append(component) def remove(self, component): self.children.remove(component) def operation(self): print(f"Composite {self.name} 执行操作") for child in self.children: child.operation() # 客户端代码 leaf1 = Leaf("1") leaf2 = Leaf("2") leaf3 = Leaf("3") composite1 = Composite("A") composite1.add(leaf1) composite1.add(leaf2) composite2 = Composite("B") composite2.add(leaf3) root = Composite("Root") root.add(composite1) root.add(composite2) root.operation()
在这个示例中,Component 定义了组件接口,Leaf 表示叶子节点,Composite 表示组合节点。客户端通过组合不同的叶子节点和组合节点来构建树形结构,然后调用根节点的 operation 方法,整个树形结构的操作被递归执行。
优缺点和适用场景✨
优点 缺点 适用场景 1. 简化客户端代码,统一了对象的处理方式。 1. 可能会使设计过度一般化,增加系统的复杂性。 1. 当需要构建具有层次结构的对象或树形结构,并希望统一处理叶子节点和组合节点时。 2. 客户端可以一致地对待单个对象和组合对象。 2. 叶子节点和组合节点在接口上可能存在差异。 2. 当希望用户能够递归地访问和处理组件集合,而无需关心组件的具体类型时。 3. 支持递归组合,可以灵活地构建复杂的树形结构。 3. 可能对单个组件和整个组合结构的处理效率不同。
6、享元(蝇量)模式
享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享尽可能多的对象来最小化内存或计算开销。它通过共享大量细粒度对象的方式来减少应用程序所需的内存使用量或计算开销。
通过共享技术来有效的支持大量细粒度的对象。
代码示例👋
class Flyweight: def __init__(self, shared_state): self._shared_state = shared_state def operation(self, unique_state): s = self._shared_state u = unique_state print(f"共享状态: {s}, 唯一状态: {u}") class FlyweightFactory: def __init__(self): self._flyweights = {} def get_flyweight(self, shared_state): if shared_state not in self._flyweights: self._flyweights[shared_state] = Flyweight(shared_state) return self._flyweights[shared_state] def main(): factory = FlyweightFactory() flyweight1 = factory.get_flyweight("共享状态1") flyweight1.operation("唯一状态1") flyweight2 = factory.get_flyweight("共享状态2") flyweight2.operation("唯一状态2") flyweight3 = factory.get_flyweight("共享状态1") # 已经存在的共享状态 flyweight3.operation("唯一状态3") if __name__ == "__main__": main()
在这个示例中,Flyweight 类表示享元对象,其中包含一个共享状态 _shared_state。FlyweightFactory 类负责创建和管理享元对象,并根据需要返回现有的享元对象或创建新的享元对象。在 main 函数中,我们演示了如何使用享元模式创建享元对象,并传入不同的唯一状态进行操作。
注意,当请求相同的共享状态时,FlyweightFactory 将返回现有的享元对象,而不是创建新的对象。
优缺点和适用场景✨
优点 缺点 适用场景 1. 减少内存使用:通过共享相同的对象实例,减少内存占用。 1. 增加了系统的复杂性:需要对对象进行状态的划分,增加了设计难度。 1. 系统中存在大量相似对象,可以共享内部状态的情况。 2. 提高性能:减少对象的数量,提高系统的运行效率。 2. 不适用于所有情况:只有部分对象状态可以被共享时才适用。 2. 对象的状态可以划分为内部状态和外部状态,内部状态相对稳定。 3. 简化对象管理:减少对象的创建和销毁,简化系统的维护。 3. 应用程序需要频繁创建和销毁大量相似对象的情况。
7、桥梁模式(桥模式、桥接模式) 🌉 🌉 🌉
将抽象部分和它的实现部分分离,使它们都可以独立的变化。将抽象与实现解耦
当一个类里面有一些功能,其他类也可能用到,可以把其中的功能抽离出单独的一个类
然后再使用桥接器,把这两者融合到一起使用,就达到了我们最初一个类里面也可以实现的功能
桥模式是将一个事物的两个维度分离,使其都可以独立地变化。
当事物有两个维度的表现,两个维度都可能扩展时使用。
优点是:抽象和实现相分离,扩展能力强。
代码示例👋
# 实现部分接口 class DrawingAPI: def draw_circle(self, x, y, radius): pass # 具体实现部分A class DrawingAPI1(DrawingAPI): def draw_circle(self, x, y, radius): print(f"API1.draw_circle at {x}:{y} radius {radius}") # 具体实现部分B class DrawingAPI2(DrawingAPI): def draw_circle(self, x, y, radius): print(f"API2.draw_circle at {x}:{y} radius {radius}") # 抽象部分 class Shape: def __init__(self, drawing_api): self._drawing_api = drawing_api def draw(self): pass def resize(self, radius): pass # 具体抽象部分 class CircleShape(Shape): def __init__(self, x, y, radius, drawing_api): super().__init__(drawing_api) self._x = x self._y = y self._radius = radius def draw(self): self._drawing_api.draw_circle(self._x, self._y, self._radius) def resize(self, radius): self._radius = radius def main(): shapes = [ CircleShape(1, 2, 3, DrawingAPI1()), CircleShape(5, 7, 11, DrawingAPI2()) ] for shape in shapes: shape.draw() if __name__ == "__main__": main()
在这个示例中,
DrawingAPI
是实现部分的接口,它定义了绘制圆形的方法。DrawingAPI1
和DrawingAPI2
是具体的实现部分,分别实现了绘制圆形的方法。
Shape
是抽象部分的基类,它包含一个对实现部分的引用。CircleShape
是具体的抽象部分,它继承自Shape
并实现了绘制和调整圆形的方法。在
main
函数中,我们创建了两个不同的圆形,每个圆形使用不同的实现部分进行绘制。这样,我们可以独立地变化抽象部分和实现部分,而不会影响到彼此。
优缺点和适用场景✨
优点 缺点 适用场景 分离抽象和实现,易于扩展和维护 增加了类和对象的数量 当存在多个实现细节时,且每种细节可能都需要变化时 提高了系统的灵活性 增加了系统的复杂度 当希望在抽象和实现部分之间建立稳定的关联时 可以独立地改变抽象部分和实现部分 需要正确识别抽象和实现之间的关系 当需要实现跨多个平台的图形用户界面时 可以更容易地对系统进行组件化和复用 容易造成类的数量爆炸 当希望避免静态继承的方式,而采用对象组合的方式时
六、行为类设计模式 (11种)
1、策略模式
策略模式——适用在干同一件事时,但是对于这件事,在不同情况下,会使用不同方法(也就是采用不同的策略)
定义一系列算法,把它们封装起来,并且使它们可以相互替换。
代码示例👋
# 定义策略接口 class PaymentStrategy: def pay(self, amount): pass # 具体策略类:支付宝支付 class AlipayStrategy(PaymentStrategy): def pay(self, amount): print(f"支付宝支付:{amount} 元") # 具体策略类:微信支付 class WechatPayStrategy(PaymentStrategy): def pay(self, amount): print(f"微信支付:{amount} 元") # 上下文类 class PaymentContext: def __init__(self, payment_strategy): self._payment_strategy = payment_strategy def pay_amount(self, amount): self._payment_strategy.pay(amount) def main(): # 创建具体策略对象 alipay_strategy = AlipayStrategy() wechat_strategy = WechatPayStrategy() # 使用支付宝支付 payment_context = PaymentContext(alipay_strategy) payment_context.pay_amount(100) # 使用微信支付 payment_context = PaymentContext(wechat_strategy) payment_context.pay_amount(200) if __name__ == "__main__": main()
优缺点和适用场景✨
优点 缺点 适用场景 算法可以在运行时独立地切换或替换 客户端必须知道所有的策略,并选择合适的策略 当需要在运行时选择算法的行为 降低了类之间的耦合度 增加了对象的数目 当有许多相关的类只有行为或算法不同 提供了开闭原则的支持 策略类的数量增加可能会导致类爆炸 当一个类有多种变化,且每种变化有不同的行为时 提供了代码复用的机会 客户端需要了解各种策略的差异 当某个类需要在多种情况下使用不同的行为
2、责任链模式
说白了,对象的关系,就像一个链表
如果一个员工请求请假,首先请求会被发送给部长。
如果请假天数在部长的权限范围内(小于等于3天),部长可以直接批准请求,责任链到此结束。
如果请假天数超出了部长的权限范围,则请求会被传递给经理(经理同样会检查请假天数)。如果在经理的权限范围内(小于等于7天),经理可以批准请求,责任链到此结束。
如果请假天数超出了经理的权限范围,则请求会被传递给老板如果在老板的权限范围内(小于等于20天),老板可以批准请求,责任链到此结束。
如果请假天数超过了老板的权限范围,则老板会拒绝请求,责任链到此结束,并要求员工辞职。就是这么一个逻辑!!
责任链模式的内容:
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链并沿着这条链传递该请求,直到有一个对象处理它为止。责任链的角色有抽象处理者、具体处理者和客户端。
代码示例👋
class LeaveRequest: def __init__(self, name, days): self.name = name self.days = days class Approver: def __init__(self, name, authority): self.name = name self.authority = authority self.next_approver = None def set_next_approver(self, next_approver): self.next_approver = next_approver def process_request(self, request): pass class Minister(Approver): def __init__(self, name, authority): super().__init__(name, authority) def process_request(self, request): if request.days <= self.authority: print(f"{self.name}批准了{request.name}的请假申请,共{request.days}天。") elif self.next_approver: self.next_approver.process_request(request) else: print(f"请假天数超出{self.name}的权限范围,无法处理。") class Manager(Approver): def __init__(self, name, authority): super().__init__(name, authority) def process_request(self, request): if request.days <= self.authority: print(f"{self.name}批准了{request.name}的请假申请,共{request.days}天。") elif self.next_approver: self.next_approver.process_request(request) else: print(f"请假天数超出{self.name}的权限范围,无法处理。") class Boss(Approver): def __init__(self, name, authority): super().__init__(name, authority) def process_request(self, request): if request.days <= self.authority: print(f"{self.name}批准了{request.name}的请假申请,共{request.days}天。") else: print(f"{self.name}拒绝了{request.name}的请假申请,要求辞职!") def main(): minister = Minister("部长", 3) manager = Manager("经理", 7) boss = Boss("老板", 20) minister.set_next_approver(manager) manager.set_next_approver(boss) requests = [LeaveRequest("员工A", 2), LeaveRequest("员工B", 5), LeaveRequest("员工C", 10)] for request in requests: minister.process_request(request) if __name__ == "__main__": main()
在这个示例中,有三个具体处理者:部长、经理和老板。每个处理者都有一个权限范围,超出权限范围的请求会被传递给下一个处理者。责任链通过
set_next_approver
方法链接了这些处理者,形成了一条链。当有请假请求时,责任链从部长开始处理,逐级传递,直到有一个处理者处理完请求为止。
优缺点和适用场景✨
优点 描述 松耦合 请求发送者和接收者解耦,发送者不需要知道具体的处理者,只需将请求发送到责任链上即可。 灵活性 可动态添加、移除处理者,灵活调整责任链的结构,符合开闭原则。 可扩展性 可以灵活地增加新的处理者,无需修改现有代码,符合单一职责原则。 符合单一职责原则 每个处理者只负责处理自己能处理的请求,符合单一职责原则,易于维护和扩展。
缺点 描述 请求可能无法被处理 如果责任链没有正确配置或者没有合适的处理者处理请求,请求可能会被漏掉而无法处理。 性能影响 需要遍历整个责任链,直到找到合适的处理者,可能会影响性能,尤其在责任链很长的情况下。
适用场景 描述 多个对象可以处理同一请求 当请求需要被多个对象处理,并且处理者不确定时,可以使用责任链模式。 请求发送者和接收者解耦 当请求发送者不需要知道具体的接收者时,可以使用责任链模式,实现请求发送者和接收者的解耦。 动态指定处理流程 当处理流程需要动态调整或者不确定时,可以使用责任链模式,灵活配置责任链的处理流程。
3、命令模式
将命令请求封装为一个对象,使得可以用不同的请 求来进行参数化。
代码示例👋
from abc import ABC, abstractmethod # 命令接口 class Command(ABC): @abstractmethod def execute(self): pass # 具体命令类1 class LightOnCommand(Command): def __init__(self, light): self.light = light def execute(self): self.light.turn_on() # 具体命令类2 class LightOffCommand(Command): def __init__(self, light): self.light = light def execute(self): self.light.turn_off() # 接收者类 class Light: def turn_on(self): print("灯已打开") def turn_off(self): print("灯已关闭") # 请求者类 class RemoteControl: def __init__(self): self.command = None def set_command(self, command): self.command = command def press_button(self): if self.command: self.command.execute() # 客户端 if __name__ == "__main__": # 创建接收者对象 light = Light() # 创建具体命令对象 light_on = LightOnCommand(light) light_off = LightOffCommand(light) # 创建请求者对象 remote_control = RemoteControl() # 设置命令 remote_control.set_command(light_on) # 请求者发出请求 remote_control.press_button() # 输出:灯已打开 # 设置另一个命令 remote_control.set_command(light_off) # 请求者再次发出请求 remote_control.press_button() # 输出:灯已关闭
优缺点和适用场景✨
优点 描述 解耦 将请求发送者和接收者解耦,发送者只需知道如何发送命令,而不需要知道具体的接收者和执行者。 可扩展性 可以方便地新增命令类,无需修改现有的代码,符合开闭原则。 撤销和重做 可以实现命令的撤销和重做,通过保存命令历史记录,可以逆向执行命令或者重新执行命令。 容易组合命令 可以将多个命令组合成一个复合命令,实现更复杂的操作,而不需要改变现有的命令类。 记录请求 可以将命令对象存储起来,作为日志或者队列的一部分,用于记录、审计、延迟执行等。
缺点 描述 类膨胀 每个命令都需要设计一个具体的命令类,可能导致类的数量过多,增加系统的复杂度。 过多的细节 每个具体命令都需要单独实现,可能会导致系统中存在大量的细小的命令类,增加维护成本。 可能引入额外的延迟 每个命令都需要封装成对象,可能会引入额外的执行开销,导致一定程度上的性能损失。 命令模式适用于请求发送者和接收者需要解耦、需要支持撤销和重做操作、需要支持批处理请求以及动态命令参数等场景。
适用场景 描述 请求发送者和接收者解耦 当请求发送者不需要知道具体的接收者时,可以使用命令模式,实现请求发送者和接收者的解耦。 撤销和重做操作 当需要实现撤销和重做操作时,可以使用命令模式,通过保存命令历史记录实现撤销和重做功能。 批处理请求 当需要将请求排队、记录请求日志、支持撤销和重做等功能时,可以使用命令模式。 动态命令参数 当命令的参数需要根据不同情况动态确定时,可以使用命令模式,将命令封装成对象。
4、中介者模式
中介者模式:用一个中介对象来封装一系列的对象交互。
当两个对象,或多个对象需要相互交互(传递消息的时候)
可以创建一个中介对象来负责他们之间的消息传递
在软件架构中,中介者模式的思想可以通过引入各种中间件来实现。这些中间件可以在不同的层次和场景中发挥作用,促进组件之间的通信和协作,从而实现解耦、提高系统灵活性和可维护性等目标。
比如缓存中间件:缓存中间件用于存储和管理系统中的数据缓存,以提高数据访问速度和性能。常见的缓存中间件包括 Redis、Memcached 等
代码示例👋
from abc import ABC, abstractmethod # 中介者接口 class Mediator(ABC): @abstractmethod def send(self, message, colleague): pass # 具体中介者类 class ConcreteMediator(Mediator): def __init__(self, colleague1, colleague2): self.colleague1 = colleague1 self.colleague2 = colleague2 def send(self, message, colleague): if colleague == self.colleague1: self.colleague2.receive(message) else: self.colleague1.receive(message) # 同事类接口 class Colleague(ABC): def __init__(self, mediator): self.mediator = mediator @abstractmethod def send(self, message): pass @abstractmethod def receive(self, message): pass # 具体同事类1 class ConcreteColleague1(Colleague): def send(self, message): print("同事1发送消息:", message) self.mediator.send(message, self) def receive(self, message): print("同事1接收消息:", message) # 具体同事类2 class ConcreteColleague2(Colleague): def send(self, message): print("同事2发送消息:", message) self.mediator.send(message, self) def receive(self, message): print("同事2接收消息:", message) # 客户端 if __name__ == "__main__": # 创建中介者对象 mediator = ConcreteMediator(None, None) # 创建具体同事对象,并指定中介者 colleague1 = ConcreteColleague1(mediator) colleague2 = ConcreteColleague2(mediator) # 设置中介者的同事对象 mediator.colleague1 = colleague1 mediator.colleague2 = colleague2 # 同事1发送消息给同事2 colleague1.send("Hello, colleague2!") # 输出:同事1发送消息: Hello, colleague2! 同事2接收消息: Hello, colleague2! # 同事2发送消息给同事1 colleague2.send("Hi, colleague1!") # 输出:同事2发送消息: Hi, colleague1! 同事1接收消息: Hi, colleague1!
在这个示例中,Mediator是中介者接口,定义了中介者对象的发送方法。ConcreteMediator是具体的中介者类,实现了中介者接口的发送方法,并维护了各个同事对象之间的关系。Colleague是同事类的接口,定义了同事对象的发送和接收方法。ConcreteColleague1和ConcreteColleague2是具体的同事类,实现了同事接口的发送和接收方法,并通过中介者对象进行通信。
优缺点和适用场景✨
优点 缺点 适用场景 1. 解耦合:将系统中的各个组件解耦,使它们之间的通信通过中介者进行,减少了组件之间的直接依赖关系。 1. 中介者过于庞大:随着系统的增长,中介者可能会变得庞大复杂,难以维护。 1. 多对多关系:系统中存在复杂的多对多关系,组件之间的交互复杂且频繁。 2. 集中控制:中介者可以集中管理和控制系统中的各个组件,通过中介者可以更容易地实现系统级别的功能和协作。 2. 单点故障:中介者成为系统中的单点故障,一旦中介者出现问题,可能导致整个系统的崩溃。 2. 分布式系统:系统中的各个组件分布在不同的节点上,需要通过中介者进行统一的通信和协调。 3. 可扩展性:由于组件之间的通信通过中介者进行,因此添加新的组件或调整现有组件的行为相对容易。 3. 性能损失:中介者模式可能会引入额外的性能开销,因为所有的通信都要经过中介者进行转发。 3. 复杂的业务逻辑:系统中存在复杂的业务逻辑,各个组件之间的交互规则多变且不确定。 4. 灵活性:中介者模式可以灵活地调整系统中各个组件之间的交互方式,从而更好地适应需求变化。 4. 过度集中:过度使用中介者模式可能会导致系统过度集中,使得系统结构变得僵化和难以维护。 4. UI 组件交互:例如图形用户界面(GUI)应用程序中的多个组件之间的交互和通信。 5. 多模块系统:系统由多个模块组成,模块之间需要频繁地进行通信和协作,但又不希望直接耦合在一起。
5、模板模式
定义一个算法结构,而将一些步骤延迟到子类实现。
内容:定义一个操作中的算法骨架,将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。使用模板方法,需要用到两种角色,分别是抽象类和具体类。抽象类的作用是是定义抽象类(钩子操作),实现一个模板方法作为算法的骨架。具体类的作用实现原子操作。
代码示例👋
from abc import ABC, abstractmethod # 抽象基类定义了算法的框架 class GameCharacter(ABC): def play(self): self.initialize() self.attack() self.finish() @abstractmethod def initialize(self): pass @abstractmethod def attack(self): pass @abstractmethod def finish(self): pass # 具体子类实现了具体的算法步骤 class Warrior(GameCharacter): def initialize(self): print("Warrior is preparing for battle.") def attack(self): print("Warrior is attacking with a sword.") def finish(self): print("Warrior is resting after battle.") class Mage(GameCharacter): def initialize(self): print("Mage is gathering magical energy.") def attack(self): print("Mage is casting a fireball spell.") def finish(self): print("Mage is meditating after casting spells.") # 客户端代码 if __name__ == "__main__": warrior = Warrior() mage = Mage() print("Warrior's turn:") warrior.play() print("\nMage's turn:") mage.play()
在这个示例中,
GameCharacter
是抽象基类,定义了游戏角色的通用流程,包括initialize()
、attack()
和finish()
方法。Warrior
和Mage
类是具体子类,分别实现了这三个方法来完成战士和法师的特定行为。当客户端调用
play()
方法时,每个角色都会依次执行初始化、攻击和结束的步骤,但每个角色的具体行为是不同的。
优缺点和适用场景✨
优点 缺点 适用场景 1. 提高代码复用性:模板模式将相同的算法或流程封装在父类中,子类只需实现特定的步骤,可以提高代码的复用性。 1. 灵活性受限:模板模式在一定程度上限制了子类的灵活性,因为子类必须遵循父类定义的算法或流程。 1. 定义一组标准化的算法或流程:例如,一个类库中的多个类需要实现相似的操作,但每个类的具体实现略有不同。 2. 提高扩展性:通过模板模式,可以方便地增加新的具体子类来扩展系统的功能,而不需要修改现有的代码。 2. 复杂度增加:当系统中的算法或流程变得复杂时,模板模式可能会导致父类变得庞大复杂,难以维护和理解。 2. 框架设计:例如,框架中提供了一些通用的算法或流程,但允许用户根据自己的需求定制部分步骤,以实现定制化的功能。 3. 提高可维护性:模板模式将公共的部分提取到父类中,使得系统的结构更清晰,易于理解和维护。 3. 不适用于每个场景:并不是所有的算法或流程都适合使用模板模式,有时候可能会引入不必要的复杂性。 3. 设计固定的流程:例如,某些业务流程中包含了固定的步骤和顺序,不同的业务场景只是在某些步骤上有所差异。 4. 提高可定制性:虽然模板模式定义了算法或流程的框架,但允许子类根据需要重写某些步骤,以实现定制化的功能。 4. 增加了继承关系:模板模式使用了继承来实现算法或流程的复用,可能会导致类之间的耦合度增加,影响系统的灵活性。 4. 实现标准化的接口:例如,一个接口中定义了一组标准化的操作流程,不同的实现类只需根据具体需求实现部分具体步骤。 5. 设计具有固定的生命周期:例如,一些对象的生命周期需要遵循特定的流程,包括初始化、操作、清理等步骤。
6、迭代器模式
一种遍历访问聚合对象中各个元素的方法,不暴 露该对象的内部结构。
代码示例👋
from __future__ import annotations from typing import Any, List # 创建一个抽象的迭代器接口 class Iterator: def has_next(self) -> bool: pass def next(self) -> Any: pass # 创建一个具体的迭代器类,用于遍历列表 class ListIterator(Iterator): def __init__(self, collection: List[Any]): self._collection = collection self._index = 0 def has_next(self) -> bool: return self._index < len(self._collection) def next(self) -> Any: if not self.has_next(): raise StopIteration("End of collection reached.") item = self._collection[self._index] self._index += 1 return item # 创建一个聚合对象类,其中包含一个返回迭代器的方法 class Aggregate: def __init__(self): self._data = [] def add_item(self, item: Any): self._data.append(item) def get_iterator(self) -> Iterator: return ListIterator(self._data) # 客户端代码 if __name__ == "__main__": aggregate = Aggregate() aggregate.add_item("Item 1") aggregate.add_item("Item 2") aggregate.add_item("Item 3") iterator = aggregate.get_iterator() print("Iterating through the collection:") while iterator.has_next(): item = iterator.next() print(item)
优缺点和适用场景✨
优点 缺点 适用场景 简化集合遍历:提供了一种统一的方法来遍历聚合对象,简化了集合元素的访问。 增加复杂性:引入了额外的类和接口,增加了代码的复杂性。 数据集合操作频繁:当需要频繁遍历数据集合中的元素,并且希望统一遍历接口时,迭代器模式特别有用。 封装迭代算法:将遍历算法封装在迭代器中,使得客户端无需了解迭代算法的细节。 不适用于简单集合:对于简单的数据结构,使用迭代器模式可能会显得繁琐和不必要。 需要遍历不同类型集合:当需要遍历不同类型的聚合对象,并且希望使用统一的迭代接口时,迭代器模式非常适用。 支持多种遍历方式:可以定义不同的迭代器类来支持不同的遍历方式,如顺序遍历、逆序遍历等。 性能问题:对于某些特定情况下,迭代器模式可能会引入一些性能开销,如频繁创建迭代器对象等。 需要封装遍历算法:当希望封装遍历算法,并且允许客户端通过不同的迭代器类选择不同的遍历方式时,迭代器模式非常适用。
7、访问者模式
访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
代码示例👋
# 定义元素基类 class Element: def accept(self, visitor): pass # 定义具体元素类 class ConcreteElementA(Element): def accept(self, visitor): visitor.visit_concrete_element_a(self) def operation_a(self): return "Operation A on ConcreteElementA" class ConcreteElementB(Element): def accept(self, visitor): visitor.visit_concrete_element_b(self) def operation_b(self): return "Operation B on ConcreteElementB" # 定义访问者基类 class Visitor: def visit_concrete_element_a(self, element_a): pass def visit_concrete_element_b(self, element_b): pass # 定义具体访问者类 class ConcreteVisitor(Visitor): def visit_concrete_element_a(self, element_a): print("Visitor is executing operation on ConcreteElementA:", element_a.operation_a()) def visit_concrete_element_b(self, element_b): print("Visitor is executing operation on ConcreteElementB:", element_b.operation_b()) # 客户端代码 if __name__ == "__main__": elements = [ConcreteElementA(), ConcreteElementB()] visitor = ConcreteVisitor() for element in elements: element.accept(visitor)
在这个示例中,我们定义了两种具体的元素类
ConcreteElementA
和ConcreteElementB
,它们都继承自元素基类Element
,并且实现了accept
方法,用来接受访问者的访问。然后,我们定义了访问者基类Visitor
,其中包含了两个访问具体元素的方法visit_concrete_element_a
和visit_concrete_element_b
。最后,我们定义了一个具体的访问者类ConcreteVisitor
,它继承自访问者基类,并且实现了对两种具体元素的访问操作。在客户端代码中,我们创建了两个具体元素对象,并且让访问者对象访问这些元素。
优缺点和适用场景✨
优点 缺点 适用场景 分离数据结构与算法:访问者模式将数据结构与算法分离,使得可以在不修改数据结构的情况下增加新的操作。 增加新元素困难:如果数据结构经常变化或者需要添加新的操作,可能需要修改所有的访问者类,导致维护困难。 数据结构稳定:当数据结构相对稳定,但是需要经常添加新的操作时,访问者模式非常适用。 新增操作方便:添加新的操作只需要增加新的访问者类,不需要修改已有的数据结构,符合开闭原则。 数据结构复杂:如果数据结构过于复杂,可能需要创建大量的访问者类,增加了系统的复杂度。 数据结构与操作分离:当数据结构相对稳定,但是需要经常添加新的操作时,访问者模式非常适用。 增加新的元素易于扩展:如果需要在已有的数据结构上增加新的元素,只需要修改访问者类即可,而不需要修改已有的操作。 违反单一职责原则:访问者模式可能会导致访问者类承担过多的责任,违反了单一职责原则。 操作与数据结构解耦:当希望将操作与数据结构解耦,以便灵活地添加新的操作时,访问者模式非常适用。
111
8、观察者模式(“发布-订阅”模式)
观察者模式应用比较广泛,又被称为“发布-订阅”模式。它用来定义对象间一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都得到通知并被自动更新。观察者模式的角色有:抽象主题、具体主题(发布者)、抽象观察者和具体观察者(订阅者)。
讲者与听众模式
听众会自动被演讲者通知消息(也就是说听众会被主动推送消息)
代码示例👋
# 定义观察者接口 class Observer: def update(self, message): pass # 定义具体的观察者类 class ConcreteObserver(Observer): def __init__(self, name): self.name = name def update(self, message): print(f"{self.name} received message: {message}") # 定义主题(发布者)类 class Subject: def __init__(self): self.observers = [] def attach(self, observer): self.observers.append(observer) def detach(self, observer): self.observers.remove(observer) def notify(self, message): for observer in self.observers: observer.update(message) # 客户端代码 if __name__ == "__main__": # 创建具体观察者对象 observer1 = ConcreteObserver("Observer 1") observer2 = ConcreteObserver("Observer 2") observer3 = ConcreteObserver("Observer 3") # 创建主题对象 subject = Subject() # 添加观察者到主题 subject.attach(observer1) subject.attach(observer2) subject.attach(observer3) # 发布消息 subject.notify("Hello, observers!") # 移除一个观察者 subject.detach(observer2) # 再次发布消息 subject.notify("Another message")
在这个示例中,我们定义了一个观察者接口
Observer
,其中包含一个update
方法用来接收消息。然后,我们定义了一个具体的观察者类ConcreteObserver
,它实现了update
方法来处理接收到的消息。接着,我们定义了主题(发布者)类Subject
,它维护了一个观察者列表,并提供了attach
、detach
和notify
方法,分别用来添加、移除观察者以及通知观察者。在客户端代码中,我们创建了几个具体观察者对象,并将它们添加到主题中,然后发布了一条消息。同时,我们也演示了如何移除一个观察者并再次发布消息。
优缺点和适用场景✨
优点 缺点 适用场景 1. 降低模块之间的耦合度 1. 如果一个主题有很多观察者,通知开销可能较大 1. 当一个对象的改变需要同时通知其他多个对象时 2. 支持广播通信 2. 如果观察者之间有循环依赖关系,可能导致系统崩溃 2. 当一个对象的状态发生变化时,希望通知其他多个对象做出响应 3. 主题和观察者可以独立地扩展 3. 如果观察者太多或者通知过于频繁,可能导致性能问题 3. 当一个对象有两个或多个方面的变化需要通知其他对象 4. 支持动态关联关系 4. 可能导致系统中产生大量的细粒度对象 4. 当一个抽象模型有两个方面,其中一个依赖于另一个方面。封装这个模型就会导致需要同步更新其他对象,而不是依赖于具体的类。
9、解释器模式 🤖 🤖 🤖
给定一个语言,定义它的文法的一种表示,并定义一个解释器。
解释器模式通常用于处理语言解释,比如编译器、正则表达式引擎等。
代码示例👋
# 抽象表达式类 class Expression: def interpret(self, context): pass # 终结符表达式类(数字) class NumberExpression(Expression): def __init__(self, value): self.value = value def interpret(self, context): return self.value # 非终结符表达式类(加法) class AddExpression(Expression): def __init__(self, left, right): self.left = left self.right = right def interpret(self, context): return self.left.interpret(context) + self.right.interpret(context) # 非终结符表达式类(减法) class SubtractExpression(Expression): def __init__(self, left, right): self.left = left self.right = right def interpret(self, context): return self.left.interpret(context) - self.right.interpret(context) # 上下文类(用于存储解释器的上下文信息,这里简单起见只有数字) class Context: def __init__(self, data): self.data = data # 客户端代码 if __name__ == "__main__": # 构建解释器树:3 + (4 - 2) expression_tree = AddExpression(NumberExpression(3), SubtractExpression(NumberExpression(4), NumberExpression(2))) # 创建上下文 context = Context({}) # 解释执行表达式 result = expression_tree.interpret(context) # 输出结果 print("Result:", result) # 应该输出 5
在这个示例中,我们定义了抽象表达式类
Expression
,以及终结符表达式类NumberExpression
(用于表示数字)和非终结符表达式类AddExpression
和SubtractExpression
(用于表示加法和减法)。通过构建表达式树,然后利用上下文信息进行解释执行,最后得到结果。
优缺点和适用场景✨
优点 缺点 适用场景 1. 灵活性高,易于扩展 1. 可能会导致类的数量急剧增加 1. 当语言的语法规则相对简单,并且需要频繁添加新规则时 2. 易于实现语法树 2. 解释器模式对于复杂的语法可能不够灵活 2. 当需要解释一种语言的语法,或者需要实现一种简单的DSL时 3. 增加新的解释规则较为方便 3. 可能会导致性能问题 3. 当存在一种语言或者规则需要解释执行的情况下 4. 可以较容易地实现某些特定的功能
10、备忘录模式 📝 📝 📝
备忘录模式:在不破坏封装的前提下,保持对象的内部状态
代码示例👋
# 备忘录类 class EditorMemento: def __init__(self, content): self._content = content def get_content(self): return self._content # 编辑器类 class TextEditor: def __init__(self): self._content = "" def type(self, text): self._content += text def get_content(self): return self._content def create_memento(self): return EditorMemento(self._content) def restore(self, memento): self._content = memento.get_content() # 客户端代码 if __name__ == "__main__": # 创建文本编辑器 editor = TextEditor() # 输入文本 editor.type("Hello, ") print("Current Content:", editor.get_content()) # 输出: Hello, # 创建备忘录 memento = editor.create_memento() # 继续输入文本 editor.type("world!") print("Current Content:", editor.get_content()) # 输出: Hello, world! # 撤销操作,恢复到之前的状态 editor.restore(memento) print("Restored Content:", editor.get_content()) # 输出: Hello,
在这个示例中,我们定义了备忘录类
EditorMemento
和编辑器类TextEditor
。编辑器类具有输入文本、获取内容、创建备忘录和恢复备忘录的功能。客户端代码可以利用这些功能来实现文本编辑器的撤销操作。
优缺点和适用场景✨
优点 缺点 适用场景 1. 封装性好:备忘录模式将备忘录的创建、存储和恢复功能封装在备忘录类中,提高了代码的模块化和可维护性。 1. 资源消耗大:如果需要频繁创建和恢复备忘录,可能会消耗大量的内存和处理时间。 1. 撤销功能:当需要实现撤销操作时,备忘录模式可以轻松地记录对象的历史状态,并在需要时进行恢复。 2. 灵活性高:备忘录模式可以灵活地管理对象的状态,允许在不破坏封装性的前提下进行状态的存储和恢复。 2. 对性能有影响:如果备忘录对象过多或过大,可能会对系统性能产生影响,特别是在需要频繁操作备忘录时。 2. 快照功能:当需要在系统中实现快照功能,即保存对象的当前状态以便将来恢复时,备忘录模式是一个不错的选择。 3. 事务回滚:在需要实现事务回滚的系统中,备忘录模式可以帮助记录事务之前的状态,并在回滚时进行恢复。 4. 缓存管理:在需要实现缓存管理的系统中,备忘录模式可以用于记录对象的历史状态,以便在需要时进行快速恢复,提高系统性能。
11、状态模式 🔰 🔰 🔰
状态模式:允许一个对象在其对象内部状态改变时改变它的行为。
代码示例👋
下面是一个使用状态模式的简单示例,假设我们有一个电梯系统,电梯有三种状态:关闭状态、开启状态和运行状态。我们将使用状态模式来管理电梯的状态,并实现相应的行为。
# 状态接口 class State: def handle(self): pass # 关闭状态 class ClosedState(State): def handle(self): print("电梯处于关闭状态") # 开启状态 class OpenedState(State): def handle(self): print("电梯处于开启状态") # 运行状态 class RunningState(State): def handle(self): print("电梯处于运行状态") # 环境类,电梯 class Elevator: def __init__(self): self.state = ClosedState() # 初始状态为关闭状态 def set_state(self, state): self.state = state def open_door(self): self.state.handle() def close_door(self): self.state.handle() def run(self): self.state.handle() # 测试 if __name__ == "__main__": elevator = Elevator() elevator.open_door() # 输出:电梯处于开启状态 elevator.close_door() # 输出:电梯处于关闭状态 elevator.run() # 输出:电梯处于运行状态
在这个示例中,我们定义了三种状态类:
ClosedState
、OpenedState
和RunningState
,它们都实现了State
接口。然后我们有一个Elevator
类作为环境类,它有一个状态属性,并且可以根据不同的状态来执行不同的行为。在测试中,我们创建了一个电梯对象,并依次调用了打开门、关门和运行这些方法,根据当前状态的不同,输出了相应的信息。
优缺点和适用场景✨
优点 缺点 适用场景 1. 增强可维护性:状态模式将每种状态封装成一个类,使得代码结构更清晰,易于理解和维护。 1. 类数量增加:每种状态都需要一个类来表示,可能会导致类的数量增加,使得系统变得复杂。 1. 对象的状态转换:当对象的状态可以根据其内部状态的改变而改变,并且对象在不同状态下具有不同的行为时,适合使用状态模式。 2. 增强扩展性:由于每种状态都被封装成一个类,因此可以很容易地添加新的状态,而不会影响到其他已有的状态类。 2. 状态切换逻辑复杂:如果对象的状态转换逻辑比较复杂,可能会导致状态类之间的耦合性增加,使得系统难以维护和扩展。 2. 对象的行为取决于其状态:当对象的行为取决于其状态,并且对象需要在运行时根据状态改变行为时,适合使用状态模式。 3. 减少条件语句:状态模式通过将状态转换逻辑封装到状态类中,可以减少条件语句的使用,提高代码的可读性和可维护性。 3. 状态类的创建:如果状态类的创建过多或过于复杂,可能会增加系统的开销,并且使得系统变得臃肿。 3. 有限状态机:当对象具有有限个状态,并且在不同状态下具有不同的行为时,适合使用状态模式来实现有限状态机。
💟 收集的资源分享
yolov8学习笔记:
408笔记:
Python的设计模式+代码:
🚀 优质视频分享
Python之常用设计模式_哔哩哔哩_bilibiliPython之常用设计模式共计17条视频,包括:1 设计模式与面向对象介绍、2 面向对象设计原则、3 简单工厂模式等,UP主更多精彩视频,请关注UP账号。https://www.bilibili.com/video/BV19541167cn【设计模式inPython】策略模式:不要再用一个类装所有方法啦!_哔哩哔哩_bilibili设计模式指的是对于某种问题的解决方案,在我们设计程序时,使用正确的设计模式将极大提高我们的代码效率。我会在这个视频中介绍设计模式中的策略模式,它常被用于需要实现不同策略的场景。视频文字版已放在 -> https://huccihuang.github.io/posts/Strategy-pattern/IDE: VSCodeColorTheme: MonokaiPro, 视频播放量 6601、弹幕量 13、点赞数 576、投硬币枚数 159、收藏人数 384、转发人数 30, 视频作者 Hucci写代码, 作者简介 分享有用的知识。,相关视频:99%的人都学错了,4分钟重新认识设计模式,设计模式-策略模式(Strategy Pattern)-保姆级攻略,实战项目,混合使用太香了:策略设计模式+工厂模式+模板方法模式,设计模式系列第0集:图例介绍,Python之常用设计模式,30分钟正则表达式教程,带大家感受一下没有GIL的CPython,命令模式:构建“捷径(Shortcuts)”功能的正确方式?,学设计模式前,请务必先看完这个!,C++设计模式入门https://www.bilibili.com/video/BV19E42137eX