基础原则
类与类之间主要有6种关系,他们分别是:依赖、关联、聚合、组合、继承(泛化)、实现。他们的耦合度依次增强。
依赖关系(dependence):假设A类的变化引起了B类的变化,则说名B类依赖于A类。对于两个相对独立的对象,当一个对象负责构造另一个对象的实例,或者依赖另一个对象的服务时,这两个对象之间主要体现为依赖关系。类A当中使用了类B,其中类B是作为类A的方法参数、方法中的局部变量、或者静态方法调用。大多数情况下,依赖关系体现在某个类的方法使用另一个类的对象作为参数。
依赖关系有如下三种情况:
1、A类是B类中的(某个方法的)局部变量;
2、A类是B类方法当中的一个参数;
3、A类向B类发送消息,从而影响B类发生变化;
关联关系(association):两个相对独立彼此不受影响的类,一个类的实例与另一个类的一些特定实例存在固定的对应关系。它是一种结构化关系,用于表示一类对象与另一类对象之间有联系。通常将一个类的对象作为另一个类的属性。
单向关联:类A当中使用了类B,其中类B是作为类A的成员变量。
双向关联:类A当中使用了类B作为成员变量;同时类B中也使用了类A作为成员变量。
自关联: 在系统中可能会存在一些类的属性对象类型为该类本身,这种特殊的关联关系称为自关联。
重数性关联: 重数性关联关系又称为多重性关联关系(Multiplicity):表示一个类的对象与另一个类的对象连接的个数。在UML中多重性关系可以直接在关联直线上增加一个数字表示与之对应的另一个类的对象的个数。
表示方式 | 多重性说明 |
1..1 或简写为 1 | 表示类的一个对象只与另一个类的一个对象有关联 |
0..* | 表示类的一个对象与另一个类的零个或多个对象有关联;或只写 0 表示没有关联 |
1..* | 表示类的一个对象与另一个类的一个或多个对象有关联 |
0..1 | 表示类的一个对象与另一个类的零个或一个对象有关联 |
m..n | 表示类的一个对象与另一个类的最少m、最多n个对象有关联 (m<=n) |
聚合关系(aggregation):是关联关系的一种,耦合度强于关联,他们的代码表现是相同的,仅仅是在语义上有所区别:关联关系的对象间是相互独立的;而聚合关系的对象之间存在着包容关系,他们之间是“整体-部分”的相互关系,即has-a的关系,整体与部分可以分离的,可以具有各自的生命周期,部分可以脱离整体对象独立存在。
组合(composition):一种耦合度更强的关联关系,也称强聚合。表示“整体-部分”的关联关系,“整体”负责“部分”的生命周期,他们之间是共生共死的,整体的生命周期结束也就意味着部分的生命周期结束;并且“部分”单独存在时没有任何意义。比如在构造函数中实例化了Child
继承(泛化 generalization):表示类与类(或者接口与接口)之间的父子关系。
实现关系(Implementation):是用来规定接口和实线接口的类或者构建结构的关系,接口是操作的集合,而这些操作就用于规定类或者构建的一种服务。
表示一个类实现一个或多个接口的方法。接口定义好操作的集合,由实现类去完成接口的具体操作。
- 抽象--核心思想是不变性的概念。去除不关心的属性,而强化重要的属性,帮助人们思考要做什么。
- 封装--核心是分离关注点和信息隐藏,让程序借助最少的工作进行可靠的修改。
- 模块化--核心思想是分而治之,各个模块应当高内聚、低耦合。
- 层次结构--核心是对抽象的分级和排序,可以简化对系统的理解。
软件设计原则:多聚合,少继承;低耦合,高内聚; 面向抽象编程;封装变化;实现重用(代码/算法重用)。
低耦合:实现最简单的依赖关系,尽可能减少类/模块/层次/系统发生修改对其它类/模块/层次/系统造成影响,将影响范围简单化。比如实现单向依赖,实现抽象的耦合;
高内聚:体现在以隔离为目标进行同一管理,一方面代表职责的统一管理,一方面体现了关系的有效隔离;
SOLID原则
单一职责原则(Single Responsibility Principle, SRP)
核心思想:一个类只负责一个功能领域中的相应职责,应该只有一个引起它变化的原因。核心就是解耦和增强内聚性。
一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,导致职责依赖;因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。单一职责,通常意味着单一的功能,因此不要为类实现过多的功能点,以保证只有一个引起它变化的原因;
通常以Facade模式和Proxy模式分离职责;
开闭原则(Open-Closed Principle, OCP)--对扩展开放,对修改关闭
一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展(软件实体可以指一个软件模块、一个由多个类组成的局部结构或一个独立的类)。是SRP很好的补充。抽象编程是开闭原则的关键。是最重要的原则,里氏替换原则和合成/聚合复用原则为开放闭合原则的实现提供保证;
通过接口、抽象类等机制定义系统的抽象层,再通过具体类来进行扩展,让类依赖于固定的抽象,所以对修改是封闭的。如果需要修改系统的行为,无须对抽象层进行任何改动,只需要增加新的具体类覆写方法来实现新的业务功能即可,实现在不修改已有代码的基础上扩展系统的功能,达到开闭原则的要求。使得软件系统在拥有适应性和灵活性的同时具备较好的稳定性和延续性。
通常以Template Method 模式和Strategy模式实现;封装变化,是实现开放封闭原则的重要手段;
里氏代换原则(Liskov Substitution Principle,LSP)
子类可以扩展父类的功能,但不能改变父类原有的功能;是实现开闭原则的重要方式之一,在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
里氏代换原则核心思想:子类必须能够替换其基类,但基类不一定能够替换子类。
LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
是关于继承机制的应用原则,是实现开闭原则的具体规范;
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类中可以增加自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
接口隔离原则(Interface Segregation Principle,ISP)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。强调接口的单一性;
接口隔离的手段:1.委托分离,通过增加一个新的类型来委托客户的需求,隔离客户和接口的直接依赖,但会增加系统的开销;2.多重继承隔离,通过接口多继承来实现客户需求,推荐使用;
接口隔离原则跟单一职责原则区别:
其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。
其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。
依赖倒置原则(Dependency Inversion Principle,DIP):
依赖倒置体现在两个方面:
高层模块不应该依赖于低层模块,二者都应该依赖其抽象;抽象的稳定性决定了系统的稳定性,因为抽象是保存不变的;
抽象不应该依赖具体,具体应该依赖抽象。即相互依赖的组件应该通过抽象来进行交互,而不是直接通过具体来实现;
核心思想:通过抽象机制有效解决类层次之间的关系,降低耦合的粒度,实现对抽象的依赖;对接口编程;面向抽象编程;
依赖倒置是实现开发封闭原则的前提和基础,为有效解决层次之间的依赖关系寻求了最佳方案;
依赖倒置是对抽象和依赖的基本原则和基本思想的哲学解释;
依赖倒置是框架设计的核心思想;
依赖倒置是控制反转和依赖注入的思想基础;
迪米特法则,又称最少知道原则(Law of Demeter,LoD):
一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。尽量降低类与类之间的耦合。
一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。
合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
尽量使用合成/聚合的方式,而不是使用继承。在新对象中聚合已有的对象,使之成为新对象的成员。从而通过操作这些对象达到复用的目的;
分离关注点(Separation of concerns,SoC)原则
定义了实现低内聚高耦合目标的同一规则;
一个关注点就是一个特定的目标或概念、一块我们感兴趣的的区域;
分离关注点:将跨多个组件的应用程序或用例责任分离开来,每个组件都有自己的特定职责。“关注”可以用一组特定的功能或行为相关联;
分离关注点对只与关注点相关联的软件组成部分进行标识、封装和操纵关注点的能力;
分离关注点可以产生高内聚、低耦合的系统;
软件系统,可看作由一组关注点组成。直切关注点,直接的业务关注点。
横切关注点(cross-cutting concerns):为直切关注点提供服务(系统级的关注点);
系统级的关注点有日志、事务完整性、授权、安全性及性能问题等许多关注点,会在多个模块中出现;
AOP(Aspect-Oriented Programming)面向切面编程:
可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需 要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。在更高的抽象层次上以模块化横切关注点为目标的新型程序设计范型。