@[TOC]
一、设计模式的目的:
1)代码重复性(即:相同功能的代码,不用多次编写)
2)可读性(即:编程规范怀,便于其他程序员的阅读和理解)
3)可扩展性(即:当需要增加新的功能时,非常的方便,称为可维护)
4)可靠性(即:当我们增加新的功能后,对原来的功能没有影响)
5)使程序呈现高内聚、低耦合的特性
二、设计模式的七大原则:
1.单一职责原则
基本介绍
对类来说,一个类应该只负责一项职责,如果一个类承担的职责太多,就等于把这些职责耦合在一起了,一个职责的变化可能 会削弱或者抑制这个类完成其他职责的能力,这样的耦合会导致在需求发生改变时,会对代码进行重构的可能,这严重违背了可维护性,大大的降低了生产速度与开发周期。
应用场景,实现moto和汽车在公路上跑,飞机在天空中飞
- singleresponsibilit1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class singleresponsibilit1 {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("moto");
vehicle.run("汽车");
vehicle.run("飞机");
}
}
//交通工具类
class Vehicle{
public void run(String vehicle){
System.out.println(vehicle+"在公路上运行");
}
} - 测试
很显然,这样的做法不满足应用的场景,所以我们要对此操作进行相应的修改
- singleresponsibilit2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public class singleresponsibilit2 {
public static void main(String[] args) {
RoadVehicle roadVehicle = new RoadVehicle();
roadVehicle.run("moto");
roadVehicle.run("汽车");
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("飞机");
}
}
class RoadVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在公路上跑");
}
}
class AirVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在天空飞");
}
}
class WaterVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在水里");
}
} - 测试
这次的修改虽然满足了我们的需求,但是这样的做法一般是不提倡使用的,因为它消耗了太多的资源,但是它却满足单一职责的原则,我们也可以再改一下
- singleresponsibilit3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class singleresponsibilit3 {
public static void main(String[] args) {
Vehicle2 vehicle2 = new Vehicle2();
vehicle2.run("moto");
vehicle2.run("汽车");
vehicle2.runAir("飞机");
}
}
class Vehicle2{
public void run(String vehicle){
System.out.println(vehicle+"在公路上运行");
}
public void runAir(String vehicle){
System.out.println(vehicle+"在空中飞");
}
public void runWater(String vehicle){
System.out.println(vehicle+"在水里游");
}
} - 测试
这次的改变只用了一个类,就实现了功能,在一个类中,每个方法独自实现自己的职责,它也满足单一职责原则。
注意事项和细节:
1)降低类的复杂度,一个类只负责一项职责
2)提高类的可读性,可维护性
3)降低变更引起的风险
4)当类中的方法比较少时,可以在方法级别保持单一职责原则
2.接口隔离原则
基本介绍:
客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。接口的应该独立。
应用场景:A类通过接口Interface1依赖类B,类C通过接口Interfa1依赖类D
Interface1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80public class Segregation1 {
public static void main(String[] args) {
}
}
interface Interface1{
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
class B implements Interface1{
public void operation1(){
System.out.println("B 实现了operation1");
}
public void operation2(){
System.out.println("B 实现了operation2");
}
public void operation3(){
System.out.println("B 实现了operation3");
}
public void operation4(){
System.out.println("B 实现了operation4");
}
public void operation5(){
System.out.println("B 实现了operation5");
}
}
class D implements Interface1{
public void operation1(){
System.out.println("D 实现了operation1");
}
public void operation2(){
System.out.println("D 实现了operation2");
}
public void operation3(){
System.out.println("D 实现了operation3");
}
public void operation4(){
System.out.println("D 实现了operation4");
}
public void operation5(){
System.out.println("D 实现了operation5");
}
}
class A{//A类通过接口Interface1依赖B类,但是只会用到1.2.3方法
public void depend1(Interface1 i){
i.operation1();
}
public void depend2(Interface1 i){
i.operation2();
}
public void depend3(Interface1 i){
i.operation2();
}
}
class C{//C类通过接口Interface1依赖D类,但是只会用到1.4.5方法
public void depend1(Interface1 i){
i.operation1();
}
public void depend4(Interface1 i){
i.operation4();
}
public void depend5(Interface1 i){
i.operation5();
}
}类图表示
从上面Interface1不是最小的接口,如果要增加需求时,改动太大,所以我们可以把Interface1再进行一个细分
Segregation1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81public class Segregation2 {
public static void main(String[] args) {
A a = new A();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());
C c = new C();
c.depend1(new D());
c.depend4(new D());
c.depend5(new D());
}
}
interface Interface1{
void operation1();
}
interface Interface2{
void operation2();
void operation3();
}
interface Interface3{
void operation4();
void operation5();
}
class B implements Interface1,Interface2{
public void operation1(){
System.out.println("B 实现了operation1");
}
public void operation2(){
System.out.println("B 实现了operation2");
}
public void operation3(){
System.out.println("B 实现了operation3");
}
}
class D implements Interface1,Interface3 {
public void operation1(){
System.out.println("D 实现了operation1");
}
public void operation4(){
System.out.println("D 实现了operation4");
}
public void operation5(){
System.out.println("D 实现了operation5");
}
}
class A{//A类通过接口Interface1,Interface2依赖B类,但是只会用到1.2.3方法
public void depend1(Interface1 i){
i.operation1();
}
public void depend2(Interface2 i){
i.operation2();
}
public void depend3(Interface2 i){
i.operation2();
}
}
class C{//C类通过接口Interface1,Interface3依赖D类,但是只会用到1.4.5方法
public void depend1(Interface1 i){
i.operation1();
}
public void depend4(Interface3 i){
i.operation4();
}
public void depend5(Interface3 i){
i.operation5();
}
}测试结果
- 类图形式
*总结:
隔离原则实质就是我们实现某个类时,它要实现的这个接口必须是最小的,不能包含其他的方法,只能提供给要实现的类使用,一个类与另一个类的依赖是建立在接口之上的。
3.依赖倒转(倒置)原则
基本介绍:
依赖倒转的原则:
1)高层模块不应该依赖低层模块,二者都应该依赖其抽象
2)抽象不应该依赖细节,细节应该依赖抽象
3)依赖倒转(倒置)的中心思想是面向接口编程
4)依赖倒转原则的设计理念:相对于细节的多变性,抽象的东西要稳定的多。心抽象 的其他搭建的架构比心细节为基础的架构要稳定的多。在java中,抽象指的是接口或者是抽象类,细节就是具体的实现类
5)使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
应用场景:完成Person接收消息的功能
- DependecyInversion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class DependecyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
class Email{
public String getInfo(){
return "电子邮件信息:hello";
}
}
//完成person接收消息的功能
class Person{
public void receive(Email email){
System.out.println(email.getInfo());
}
} - 测试
这里我们发现在使用Email接收消息时,竟然引用了一个具体的类,这可是面向接口编程的大忌啊。
狗娃同学:这样的做法简单容易理解,并且我可以直接的去操作Email,没什么毛病吧!
小编:狗娃,如果我们的需求发生改变,现在要接收微信消息,是不是我们要重新写具体的类,再调用,这样的做法是非常的麻烦,所以下面我们来引用面向接口编程的思路解决问题!
- DependecyInversion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public class DependecyInversion {
public static void main(String[] args) {
Person person = new Person();
//传入Email
person.receive(new Email());
System.out.println();
//传入WeiXin
person.receive(new WeiXin());
}
}
//定义接口
interface IReceiver{
public String getInfo();
}
class Email implements IReceiver{
public String getInfo(){
return "电子邮件信息:hello";
}
}
//添加了一个新功能
class WeiXin implements IReceiver{
public String getInfo() {
return "微信消息:helloWeiXin";
}
}
class Person{
//现在依赖的是接口
public void receive(IReceiver receiver){
System.out.println(receiver.getInfo());
}
} - 测试
注意:
1)低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好。
2)变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化。
3)继承时遵循里氏替换原则。
依赖关系传递的三种方式
1)接口传递
1 | interface IOpenAndClose{ |
- 测试
1
2
3
4
5
6
7
8
9public class DependecyInversion {
public static void main(String[] args) {
ChangHong changHong = new ChangHong();
OPenAdnClose oPenAdnClose = new OPenAdnClose();
oPenAdnClose.open(changHong);
}
}- 结果
2)构造方法传递1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24interface IOpenAndClose{
public void open();
}
interface ITv{
public void play();
}
class OPenAdnClose implements IOpenAndClose{
public ITv tv;
public OPenAdnClose(ITv iTv){
this.tv = tv;
}
public void open() {
this.tv.play();
}
}
class ChangHong implements ITv{
public void play() {
System.out.println("构造方法-----长虹电视打开了!");
}
}
- 结果
- 测试
1
2
3
4
5
6
7
8
9public class DependecyInversion {
public static void main(String[] args) {
ChangHong changHong = new ChangHong();
OPenAdnClose oPenAdnClose = new OPenAdnClose(changHong);
changHong.play();
}
}- 结果
- 结果
3)setter方式传递
1 | interface IOpenAndClose{ |
- 测试方法
1
2
3
4
5
6
7
8
9
10public class DependecyInversion {
public static void main(String[] args) {
ChangHong changHong = new ChangHong();
OPenAdnClose oPenAdnClose = new OPenAdnClose();
//使用setter注入电视
oPenAdnClose.setTv(changHong);
changHong.play();
}
} - 结果
4.里氏替换原则
基本介绍:
1)所有引用基类的地方必须能透明地使用其子类的对象
2)在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
3)里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合、组合、依赖来解决问题。
应用场景:A类定义两数相减操作,B类继承A类,不小心重写了A类的方法,然后newA类,newB类,实现两数相减操作时,异常!
- Liskov
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class Liskov {
public static void main(String[] args) {
A a = new A();
System.out.println("11-3="+a.func1(11,3));
System.out.println("1-8="+a.func1(1,8));
System.out.println("-------------------------");
B b = new B();
System.out.println("11-3="+b.func1(11,3));
System.out.println("1-8="+b.func1(1,8));
System.out.println("11+3+9="+b.func2(11,3));
}
}
class A{
public int func1(int num1,int num2){
return num1- num2;
}
}
class B extends A{
public int func1(int a,int b){
return a + b;
}
public int func2(int a,int b){
return func1(a,b)+9;
}
} - 运行结果
当然我们也有解决的方法,通用的作法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系代替。
- Liskov
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42public class Liskov {
public static void main(String[] args) {
A a = new A();
System.out.println("11-3="+a.func1(11,3));
System.out.println("1-8="+a.func1(1,8));
System.out.println("-------------------------");
B b = new B();
//B类不再继承A类,因此调用者,不会再func1是求减法
System.out.println("11+3="+b.func1(11,3));
System.out.println("1+8="+b.func1(1,8));
System.out.println("11+3+9="+b.func2(11,3));
}
}
//创建一个更加基础的基类
class Base{
}
class A extends Base{
public int func1(int num1,int num2){
return num1- num2;
}
}
class B extends Base {
private A a = new A();
public int func1(int a,int b){
return a + b;
}
public int func2(int a,int b){
return func1(a,b)+9;
}
//我们仍然想使用A的方法
public int func3(int a,int b){
return this.a.func1(a,b);
}
} - 测试
现在要实现的功能是正确的,所以在使用继承时,有时候必须借助一个基类,来降低两个类的耦合度,从而实现功能!
5.开闭原则(OCP)
基本介绍
1)开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则。
2)一个软件实体如类,模块和函数应该对扩展开放(提供方),对修改关闭(使用方)。用抽象构建框架,用实现扩展细节。
3)当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
4)编程中遵循其他原则,使用设计模式的目的就是遵循开闭原则。
应用场景:
一个画图形的设计
Shape
1
2
3public class Shape {
int m_type;
}Circle
1
2
3
4
5
6public class Circle extends Shape {
Circle(){
super.m_type=2;
}
}Rectangle
1
2
3
4
5
6
7public class Rectangle extends Shape {
Rectangle(){
super.m_type=1;
}
}drawRectangle
1
2
3public class drawRectangle {
}GraphicEditor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//使用方
public class GraphicEditor {
public void drawShape(Shape s){
if (s.m_type == 1){
drawRectangle(s);
}else if (s.m_type == 2){
drawCircle(s);
}
}
public void drawRectangle(Shape r){
System.out.println("绘制矩形");
}
public void drawCircle(Shape r){
System.out.println("绘制圆形");
}
}类图表示形式
测试
1
2
3
4
5public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
}结果
狗娃同学:我觉得上面的做法比较的好理解,简单容易操作。
小编:狗娃,你觉得可以,但是如果增加一个图形种类,比如三角形,这样的代码就需要很大的改动,首先我们得新建一个三角类,并且在使用方那里要添加绘制三角形的方法,添加判断条件为3时,才能绘制三角形,修改的代码量实在是太多了!由此可以看出上面的做法违反了OCP原则,对扩展开放(提供方),对修改关闭(使用方)。所以我们必须对上面的方法进行改进!Shape
1
2
3
4
5
6abstract class Shape {
int m_type;
public abstract void draw();//抽象方法
}Circle
1
2
3
4
5
6abstract class Shape {
int m_type;
public abstract void draw();//抽象方法
}Rectangle
1
2
3
4
5
6
7
8
9
10
11public class Rectangle extends Shape {
Rectangle(){
super.m_type=1;
}
public void draw() {
System.out.println("绘制矩形");
}
}Triangle
1
2
3
4
5
6
7
8
9
10
11//新增加的图形
public class Triangle extends Shape{
Triangle(){
super.m_type = 3;
}
public void draw() {
System.out.println("绘制三角形");
}
}drawRectangle
1
2
3public class drawRectangle {
}GraphicEditor
1
2
3
4
5
6
7//使用方
public class GraphicEditor {
public void drawShape(Shape s){
s.draw();
}
}类图表示形式
我们在更改时,将Shape定义为了abstract的类,并提供了抽象 访求draw,目的是让所有的子类去实现Shape类,并重写draw方法,因此如果有再有新的图形的话我们只需要继承Shape,实现draw方法,使用方(GraphicEditor)的代码并不用改变,只需要变动提供方,从而使得满足了ocp原则!
6.迪米特法则
基本介绍
1)一个对象应该对其他对象保持最少的了解
2)类与类关系越密切,耦合度越大
3)迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的
越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内
部。对外除了提供的public方法,不对外泄露任何信息
4)迪米特法则还有个更简单的定义:只与直接的朋友通信
5)直接的朋友:每个对象都会与其他对象由耦合关系,只要两个对象之间有耦合关系
我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合
等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而
出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量
的形式出现在类的内部。
注意:
1)迪米特法则的核心是降低类之间的耦合
2)由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系。
7.合成复用原则
基本介绍
原则是尽量使用合成/聚合的方式,而不是是使用继承
类与类之间使用继承会导致类之间的紧密度太度,所以我们通常使用依赖、组合、聚合来代替继承。
三、设计模式的原则总结:
1)找出应用中需要变化处,把它们独立出来,不要和那些不需要变化的代码混在一起。
2)针对接口编程,而不是针对实现编程。
3)为了交互对象之间的松耦合设计而努力。