1 设计模式概述
1.1 设计模式的定义与分类
设计模式的定义
Design patterns are descriptions of communicating objects and classes that are customized to solve a general design problem in a particular context.
翻译过来就是:设计模式是在特定环境下为了解决某一通用软件设计问题提供的一套定制的解决方案,该方案描述了对象和类之间的相互作用。
它是一套被反复使用的代码设计经验总结,使用设计模式的目的是代码可重用,让代码更容易被他人理解并提高代码的可靠性。
1.2 设计模式的分类
1.2.1 根据目的分类
- 创建型模式:主要用于创建对象,包括工厂模式,抽象工厂模式,建造者模式,原型模式和单例模式 。
- 结构型模式:主要用于处理类或对象的组合,包括适配器模式,桥接模式,组合模式,装饰模式,外观模式,享元模式和代理模式 。
- 行为型模式:主要用于描述类或对象怎样交互和分配职责,包括职责链模式,命令模式,解释器模式,迭代器模式,中介者模式,备忘录模式,观察者模式,状态模式,策略模式,模板方法模式和访问者模式 。
1.2.2 根据范围分类
- 类模式:处理类和子类之间的关系,这些关系通过继承建立,在编译时就被确定,属于静态关系。
- 对象模式:处理对象间的关系,这些关系在运行时变化,具有动态性。
1.3 设计模式的优点
设计模式是从许多优秀的软件系统中总结出的成功的、能够实现可维护性复用的设计方案,使用这些方案可以避免做一些重复性的工作,而且可以设计出高质量的软件系统。
2 面向对象设计原则
2.1 面向对象设计原则概述
软件的可维护性和可复用性是两个非常重要的软件质量衡量属性。可维护性指的是软件能够被理解、改正、适应及扩展的难易程度,可复用性是指软件能够被重复使用的难易程度。
面向对象设计的目标之一在于支持可维护性复用,一方面需要实现源代码的复用,另一方面要确保系统能够易于扩展和修改。面向对象设计原则为支持可维护性复用而诞生,这些原则蕴含在很多设计模式之中。
2.2 单一职责原则
Single Responsibility Principle: Every object should have a single responsibility, and that responsibility should be entirely encapsulated by the class.
翻译过来就是:一个对象应该只包含单一的职责,并且该职责被完整的封装在一个类中。
单一职责原则用于控制类的粒度大小,它还有另一种定义方式:就一个类而言,应该只有一个引起它变化的原因。
单一职责原则是实现高内聚,低耦合的指导原则,需要发现类的不同职责并将其分离。
2.3 开闭原则
Open-Closed Principle: Software entities should be open for extension, but closed for modification.
翻译过来就是:软件实体应该对扩展开放,对修改关闭。
软件实体可以是一个软件模块,一个由多个类组成的局部结构或者一个独立的类,开闭原则就是指软件实体应该尽量在不修改原有代码的情况下进行扩展。
2.4 里氏代换原则
Liskov Substitution Principle: Functions that use pointers or references to base classes must be able to use objects of derived classes without knowong it.
翻译过来就是:所有引用基类的地方必须能透明地使用其子类的对象。
里氏代换原则表明,在软件中将一个基类对象替换成它的子类对象,程序将被不会产生任何错误和异常,反过来则不成立。
里氏代换原则是实现开闭原则的重要方式之一,由于在使用基类的地方都能使用子类对象,因此在程序中应尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
2.5 依赖倒转原则
Dependence Inversion Principle: High level modules should not depend upon low level modules, both should depend upon abstractions. Abstractions should not depend upon details, details should depend upon abstractions.
翻译过来就是:高层模块不应该依赖低层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
依赖倒转原则要求针对接口编程,不要针对实现编程。
依赖倒转原则要求在程序代码中传递参数或者关联对象尽量引用层次高的抽象层,即使用接口或者抽象类进行变量类型声明、参数类型声明、方法返回类型声明等,而不用具体类。为了确保依赖倒转原则的应用,一个具体类应该只实现接口或抽象类中声明过的方法,而不用给出多余的方法,否则将无法调用在子类中增加的新方法。
在实现依赖倒转原则时需要针对抽象层编程,而将具体类的对象通过依赖注入的方式注入到其他对象中。常用的注入方式有3种,构造注入、设值注入和接口注入。
- 构造注入:通过构造函数传入具体类的对象
- 设值注入:通过 Setter 方法传入具体类的对象
- 接口注入:通过在接口中声明的业务方法传入具体类的对象
这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。
2.6 接口隔离原则
Interface Segregation Principle: Clients should not be forced to depend upon interfaces that they do not use.
翻译过来就是:客户端不应该依赖那些它不需要的接口。
根据接口隔离原则,当一个大接口太大时需要将它分割成一些更细小的接口,使用该接口的客户端只需知道与之相关的方法即可。
在面向对象编程语言中,实现一个接口需要实现该接口中定义的所有方法,因此大的接口使用起来不一定方便,为了使接口的职责单一,需要将大接口中的方法根据其职责不同分别放在不同的小接口中,以确保每个接口使用起来都较为方便,并承担某一单一角色。
2.7 合成复用原则
Compsite Reuse Principle: Favor composition of objects over inheritance as a reuse mechanism.
翻译过来就是:优先使用对象组合,而不是通过继承来达到复用的目的。
合成复用原则就是在一个新对象里通过关联关系(组合/聚合)来使用一些已有的对象,使之成为新对象的一部分,新对象通过委派调用已有对象的方法来达到复用功能的目的。即在复用时尽量使用组合/聚合关系,少用继承。
在面向对象设计中可以通过两种方法复用已有的设计和实现,即组合/聚合关系或继承。但首先应该考虑使用组合/聚合关系,将已有的对象纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能,且可以保持已有对象的内部实现细节对于新对象不可见,使系统更加灵活,降低类与类之间的耦合度。
通过继承来复用的主要问题是会破坏系统的封装性,因为继承会将基类的实现细节暴露给子类,而且如果基类发生改变,子类的实现也不得不发生改变;从基类继承而来的实现是静态的,不能在运行时发生改变,没有足够的灵活性。
2.8 迪米特法则
Law of Demeter: Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
翻译过来就是:每一个软件单元对其他单元都只有最少的信息,而且局限于那些与本单元密切相关的软件单元。
迪米特法则要求一个软件实体应该尽可能少地与其他实体发生相互作用。如果一个系统符合迪米特法则,那么当其中某一个模块发生变化时就会尽量少的影响其他模块,扩展会相对容易。应用迪米特法则可降低系统的耦合度,使类和类之间保持松散的关系。
在迪米特法则中,与本单元密切相关的单元括以下几类:
- 当前对象本身
- 以参数形式传入当前对象方法中的其他对象
- 当前对象的成员对象
- 当前对象所创建的对象