首页 > 其他分享 >【面向对象设计的七大原则】

【面向对象设计的七大原则】

时间:2024-01-06 11:33:25浏览次数:26  
标签:代码 原则 self 七大 面向对象 print class def

(文章目录)

前言

面向对象设计(OOD)是现代软件工程中的核心,其核心思想在于通过抽象化实体的特征和行为来模拟现实世界,这种方法不仅仅是一种编程范式,更是一种设计哲学。在编程领域,它帮助开发者通过类和对象的组织和交互,来构建出模块化、灵活且易于维护的软件系统。而面向对象设计的七大原则,常被称为“OOD七大宝典”,它们分别是单一职责原则(SRP)、开闭原则(OCP)、里氏替换原则(LSP)、依赖倒置原则(DIP)、接口隔离原则(ISP)、迪米特法则(LoD)和合成复用原则(CRP)。

一、单一职责原则(SRP)

单一职责原则(SRP)其核心观点是一个类应该仅有一个变化的原因。这个原则是由罗伯特·C·马丁提出,他在《敏捷软件开发:原则、模式与实践》一书中明确提到,单一职责原则的目标是实现类的高内聚与低耦合。高内聚指的是一个类或模块专注于完成一项任务或有一系列相关性很强的功能,而低耦合则意味着类与类之间的依赖关系简单明了,易于理解和维护。

1.定义与解释

SRP 告诉我们,不应该有多于一个导致类变化的原因,因为这通常意味着类承担了太多职责。当一个类有多个责任时,变化的可能性增加,因为有多个功能可能需要变化。这会导致软件的脆弱性增加,因为变更可能影响到与该类有依赖关系的其他部分。另外,这样的类很难进行测试和维护,因为改动一处可能需要在多个地方进行测试和调整。

对于面向对象设计而言,SRP 有着重要的意义。首先,它减轻了理解单个类的负担,因为每个类只负责一件事情。其次,它减少了代码的修改成本,当修改一个类时,不必担心会影响到类的其他功能。最后,它提升了代码的可复用性,因为职责单一的类更容易被其他部分的代码所复用。

2.示例

违反SRP的代码:

class User:
    def __init__(self, name: str):
        self.name = name

    def get_user_data(self):
        pass  # Logic to fetch user data from a database

    def save_user_data(self, user_data):
        pass  # Logic to save user data to a database

    def generate_user_report(self):
        pass  # Logic to generate a report of the user's data

    def send_user_report(self):
        pass  # Logic to send the user's report through email

在这个例子中,User 类负责了多个任务:从数据库中获取和保存用户数据、生成用户报告和发送报告。如果需要改变报告的格式或发送方式,就可能需要修改 User 类,这违反了 SRP。

遵循SRP的代码:

class User:
    def __init__(self, name: str):
        self.name = name

class UserDataBase:
    @staticmethod
    def get_user_data(user: User):
        pass  # Logic to fetch user data from a database

    @staticmethod
    def save_user_data(user: User, user_data):
        pass  # Logic to save user data to a database

class UserReport:
    @staticmethod
    def generate(user: User):
        pass  # Logic to generate a report of the user's data

class ReportMailer:
    @staticmethod
    def send(report):
        pass  # Logic to send the report through email

在遵循SRP的版本中,我们将不同的职责分配到了不同的类中。现在,如果报告的发送方式需要变更,我们只需修改 ReportMailer 类,而不会影响到其他类。这样每个类都只有一个变化的原因,从而遵循了单一职责原则。

二、开闭原则(OCP)

开闭原则(OCP)为软件的可维护性和可扩展性提供了指导。该原则由 Bertrand Meyer 提出,其核心思想是“软件实体应当对扩展开放,对修改封闭”,这意味着软件的行为应该可以扩展,而不需要修改现有代码。

1.定义与解释

开闭原则鼓励我们写出可以在不更改现有代码的情况下增加新功能的代码。这可以通过抽象化和使用接口或抽象类来实现,让系统的各个部分相互独立,从而能够适应变化。当需求变化或系统需要扩展新功能时,我们可以通过添加新的代码而不是修改旧代码来实现,这降低了系统可能因更改而产生的风险。

OCP对软件开发的影响深远,因为它促使开发者进行更精心的设计。它要求设计者考虑到代码未来的变化,这样,当新需求出现时,可以通过新增代码而不是修改旧代码来应对。这种做法不仅能够减少可能引入的错误,也使得软件系统更加稳定和灵活。

2.示例

考虑一个简单的报表生成系统,要求能够支持不同类型的报表格式。而不使用OCP,我们可能会得到以下设计:

class ReportGenerator:
    def generate(self, data, report_type):
        if report_type == "PDF":
            # Logic to generate PDF report
            pass
        elif report_type == "HTML":
            # Logic to generate HTML report
            pass
        # To add a new report format, we have to modify this class

这种设计违反了 OCP,因为每次添加新的报表格式时,我们都需要修改 ReportGenerator 类。按照 OCP 重构的设计应该是这样的:

class ReportGenerator:
    def generate(self, data, report_formatter):
        return report_formatter.format(data)

class ReportFormatter:
    def format(self, data):
        raise NotImplementedError("Subclasses should implement this!")

class PDFReportFormatter(ReportFormatter):
    def format(self, data):
        # Logic to generate PDF report
        pass

class HTMLReportFormatter(ReportFormatter):
    def format(self, data):
        # Logic to generate HTML report
        pass

# Now if we need to add a new format, we just add a new formatter.
# No need to change any existing class.

在这个遵循OCP的设计中,我们定义了一个 ReportFormatter 抽象基类,并为每种报表格式提供了具体的实现。ReportGenerator 类不需要知道具体的报表格式,它通过 ReportFormatter 的接口与报表格式进行交互。如果我们想要添加一个新的报表格式,我们只需添加一个新的 ReportFormatter 子类,无需修改 ReportGenerator 或其他任何已存在的格式器代码。这样,我们就实现了对扩展的开放和对修改的封闭。

三、里氏替换原则(LSP)

里氏替换原则(LSP)是由芭芭拉·里提出的一个面向对象的设计原则。它是一个关于继承和子类型的原则,要求子类对象能够替换其父类对象被使用,而不破坏程序的正确性。这意味着一个程序中的对象应当可以在不改变程序正确性的前提下,被它的子类所替换。

1.定义与解释

LSP原则的关键是确保子类可以完整地替代父类。一个使用基类对象的函数在使用子类对象替代后,不应当出现任何错误或异常。子类在继承和扩展父类的行为时必须保持一致性,不能改变父类原有的行为。

遵循LSP可以提高代码的模块化和可重用性,同时也使得类的新旧版本兼容。它强制性地规定了父类与子类之间行为的一致性,使得任何基于父类的代码都可以安全地使用子类对象,从而提高了代码的可维护性。

2.示例

违反LSP的代码:

class Rectangle:
    def set_width(self, width):
        self.width = width

    def set_height(self, height):
        self.height = height

    def get_area(self):
        return self.width * self.height

class Square(Rectangle):
    def set_width(self, width):
        self.width = width
        self.height = width  # This change breaks the LSP

    def set_height(self, height):
        self.width = height  # This change breaks the LSP
        self.height = height

def print_area(rect: Rectangle):
    rect.set_width(4)
    rect.set_height(5)
    assert rect.get_area() == 20, "Area not correct."

my_square = Square()
print_area(my_square)  # This will fail the assert

在这个例子中,Square 是从 Rectangle 继承的,但是通过重写 set_widthset_height 方法来保持两边相等,它违反了LSP。在 print_area 函数中使用 Square 代替 Rectangle 会导致断言失败,因为它期望长方形的面积,但 Square 改变了行为。

遵循LSP的代码:

class Shape:
    def get_area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def get_area(self):
        return self.width * self.height

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def get_area(self):
        return self.side * self.side

def print_area(shape: Shape):
    assert shape.get_area() == 20, "Area not correct."

my_rectangle = Rectangle(4, 5)
print_area(my_rectangle)  # This will pass the assert

my_square = Square(4.472)
print_area(my_square)  # This will also pass the assert

在遵循LSP的例子中,我们引入了一个共同的基类 Shape,它定义了 get_area 方法的接口,而 RectangleSquare 都各自实现了这个方法。这样,不管我们传给 print_area 函数的是 Rectangle 对象还是 Square 对象,它都会正确地计算面积,因为两个类都遵守了 Shape 接口的契约。

四、依赖倒置原则(DIP)

依赖倒置原则(DIP)是一个促进高度解耦和可维护代码设计的面向对象设计原则。这个原则的核心是要求代码依赖于抽象而不是具体实现,进而实现了高层模块与低层模块的相对独立。

1.定义与解释

DIP指出高层模块(比如策略决定者或业务规则)不应该依赖于低层模块(比如具体的数据访问实现或具体的服务实现),而应当两者都依赖于一套预先定义好的抽象接口或类。同样,抽象不应依赖于具体的实现细节,而是具体的实现细节应该依赖于抽象。

DIP对设计灵活性的提升意义重大,它允许开发者改变应用程序的行为而不需要改变高层模块。只要底层模块遵循同样的抽象,就可以自由地更换它们。这种灵活性使得应用程序更容易适应变化,易于扩展,且具有更好的可维护性。

2.示例

不遵循DIP的代码:

class LightBulb:
    def turn_on(self):
        print("LightBulb: turned on...")

    def turn_off(self):
        print("LightBulb: turned off...")

# 高层模块直接依赖于低层模块的具体实现。
class Switch:
    def __init__(self):
        self.bulb = LightBulb()

    def operate(self):
        self.bulb.turn_on()  # Tight coupling here

# 如果我们决定更换LightBulb类,Switch类也必须修改。

在这个例子中,Switch 类(一个高层模块)直接依赖于 LightBulb 类(一个低层模块)。如果我们想要替换 LightBulb 类(例如,使用一个LED灯类),我们也必须修改 Switch 类。

遵循DIP的代码:

from abc import ABC, abstractmethod

class Switchable(ABC):  # This is the abstraction
    @abstractmethod
    def turn_on(self):
        pass

    @abstractmethod
    def turn_off(self):
        pass

class LightBulb(Switchable):  # Low-level module depends on abstraction
    def turn_on(self):
        print("LightBulb: turned on...")

    def turn_off(self):
        print("LightBulb: turned off...")

class Fan(Switchable):  # Another low-level module depending on the same abstraction
    def turn_on(self):
        print("Fan: turned on...")

    def turn_off(self):
        print("Fan: turned off...")

class Switch:  # High-level module also depends on the abstraction
    def __init__(self, device: Switchable):
        self.device = device

    def operate(self):
        self.device.turn_on()  # Using the abstraction

# Now we can pass any class that implements Switchable without changing the Switch class.
my_switch = Switch(LightBulb())
my_switch.operate()

my_switch = Switch(Fan())
my_switch.operate()

在遵循DIP的版本中,我们定义了一个 Switchable 抽象基类,LightBulbFan 都实现了这个接口。Switch 类现在依赖于 Switchable 接口,而不是具体的 LightBulb 类,这使得我们可以不修改 Switch 类的情况下,轻松地替换 LightBulb 为任何实现了 Switchable 的类,比如 Fan。这样,即使底层的实现细节变化了,高层模块也不需要做出任何改

五、接口隔离原则(ISP)

接口隔离原则(Interface Segregation Principle, ISP)是面向对象设计原则中的一个重要原则,它提倡在设计接口时应当小而专一,而不是大而全面。

1.定义与解释

ISP的核心思想是客户端不应该被迫依赖于它们不使用的接口。一个类对另一个类的依赖应该基于最小的接口。这意味着,如果一个接口变得太“肥大”,我们应该将它分割成更小且更具针对性的几个接口,这样客户端只需要知道它们真正使用的方法。这样做可以减少系统中不必要的依赖关系,从而降低变更引入的风险,使系统更容易维护和扩展。

2.示例

不遵循ISP的代码:

class Worker:
    def work(self):
        print("Working...")

    def eat(self):
        print("Eating...")

# 这里的Robot类被迫实现了它不需要的eat方法。
class Robot(Worker):
    def work(self):
        print("Robot working...")

    def eat(self):
        # Robots do not eat, but we have to implement the method
        pass

# Human workers can use both methods.
class Human(Worker):
    def work(self):
        print("Human working...")

    def eat(self):
        print("Human eating...")

在上面的代码中,Robot 类被迫实现了 eat 方法,这是它不需要的,因为机器人不需要“吃”。

遵循ISP的代码:

from abc import ABC, abstractmethod

class Workable(ABC):
    @abstractmethod
    def work(self):
        pass

class Eatable(ABC):
    @abstractmethod
    def eat(self):
        pass

class Worker(Workable, Eatable):
    def work(self):
        print("Working...")

    def eat(self):
        print("Eating...")

class Robot(Workable):
    def work(self):
        print("Robot working...")

# Robots only need to implement Workable interface.
# Humans can implement both interfaces.
class Human(Worker):
    def work(self):
        print("Human working...")

    def eat(self):
        print("Human eating...")

在遵循ISP的版本中,我们创建了两个接口:WorkableEatableRobot 类只实现了 Workable 接口,而 Human 类可以实现两个接口。这样,每个类只需要关心它真正需要的接口,从而避免了不必要的依赖。

六、迪米特法则(LoD)

迪米特法则(Law of Demeter, LoD),也称为最少知识原则,是一种用于降低软件实体之间耦合的设计指导原则。

1.定义与解释

迪米特法则建议,一个软件实体应当尽可能少地与其他实体发生相互作用。这意味着一个对象应该对其他对象保持最少的了解,并且只与直接的朋友通信。在这个原则下,每个单元对于其他单元只知道必须的信息,不需要了解那些不必要的内部细节。

遵循LoD原则的系统通常具有更好的维护性和更高的模块化程度,因为它减少了对象间的直接依赖。在面向对象编程中,迪米特法则可以通过只调用属于以下范畴的方法来应用:

  • 当前对象本身的方法。
  • 作为方法参数传入的对象的方法。
  • 当前对象的任何成员对象的方法。
  • 对象自身创建或实例化的任何对象的方法。

2.示例

不遵循LoD的代码:

class Paper:
    def getDetails(self):
        # returns details about the paper
        return "This is a paper."

class Printer:
    def printPaper(self, paper):
        print(paper.getDetails())

class Copier:
    def startCopy(self):
        paper = Paper()
        printer = Printer()
        # Copier has knowledge of Paper's methods, which it shouldn't have.
        print_details = paper.getDetails()
        printer.printPaper(print_details)

copier = Copier()
copier.startCopy()

在上述代码中,Copier 类直接调用了 Paper 类的 getDetails 方法。这违反了LoD原则,因为 Copier 知道了 Paper 类的详细实现。

遵循LoD的代码:

class Paper:
    def getDetails(self):
        return "This is a paper."

class Printer:
    def printPaper(self, paper):
        # Printer is responsible for knowing how to print paper.
        print(paper.getDetails())

class Copier:
    def startCopy(self):
        paper = Paper()
        printer = Printer()
        # Copier now only talks to Printer, does not need to know about Paper details.
        printer.printPaper(paper)

copier = Copier()
copier.startCopy()

在遵循LoD的代码中,Copier 类不再直接调用 Paper 类的方法,而是通过 Printer 类来间接完成工作。Copier 不需要知道 Paper 的内部细节,这减少了类之间的依赖,降低了耦合度。

七、合成复用原则(CRP)

合成复用原则(Composition over Inheritance Principle, CRP)是面向对象设计的一项重要原则,它鼓励使用合成/聚合关系代替继承关系来达到代码复用的目的。

1.定义与解释

合成复用原则主张在设计软件时,应当尽量使用合成/聚合的方式,而非继承关系来实现代码的复用。继承虽然能提供一种直观的复用机制,但它也带来了紧密的耦合和泛化的问题,尤其是在层次较深的继承体系中,底层改动可能导致链式反应。相反,合成/聚合不仅提高了代码的复用性,还增加了代码的灵活性和可维护性。

合成(Composition)是指一个类中包含了另一个类的实例,从而能够调用其方法进行工作。聚合(Aggregation)类似于合成,但它允许单独的生命周期,即组成对象可以独立于创建它的对象存在。

2.示例

使用继承的代码:

class Vehicle:
    def move(self):
        print("Moving...")

class Car(Vehicle):
    def move(self):
        print("Car is moving.")

class Boat(Vehicle):
    def move(self):
        print("Boat is moving.")

# Car and Boat inherit move behavior from Vehicle, tightly coupling them.

上述代码通过继承来复用 move 方法,但这样 CarBoat 类与 Vehicle 类紧密耦合。

使用合成复用的代码:

class Movement:
    def move(self):
        print("Moving...")

class Vehicle:
    def __init__(self, movement):
        self.movement = movement

    def startMoving(self):
        self.movement.move()

class Car:
    # Car has-a Movement, not is-a Vehicle.
    def __init__(self):
        self.movement = Movement()

    def move(self):
        self.movement.move()

class Boat:
    # Similarly, Boat has-a Movement.
    def __init__(self):
        self.movement = Movement()

    def move(self):
        self.movement.move()

# Vehicle composition allows for flexibility and reuse without tight coupling.
car = Car()
boat = Boat()

car.move()  # Outputs: Moving...
boat.move()  # Outputs: Moving...

在使用合成复用原则的代码中,CarBoat 类都包含了 Movement 类的实例。这允许 CarBoat 独立于 Movement 类变化,只要 Movement 的接口保持不变。此外,如果需要为特定类型的车辆添加特殊的移动方式,我们可以很容易地扩展 Movement 类或创建一个新的移动类来满足需求,而不需要改变现有的车辆类。这就是合成复用原则的强大之处,它提高了代码的灵活性,减少了因继承而产生的风险。

总结

遵循这些原则能够帮助避免常见的设计陷阱,比如过度耦合、不易维护的代码和难以扩展的系统等。尽管应用这些原则可能会增加初期的设计复杂性,但长远来看,它们为软件的可持续发展奠定了坚实的基础。正确应用这些原则,将使我们能够构建出可扩展、易于维护且高效的面向对象系统。

标签:代码,原则,self,七大,面向对象,print,class,def
From: https://blog.51cto.com/u_16202095/9126168

相关文章

  • 面向对象三大特性
    面向对象三大特性面向对象编程有三大特性:封装、继承、多态其中最重要的一个特性就是封装。封装指的就是把数据与功能都整合到一起除此之外,针对封装到对象或者类中的属性,我们还可以严格控制对它们的访问,分两步实现:隐藏与开放接口【一】封装(1)什么是封装在程序设计......
  • 如何为OpenHarmony贡献(11):英文资料的一些行文原则
    言简意赅用简短的话语表达丰富的想法。现代设计以简约主义为基础,我们在表达观点的时候,应尽量做到言简意赅。示例:BadIfyou'rereadytocontributetoourcommunityasanindividualdeveloper,contactusbysendinganemail.GoodReadytocontribute?Feelfreetoconta......
  • java面向对象:类(二)
    1.Java面向对象:类1.1作用域1.1.1基本使用面向对象中,变量作用域是非常重要的知识点在java编程中,主要的变量就是属性(成员变量)和局部变量我们所获得局部变量一般是指在成员方法中定义得变量java中作用域得分类:全局变量:也就是属性,作用域为整个类,可以不赋值,直接使用,因为有默......
  • python面向对象之派生、组合、抽象类、反射
    【派生】在子类派生的新方法中如何重用父类的功能?  【组合】(定义) (案例) (组合和继承的区别) 【抽象类】(定义) (案例) 实例化 (总结) 【反射】什么是反射 如何反射 实现反射机制的步骤 解决办法 ......
  • 面向对象编程(上)
    面向对象内容的三条主线1.Java类及类的成员:属性、方法、构造器;代码块、内部类2.面向对象的三大特征:封装性、继承性、多态性、(抽象性)3.其它关键字:this、super、static、final、abstract、interface、package、import等面向对象的思想概述Java语言的基本元素:类和对象类......
  • 面向对象编程(中)
    关键字:static(静态)作用范围可以用来修饰的结构:主要用来修饰类的内部结构属性、方法、代码块、内部类static修饰属性静态变量(或类变量)静态属性vs非静态属性属性,是否使用static修饰,又分为:静态属性vs非静态属性(实例变量)实例变量:我们创建了类的多个对象,每个对象都独立的......
  • 产品设计十大基本原则
    https://coffee.pmcaff.com/article/3788693565522048/pmcaff?utm_source=forum&newwindow=11.信息架构的五帽架原理:排序、字母、地理位置、时间、分类,所有的信息展示均要符合这5类。2.衍生信息分层的原则:任何列表、详情,都需要首先考虑排序(优先级分层),然后考虑分类。例如某个列表,......
  • 代码质量-开闭原则
    前言什么是开闭原则?开闭原则(Open-ClosedPrinciple,OCP)是面向对象设计中的一个重要原则。它指出软件实体(如类、模块、函数等)应该对扩展开放,对修改封闭。这意味着一个实体允许其行为被扩展,但不允许修改其源代码。不遵循开闭原则的代码示例假设有一个简单的类,用于根据不同的形......
  • 软件开发隐藏报价和虚假信息协议书,明确的如下2点关键原则
    软件开发外包已成为众多企业实现技术创新、降低成本的有效途径。然而,在这一领域中,不透明的定价策略和虚假信息问题屡见不鲜,给交易双方带来了不少困扰与风险。因此,为了保障企业的合法权益,签署包含隐藏报价和虚假信息赔偿条款的外包合同显得尤为重要。如下参考“东莞梦幻网络科技”......
  • 2024年需要关注的七大网络安全威胁
    新颖创新技术的兴起和迅速采用已极大地改变了各行各业的全球网络安全和合规格局,比如生成式人工智能、无代码应用程序、自动化和物联网等新技术。网络犯罪分子正转而采用新的技术、工具和软件来发动攻击,并造成更大的破坏。因此,《2023年网络安全风险投资网络犯罪报告》预测,与网络犯罪......