设计模式基础
一、封装
- 对象的行为(状态信息)对于外部世界来世来说是私密的
- 客户端不能直接操作来改变对象的内部状态,需要通过特定的成员函数(例如get、set)
- Python中的封装(数据和方法的隐藏)的概念不是隐式的,可以在变量或函数名前面加前缀__,将其可访问性变为私有
二、继承
- 继承表示一个类可以继承父类的(大部分)功能
- 继承被描述为一个重用基类中定义表的功能并允许对原始软件的实现进行独立扩展的选项
- 继承可以利用不同类的对象之间的关系建立层次结构
三、多态
-
有两种类型
对象根据输入参数提供方法的不同实现
不同类型的对象可以使用相同的接口
-
python内置多态
+操作符可以进行整数运算,也可以连接字符串
字符串、元组、列表都可以通过证书索引进行访问
四、抽象
- 提供了一个简单的客户端接口,客户端就可以通过该接口与类的对象进行交互,并能调用这个类中定义的各个方法
- 它将内部的复杂性定义为一个接口,这样客户端就不需要知道内部细节了
五、组合
-
组合是一种将对象或类组合成更复杂的数据结构或软件实现的方法
-
在组合中,一个对象可用于调用其他模块中的成员函数,这样以来,无需通过继承就可以实现基本功能的跨模块使用
-
示例:类A的对象被组合到了类B中
class A(object): def a1(self): print("a1") class B(object): def b(self): print("b") A().a1() objectB = B() objectB.b()
设计原则
一、开放封闭原则
(Open-Closed Principle)
类或者对象及其方法
对于扩展来说,应该是开放的
,但是对于修改来说,应该是封闭的
为了实现所需行为,用户必须通过扩展抽象基类
来创建类的实现,而不是通过修改抽象类
, 有如下优点
- 现有的类不会被修改,因此退化的可能性较小
- 它还助于保持以前代码的向后兼容性
不符合开放封闭原则的例子
假如有一个图形类Shape,有一个求面积的方法calculate_area,最初只支持求矩形面积
class Shape:
def calculate_area(self, width, height):
return width * height
现在需要新增一个求圆的面积
class Shape:
def calculate_area(self, width, height):
return width * height
def calculate_area_circle(self, radius):
return 3.14 * radius * radius
这种修可能导致以下问题
- 违反了开放封闭原则,因为我们直接修改了Shape类来添加新功能
- 可能会影响到已经使用Shape类的其他部分代码,需要对它们进行修改
- 如果我们有大量的图像类需要支持,每次都修改Shape会很麻烦也容易出错
符合开放封闭的例子
我们可以使用抽象基类和继承来扩展现有的功能,而不是直接修改已有的类
先定一个一个抽象基类Shape,包含一个抽象方法calculate_area()
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def calculate_area(self):
pass
现在任何想要支持计算面积的具体图形类都必须继承自Shape并实现calculate_area()方法
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def calculate_area(self):
return 3.14 * self.radius * self.radius
现在我们可以通过创建新的具体图形类来扩展功能,而不需要修改已有的抽象基类或其他图形类,这符合封闭开放原则
二、依赖倒置原则
控制反转原则(Dependency Inversion Principle)
- 高层级的模块不应该依赖于低层级的模块,它们都应该依赖于抽象
- 细节(具体实现)应该依赖于抽象,而不是抽象依赖于细节
- 基本模块和从属模块之间应该提供一个抽象层来耦合
优点
- 消弱了模块之间的紧耦合,因此消除了系统中的复杂性/刚性
- 由于在依赖模块之间有一个明确的抽象层(由钩子或者参数提供),因此便于通过更好的方式处理模块之间的依赖关系
支付服务例子
我们有一个抽象基类 PaymentService
,它定义了一个名为 process_payment()
的抽象方法。然后,我们创建了两个具体的支付服务类 PayPalPaymentService
和 CreditCardPaymentService
,它们分别继承自 PaymentService
并实现了 process_payment()
方法。
接下来,我们有一个名为 Order
的类,它依赖于抽象的 PaymentService
,并在其构造函数中接收一个 payment_service
参数。在 checkout()
方法中,我们通过调用 payment_service.process_payment()
来处理付款。
在示例的最后,我们创建了两个具体的支付服务对象 paypal_payment_service
和 credit_card_payment_service
,并分别将它们作为参数传递给 Order
类的实例。这里就体现了依赖注入的思想,高层模块 Order
依赖于抽象的 PaymentService
,而不是具体的实现类。这样,我们可以轻松地切换不同的支付服务对象,而不需要修改 Order
类的代码。
from abc import ABC, abstractmethod
class PaymentService(ABC):
@abstractmethod
def process_payment(self, amount):
raise NotImplemented
class WechatPaymentService(PaymentService):
def process_payment(self, amount):
print(f"wechat payment {amount} RMB")
class AlipayPaymentService(PaymentService):
def process_payment(self, amount):
print(f"Alipay payment {amount} RMB")
class Order:
def __init__(self, payment_service):
self.payment_service = payment_service
def checkout(self, amount):
self.payment_service.process_payment(amount)
wechat_payment_service = WechatPaymentService()
order1 = Order(wechat_payment_service)
order1.checkout(100)
alipay_payment_service = AlipayPaymentService()
order2 = Order(alipay_payment_service)
order2.checkout(1000)
消息服务例子
通知服务接口(Notification Service Interface):定义了发送通知的方法,例如 send_notification()
。
具体通知服务类(Concrete Notification Service Classes):实现了通知服务接口,每个类负责不同的通知渠道,例如电子邮件通知服务类、短信通知服务类、推送通知服务类等。
核心业务逻辑类(Core Business Logic Class):负责处理业务逻辑,需要发送通知给用户。
您创建了具体的通知服务类 MailNotification
和 SMSNotification
,它们分别实现了通知服务接口中的方法。
from abc import ABC, abstractmethod
class Notification(ABC):
@abstractmethod
def send_notification(self, message):
raise NotImplemented
class MailNotification(Notification):
def send_notification(self, message):
print("邮件通知 " + message)
class SMSNotification(Notification):
def send_notification(self, message):
print("短信通知 " + message)
class Logic:
def __init__(self, notification):
self.notification = notification
def run(self, message):
self.notification.send_notification(message)
mail_notification = MailNotification()
logic1 = Logic(mail_notification)
logic1.run("logic1")
sms_notification = SMSNotification()
logic2 = Logic(sms_notification)
logic2.run("logic2")
日志服务例子
假设我们有一个日志记录器接口 Logger
,它定义了记录日志的方法 log()
。然后我们创建两个具体的日志记录器类 FileLogger
和 DatabaseLogger
,它们分别将日志消息写入文件和数据库。
假设我们有一个 UserService
类负责处理用户相关的业务逻辑,并需要记录一些日志。
UserService
类在构造函数中接收一个 logger
参数,该参数是一个实现了 Logger
接口的对象。这样, UserService
类依赖于抽象的 Logger
接口,而不是具体的实现类。
现在,我们可以创建不同的日志记录器对象,并将它们注入到 UserService
类中进行日志记录。
from abc import ABC, abstractmethod
class Logger(ABC):
@abstractmethod
def log(self, message):
raise NotImplemented
class FileLogger(Logger):
def log(self, message):
print(f"{message} 存入文件")
class DatabaseLogger(Logger):
def log(self, message):
print(f"{message} 存入数据库")
class UserService:
def __init__(self, logger):
self.logger = logger
def register_user(self, username):
self.logger.log(f"User registered {username}")
def delete_user(self, username):
self.logger.log(f"User deleted {username}")
file_logger = FileLogger()
user_service = UserService(file_logger)
user_service.register_user("orris")
user_service.delete_user("orris")
database_logger = DatabaseLogger()
user_service = UserService(database_logger)
user_service.register_user("iris")
user_service.delete_user("iris")
三、接口隔离原则
(Interface Segregation Principle)
强调客户端不应该强迫依赖它们不需要使用的接口。简而言之,接口隔离原则鼓励将大型接口拆分为更小、更具体的接口,以便客户端只需关注自己需要的那部分
from abc import ABC, abstractmethod
# 通用的电子设备接口
class ElectronicDevice(ABC):
@abstractmethod
def power_on(self):
pass
@abstractmethod
def power_off(self):
pass
# 音频设备接口
class AudioDevice(ABC):
@abstractmethod
def play_sound(self):
pass
# 显示设备接口
class DisplayDevice(ABC):
@abstractmethod
def display_image(self):
pass
# 具体的音频设备类
class Speaker(AudioDevice):
def play_sound(self):
print("Playing sound")
# 具体的显示设备类
class Screen(DisplayDevice):
def display_image(self):
print("Displaying image")
# 具体的电子设备类
class Television(ElectronicDevice, AudioDevice, DisplayDevice):
def __init__(self, audio_device, display_device):
self.audio_device = audio_device
self.display_device = display_device
def power_on(self):
print("Powering on the television")
def power_off(self):
print("Powering off the television")
def play_sound(self):
self.audio_device.play_sound()
def display_image(self):
self.display_device.display_image()
# 客户端代码
def operate_device(device):
if isinstance(device, ElectronicDevice):
device.power_on()
if isinstance(device, AudioDevice):
device.play_sound()
if isinstance(device, DisplayDevice):
device.display_image()
device.power_off()
# 使用示例
speaker = Speaker()
screen = Screen()
television = Television(speaker, screen)
print("Television:")
operate_device(television)
四、单一职责原则
(Single Responsibility Principle)
类的职责单一,引起变化的原因单一
反例:负责解析文本文件,并将其内容存储到数据库中
class FileParser:
def __init__(self, file_path):
self.file_path = file_path
def parse_file(self):
# 解析文件的逻辑
pass
def save_to_database(self):
# 将解析的内容保存到数据库的逻辑
pass
# 这种设计违反了单一职责原则,因为一个类应该只负责一个单一的职责。
# 在这种情况下,应该将解析文件和保存到数据库的逻辑拆分为两个独立的类,
# 每个类负责一个单一的职责。
修改
class FileParser:
def __init__(self, file_path):
self.file_path = file_path
def parser_file(self):
# 解析文件的逻辑
pass
class DatabaseServer:
def __init__(self, data):
self.data = data
def save_to_database(self):
# 将数据保存到数据库的逻辑
pass
学生管理系统
class Student:
"""
存储学生的姓名和学生ID。
存储学生所参与的课程列表。
允许学生注册(enroll)课程。
显示学生所参与的课程。
"""
def __init__(self, name, student_id):
self.name = name
self.student_id = student_id
self.courses = []
def enroll_courses(self, course):
self.courses.append(course)
course.add_student(self)
def display_course(self):
for course in self.courses:
print(course.name)
class Course:
"""
存储课程的名称。
存储选修该课程的学生列表。
允许向课程中添加学生。
显示选修该课程的学生。
"""
def __init__(self, name):
self.name = name
self.students = []
def add_student(self, student: Student):
self.students.append(student)
def display_student(self):
for student in self.students:
print(student.name)
student1 = Student("Orris", 1)
student2 = Student("Iris", 2)
course1 = Course("Math")
course2 = Course("History")
student1.enroll_courses(course1)
student2.enroll_courses(course1)
student2.enroll_courses(course2)
student1.display_course()
print()
student2.display_course()
print()
course1.display_student()
print()
course2.display_student()
# Student类负责处理与学生相关的功能,而Course类负责处理与课程相关的功能。
UserManager
反例
class UserManager:
def register_user(self, username, password):
# 用户注册逻辑
pass
def login_user(self, username, password):
# 用户登录逻辑
pass
def validate_user(self, username, password):
# 用户验证逻辑
pass
def reset_password(self, username):
# 重置用户密码逻辑
pass
def delete_user(self, username):
# 删除用户逻辑
pass
# 这个Usermanager类负责多个不同的职责
# 包括用户注册、登录、验证、重置密码和删除用户等
# 这种设计违反了单一职责原则,因为一个类应该只负责一个单一的职责
修复
# 应该将这些功能拆分到不同的类中,
# 每个类专注于一个特定的用户管理功能,以遵守单一职责原则。
class UserManager:
def register_user(self, username, password):
# 用户注册逻辑
pass
def login_user(self, username, password):
# 用户登录逻辑
pass
def validate_user(self, username, password):
# 用户验证逻辑
pass
def reset_password(self, username):
# 重置用户密码逻辑
pass
def delete_user(self, username):
# 删除用户逻辑
pass
class UserRegistration:
def register_user(self, username, password):
# 用户注册逻辑
pass
class UserAuthentication:
def login_user(self, username, password):
# 用户登录逻辑
pass
def validate_user(self, username, password):
# 用户验证逻辑
pass
class UserPasswordManagement:
def reset_password(self, username):
# 重置用户密码逻辑
pass
class UserDeletion:
def delete_user(self, username):
# 删除用户逻辑
pass
# UserRegistration类专注于用户注册的职责
# UserAuthentication类专注于用户登录和验证的职责
# UserPasswordManagement类专注于用户密码管理的职责
# UserDeletion类专注于用户删除的职责
# 用户注册对象
registration = UserRegistration()
# 用户认证对象
authentication = UserAuthentication()
# 密码管理对象
password_management = UserPasswordManagement()
# 删除用户对象
deletion = UserDeletion()
# 用户注册
registration.register_user("Orris", "12345")
# 用户登录
authentication.login_user("Orris", "12345")
# 验证用户
authentication.validate_user("Orirs", "12345")
# 重置密码
password_management.reset_password("Orris")
# 删除用户
deletion.delete_user("Orris")
五、里氏替换原则
(Liskov Substitution Principle)
派生类必须能够完全取代基类
# 假设我们有一个基类 Shape,它有一个计算面积的方法 calculate_area()。
# 然后我们有两个子类 Rectangle(矩形)和 Square(正方形),它们都继承自 Shape。
# 根据里氏替换原则,子类应该能够替换父类并且行为保持一致。
# 在这个例子中,我们可以通过让子类重写父类的方法来实现这一点。
class Shape:
def calculate_area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
class Square(Shape):
def __init__(self, side_length):
self.side_length = side_length
def calculate_area(self):
return self.side_length * self.side_length
def print_area(shape):
area = shape.calculate_area()
print(f"The area is: {area}")
rectangle = Rectangle(4, 5)
square = Square(4)
print_area(rectangle) # 输出:The area is: 20
print_area(square) # 输出:The area is: 16
六、合成/聚合复用原则
(Composition/Aggregation Reuse Principle)
原则: 强调对象之间建立合成或聚合关系,而不是通过继承来实现代码复用
优势:
- 灵活性:合成/聚合关系更加灵活,可以动态地在运行时改变对象的组成,而不需要修改源代码。
- 低耦合:通过合成/聚合关系,对象之间的耦合度较低,它们可以独立地进行修改和扩展,不会相互影响
- 可复用性:通过组合不同的对象,可以创建更多的组合方式,实现更大的代码复用
继承关系限制:
- 静态:继承关系在编译时就已经确定,无法在运行时进行动态调整
- 紧耦合:子类与父类之间存在紧密的耦合,子类的实现依赖于父类的具体细节,修改父类可能影响所有子类
- 层次结构复杂:继承关系可能会导致类之间形成复杂的层次结构,难以管理和理解
汽车和引擎例子
使用继承
可以创建一个名为 “Car” 的类,并让它继承一个 “Engine” 类。这样,每个汽车对象都将包含一个特定类型的引擎。
这种设计有一些限制。要求每个汽车都必须有一个引擎,而且引擎的类型是固定的,如果需要创建一辆没有引擎的汽车,或者要更换不同类型的引擎,就会很困难
class Engine:
def start(self):
print("Engine started")
def stop(self):
print("Engine stop")
class Car(Engine):
def start(self):
super().start()
print("Cat started")
def stop(self):
super().stop()
print("Car stoped")
car = Car()
car.start()
car.stop()
使用合成、聚合复用原则
如果使用合成、聚合原则,每个汽车对象都可以聚合一个特定类型的引擎,而且可以在需要时动态的更换引擎
class Engine:
def start(self):
print("Engine started")
def stop(self):
print("Engine stoped")
class Car:
def __init__(self, engine:Engine):
self.engine = engine
def start(self):
self.engine.start()
print("Car started")
def stop(self):
self.engine.stop()
print("Car stoped")
engine = Engine()
car = Car(engine)
car.start()
car.stop()
七、迪米特法则
(Law of Demeter)
- 只与直接的朋友通信:一个对象只应该调用其成员变量、方法的输入参数、方法内部创建的对象、方法返回的对象,而不应该调用其他对象的成员变量的方法。
- 朋友的定义:直接的朋友包括当前对象的成员变量、方法的输入参数、方法的返回值等、不包括对象内部的其他对象
- 封装隐私信息:一个对象不应该暴露其内部的详细信息,而应该通过接口提供有限的访问权限
# 假设我们有一个订单处理系统
# 其中包含订单(Order)、顾客(Customer)和仓库(Warehouse)三个类。
# 订单需要根据顾客的要求,从仓库中获取商品,并进行发货
class Customer:
def __init__(self, name):
self.name = name
self.items = []
def add_items(self, item):
self.items.append(item)
def get_items(self):
return self.items
class Order:
def __init__(self, customer):
self.customer = customer
def process_order(self, warehouse):
items = self.customer.get_items()
warehouse.prepare_shipment(items)
warehouse.ship_order()
class Warehouse:
def __init__(self):
self.inventory = []
def add_item(self, item):
self.inventory.append(item)
def prepare_shipment(self, items):
print("Preparing shipment for items:")
for item in items:
if item in self.inventory:
print(item)
self.inventory.remove(item)
def ship_order(self):
print("Shipping order")
设计模式分类
一、创建型模式
- 它们的运行机制基于对象的创建
- 它们将对象创建的细节隔离开来
- 代码与所创建的对象类型无关
- 典型例子是
单例模式
二、结构型模式
- 它们致力于设计出能够通过组合获得更强大功能的对象和类的结构
- 重点是简化结构并识别类和对象之间的关系
- 它们主要关注类的继承和组合
- 典型例子是
适配器模式
行为型模式
- 它们关注对象之间的交互以及对象的响应性
- 对象应该能够交互,同时仍然能保持松散耦合
...