设计模式原则
1. 单一职责原则(Single Responsibility Principle, SRP)
一个类应该只有一个引起它变化的原因。简单来说,一个类应该只负责一项职责。这有助于使类更加紧凑和可维护。
假设我们有一个UserProfile类,它既处理用户信息的存储,也负责用户信息的显示逻辑。
pythonCopy code
class UserProfile:
def __init__(self, name, email):
self.name = name
self.email = email
def save_user(self):
print(f"Saving {self.name} to database")
def display_user(self):
print(f"User: {self.name}, Email: {self.email}")
这个UserProfile类违反了单一职责原则,因为它既处理用户信息的存储,也处理显示逻辑。根据SRP,我们应该将这个类拆分为两个类,每个类只负责一项职责。
pythonCopy code
class UserStorage:
def save_user(self, user):
print(f"Saving {user.name} to database")
class UserDisplay:
def display_user(self, user):
print(f"User: {user.name}, Email: {user.email}")
通过这种方式,我们将存储逻辑和显示逻辑分离到不同的类中,每个类只负责一个职责。
2. 开闭原则(Open-Closed Principle, OCP)
软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着应该能够在不修改现有代码的基础上扩展类的行为。
假设我们有一个图形界面应用,其中有一个Shape类和一个GraphicEditor类用于绘制形状。
class Shape:
def draw(self):
pass
class Circle(Shape):
def draw(self):
print("Drawing a circle")
class Square(Shape):
def draw(self):
print("Drawing a square")
class GraphicEditor:
def draw_shape(self, shape):
shape.draw()
在这个例子中,GraphicEditor类遵循了开闭原则。当我们需要增加新的形状时(例如,添加一个Triangle类),我们不需要修改GraphicEditor的代码。我们只需添加一个新的形状类继承自Shape,并实现draw方法。
class Triangle(Shape):
def draw(self):
print("Drawing a triangle")
GraphicEditor类对扩展开放(我们可以添加更多的形状类),但对修改封闭(添加新形状时不需要修改GraphicEditor的代码)。
3. 里氏替换原则(Liskov Substitution Principle, LSP)
子类对象应该能够替换其超类对象被使用,而不破坏程序的正确性。换句话说,子类应该遵守父类的行为协议。
假设有一个关于鸟类的类层次结构,其中Bird是一个超类,有一个fly方法。Sparrow和Ostrich是它的两个子类。
class Bird:
def fly(self):
pass
class Sparrow(Bird):
def fly(self):
print("Sparrow can fly")
class Ostrich(Bird):
def fly(self):
print("Ostrich cannot fly")
这里,Ostrich类违反了LSP,因为鸵鸟实际上是不能飞的,但继承自Bird类使得它必须实现fly方法。这破坏了程序的正确性,因为它违反了鸟类的行为协议。
一个更好的设计是将Bird类分为两个接口:FlyingBird和NonFlyingBird,以此来遵循LSP。
class Bird:
pass
class FlyingBird(Bird):
def fly(self):
pass
class NonFlyingBird(Bird):
pass
class Sparrow(FlyingBird):
def fly(self):
print("Sparrow can fly")
class Ostrich(NonFlyingBird):
pass
在这个设计中,Sparrow和Ostrich分别继承自符合它们行为的类,从而遵守了LSP。
4. 接口隔离原则(Interface Segregation Principle, ISP)
不应该强迫客户依赖于它们不使用的接口。应该将臃肿的接口分割成更小和更具体的接口,让实现类只需要关心它们真正需要的接口。
假设我们有一个多功能打印机的接口,包括打印、扫描和复印的功能。
class MultiFunctionPrinter:
def print_document(self):
pass
def scan_document(self):
pass
def copy_document(self):
pass
如果有一种打印机只支持打印功能,不支持扫描和复印,那么实现MultiFunctionPrinter接口就迫使该打印机类依赖于它不使用的接口,违反了ISP。
为了遵循ISP,我们应该将MultiFunctionPrinter接口分割成更小的接口:
class Printer:
def print_document(self):
pass
class Scanner:
def scan_document(self):
pass
class Copier:
def copy_document(self):
pass
然后,根据不同设备的功能,实现相应的接口:
class SimplePrinter(Printer):
def print_document(self):
print("Print document")
class AdvancedPrinter(Printer, Scanner, Copier):
def print_document(self):
print("Print document")
def scan_document(self):
print("Scan document")
def copy_document(self):
print("Copy document")
这样,每个类只依赖于它真正需要的接口,从而遵循了ISP。
5. 依赖倒置原则(Dependency Inversion Principle, DIP)
高层模块不应该依赖低层模块,它们都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。这意味着要通过抽象(接口或抽象类)来传递依赖关系,而不是具体类。
考虑一个简单的消息发送功能,我们希望能够通过不同的渠道(如电子邮件、短信)发送消息。按照依赖倒置原则,我们应该依赖于抽象而不是具体的实现。
首先,定义一个消息发送的接口(抽象):
class MessageSender:
def send(self, message):
pass
然后,实现具体的发送方式:
class EmailSender(MessageSender):
def send(self, message):
print(f"Sending email: {message}")
class SMSSender(MessageSender):
def send(self, message):
print(f"Sending SMS: {message}")
最后,高层模块依赖于MessageSender接口,而不是具体的EmailSender或SMSSender:
class Notification:
def __init__(self, sender: MessageSender):
self.sender = sender
def notify(self, message):
self.sender.send(message)
这样,我们就可以在不修改Notification类的情况下,灵活地更换消息发送的方式。
6. 合成复用原则(Composite Reuse Principle, CRP)
尽量使用对象组合(has-a关系),而不是继承关系来达到复用的目的。继承是一种强耦合的结构,而组合则提供了更大的灵活性。
假设我们有一个Car类,它具有启动和停止的功能。现在,我们希望扩展功能,增加一个警报系统。根据合成复用原则,我们应该通过组合来复用Alarm的功能,而不是通过继承。
首先,定义一个警报类:
class Alarm:
def start_alarm(self):
print("Alarm started")
def stop_alarm(self):
print("Alarm stopped")
然后,通过组合将Alarm类的功能添加到Car类中:
class Car:
def __init__(self):
self.alarm = Alarm() # 组合
def start(self):
self.alarm.start_alarm()
print("Car started")
def stop(self):
self.alarm.stop_alarm()
print("Car stopped")
通过这种方式,我们可以灵活地为Car添加更多的功能,而不是通过创建一个继承层次结构。这样做不仅减少了耦合,还提高了代码的可维护性和复用性。
遵循这些原则可以帮助开发者设计出更健壮、易于维护和扩展的面向对象系统。然而,在实际应用中,可能需要根据具体情况权衡这些原则的应用,以找到最适合当前项目的设计。
7. 迪米特法则(Law of Demeter, LoD)/最少知识原则
一个对象应该对其他对象有尽可能少的了解。简单来说,每个单元应该只与其直接的朋友通信,避免与更多的对象建立直接的联系,以减少系统间的相互依赖。
迪米特法则(Law of Demeter, LoD)旨在降低类之间的耦合度,通过限制对象之间的交互来提高模块的独立性。以下是一个简单的例子来说明这个原则:
违反迪米特法则的示例
假设有一个Bookstore类,它需要打印出某本书的作者名。在这个场景中,Bookstore需要从Book对象获取Author对象,然后再从Author对象获取作者名。
class Author:
def __init__(self, name):
self.name = name
class Book:
def __init__(self, author):
self.author = author
class Bookstore:
def print_author_name(self, book):
print(book.author.name)
在上面的例子中,Bookstore类直接访问了Book对象的author属性,然后又访问了Author对象的name属性。这违反了迪米特法则,因为Bookstore类需要了解Book和Author之间的关系细节。
遵守迪米特法则的示例
为了遵守迪米特法则,我们可以让Book类提供一个方法来获取作者的名字,这样Bookstore就不需要直接与Author对象交互了。
class Author:
def __init__(self, name):
self.name = name
class Book:
def __init__(self, author):
self.author = author
def get_author_name(self):
return self.author.name
class Bookstore:
def print_author_name(self, book):
print(book.get_author_name())
在这个修改后的例子中,Bookstore只与Book对象交互,并通过Book对象提供的get_author_name方法间接获取到作者的名字。这样,Bookstore就不需要知道Book和Author之间的内部结构了,从而降低了耦合度。
通过遵守迪米特法则,我们可以设计出更加松耦合和更易于维护的系统。每个类仅与其直接的朋友通信,减少了对系统其他部分的依赖,使得每个部分都可以更独立地变化和复用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)