面向对象的特征和设计原则
转载自:https://blog.csdn.net/rankun1/article/details/50789571 以及https://www.cnblogs.com/corvoh/p/5747856.html
面向对象三大特征【转载】
封装
把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
这段话我觉得描述封装非常好:基本的变量已经不再浮游于一大段一大段的程序中了,它们已经放弃了(其实是程序员不用这种方式了)这种自由自在的存在方式,而是安稳的寄 居于庞大而蹒跚的“对象”内部,与外界隔开来,通过迂回曲折的间接途径与外部世界联系和通信。而这些对象,就是它们这些基本变量的生存机器!在面向过程的开发中,变量被暴露在整个程序中,不小心的一个修改就可能导致整个程序出错。 所以封装有利于我们让自己的程序更健壮。
继承
多态
UML概念讲解
泛化:体现在编程语言中,就是继承,而泛化是UML中的一个术语。假设类A和类B,若在逻辑上类B是类A的一种(比如男人是一种人),则允许类B继承类A的功能和属性。
组合:类A和类B,如果逻辑上类A是类B的一部分(a part of),则不允许类B从类A派生,例如眼睛、鼻子、口、耳都是头的一部分,那么头是不能从其中任何一个继承的,但是眼睛、鼻子、口、耳则可以组合出头。
聚合:聚合的类型分为无、共享(聚合)、复合(组合)三种。
共享(聚合:aggregation):在UML中使用空心菱形表示聚合关系,聚合是一种has a的关系,如下图所示:
这表示一种依赖关系,意味着类A被修改,类B将受到影响。
面向对象的设计原则
单一职责原则SRP(Single Responsibility Principle)
是指一个类的功能要单一,不能包罗万象。(就一个类而言,应该仅有一个引起它变化的原因)。
开放-封闭原则OCP(Open-Close Principle)
是说软件实体(类、模块、函数等等)应该可以扩展,但是不可更改。目的是在面对需求的变更时,能够使系统保持相对稳定,可以进行版本的迭代。
Liskov替换原则(Liskov-Substituion Principle)
其核心思想是:子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类。
Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。
Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。
Liskov替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。
依赖倒置原则(Dependecy-Inversion Principle)
其实就是针对接口编程,其核心思想是:高层模块不应该依赖低层模块,两个都应该依赖抽象。抽象不应该依赖袭击。细节应该依赖抽象。例如:为了方便使用数据库,我们把常用代码封装成函数库,这样在做项目时,需要访问数据库就去调用这些低层函数就可以,这就叫做高层依赖低层。但是现在,如果新的项目,我们发现高层的业务逻辑是一致的,但是客户希望使用另外的数据库,这样我们就没办法复用这些高层模块了,因为它和具体的数据库捆绑在了一起。那么处理办法就是在设计时,将低层和高层解耦,低层向高层提供稳定的接口,那么只要接口不发生变化,任何的更改都不担心引起其它不好的影响。而此时低层模块和高层模块都应该依赖于抽象,简单的说就是依赖接口或者抽象类。这样无论是低层模块还是高层模块都能得到复用。
迪米特法则(Lod)----这不是面向对象的5个设计原则之一
也叫最小知识原则。如果两个类不必彼此直接通信,那么这两个类就不应单发生直接的相互作用。如果其中一个类需要调用另一个类的某个方法的话,可以通过第三者转发这个调用。首先,在类的结构设计上,每一个类应该尽量降低成员的访问权限。迪米特法则的根本就是强调了类之间的松耦合。
ISP 接口隔离原则(Interface-Segregation Principle)
其核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口。
具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。
接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。
分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。
这个原则的意思是:使用多个专门的接口比使用单个接口要好的多!
为了减少接口的定义,将许多类似的方法都放在一个接口中,最后发现,维护和实现接口的时候花了太多精力,而接口所定义的操作相当于对客户端的一种承诺,这种承诺当然是越少越好,越精练越好,过多的承诺带来的就是你的大量精力和时间去维护!
模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来。
耦合
简单地说,软件工程中对象之间的耦合度就是对象之间的依赖性。指导使用和维护对象的主要问题是对象之间的多重依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。
有软硬件之间的耦合,还有软件各模块之间的耦合。
耦合性是程序结构中各个模块之间相互关联的度量。它取决于各个模块之间的接口的复杂程度、调用模块的方式以及哪些信息通过接口。
耦合可以分为以下几种,它们之间的耦合度由高到低排列如下:
- 内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
- 公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
- 外部耦合 。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
- 控制耦合 。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
- 标记耦合 。若一个模块A通过接口向两个模块B和C传递一个公共参数,那么称模块B和C之间存在一个标记耦合。
- 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
- 非直接耦合 。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
总结
耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。