LieBrother

当才华撑不起野心时,应该静下心来学习;当能力驾驭不了目标时,应该沉下心来历练。


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

创建型模式:抽象工厂

发表于 2019-01-20   |     |   阅读次数

个人博客原文:
创建型模式:抽象工厂

景

五大创建型模式之三:抽象工厂。

简介

姓名 :抽象工厂

英文名 :Abstract Factory Pattern

价值观 :不管你有多少产品,给我就是了

个人介绍 :

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
(为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。)
(来自《设计模式之禅》)

今天讲的是抽象工厂模式,小伙伴可能有疑问,抽象工厂和工厂方法之间都有工厂,那肯定是有什么联系的,具体是什么关系呢?简单的说:工厂方法是在解决一个产品多个层级方面的事情;而抽象工厂致力于解决多个产品多个层级方面的事情。举个例子:汽车是由很多零件组成的,比如引擎、轮胎、方向盘等等。现在如果我们是轮胎生产方,要生产宝马轮胎和奔驰轮胎,要用工厂方法还是抽象工厂实现呢?答案是:工厂方法。轮胎是一个产品,宝马轮胎和奔驰轮胎是 2 个不同层级的轮胎,所以用工厂方法解决就足够。假如现在我们是汽车生产方,要生产宝马汽车和奔驰汽车,汽车又包含轮胎和方向盘等等,要用哪个来实现?既然是上面的是工厂方法,那这个就用抽象工厂,因为这涉及到多个产品(轮胎、方向盘等等)和 2 个层级(宝马和奔驰)。这里还没有讲抽象工厂的概念就说了工厂方法和抽象方法的区别,是不是有点陌生?嗯,先记住这个概念,分清楚两者的区别。在不同场景使用不同的设计模式。

上面定义中:为创建一组相关或相互依赖的对象提供一个接口。这样子理解这句话,比如上面说的轮胎和方向盘,宝马汽车用的轮胎和方向盘需要都是宝马品牌的,也就是说在安装宝马汽车的轮胎和方向盘的时候,得用宝马生产的轮胎和方向盘,重要的一点是:轮胎和方向盘是互相依赖的,不能在宝马汽车上安装奔驰轮胎和宝马方向盘,因为有这个依赖关系,所以我们需要提供一个额外的接口,来保证宝马汽车使用的轮胎和方向盘都是宝马生产的。这就是抽象工厂干的事情。

你要的故事

上面用汽车安装轮胎和方向盘的例子,那这里为了让大家能深入理解,就不用其他例子了。在一个设计模式讲解的过程中,我觉得用一个案例来讲解可以减少读者的阅读理解成本,为了写设计模式这一系列文章,看了不少设计模式方面的书籍,有些书籍在讲解一个设计模式的时候,用了不止一个例子,读完之后印象不是很深刻。这个系列写完之后,想要的效果是:不需要记住设计模式的定义,把这些故事以及故事对应是讲哪个设计模式都记住了,就真正掌握了这些内容了。

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
public class AbstractFactoryTest {

public static void main(String[] args) {
// 宝马员工安装轮胎和方向盘
AbstractCarFactory bmwCarFacatory = new BMWCarFactory();
bmwCarFacatory.installWheel();
bmwCarFacatory.installSteeringWheel();

// 奔驰员工安装轮胎和方向盘
AbstractCarFactory mercedesCarFacatory = new MercedesCarFacatory();
mercedesCarFacatory.installWheel();
mercedesCarFacatory.installSteeringWheel();
}

}

/**
* 汽车抽象工厂
*/
interface AbstractCarFactory {

void installWheel();

void installSteeringWheel();

}

/**
* 宝马工厂
*/
class BMWCarFactory implements AbstractCarFactory {

@Override
public void installWheel() {
WheelFacatory wheelFacatory = new BMWWheelFacatory();
String wheel = wheelFacatory.createWheel();
System.out.println("安装轮胎:" + wheel);
}

@Override
public void installSteeringWheel() {
SteeringWheelFacatory steeringWheelFacatory = new BMWSteeringWheelFacatory();
String steeringWheel = steeringWheelFacatory.createSteeringWheel();
System.out.println("安装方向盘:" + steeringWheel);
}
}

/**
* 奔驰工厂
*/
class MercedesCarFacatory implements AbstractCarFactory {

@Override
public void installWheel() {
WheelFacatory wheelFacatory = new MercedesWheelFacatory();
String wheel = wheelFacatory.createWheel();
System.out.println("安装轮胎:" + wheel);
}

@Override
public void installSteeringWheel() {
SteeringWheelFacatory steeringWheelFacatory = new MercedesSteeringWheelFacatory();
String steeringWheel = steeringWheelFacatory.createSteeringWheel();
System.out.println("安装方向盘:" + steeringWheel);
}
}

/**
* 轮胎工厂
*/
interface WheelFacatory {

String createWheel();

}

/**
* 宝马轮胎工厂
*/
class BMWWheelFacatory implements WheelFacatory {

@Override
public String createWheel() {
System.out.println("宝马轮胎工厂生产轮胎");
return "宝马轮胎";
}
}

/**
* 奔驰轮胎工厂
*/
class MercedesWheelFacatory implements WheelFacatory {

@Override
public String createWheel() {
System.out.println("奔驰轮胎工厂生产轮胎");
return "奔驰轮胎";
}
}

/**
* 方向盘工厂
*/
interface SteeringWheelFacatory {

String createSteeringWheel();

}

/**
* 宝马方向盘工厂
*/
class BMWSteeringWheelFacatory implements SteeringWheelFacatory {

@Override
public String createSteeringWheel() {
System.out.println("宝马方向盘工厂生产方向盘");
return "宝马方向盘";
}
}

/**
* 奔驰方向盘工厂
*/
class MercedesSteeringWheelFacatory implements SteeringWheelFacatory {

@Override
public String createSteeringWheel() {
System.out.println("奔驰方向盘工厂生产方向盘");
return "奔驰方向盘";
}
}

代码:

AbstractFactoryTest.java

还是和以往一样,思维开拓一下,这里列举的是给汽车安装轮胎和方向盘,汽车不止这些,如果要加个安装引擎呢?要怎么实现?这里我就不写出来了,让小伙伴尝试一下,写出来了就理解抽象模式这个设计模式啦。

总结

简单工厂、工厂方法、抽象工厂这几个工厂相关的设计模式的基本内容都讲完了,这几个模式都是为了解耦,为了可扩展。这里要着重说一下,三者之间没有好坏之分,只有在具体的场景才能发挥它们各自的优势。在单产品多层级,层级数量不多的情况下,可以使用简单工厂,层级多且需要支持扩展,可以使用工厂方法;在多产品多层级,可以使用抽象工厂。

参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》

推荐阅读:

创建型模式:单例模式
创建型模式:工厂方法

希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号,第一时间获取文章推送阅读,也可以一起交流,交个朋友。

公众号之设计模式系列文章



公众号

创建型模式:工厂方法

发表于 2019-01-15   |     |   阅读次数

草原

简介

姓名:工厂方法

英文名:Factory method Pattern

价值观:扩展是我的专属

个人介绍:

Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses. (定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。)
(来自《设计模式之禅》)

你要的故事

还记得上一篇 单例模式 中的故事么?小明开着汽车去旅游、去学校、去聚会。这一次还是延续小明的故事,一个故事能讲 2 个设计模式,不容易呀。。。(每次想故事都想破脑袋,每一篇文章至少有 3 个故事从脑子里闪过,但最终留下的只有一个适合,为了就是能比较清晰简单的说明设计模式中的关键要点。)

简单工厂

小明家里以前不算很富裕,但是还是有一个不错的车库,什么汽车、摩托车、自行车啥的都放在这个车库里。小明每次要出去,都会到车库里面挑合适的车出发。比如,小明最近期末考试了,骑摩托车去学校考试,考完试之后,小明就准备去旅游,这次决定自驾游,开着自己家的小汽车去。这个场景我们用代码描述下。

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
public class SimpleFactoryTest {

public static void main(String[] args) {
XiaoMing xiaoMing = new XiaoMing();
// 小明骑摩托车去学校
IVehicle motorcycle = GarageFactory.getVehicle("motorcycle");
xiaoMing.goToSchool(motorcycle);

// 小明开汽车去旅游
IVehicle car = GarageFactory.getVehicle("car");
xiaoMing.travel(car);
}

}

/**
* 车库
*/
class GarageFactory {

public static IVehicle getVehicle(String type) {
if ("car".equals(type)) {
return new Car();
} else if ("motorcycle".equals(type)) {
return new Motorcycle();
}
throw new IllegalArgumentException("请输入车类型");
}

}

/**
* 交通工具
*/
interface IVehicle {
void run();
}

/**
* 汽车
*/
class Car implements IVehicle {

@Override
public void run() {
System.out.println("开汽车去。。。。");
}
}

/**
* 摩托车
*/
class Motorcycle implements IVehicle {

@Override
public void run() {
System.out.println("骑摩托车去。。。。");
}
}


class XiaoMing {

public void goToSchool(IVehicle vehicle) {
System.out.println("小明去学校");
vehicle.run();
}

public void travel(IVehicle vehicle) {
System.out.println("小明去旅游");
vehicle.run();
}

}

上面代码看懂了么? 小明家里有一个车库 GarageFactory,里面放着汽车 Car 和摩托车 Motorcycle,小明要出去的时候,就到车库选择车,通过传递参数给 GarageFactory.getVehicle(),指明要什么车,然后小明就骑着车出发了。

这个代码真正的术语叫:简单工厂模式(Simple Factory Pattern),也叫做静态工厂模式。它是工厂方法中的一个实现方式,从字面理解就可以知道,它是最简单的工厂方法实现方式。它有一点点小缺陷,就是扩展性不够好,在上面代码中,小明只能骑摩托车或者开汽车,如果小明要骑单车出去呢?势必得在 GarageFactory 中添加 if 是自行车的逻辑。这违反了哪条规则了?是不是那个允许扩展,拒绝修改的开闭原则?

不是说简单工厂这种实现方式不好,而是扩展性不够,在平时的开发中,简单工厂模式也用得不少。在这个小明家里车不多的情况下,用一个车库也是合适的。

工厂方法

小明老爸近几年赚了不少,车迷的两父子一直买车,家里的车越来越多,这时候,他们决定多建几个车库,按车类型放置。比如,有一个汽车库,一个摩托车库。这时候小明要开汽车就去汽车库,要骑摩托车就去摩托车库。代码实现如下。

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
public class FactoryMethodTest {

public static void main(String[] args) {
XiaoMing xiaoMing = new XiaoMing();
// 小明骑摩托车去学校
VehicleGarage motorcycleGarage = new MotorcycleGarage();
IVehicle motorcycle = motorcycleGarage.getVehicle();
xiaoMing.goToSchool(motorcycle);

// 小明开汽车去旅游
VehicleGarage carGarage = new CarGarage();
IVehicle car = carGarage.getVehicle();
xiaoMing.travel(car);
}

}

interface VehicleGarage {
IVehicle getVehicle();
}

/**
* 汽车车库
*/
class CarGarage implements VehicleGarage {

@Override
public IVehicle getVehicle() {
return new Car();
}
}

/**
* 摩托车车库
*/
class MotorcycleGarage implements VehicleGarage {

@Override
public IVehicle getVehicle() {
return new Motorcycle();
}
}

上面代码重用了简单工厂实现方式的交通接口以及摩托车和汽车的实现类。代码中有 2 个车库,一个是汽车车库 CarGarage,一个是摩托车库 MotorcycleGarage。如果小明要骑自行车,只需要建一个自行车车库,完全不用去修改汽车车库或者摩托车车库,就非常符合开闭原则,扩展性大大的提高。

代码链接:Factory method Pattern

总结

工厂方法模式可以说在你能想到的开源框架源码中必定会使用的一个设计模式,因为开源框架很重要一点就是要有扩展性,而工厂方法模式恰恰具有可扩展性。弄懂了工厂方法模式,以后看开源代码就很得心应手啦。

参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》

推荐阅读:

创建型模式:单例模式

希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号,第一时间获取文章推送阅读,也可以一起交流,交个朋友。

公众号之设计模式系列文章



公众号

创建型模式:单例模式

发表于 2019-01-14   |     |   阅读次数

月

简介

姓名:单例模式

英文名:Singleton Pattern

价值观:我的生活我主宰(只允许自己实例化,不愿意被其他对象实例化)

个人介绍:

Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)
(来自《设计模式之禅》)

这里的关注点有 3 个,分别是:

  1. 只有一个实例
  2. 自行实例化(也就是主动实例化)
  3. 向整个系统提供这个实例

你要的故事

我们脑洞大开来用一个故事讲解一番。

小明家里有一辆小汽车,具体什么牌子就不知道了,咱也不关注,反正他家里就这么一辆车,小明比较懒,只要一出门都会开车,例如去旅游、去学校、去聚会都会开车去。下面模拟小明出去的场景。

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
class Car {
public void run() {
System.out.println("走。。。。");
}
}


class XiaoMing {
public Car travel() {
System.out.println("小明去旅游");
Car car = new Car();
car.run();
return car;
}

public Car goToSchool() {
System.out.println("小明去学校");
Car car = new Car();
car.run();
return car;
}

public Car getTogether() {
System.out.println("小明参加聚会");
Car car = new Car();
car.run();
return car;
}
}

public class SingletonErrorTest {

public static void main(String[] args) {
XiaoMing xiaoMing = new XiaoMing();
Car car1 = xiaoMing.travel();
Car car2 = xiaoMing.goToSchool();
Car car3 = xiaoMing.getTogether();
}

}

上面小汽车只有一个方法,就是走。小明去旅游、去学校、参加聚会都开着他唯一的一辆汽车车去。是不是有人有疑问?为什么每个方法都返回 Car 对象?其实只是想在下面做一次检查,检查小明去旅游、去学校和参加聚会的车是不是同一辆。下面是检查代码:

1
2
System.out.println("car1 == car2 ? " + (car1 == car2));
System.out.println("car2 == car3 ? " + (car2 == car3));

最终结果是啥?很明显是 2 个 false。小明去旅游、去学校和参加聚会的车都不相同,小明不是只有 1 辆车?关键在于

car
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

上面我们说到了单例模式需要具备的 3 个点:**只有 1 个实例**,很显然,上面的代码不止 1 个实例,而是有 3 个 Car 实例;**自行实例化**,Car 本身没有主动实例化,而是在小明需要用到的时候才实例化;**向整个系统提供这个实例**,因为 Car 没有主动实例化,所以它没法向外部暴露提供自己出来。

我们的代码完全不符合单例模式的要求。我们要通过修改,使之符合单例模式的 3 个要点。首先需要实现的是第 2 点,把 Car 实例化从小明转为 Car 本身,如下代码

``` java
class Car1{

private static Car1 car1 = new Car1();

private Car1() {

}

public void run(){
System.out.println("走。。。。");
}
}

上面代码使用 private 修饰构造方法,使得 Car1 不能被其他使用方实例化,通过

car1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

接下来再实现第 3 点,向整个系统暴露这个实例,也就是暴露它自己。每个使用方都调用 ``` Car1.getInstance() ``` 方法来获取实例。

``` java
class Car1{

private static Car1 car1 = new Car1();

public static Car1 getInstance() {
return car1;
}

private Car1() {

}

public void run(){
System.out.println("走。。。。");
}
}

上面代码就实现了单例模式的 2 和 3 要点,第 1 要点要怎么实现呢?告诉你,不用实现,只要满足了 2 和 3 要点就可以,第 1 要点是用来检验是否是单例模式的好思路。我们检验一下

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
class Car1{

private static Car1 car1 = new Car1();

public static Car1 getInstance() {
return car1;
}

private Car1() {

}

public void run(){
System.out.println("走。。。。");
}
}

class XiaoMing1 {
public Car1 travel() {
System.out.println("小明去旅游");
Car1 car = Car1.getInstance();
car.run();
return car;
}

public Car1 goToSchool() {
System.out.println("小明去学校");
Car1 car = Car1.getInstance();
car.run();
return car;
}

public Car1 getTogether() {
System.out.println("小明参加聚会");
Car1 car = Car1.getInstance();
car.run();
return car;
}
}

public class SingletonRightHungryTest {

public static void main(String[] args) {
XiaoMing1 xiaoMing1 = new XiaoMing1();
Car1 car1 = xiaoMing1.travel();
Car1 car2 = xiaoMing1.goToSchool();
Car1 car3 = xiaoMing1.getTogether();

System.out.println("car1 == car2 ? " + (car1 == car2));
System.out.println("car2 == car3 ? " + (car2 == car3));
}

}

上面代码最后两行打印出来的结果是啥?是我们想要的:2 个 true。说明小明这几次外出开的车都是同一辆。这是最简单的单例模式的实现方式,我们经常称作饿汉式单例模式。为什么起这么古怪的名字呢?其实和对应的懒汉式单例模式有关,这是 2 个实现方式的差别,饿汉式单例模式实现方式在类加载到内存的时候,就创建好对象了,而懒汉式则是在第一次使用的时候才创建对象,也就是把创建对象的时机从加载延迟到第一次使用,所以才有懒饿之分。

下面我们来看怎么实现懒汉式单例模式。先描述一下场景:小明还没有汽车,他也不知道什么时候要买汽车,突然某一天,他想去旅游,觉得是时候买辆车了,然后他就买车去旅游了,旅游回来又开车去学校和参加聚会。

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
class Car2{

private static Car2 car2;

public static synchronized Car2 getInstance() {
if (null == car2) {
System.out.println("买车啦。。。");
car2 = new Car2();
}
return car2;
}

private Car2() {

}

public void run(){
System.out.println("走。。。。");
}
}

class XiaoMing2
{
public Car2 travel() {
System.out.println("小明去旅游");
Car2 car = Car2.getInstance();
car.run();
return car;
}

public Car2 goToSchool() {
System.out.println("小明去学校");
Car2 car = Car2.getInstance();
car.run();
return car;
}

public Car2 getTogether() {
System.out.println("小明参加聚会");
Car2 car = Car2.getInstance();
car.run();
return car;
}
}

public class SingletonRightLazyTest {

public static void main(String[] args) {
XiaoMing2 xiaoMing2 = new XiaoMing2();
Car2 car1 = xiaoMing2.travel();
Car2 car2 = xiaoMing2.goToSchool();
Car2 car3 = xiaoMing2.getTogether();

System.out.println("car1 == car2 ? " + (car1 == car2));
System.out.println("car2 == car3 ? " + (car2 == car3));
}

}

小明去旅游
买车啦。。。
走。。。。
小明去学校
走。。。。
小明参加聚会
走。。。。
car1 == car2 ? true
car2 == car3 ? true

上面附带了打印出来的结果,小明要去旅游的时候,才去买车。这就是懒汉式单例模式的实现方式。

要注意懒汉式单例模式有个很关键的一点就是 getInstance() 方法带上了 synchronized,这个是为什么呢?

首先得了解关键字 synchronized 的作用是什么:用于修饰执行方法同步,也就是说多线程并发的情况下,在一个时间点,只允许一个线程执行这个方法。

不加上这个会有什么结果?在多线程并发情况下,如果有 2 个线程同时执行到 if(null == car2),那么都判断为 true,这时 2 个线程都会执行 car2 = new Car2(),这样子就不是单例了。

总结

单例模式可以说是设计模式中最简单的一个,也是在工作中很多场景下经常用到的,比如:项目的配置文件加载、各种工具类等等。我们对于单例模式最重要的一点就是要考虑多线程并发,没有考虑这点就容易引发单例对象不单例的情况。而单例给我们带来最大的好处就是节约内存。

上面实现的两种方法是单例模式中最最最简单的 2 种实现,相信也是用得最多的实现方式。网上有不少网友分享了单例模式的很多种实现方法,大家也可以去了解,在了解之前务必已经搞懂文中这 2 种最简单的实现方式,不然会头晕的。

参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》

希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号,第一时间获取文章推送阅读,也可以一起交流,交个朋友。

公众号之设计模式系列文章

公众号

开闭原则

发表于 2019-01-09   |     |   阅读次数

景

设计模式六大原则之六:开闭原则。

简介

姓名 :开闭原则

英文名 :Open Closed Principle

价值观 :老顽童就是我,休想改变我

个人介绍 :

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.(软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的)
(来自维基百科)

停更了三四天了,这几天比较忙,不仅仅是工作上,更多是精神上。周日突然老胃病又复发了,一直疼到凌晨 4,5 点。因为这次疼得蛮厉害的,所以准备去医院看一下医生,这时候才体验到大城市就医之苦。周日晚下载了微医 App (不是做广告哈),也不知道哪家医院好,在深圳两年半还没去过医院,随便选个三甲医院:北京大学深圳医院,看了消化内科门诊的医生列表,整整这一周主任医生都预约满了,顿时很崩溃,打电话给医院预约,最快只能预约 17 号,are you kidding?App 上有个『立即问诊』功能,在线把状况告诉医生,医生一天之内接诊,需要花 60 块,我就尝试一下,没想到第二天医生回复后,说可以下午去医院看,他可以临时加号。就这样跳过了预约,直接看病,不知道你是否也苦于看病烦,可以尝试这个方法,当然,如果你有更好的方法,可以留言让更多的人了解到。

跑题了跑题了,今天是想和大家分享设计模式最后一个原则:开闭原则。这个原则要求就是允许扩展,拒绝修改。既然上面讲到看医生,那就用一个跟看病有关的例子。

故事从这里开始

小明去医院看病,医生开了阿司匹林药,小明去了收费台,付了钱,总共 20 块钱。例子的代码如下:

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
public class OcpTest {

public static void main(String[] args) {
Hospital hospital = new Hospital();
IPatient xiaoMing = new Patient("小明");
hospital.sellMedicine(xiaoMing);
}

}


class Medicine {
private String name;
private BigDecimal price;

public Medicine(String name, BigDecimal price) {
this.name = name;
this.price = price;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public BigDecimal getPrice() {
return price;
}

public void setPrice(BigDecimal price) {
this.price = price;
}
}

class Hospital {

private Medicine medicine = new Medicine("阿司匹林", new BigDecimal(20));

public void sellMedicine(IPatient patient) {
BigDecimal money = patient.pay(medicine);
System.out.println(patient.getName() + " 花了 " + money.setScale(2, BigDecimal.ROUND_UP) + " 块钱买了药:" + medicine.getName());
}

}

interface IPatient {
String getName();
BigDecimal pay(Medicine medicine);
}

class Patient implements IPatient{

private String name;

public Patient(String name) {
this.name = name;
}

@Override
public BigDecimal pay(Medicine medicines) {
return medicines.getPrice();
}

@Override
public String getName() {
return name;
}

}

第二天和朋友聚会聊起这事,小红说道:不对呀,前几天我在医院也拿了阿司匹林药,才 14 块钱呢。小花说:奇怪了,我买的是 16 块钱。小杰回应:怎么我买的是 18 块。怎么这药这么多个价格。小明 Google 搜了一下,发现价格跟社保有关,几个人便发现,原来他们都是“不同人”:小明没有社保,小红社保是一档,小花社保是二挡,小杰社保是三挡。(假设社保一档打 7 折,社保二挡打 8 折,社保三挡打 9 折,虚拟的哈)
发现了这秘密后,作为和 IT 工作相关的人,便讨论起医院系统具体实现是怎么实现的。小红说:这很简单呢,药品给不同人提供不同的价格。代码如下:

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
class Medicine {
private String name;
private BigDecimal price;

public Medicine(String name, BigDecimal price) {
this.name = name;
this.price = price;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public BigDecimal getPrice() {
return price;
}

public BigDecimal getPrice1() {
return price.multiply(new BigDecimal(0.7));
}

public BigDecimal getPrice2() {
return price.multiply(new BigDecimal(0.8));
}

public BigDecimal getPrice3() {
return price.multiply(new BigDecimal(0.9));
}

public void setPrice(BigDecimal price) {
this.price = price;
}
}

小花说:药片本身的价格是不会变的,只是给不同人不同价格,所以可以在病人获取价钱的时候去区分。代码如下:

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
class Patient implements IPatient{

private String name;
private int level;

public Patient(String name) {
this.name = name;
}

@Override
public BigDecimal pay(Medicine medicines) {
if (level == 1) {
return medicines.getPrice().multiply(new BigDecimal(0.7));
} else if (level == 2) {
return medicines.getPrice().multiply(new BigDecimal(0.8));
} else if (level == 3) {
return medicines.getPrice().multiply(new BigDecimal(0.9));
}
return medicines.getPrice();
}

@Override
public String getName() {
return name;
}

}

小杰陷入了沉思。。。
小明发话:你们说的方法都可以实现,但是总感觉不对劲,如果以后有社保四挡,还是要修改原来的代码,前 2 天设计模式老师讲的开闭原则忘记了么?里面说要对扩展开放,对修改封闭。我觉得这个药片价格是因为我们人而变的,那是不是我们可以把没社保的归为一类人,一档社保的也为一类,以此类推。我觉得这样实现更好,增加多 3 类病人,分别是一档社保、二挡社保、三挡社保。代码如下:

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 OneLevelSocialSecurityPatient implements IPatient {

private String name;

public OneLevelSocialSecurityPatient(String name) {
this.name = name;
}

@Override
public BigDecimal pay(Medicine medicine) {
return medicine.getPrice().multiply(new BigDecimal(0.7));
}

@Override
public String getName() {
return this.name;
}
}

class TwoLevelSocialSecurityPatient implements IPatient {

private String name;

public TwoLevelSocialSecurityPatient(String name) {
this.name = name;
}

@Override
public BigDecimal pay(Medicine medicine) {
return medicine.getPrice().multiply(new BigDecimal("0.8"));
}

@Override
public String getName() {
return this.name;
}
}

class ThreeLevelSocialSecurityPatient implements IPatient {

private String name;

public ThreeLevelSocialSecurityPatient(String name) {
this.name = name;
}

@Override
public BigDecimal pay(Medicine medicine) {
return medicine.getPrice().multiply(new BigDecimal("0.9"));
}

@Override
public String getName() {
return this.name;
}
}

// 测试代码
public static void main(String[] args) {
Hospital hospital = new Hospital();
IPatient xiaoMing = new Patient("小明");
hospital.sellMedicine(xiaoMing);

IPatient xiaoHong = new OneLevelSocialSecurityPatient("小红");
hospital.sellMedicine(xiaoHong);

IPatient xiaoHua = new TwoLevelSocialSecurityPatient("小花");
hospital.sellMedicine(xiaoHua);

IPatient xiaoJie = new ThreeLevelSocialSecurityPatient("小杰");
hospital.sellMedicine(xiaoJie);
}

代码:
OcpTest.java

看了他们的对话和代码,是不是能知道哪种方式更好了?对于小红来说,她没理清价格变化的原因,价格变化不在于药片;小花理清了,但是实现方式差了点,以后如果新增了四挡社保,她的实现要修改原有的代码,不符合开闭原则;小明的方法就符合开闭原则,如果新增四挡社保人员,他的方法只需要再额外扩展一个四挡社保人员就可以,不用动用其他代码。

用了这个大家可能不太喜欢的看病的场景来描述这个开闭原则,不要忌讳哈,希望大家都健健康康,远离医院。

总结

重申一下:对扩展开放,对修改封闭。如果有同学经常看一些开源框架源码就会发现,有很多很多抽象类和接口,debug 进去很绕,其实这些抽象类和接口很多都是为了扩展用,因为作为开源框架,不得不实现各种可想象到的方案,而这些都基于开闭原则来实现的。以后有机会也可以写一下源码的文章分享给大家。

参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》

这周事情比较多,更新会不及时,周五还要出差去一趟上海,周六回深圳,周日回一趟老家,各种奔波。。。

希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号,第一时间获取文章推送阅读,也可以一起交流,交个朋友。

公众号之设计模式系列文章



公众号

迪米特法则

发表于 2019-01-05   |     |   阅读次数

景

设计模式六大原则之五:迪米特法则。

简介

姓名:迪米特法则

英文名:Law of Demeter

小名:最少知识原则

小名英文名:Least Knowledge Principle

价值观:妈妈说不和陌生人说话

个人介绍:

  1. Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. (每个单元对于其他的单元只能拥有有限的知识:只是与当前单元紧密联系的单元)
  2. Each unit should only talk to its friends; don’t talk to strangers. (每个单元只能和它的朋友交谈:不能和陌生单元交谈)
  3. Only talk to your immediate friends. (只和自己直接的朋友交谈)
    (来自维基百科)

还是脑洞大开来个小故事。这故事还是比较现实一些,其实也不算是故事,就是咱们经常经历的事情,现在知识付费已经广受欢迎,18 年底更是快速猛涨,各种各样的培训、读书、音频学习软件如雨后春笋一般涌现出来。我们就拿一个读书的例子。

有一天,设计模式老师讲解了迪米特法则,同学们听得云里雾里的,老师怕同学们没掌握这个知识点,就给同学们布置了一个作业,需要同学们按迪米特法则实现。

作业是这样子的:平常在零碎的时间里,喜欢看一些书籍,一般都是电子书,现在我们看书的操作是这样的:唤醒手机,打开阅读软件,选择书籍,然后阅读。总共 3 个步骤,涉及了 3 样东西:手机、软件、书籍。同学们用代码实现这个过程。

第二天上课,同学们纷纷交了作业,老师随手一番,就看到了 2 个鲜明的例子,很明显,就是一好一坏。老师便给同学们讲解了这 2 个例子,让学生感受一番迪米特法则。

错误例子

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
public class LODErrorTest {

public static void main(String[] args) {
Phone phone = new Phone();
phone.readBook();
}

}


/**
* 错误的示范
*/
class Phone {
App app = new App();
Book book = new Book("设计模式");
public void readBook() {
app.read(book);
}

}


class App {

public void read(Book book) {
System.out.println(book.getTitle());
}

}

class Book {

private String title;

public Book(String title) {
this.title = title;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}
}

代码:
LODErrorTest

代码是完成了读书这个过程,看样子是功能实现了,细看会发现代码的逻辑不对。哪里不对呢?书籍和应用对象都在手机上,现实是我们唤醒手机,这时手机是没有书籍的,只有当我们打开阅读软件,才有书籍可以看,没有阅读软件,书籍是看不了的。因此,手机和书籍没有一毛钱关系,书籍不应该在手机里面。正常的设计是:手机里面有阅读软件,阅读软件里面有书籍,这才符合迪米特法则,按定义来说:手机和阅读软件是朋友,阅读软件和书籍是朋友,可是朋友的朋友不是朋友,也就是手机和书籍不是朋友,所以它们不应该有交集,应该离得远远的。

思考一下现实:工作中如果缺少代码复核这个步骤,就会出现这样子,后果是怎么样呢?会给后人挖坑,而且是大坑,因为和现实中的逻辑是对不上的,况且后人不知道当时的业务背景,只能看代码去熟悉,就会一步错、步步错,所以要好好把控代码质量这一关,因为代码千人千面,没法要求代码风格全部一致,但至少需要实现逻辑是清晰易懂的。

正确例子

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
public class LODRightTest {

public static void main(String[] args) {
Phone2 phone2 = new Phone2();
phone2.readBook();
}

}

/**
* 正确的示范
*/
class Phone2 {

private App2 app2 = new App2();

public void readBook() {
app2.read();
}

}


class App2 {
private Book2 book2 = new Book2("设计模式");

public void read() {
System.out.println(book2.getTitle());
}

}

class Book2 {

private String title;

public Book2(String title) {
this.title = title;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}
}

代码:
LODRightTest

这段代码就符合迪米特法则,手机中有阅读软件,阅读软件中有书籍,手机没有书籍任何影子。正确代码不用细讲,用心去感受就可以体会到。

总结

迪米特法则主要讲述的观点是高内聚、低耦合。我理解为:是你的,就别给别人;不是你的,就别拿。上面定义的朋友也是这个意思。

参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》

写到这,第五个原则了,六大原则就剩下最后一个开闭原则,凑齐 6 把大刀就可以准备去大干 23 个大汉啦。。。想想都激动。

希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号,第一时间获取文章推送阅读,也可以一起交流,交个朋友。

公众号之设计模式系列文章

公众号

1…8910…24
LieBrother

LieBrother

当才华撑不起野心时,应该静下心来学习;当能力驾驭不了目标时,应该沉下心来历练。

120 日志
38 分类
138 标签
© 2016 - 2019 LieBrother
由 Hexo 强力驱动
主题 - NexT.Mist
本站访客数人次  |  本站总访问量次