大白话讲解设计原则及其优缺点
滴答滴:设计原则我是边学边记录,有问题的欢迎大家指出。学习的过程中会借助AI工具,我想说的是我们要把AI工具当作一个学识渊博的学者或者一个便捷的工具,同时要敢于质疑它,不能盲目的觉得对方说的一定是正确的,因为有时它的回答不见得是正确的,我们要带着自己的思考去使用AI工具,不断的和它对话和探讨,最终得出我们想要的答案。
一.设计原则
设计原则是在软件开发过程中,为了提高代码的可扩展性、可维护性、可重用性、易理解性和易测试性,而遵循的一系列指导性原则。
1.单一职责原则
一个类或者一个模块只负责完成一个功能或者任务。这意味着如果一个类承担了过多的功能,那么当其中一个功能需要改变时,可能会影响到其他功能。
大白话:想象你是一名厨师,你的工作是准备晚餐。如果你不仅要负责烹饪,还要负责买菜、洗碗、打扫厨房等所有与晚餐相关的工作,那么当你需要改变晚餐的菜单时,可能会影响到你买菜、洗碗等其他工作的安排。为了避免这种情况,你可以将晚餐的准备工作拆分成多个任务,分别由不同的人或系统来完成,比如一个人负责买菜,一个人负责烹饪,另一个人负责清洁工作。
优点:
- 降低类的复杂度:类只负责单一职责,逻辑简单,易于理解和维护。
- 提高可维护性:当需要修改或扩展功能时,只需关注相关类,减少对其他部分的影响。
- 提高复用性:职责单一的类更容易被其他项目或模块重用。
缺点:
- 可能导致类的数量增加:过度遵循此原则可能会使系统中类的数量过多,增加系统的复杂性。
- 过度设计:有时为了遵守单一职责原则,可能会将本来可以合并的职责拆分开来,导致设计过于琐碎。
2.开闭原则
软件实体(如类、模块、函数等)应该对扩展开放,对修改关闭。这意味着当需要添加新功能时,应该通过扩展现有的软件实体来实现,而不是修改它们。
大白话: 假设你有一台智能手机,它支持各种应用程序。当你想要使用一个新的应用程序时,你不需要修改手机的硬件或操作系统,只需要下载并安装这个新的应用程序即可。这就是开闭原则的一个例子,手机的设计允许你通过扩展(添加新的应用程序)来增加功能,而不需要修改手机的核心部分。
优点:
- 提高软件稳定性:通过扩展而非修改来增加新功能,减少了对原有代码的干扰。
- 易于维护:新的功能通过添加新的代码实现,不影响原有功能的稳定性和可靠性。
缺点:
- 设计难度增加:为了实现开闭原则,需要充分预见未来的变化,并设计出灵活的架构和接口。
- 过度抽象:有时为了遵守开闭原则,可能会过度抽象,导致设计过于复杂,难以理解。
3.里氏替换原则
子类对象应该能够替换掉它们的父类对象,并且程序能够正确地运行。这要求子类在继承父类时,尽量不要改变父类的行为。里氏替换原则的核心观念可以归纳为以下几点:
-
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法:子类在继承父类时,可以扩展父类的功能,但不能改变父类原有的功能。这意味着子类不应该重写父类的非抽象方法,除非这些方法在子类中有完全不同的实现且这种改变是合理的。
-
子类中可以增加自己特有的方法:子类可以在继承父类的基础上,增加自己特有的方法,以实现更多的功能。
-
前置条件放大:当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)应该比父类方法的输入参数更宽松。这样可以使得子类的方法能够接受更多的输入,从而增加灵活性。
-
后置条件缩小:当子类的方法实现父类的方法时(包括重写、重载或实现抽象方法),方法的后置条件(即方法的输出或返回值)应该比父类更严格或相等。这保证了子类方法在执行后,返回的结果符合或更加严格于父类方法的预期。
大白话:想象你有一个水果店,店里卖各种水果,包括苹果和香蕉。现在,你想要引入一种新的水果——红富士苹果,它是苹果的一种特殊品种。在销售时,你可以将红富士苹果当作普通苹果来卖,顾客不会因为它是红富士苹果而拒绝购买或产生困惑。这就是里氏替换原则的一个例子,红富士苹果作为苹果的一个子类,可以替换掉父类苹果在程序(这里是销售过程)中的位置。
优点:
- 增强程序的健壮性:通过确保子类可以替换父类,使得程序的稳定性得到保证。
- 提高代码的可维护性:当父类发生变化时,子类可以继承这些变化,而不需要修改与父类相关的代码。
缺点:
- 限制子类的灵活性:由于子类必须遵循父类的行为,这可能会限制子类在某些场景下的灵活性。
- 增加设计难度:为了实现里氏替换原则,需要对类的继承关系进行仔细的设计和考虑。
4.依赖倒置原则
程序要依赖于抽象接口,不要依赖于具体实现。也就是说,我们应该通过接口或抽象类来定义类的行为,而不是直接依赖于具体的类。比如,我们定义了一个日志接口,不同的日志实现类(如文件日志、数据库日志)都实现了这个接口。这样,当需要更换日志实现方式时,只需要更换对应的实现类即可。
这个原则的核心思想是:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。这里的“高层模块”和“低层模块”是相对的,可以理解为调用者(客户端)和被调用者(具体实现),或者更一般地说,是系统中的不同组件。而“抽象”则通常指的是接口(Interface)或抽象类(Abstract Class),它们定义了操作的规范,但不提供具体的实现。在传统的软件设计中,高层模块(如业务逻辑层)往往会依赖于低层模块(如数据访问层)的具体实现。但在依赖倒置原则下,这种依赖关系被颠倒了。高层模块不再直接依赖于低层模块的具体实现,而是依赖于它们的抽象接口。低层模块则通过实现这些接口来与高层模块进行交互。
大白话:假设你是一名汽车修理工,你需要修理不同品牌的汽车。如果每种品牌的汽车都需要你学习一种特定的修理方法,那么你的工作将会非常复杂和困难。但是,如果汽车制造商都遵循一定的标准(比如使用统一的螺丝规格、接口标准等),那么你就可以使用一套通用的工具和方法来修理不同品牌的汽车。这就是依赖倒置原则的一个例子,你依赖于汽车的标准接口(抽象)来工作,而不是依赖于具体的汽车品牌(实现)。
优点:
- 降低耦合度:通过依赖抽象而不是具体实现,降低了类之间的耦合度。
- 提高可维护性:当实现发生变化时,只需要修改相关的实现类,而不需要修改依赖于这些实现的高层模块。
缺点:
- 增加设计复杂度:为了实现依赖倒置原则,需要定义更多的接口和抽象类,增加了设计的复杂度。
- 学习成本增加:对于不熟悉面向接口编程的开发者来说,理解和掌握依赖倒置原则可能需要一定的时间
5.接口隔离原则
一个类不应该依赖它不需要的接口。这要求我们将大的接口拆分成多个小的接口,客户端只需要关心它们需要的接口,从而避免客户端代码依赖于它不需要使用的接口。比如,一个动物类接口中包含了“飞”和“跑”两个方法,但并非所有动物都会飞。因此,我们可以将这个接口拆分为“会飞的动物”和“会跑的动物”两个接口,这样就不会强迫不会飞的动物去实现“飞”这个方法了。
大白话:假设你是一名厨师,你需要使用不同的厨房设备来准备晚餐。如果你有一个集成了所有厨房设备功能的超级大按钮,那么每次使用时你都需要记住每个按钮对应的功能,这将会非常繁琐和容易出错。相反,如果每个设备都有一个独立的开关或按钮,并且这些开关或按钮只控制该设备的特定功能,那么你就可以更加轻松地操作这些设备了。这就是接口隔离原则的一个例子,每个接口只包含客户端需要的方法,从而降低了系统的复杂性。
优点:
- 降低耦合度:通过细化接口,减少了接口之间的依赖关系。
- 提高灵活性:当接口发生变化时,只需要修改相关的接口和实现类,而不需要修改依赖于这些接口的客户端代码。
缺点:
- 接口数量增加:过度细化接口可能会导致接口数量过多,增加管理的复杂性。
- 设计难度增加:需要仔细分析和设计接口之间的关系,以确保接口的合理划分。
6.迪米特原则(最少知识原则)
一个对象应该对其他对象有尽可能少的了解,只与其直接的朋友通信,而不与陌生的对象发生直接的联系,这里的“直接的朋友”通常指的是出现在成员变量、方法参数、方法返回值中的类,而不是通过复杂导航或局部变量等方式间接引用的类。这有助于降低类与类之间的耦合度,提高系统的可维护性。比如,在一个班级管理系统中,学生类只需要知道班级对象的存在,而不需要知道班级中其他学生的具体信息。
大白话:想象你是一名快递员,你的任务是将包裹从A地送到B地。在送包裹的过程中,你只需要关心包裹的收件人地址、联系电话等直接信息,而不需要关心包裹里具体装的是什么东西、是谁寄的等间接信息。这就是迪米特法则的一个例子,你作为快递员只需要与包裹的直接信息进行交互,而不需要了解包裹的更多细节。
优点
- 降低耦合度:通过减少对象之间的直接依赖,迪米特原则有助于降低系统各个组件之间的耦合度。这使得系统更加灵活,易于维护和扩展。
- 提高可维护性:当系统中的组件之间的耦合度降低时,修改一个组件通常不会影响到其他组件,从而提高了系统的可维护性。
- 增强可重用性:通过封装和减少依赖,迪米特原则使得类更加独立,更容易被重用。
- 提高可扩展性:由于系统组件之间的耦合度降低,新的组件可以更容易地被集成到系统中,从而提高系统的可扩展性。
缺点
- 可能导致过度设计:如果过度追求迪米特原则,可能会导致系统中存在过多的类和接口,使得设计变得过于复杂。这可能会增加系统的复杂性和开发成本。
- 增加代码的复杂性:为了遵循迪米特原则,可能需要定义更多的接口和方法,这可能会增加代码的复杂性。开发人员需要花费更多的时间和精力来理解和维护这些代码。
- 降低性能:在某些情况下,为了遵循迪米特原则而引入的中介类或接口可能会增加系统的调用层次,从而降低系统的性能。尤其是在性能敏感的场合下,这种影响可能更加明显。
- 可能增加开发难度:对于初学者或经验不足的开发者来说,理解和应用迪米特原则可能会增加开发的难度。他们需要花费更多的时间来理解设计原则、分析系统结构并编写符合原则的代码。
7.合成/聚合复用原则
尽量使用合成/聚合(比如成员变量)的方式来实现对象之间的复用关系,而不是使用继承关系。因为继承关系会破坏封闭原则,将父类的实现细节暴露给子类。而合成/聚合关系则更加灵活,可以根据需要动态地组合对象,从而实现更好的复用性。
大白话:
假设你正在组装一个书架。书架由多个部分组成,如木板、螺丝和钉子等。这些部分(木板、螺丝等)通过合成关系组合在一起,形成了书架这个整体。在这个过程中,你没有通过继承来复用这些部分的功能,而是通过将它们组装在一起来实现书架的功能。这就是合成复用原则的一个生活实例。
再来看一个家庭与成员的例子。一个家庭由多个成员组成,如父亲、母亲和孩子等。家庭成员之间通过聚合关系联系在一起,他们共同构成了家庭这个整体。但是,每个家庭成员都有自己的生活和职业生涯,他们的生命周期并不完全依赖于家庭的存在。这就是聚合复用原则的一个生活实例。在这个例子中,家庭可以看作是一个容器或集合,它包含了多个独立的成员对象。
优点:
- 提高系统的灵活性:通过合成/聚合关系,可以在运行时动态地组合对象,实现灵活的扩展和复用。
- 降低耦合度:合成/聚合关系比继承关系更松散,降低了类之间的耦合度。
缺点:
- 设计复杂度增加:为了实现合成/聚合复用原则,需要设计更多的类和对象之间的关系。
- 性能影响:在某些情况下,过多的合成/聚合关系可能会导致性能下降,因为需要维护更多的对象关系。
更多推荐
所有评论(0)