Android设计模式(一)-六大原则及分类

Android进阶之光设计模式学习笔记,参考博客:https://blog.csdn.net/zhengzhb/article/category/926691/2

设计模式六大原则及其定义

单一职责原则

定义:就一个类而言应该只有一个能让他变化的原因

通俗点理解就是我们不能让一个类承担过多的职责。如果这个类承担过多的职责就大大真强其耦合度,其耦合度的增强着可以造成不必要的麻烦。

比如有些Android开发者在Activity中写Bean文件、网络数据处理等等操作一个Activity有上千行代码Activity过于臃肿我们在版本维护时就比较麻烦而且也容易引起Activity各种变化。

开放封闭原则

定义:类、模块、函数等应该是可以拓展的但是是不能修改的

开放封闭原则有两个含义:

  • 对于拓展是开放的
  • 对修改是封闭的

于开发来说修改需求是常有的事,但是有新需求就要把类重新修改一变显然是不够明智的。 所以在我们设计程序时,面对需求的改变尽可能的保证相对稳定,尽量通过扩展的方式来实现变化,而不是修改原有代码。

假如我们现在有一个列表,一开始查询功能过几天需要添加修改功能然后在过几天又于需要添加删除功能这个时候大多数人做法都是穿不同的值来控制来实现不同功能,然而如果又有需求需要修改我们则又要修改代码,然而我们使用开放封闭原则就是增加一个抽象类每次修改需求只需要添加一个功能子类实现其中方法即可。

里氏替换原则

定义:子类可以扩展父类的功能,但不能改变父类原有的功能

里氏替换原则四层含义:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
  • 子类中可以增加自己特有的方法
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格

例:现在我们需要完成两个数相减功能有类A来负责

1
2
3
4
5
6
7
8
9
10
11
12
13
class A{  
public int func1(int a, int b){
return a-b;
}
}

public class Client{
public static void main(String[] args){
A a = new A();
System.out.println("100-50="+a.func1(100, 50));
System.out.println("100-80="+a.func1(100, 80));
}
}

运行结果:
100-50=50
100-80=20
后来,我们需要增加一个新的功能:完成两数相加,然后再与100求和,由类B来负责。即类B需要完成两个功能:

  • 两数相减。
  • 两数相加,然后再加100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class B extends A{  

public int funcB2(int a, int b){
return func1(a,b)+100;
}
}

public class Client{
public static void main(String[] args){
B b = new B();
System.out.println("100-50="+b.func1(100, 50));
System.out.println("100+20+100="+b.funcB2(100, 20));
}
}

类B完成后,运行结果:
100-80=180
100+20+100=220

依赖倒置原则

定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
依赖倒置原则的核心思想是面向接口编程,我们依旧用一个例子来说明面向接口编程比相对于面向实现编程好在什么地方。

场景是这样的,母亲给孩子讲故事,只要给她一本书,她就可以照着书给孩子讲故事了。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Book{  
public String getContent(){
return "很久很久以前有一个阿拉伯的故事……";
}
}

class Mother{
public void narrate(Book book){
System.out.println("妈妈开始讲故事");
System.out.println(book.getContent());
}
}

public class Client{
public static void main(String[] args){
Mother mother = new Mother();
mother.narrate(new Book());
}
}

运行结果:
妈妈开始讲故事
很久很久以前有一个阿拉伯的故事……
运行良好,假如有一天,需求变成这样:不是给书而是给一份报纸,让这位母亲讲一下报纸上的故事,报纸的代码如下:

1
2
3
4
5
class Newspaper{  
public String getContent(){
return "林书豪38+7领导尼克斯击败湖人……";
}
}

这位母亲却办不到,因为她居然不会读报纸上的故事,这太荒唐了,只是将书换成报纸,居然必须要修改Mother才能读。假如以后需求换成杂志呢?换成网页呢?还要不断地修改Mother,这显然不是好的设计。原因就是Mother与Book之间的耦合性太高了,必须降低他们之间的耦合度才行。
我们引入一个抽象的接口IReader。读物,只要是带字的都属于读物:

1
2
3
interface IReader{  
public String getContent();
}

Mother类与接口IReader发生依赖关系,而Book和Newspaper都属于读物的范畴,他们各自都去实现IReader接口,这样就符合依赖倒置原则了,代码修改为:

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
class Newspaper implements IReader {  
public String getContent(){
return "林书豪17+9助尼克斯击败老鹰……";
}
}
class Book implements IReader{
public String getContent(){
return "很久很久以前有一个阿拉伯的故事……";
}
}

class Mother{
public void narrate(IReader reader){
System.out.println("妈妈开始讲故事");
System.out.println(reader.getContent());
}
}

public class Client{
public static void main(String[] args){
Mother mother = new Mother();
mother.narrate(new Book());
mother.narrate(new Newspaper());
}
}

运行结果:
妈妈开始讲故事
很久很久以前有一个阿拉伯的故事……
妈妈开始讲故事
林书豪17+9助尼克斯击败老鹰……

这样修改后,无论以后怎样扩展Client类,都不需要再修改Mother类了。这只是一个简单的例子,实际情况中,代表高层模块的Mother类将负责完成主要的业务逻辑,一旦需要对它进行修改,引入错误的风险极大。所以遵循依赖倒置原则可以降低类之间的耦合性,提高系统的稳定性,降低修改程序造成的风险。
传递依赖关系有三种方式,以上的例子中使用的方法是接口传递,另外还有两种传递方式:构造方法传递和setter方法传递。
在实际编程中,我们一般需要做到如下3点:

  • 低层模块尽量都要有抽象类或接口,或者两者都有。
  • 变量的声明类型尽量是抽象类或接口。
  • 使用继承时遵循里氏替换原则。

依赖倒置原则的核心就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置。

迪米特原则

定义:一个对象应该对其他对象保持最少的了解。

自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的。
通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。

举一个栗子:有一个集团公司,下属单位有分公司和直属部门,现在要求打印出所有下属单位的员工ID。代码如下

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
//总公司员工
class Employee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}

//分公司员工
class SubEmployee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}


//分公司
public class SubCompanyManager {
public List<SubEmployee> getSubEmployee(){
List<SubEmployee> list = new ArrayList<>();
for (int i = 0;i<20;i++){
SubEmployee subEmployee= new SubEmployee();
subEmployee.setId("分公司成员"+i);
list.add(subEmployee);
}
return list;
}
public void pintln(){
List<SubEmployee> list = getSubEmployee();
for (SubEmployee subEmployee:list){
System.out.println(subEmployee.getId());
}
}

}

// 总公司

public class CompanyManager {
public List<Employee> getEmployee(){
List<Employee> list = new ArrayList<>();
for (int i = 0;i<20;i++){
Employee employee= new Employee();
employee.setId("总公司成员"+i);
list.add(employee);
}
return list;
}
public void pintln(SubCompanyManager subCompanyManager){
subCompanyManager.pintln();
List<Employee> list = getEmployee();
for (Employee subEmployee:list){
System.out.println(subEmployee.getId());
}
}

}

//输出
public class Main {
public static void main(String[] arg ){
CompanyManager companyManager = new CompanyManager();
companyManager.pintln(new SubCompanyManager());
}
}

迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。但是凡事都有度,虽然可以避免与非直接的类通信,但是要通信,必然会通过一个“中介”来发生联系,例如本例中,总公司就是通过分公司这个“中介”来与分公司的员工发生联系的。过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。

接口隔离原则

定义:客户端不应该依赖不需要的接口,一个类对另外一个类的依赖建立在接口上

接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。本文例子中,将一个庞大的接口变更为3个专用的接口所采用的就是接口隔离原则。在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的“契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
说到这里,很多人会觉的接口隔离原则跟之前的单一职责原则很相似,其实不然。其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。

在举一个栗子:

这个图的意思是:类A依赖接口I中的方法1、方法2、方法3,类B是对类A依赖的实现。类C依赖接口I中的方法1、方法4、方法5,类D是对类C依赖的实现。对于类B和类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法),但由于实现了接口I,所以也必须要实现这些用不到的方法。
如果接口过于臃肿,只要接口中出现的方法,不管对依赖于它的类有没有用处,实现类中都必须去实现这些方法,这显然不是好的设计。如果将这个设计修改为符合接口隔离原则,就必须对接口I进行拆分。在这里我们将原有的接口I拆分为三个接口,拆分后的设计如图所示:

采用接口隔离原则对接口进行约束时,要注意以下几点:

  • 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
  • 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
  • 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

设计模式分类

GoF提出的设计模式一共分为23中,根据目的一共分为三大类

1.创建型设计模式

  • 单利模式
  • 工厂方法
  • 抽象工厂模式
  • 建造者模式
  • 原型模式

2.构造型设计模式

  • 适配器模式
  • 装饰模式
  • 代理模式
  • 外观模式
  • 桥接模式
  • 组合模式
  • 享元模式

3.行为型设计模式

  • 策略模式
  • 模板方法模式
  • 观察者模式
  • 迭代器模式
  • 责任链模式
  • 命令模式
  • 备忘录模式
  • 状态模式
  • 访问者模式
  • 中介模式
  • 解释器模式

随着设计模式的发展也涌现出很多新的设计模式:

  • 规格模式
  • 对象池模式
  • 雇工模式
  • 黑板模式
  • 空对象模式