java面向对象设计原则-软件项目中可维护性较低的原因有哪些?
软件项目中,需求是不断变化的,需求也是项目中最难把控的,需求的变更也是无法避免的。我们写的软件程序,如何能实现拥抱变化,使我们的软件达到可维护和可复用,这是一代代软件工程师不断追寻的真理。
导致一个软件的可维护性较低的原因有四个:
1、过于僵硬(Rigidity):比如在系统中新增一个功能,会变得非常复杂,涉及到很多模块的调整,这就是系统僵硬的体现。
2、过于脆弱(Fragility):比如对程序中某一个地方的修改,导致看上去没有什么关系的其他地方产生了影响,修改的同时,没有人能预测改动会给系统带来什么风险。
3、复用率低(Immobility):比如想使用程序中已有的一段代码、函数、模块时,这些已有的代码总是依赖一大堆其它的东西,很难将它们独立出来使用。
4、黏度过高(Viscosity):如果一个系统设计,不能简单的复用一个类或者通过接口来实现扩展,想扩展一个系统功能,必须破坏原始架构,就是黏度过高。
一个好的系统设计应该有如下的性质:
1、可扩展性(Extensibility):可以很容易的在系统中加入一个新的功能。
2.灵活性(Flexibility):可以很容易的实现对某个代码的修改,而不担心对其他模块产生影响。
3.可插入性(Pluggability):可以很容易的将一个类抽出去复用,或者将另一个有同样功能的接口的类加入到系统里。
常用的面向对象设计原则有6个,这6大设计原则都是以可维护性和可复用性为基础的,这些原则并不是孤立存在的,它们相互依赖相互补充,遵循这些设计原则可以有效地提高系统的复用性,同时提高系统的可维护性。
1
开闭原则
(Open-Closed Principle,OCP)
1、开闭原则定义
开闭原则:一个软件实体如类、模块应该对扩展开放,对修改关闭。即在不修改软件实体的基础上去扩展其功能。
Open(Open for extension):软件实体的行为必须是开放的、支持扩展的,而不是僵化的。
Closed(Closed for modification):在对软件实体的功能进行扩展时,不能修改已有的程序模块。
绝大部分的设计模式都符合开闭原则,开闭原则要求开发人员可以在不修改系统中现有的功能代码的前提下,而实现对应用系统的软件功能进行扩展。即要求我们应尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。它是为了适应未来的变化而对现有开发设计进行约束的一个原则。
2、开闭原则分析
开闭原则对扩展开放,对修改关闭,并不意味着我们的代码不能做任何更改,底层模块的变更,必然导致高层模块进行适配调整。
开闭原则是最基础的一个原则,抽象化是开闭原则的关键。
1、通过接口或者抽象类对扩展进行约束,不允许出现在接口或者抽象类中不存在的public方法。
2、参数类型,引用对象尽量使用接口或者抽象类,而不是具体的实现类。
3、抽象层尽量保持稳定,一旦确定就不允许修改。
2
(Single Responsibility Principle,SRP)
1、单一职责原则定义
单一职责原则的意思是:类的职责要单一java面向对象设计原则,不能将太多的职责放在一个类中。单一职责原则要求一个接口或类只能有一个原因引起变化,也就是一个接口或者类只能由一个职责。
2、单一职责原则分析
单一职责原则是高内聚性原则,避免相同的职责(也成为功能)分散到不同的类中实现,避免一个类承担过多的职责。可以减少类之间的耦合。
单一职责原则适用于接口,类,同时也适用于方法,即一个方法尽可能只做一件事情。
一般单一职责原则很难在项目中体现,因为单一职责的标准是用“职责”和“变化原因”来衡量一个接口或者类设计的是否优雅,但是“职责”和“变化原因”是不可度量的,项目或需求不同,会有不同的体现。
再者,本来一个类可以实现的功能,按照单一职责拆分成多个类,然后再用聚合或者组合的方式耦合起来,这样增加了系统的复杂性。
因此,对应单一职责原则,不能一味地追求职责单一化,适可而止就行。
3
里氏替换原则
(Liskov Substitution Principle, LSP)
1、里氏替换原则定义
里氏替换原则的意思是:在软件系统中,一个可以接受基类对象的地方必然可以接受一个子类对象,即凡是父类能出现的地方,子类都可以进行替代。
把基类替换成它的子类,程序将不会产生任务错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类的话,那么它不一定能够使用基类。
里氏替换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
2、里氏替换原则分析
里氏替换原则主要是针对继承的设计原则,它为良好的继承定义了一个规范。
1、子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
2、子类可以增加自己特有的方法。
3、当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)可以放大(比父类方法的输入参数更宽松)。
4、当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)可以被缩小(比父类更严格)。
如果父类的某些方法在子类中已经不能使用,则建议断开父子继承关系,采用依懒,聚合,组合关系代替继承。
4
2
依懒倒置原则
(Dependency Inversion Principle, DIP)
1、依懒倒置原则定义
依懒倒置原则的意思是:高层模块不应该依懒底层模块,它们都应该依懒抽象,抽象不应该依懒于细节,细节应该依懒于抽象。
另外一种表述为:要针对抽象层编程,而不要针对具体实现类编程。
2、依懒倒置原则分析
1、如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要手段。
2、依赖倒置原则的常用实现方式之一是在代码中使用抽象类,而将具体类放在配置文件中。
类之间的耦合关系
1、零耦合关系
2、具体耦合关系
3、抽象耦合关系
依赖倒置原则要求客户端依赖与抽象耦合,以抽象方式耦合是依赖倒转原则的关键。
依懒倒置就是编写程序需要的是对现实世界的事物进行抽象,抽象的结果就是有了抽象类和接口,然后我们用抽象间的依懒代替了实实在在的实现类间的依懒。
依懒的三种写法
1、构造函数传递依懒对象,也叫构造函数注入
2、Setter方法传递依懒对象,也叫Setter依懒注入
3、接口声明依懒对象,也叫接口注入,就是在接口的方法参数中声明依懒对象。
5
接口隔离原则
(Interface Segregation Principle, ISP)
1、接口隔离原则定义
接口隔离原则的意思是:客户端不应该依赖那些它不需要的接口,一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。
接口隔离原则要求我们不要建立功能丰富的庞大接口java面向对象设计原则,使用多个专门的接口来取代一个统一的接口。每一个接口应该承担一种相对独立的角色,不多不少,不干不该干的事,该干的事都要干。
接口隔离原则告诉我们:使用多个隔离的接口,比使用单个接口要好,尽量降低类之间的耦合度。
2、接口隔离原则分析
1、一个接口就只代表一个角色,每个角色都有它特定的一个接口。
2、接口仅仅提供客户端需要的行为,即所需的方法,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。
3、使用接口隔离原则拆分接口时,首先必须满足单一职责原则,将一组相关的操作定义在一个接口中,且在满足高内聚的前提下,接口中的方法越少越好。
6
迪米特法则
(Law of Demeter, LoD)
1、迪米特法则的定义
迪米特法则又称最少知识原则(Least Knowledge Principle, LKP),它有多种定义方法,其中几种典型定义如下:
1、不要和“陌生人”说话。
2、只与你的直接朋友通信。
3、 每一个软件实体对其他的实体都只有最少的知识,而且局限于那些与本实体密切相关的软件实体。
迪米特法则的意思是:一个软件实体对其他实体的引用越少越好,或者说如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,而是通过引入一个第三者发生间接交互。
2、迪米特法则的分析
迪米特法则的核心观念就是创建松耦合的类。简单地说,迪米特法则就是指一个软件实体应当尽可能少的与其他实体发生相互作用。这样,当一个模块修改时,就会尽量少的影响其他的模块,扩展会相对容易。
在迪米特法则中,对于一个对象,其朋友包括以下几类:
(1) 当前对象本身(this);
(2) 以参数形式传入到当前对象方法中的对象;
(3) 当前对象的成员对象;
(4) 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
(5) 当前对象所创建的对象。
任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。
7
合成复用原则
(Composite Reuse Principle, CRP)
1、合成复用原则的定义
合成复用原则又称为组合/聚合复用原则(Composition/ Aggregate Reuse Principle, CARP),其定义如下:尽量使用对象组合,而不是继承来达到复用的目的。
合成复用原则就是指在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用其已有功能的目的。简言之:要尽量使用组合/聚合关系,少用继承。
2、合成复用原则的分析
在面向对象设计中,可以通过继承或者组合/聚合关系,在不同的环境中复用已有的设计和实现。
继承复用:实现简单,易于扩展。破坏系统的封装性;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;只能在有限的环境中使用。又叫“白箱”复用。
组合/聚合复用:耦合度相对较低,选择性地调用成员对象的操作;可以在运行时动态进行。又叫“黑箱”复用。
组合/聚合可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用。
在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。
8
3
结束语
软件设计的根本就是要应对需求的变化,实际项目中需求变化又是不可预料的,面向对象设计给我们总结了6大设计原则来应对未来的变化:
Single Responsibility Principle:单一职责原则
Open Closed Principle:开闭原则
Liskov Substitution Principle:里氏替换原则
Law of Demeter:迪米特法则
Interface Segregation Principle:接口隔离原则
Dependency Inversion Principle:依赖倒转原则
把这6个原则的首字母联合起来就是Solid(稳定的),也就是说把这6个原则结合起来使用就可以建立稳定、灵活、可扩展、可复用的系统
最后,我自己是一名从事了多年开发的Java老程序员,辞职目前在做自己的Java私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的Java学习干货,可以送给每一位喜欢Java的小伙伴,想要获取的可以关注我的头条号并在后台私信我:01,即可免费获取。