设计模式原则

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方法。SparrowOstrich是它的两个子类。


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类分为两个接口:FlyingBirdNonFlyingBird,以此来遵循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

在这个设计中,SparrowOstrich分别继承自符合它们行为的类,从而遵守了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接口,而不是具体的EmailSenderSMSSender

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类需要了解BookAuthor之间的关系细节。

遵守迪米特法则的示例

为了遵守迪米特法则,我们可以让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就不需要知道BookAuthor之间的内部结构了,从而降低了耦合度。
通过遵守迪米特法则,我们可以设计出更加松耦合和更易于维护的系统。每个类仅与其直接的朋友通信,减少了对系统其他部分的依赖,使得每个部分都可以更独立地变化和复用。

posted @   35-brother  阅读(6)  评论(0编辑  收藏  举报
编辑推荐:
· 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)
点击右上角即可分享
微信分享提示