Java设计模式7大原则
单一职责原则
单一职责原则是最简单的面向对象设计原则, 它用于控制类的功能粒度大小。单一职责原则定义如下: 单一职责原则(Single Responsibility Principle, SRP): 一个类只负责一个功能领域中的相应职责, 或者可以定义为: 就一个类而言, 应该只有一个引起它变化的原因。承担的职责越多, 它被复用的可能性就越小, 而且一个类承担的职责过多, 就相当于将这些职责耦合在一起, 当其中一个职责变化时, 可能会影响其他职责的运作, 因此要将这些职责进行分离, 将不同的职责封装在不同的类中, 即将不同的变化原因封装在不同的类中, 如果多个职责总是同时发生改变则可将它们封装在同一类中。单一职责原则是实现高内聚、 低耦合的指导方针, 它是最简单但又最难运用的原则, 需要设计人员发现类的不同职责并将其分离, 而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。
开放封闭原则
一个软件实体应当对扩展开放, 对修改关闭,即软件实体应尽量在不修改原有代码的情况下进行扩展。
在开闭原则的定义中, 软件实体可以指一个软件模块、 一个由多个类组成的局部结构或一个独立的类。任何软件都需要面临一个很重要的问题, 即它们的需求会随时间的推移而发生变化。系统需要面对新的需求时, 我们应该尽量保证系统的设计框架是稳定的。 如果一个软件设计符合开闭原则, 那么可以非常方便地对系统进行扩展, 而且在扩展时无须修改现有代码, 使得软件系统在拥有适应性和灵活性的同时具备较好的稳定性和延续性。 随着软件规模越来越大, 软件寿命越来越长, 软件维护成本越来越高, 设计满足开闭原则的软件系统也变得越来越重要。
为了满足开闭原则, 需要对系统进行抽象化设计, 抽象化是开闭原则的关键。 在Java、 C#等编程语言中, 可以为系统定义一个相对稳定的抽象层, 而将不同的实现行为移至具体的实现层中完成。 在很多面向对象编程语言中都提供了接口、 抽象类等机制, 可以通过它们定义系统的抽象层, 再通过具体类来进行扩展。 如果需要修改系统的行为, 无须对抽象层进行任何改动, 只需要增加新的具体类来实现新的业务功能即可, 实现在不修改已有代码的基础上扩
展系统的功能, 达到开闭原则的要求。
李氏替换原则
里氏代换原则由2008年图灵奖得主、 美国第一位计算机科学女博士Barbara Liskov教授和卡内基·梅隆大学Jeannette Wing教授于1994年提出。 其严格表述如下: 如果对每一个类型为S的对象o1, 都有类型为T的对象o2, 使得以T定义的所有程序P在所有的对象o1代换o2时, 程序P的行为没有变化, 那么类型S是类型T的子类型。 这个定义比较拗口且难以理解, 因此我们一般使用它的另一个通俗版定义: 里氏代换原则(Liskov Substitution Principle, LSP): 所有引用基类( 父类) 的地方必须能透明地使用其子类的对象。
里氏代换原则告诉我们, 在软件中将一个基类对象替换成它的子类对象, 程序将不会产生任何错误和异常, 反过来则不成立, 如果一个软件实体使用的是一个子类对象的话, 那么它不一定能够使用基类对象。
例如有两个类, 一个类为BaseClass, 另一个是SubClass类, 并且SubClass类是BaseClass类的子类, 那么一个方法如果可以接受一个BaseClass类型的基类对象base的话, 如: method1(base),那么它必然可以接受一个BaseClass类型的子类对象sub, method1(sub)能够正常运行。 反过来的代换不成立, 如一个方法method2接受BaseClass类型的子类对象sub为参数: method2(sub),那么一般而言不可以有method2(base), 除非是重载方法。
里氏代换原则是实现开闭原则的重要方式之一, 由于使用基类对象的地方都可以使用子类对象, 因此在程序中尽量使用基类类型来对对象进行定义, 而在运行时再确定其子类类型, 用子类对象来替换父类对象。
在使用里氏代换原则时需要注意如下几个问题:
(1)子类的所有方法必须在父类中声明, 或子类必须实现父类中声明的所有方法。 根据里氏代换原则, 为了保证系统的扩展性, 在程序中通常使用父类来进行定义, 如果一个方法只存在子类中, 在父类中不提供相应的声明, 则无法在以父类定义的对象中使用该方法。
(2) 我们在运用里氏代换原则时, 尽量把父类设计为抽象类或者接口, 让子类继承父类或实现父接口, 并实现在父类中声明的方法, 运行时, 子类实例替换父类实例, 我们可以很方便地扩展系统的功能, 同时无须修改原有子类的代码, 增加新的功能可以通过增加一个新的子类来实现。 里氏代换原则是开闭原则的具体实现手段之一。
(3) Java语言中, 在编译阶段, Java编译器会检查一个程序是否符合里氏代换原则, 这是一个与实现无关的、 纯语法意义上的检查, 但Java编译器的检查是有局限的。
依赖倒置原则
如果说开闭原则是面向对象设计的目标的话, 那么依赖倒转原则就是面向对象设计的主要实现机制之一, 它是系统抽象化的具体实现。
依赖倒转原则定义如下: 依赖倒转原则(Dependency Inversion Principle, DIP): 抽象不应该依赖于细节, 细节应当依赖于抽象。 换言之, 要针对接口编程, 而不是针对实现编程。
依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中, 尽量引用层次高的抽象层类, 即使用接口和抽象类进行变量类型声明、 参数类型声明、 方法返回类型声明, 以及数据类型的转换等, 而不要用具体类来做这些事情。 为了确保该原则的应用, 一个具体类应当只实现接口或抽象类中声明过的方法, 而不要给出多余的方法, 否则将无法调用到在子类中增加的新方法。
在引入抽象层后, 系统将具有很好的灵活性, 在程序中尽量使用抽象层进行编程, 而将具体类写在配置文件中, 这样一来, 如果系统行为发生变化, 只需要对抽象层进行扩展, 并修改配置文件, 而无须修改原有系统的源代码, 在不修改的情况下来扩展系统的功能, 满足开闭原则的要求。
接口隔离原则
接口隔离原则定义如下:接口隔离原则(Interface Segregation Principle, ISP): 使用多个专门的接口, 而不使用单一的总接口, 即客户端不应该依赖那些它不需要的接口。
根据接口隔离原则, 当一个接口太大时, 我们需要将它分割成一些更细小的接口, 使用该接口的客户端仅需知道与之相关的方法即可。 每一个接口应该承担一种相对独立的角色, 不干不该干的事, 该干的事都要干。
合成复用原则
合成复用原则(Composite Reuse Principle, CRP): 尽量使用对象组合, 而不是继承来达到复用的目的。
合成复用原则就是在一个新的对象里通过关联关系( 包括组合关系和聚合关系) 来使用一些已有的对象, 使之成为新对象的一部分; 新对象通过委派调用已有对象的方法达到复用功能的目的。 简言之: 复用时要尽量使用组合/聚合关系( 关联关系) , 少用继承。
迪米特法则
迪米特法则又称为最少知识原则(LeastKnowledge Principle, LKP), 其定义如下:
迪米特法则(Law of Demeter, LoD): 一个软件实体应当尽可能少地与其他实体发生相互作用。
如果一个系统符合迪米特法则, 那么当其中某一个模块发生修改时, 就会尽量少地影响其他模块, 扩展会相对容易, 这是对软件实体之间通信的限制, 迪米特法则要求限制软件实体之间通信的宽度和深度。 迪米特法则可降低系统的耦合度, 使类与类之间保持松散的耦合关系。