设计模式的使用并不一定会提升效率,我们需要根据实际情况来判断是否需要使用设计模式。在很多时候,只要遵循这些OO原则来写代码,也相当于使用了设计模式。毕竟设计模式就是根据这些OO原则形成的一套规范。
1,单一职责原则(Single Responsibility Principle,SRC)
责任驱动的设计(RDD—responsibility drivendesign)
1)定义
一个类,应该只有一个引起它变化的原因。
注意:这个变化是同一个行为的变化,如果行为不同,那么违反了SRC原则。
2)举例
- 再bean里面又加入了逻辑处理,违反了类的单一职责原则。
2,开闭原则(OCP)
1)定义
Software entities like classes,modules and functions should be open for extension but closed for modifications.
(一个软件实体如类、模块和函数应该:对扩展开放,对修改关闭。)
即:用抽象构建框架,用实现扩展细节
2)实现方法
A. 抽象约束
通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。抽象是对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义:
- 通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法;
- 参数类型、引用对象尽量使用接口或者抽象类,而不是实现类;
- 抽象层尽量保持稳定,一旦确定即不允许修改。
B. 封装变化
对变化的封装包含两层含义:
- 将相同的变化封装到一个接口或抽象类中;
- 将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。
封装变化,也就是受保护的变化(protected variations),找出预计有变化或不稳定的点,我们为这些变化点创建稳定的接口,准确地讲是封装可能发生的变化,一旦预测到或“第六感”发觉有变化,就可以进行封装。
23个设计模式都是从各个不同的角度对变化进行封装的。
3,里氏替换原则(Liskov Substitution Principle,LSP)
1)定义
所有引用基类的地方必须能透明地使用其子类的对象。
通俗点讲:只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。即:子类可以扩展父类的功能,但不能改变父类原有的功能。
为了保障该原则,进行如下限制:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
- 子类中可以增加自己特有的方法
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
2)举例
鸟一般都会飞行,如燕子的飞行速度大概是每小时 120 千米。但是新西兰的几维鸟由于翅膀退化无法飞行。假如要设计一个实例,计算这两种鸟飞行 300 千米要花费的时间。显然,拿燕子来测试这段代码,结果正确,能计算出所需要的时间;但拿几维鸟来测试,结果会发生“除零异常”或是“无穷大”,明显不符合预期。
程序运行错误的原因是:几维鸟类重写了鸟类的 setSpeed(double speed) 方法,这违背了里氏替换原则。正确的做法是:取消几维鸟原来的继承关系,定义鸟和几维鸟的更一般的父类,如动物类,它们都有奔跑的能力。几维鸟的飞行速度虽然为 0,但奔跑速度不为 0,可以计算出其奔跑 300 千米所要花费的时间。
4,接口隔离原则(ISP )
1)定义
客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上。
即:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
2)实现
类可以实现多个接口
5,依赖倒置原则(DIP)
1)定义
高层模块不应该依赖低层模块,两者都应该依赖其抽象;
- 抽象不应该依赖细节;
- 细节应该依赖抽象。
每一个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是低层模块,原子逻辑的再组装就
是高层模块
抽象就是指接口或抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化,也就是可以加上一个关键字new产生一个对象
java语言中的表现:
-
模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
-
接口或抽象类不依赖于实现类;
-
实现类依赖接口或抽象类。
-
变量不可以持有具体类的引用(不用new,用工场方法避免。若具体类为不变,如String,也可以)
-
不要让类派生自具体类,应派生于超类(抽象类或接口)
-
不要覆盖基类中已实现的方法(若要覆盖,可能也并不适合被继承)
更加精简的定义就是“面向接口编程”——OOD(Object-Oriented Design,面向对象设计)的精髓之一
2)举例
司机类和奔驰车类都属于细节,并没有实现或继承抽象,它们是对象级别的耦合。司机有一个drive()方法,用来开车,奔驰车有一个run()方法,用来表示车辆运行,并且奔驰车类依赖于司机类,用户模块表示高层模块,负责调用司机类和奔驰车类。
那么”张三司机不仅要开奔驰车,还要开宝马车“怎么实现呢?
在新增低层模块时,只修改了高层模块(业务场景类),对其他低层模块(Driver类)不需要做任何修改,可以把"变更"的风险降低到最低。在Java中,只要定义变量就必然有类型,并且可以有两种类型:表面类型和实际类型,表面类型是在定义时赋予的类型,实际类型是对象的类型。就如上面的例子中,小李的表面类型是IDriver,实际类型是Driver。
6,最少知识原则(Least Knowledge Principle,LKP、迪米特法则 Law of Demeter,LoD)
1)定义
只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。
即:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
2)优缺点
- 优点
类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。同时解决了循环依赖问题。 - 缺点
产生了大量的中转或跳转类,导致系统的复杂性提高,同时也为维护带来了难度。
3)实现
我们应该调用以下方法:
- 该对象本身。
- 被当做方法的参数而传递进来的对象。
- 此方法所创建或实例化的任何对象。
- 对象的任何组件。
7,其它
- 高内聚,低耦合
类之间应该零耦合,或者只有传导耦合,换句话说,类之间要么没关系,要么只使用另一个类的接口提供的操作。 - 透明性
调用者只知道,这个方法我可以调用,可以返回什么,但是里面具体怎么操作(实现细节),不知道。 就像玻璃,我们知道它的存在但是看不见它。
面向接口编程,不针对实现编程。 - 合成复用:优先使用组合,而非继承。
类的复用分为 继承复用 和 合成复用 两种,继承复用简单、易实现的,但破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。 它限制了复用的灵活性。
合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。 - 好莱坞原则:高层组件调用低层组件,低层组件不能调用高层
应尽量避免让高层和低层组件之间有明显的环状依赖。
举例:模板方法 - 封装变化
找出程序中会变化的方面,然后将其和固定不变的方向相分离。
8, GRASP(9个对象设计和职责分配职责)
参考目录:
1)《Head First设计模式》 Eric Freeman & Elisabeth Freeman with Kathy Sierra & Bert Bates著。O’Reilly Taiwan公司译。UML China改编。
2)《UML和模式应用(原书第3版)》 (美)Craig Larman著 李洋,郑(yan) 等译
1)多态性
问题:如何处理基于类型的选择?如何创建可插拔的软件构件?
基于类型的选择:
条件变化是程序的一个基本主题。如果使用if-the-else或case语句的条件逻辑来设计程序,那么当出现新的变化时,则需要修改这些case逻辑–通常遍布各处。这种方法很难方便地扩展有变化的程序,因为可能需要修改程序的多个地方。
可插拔软件构件:
客户-服务器关系中的可视化构件,如何才能替换服务器构件,而不对客户端产生影响呢?
解决方案:
当相关选择或行为随类有所不同时,使用多态操作作为变化的行为类型分配职责。推论:不要测试对象的类型,也不要使用条件逻辑来执行基于类型的不同选择。
如java将case替换为反射。
2)纯虚构
问题:不想违背高内聚低耦合或其他目标,但是基于专家模式所提供的方案又不合适时,哪些对象应该承担这一职责?
面向对象设计有时会被描述为:实现软件类,使其表示真实世界问题领域的概念,以降低表示差异。但是,在很多情况下,只对领域层对象分配职责会导致不良高内聚或耦合,或者降低复用潜力。
解决方案:
对人为制造的类分配一组高内聚的职责,该类并不代表问题领域的概念–虚构的事物,用以支持高内聚低耦合和复用。这种类是凭空虚构的。
英文中,纯虚构(pure fabrication)这一习语的含义是:当我们穷途末路时所捏造的某物。
3)间接性
问题:为了避免两个或多个事物之间直接耦合,应该如何分配职责?如何使对象解耦合,以支持耦合并提高复用性潜力?
解决方案:将职责分配给中介对象,使其作为其他构件或服务之间的媒介,以避免它们之间的直接耦合。中介实现了其他构件之间的间接性。
4)防止变异
问题:如何设计对象、子系统和系统,使其内部的变化或不稳定性不会对其他元素产生不良影响?
解决方案:识别预计变化或不稳定之处,分配职责用以在这些变化之外创建接口。
注意:这里使用的“接口”指的是广泛意义上的访问视图,而不仅仅是诸如Java接口等字面含义。