软件开发设计模式,不见得都会用到,但不得不知道。
设计模式是经验总结的成果,说里边蕴藏着多妙的思想也好,说已经成为开发的规范也罢,无非就是提升系统稳定性与可维护性的手段。
它们是设计方法,但也不是全能的。
现在编程世界里出现众多框架,约定规范,日趋完善的IDE,归根结底就是为了效益。低成本,高效率,是最终目的。
java中有23种设计模式,都是围绕六大设计原则的具体运用。
六大设计原则看起来容易理解,实际实现起来也偶有冲突,取舍适当即可。
那就开始知道,皮毛也是毛,蚂蚁肉也是肉!
开闭原则
软件实体应当对扩展开放,对修改关闭。软件开发的终极目标。
当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。
软件实体包括:项目划分的模块,类/接口,方法。
实现方法:
“抽象约束、封装变化”。通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。
好处:
- 软件测试可以只测扩展的代码,不必重复测试基础代码
- 提升可复用性
- 提高软件的可维护性
里氏替换原则
更愿意称之为继承原则。继承必须确保超类所拥有的性质在子类中仍然成立。
通俗来讲,子类可以扩展父类的功能,但不能改变父类原有的功能。子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
实现方法:
使用继承时,遵循里氏替换原则。类T2继承类T1时,除添加新的方法完成新增功能P2外,尽量不要重写父类T1的方法,也尽量不要重载父类T1的方法。
例外:当父类存在缺省实现的方法时;需要实现多态时要求重写/重载;部分子类继承父类(类方法体为空)时,必须要求重写。
好处:
继承原则实际上保证父类的可复用性,是继承复用的基础。
依赖倒置原则
核心要义是面向接口编程,不要面向实现编程。高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
在软件设计中,细节具有多变性,而抽象层则相对稳定,因此以抽象为基础搭建起来的架构要比以细节为基础搭建起来的架构要稳定得多。这里的抽象指接口或抽象类,细节指具体实现类。
使用接口或者抽象类的目的是制定好规范和契约。
实现方法:
- 每个类尽量提供接口或抽象类,或者两者都具备
- 变量的声明类型尽量是接口或者是抽象类
- 任何类都不应该从具体类派生
- 使用继承时尽量遵循里氏替换原则
好处:
- 降低类之间的耦合性
- 提高系统稳定性
- 减少并行开发引起的风险
- 提高代码的可读性与可维护性
单一职责原则
就是说每个类的功能相对独立。单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。
若一个类T有职责A、B,当需要变更职责A时,更改类T可能会影响职责B的正常应用
实现方法:
看似简单实则不易,只是需要优秀设计。需要设计人员发现类的不同职责并将其分离,再封装到不同的类或模块中。考验分析设计能力和重构经验。
好处:
- 降低类的复杂度
- 提高类的可读性
- 提高系统的可维护性
- 变更引起的风险降低
接口隔离原则
一个类对另一个类的依赖应该建立在最小的接口上。客户端不应该被迫依赖于它不使用的方法。程序员需要尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。
要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想。
但单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
且单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。
实现方法:
- 接口尽量小,但是要有限度
- 为依赖接口的类定制服务
- 接口拆分的标准需要深入了解业务逻辑
- 使接口用最少的方法去完成最多的事情
好处:
- 粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性
- 高了系统的内聚性,降低了系统的耦合性
- 能减少项目工程中的代码冗余(过大的大接口里面通常放置许多不用的方法,这些方法可能被冗余实现)
注意接口的粒度。如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。
迪米特原则
只与你的直接朋友交谈,不跟“陌生人”说话。最少知识原则,只知道朋友。如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。
这里的朋友是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
目的就是降低类之间的耦合度,提高模块的相对独立性。
实现方法:
从依赖者的角度来说,只依赖应该依赖的对象。从被依赖者的角度说,只暴露应该暴露的方法。
- 在类的结构设计上,尽量降低类成员的访问权限
- 在类的设计上,优先考虑将一个类设置成不变类
- 在对其他类的引用上,将引用其他对象的次数降到最低。
- 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)
- 谨慎使用序列化(Serializable)功能
好处:
- 降低了类之间的耦合度,提高了模块的相对独立性
- 由于亲合度降低,从而提高了类的可复用率和系统的扩展性
过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。
合成复用原则
组合/聚合复用原则。在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。
通常类的复用分为继承复用和合成复用两种。
继承复用也叫“白箱”复用,简单易实现。但
- 子类与父类的耦合度高
- 继承会将父类的实现细节暴露给子类(父类透明)
- 从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化
组合或聚合复用,将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象。这样
- 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用
- 新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口
- 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象
实现方法:
合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。就是需要某类里边的方法,不用去继承该类,直接以其对象作为本类的成员,再进行调用。