LieBrother

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


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

结构型模式:组合模式

发表于 2019-04-24   |     |   阅读次数

文章首发:
结构型模式:组合模式

夜晚

七大结构型模式之三:组合模式。

简介

姓名 :组合模式

英文名 :Composite Pattern

价值观 :

个人介绍 :
Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
(来自《设计模式之禅》)

你要的故事

今天咱们再讲讲咱们程序猿的组织架构。技术类的组织架构比较单一,基本上都是这样:经理—>组长—>工程师,如下图所示。

IT组织架构

各个公司的 title 可能不太一样,但是基本是差不多这种架构,按职业发展,从入职到能独立开发需求便为工程师,从独立开发需求到能带小团队开发便为组长,从带小团队开发到能带几个团队一起协作开发便为经理。

假设目前有一家公司,技术部就 4 个人,大熊担任经理,中熊担任组长,小熊1和小熊2担任工程师。下面的代码都围绕这个假设编写。

非组合模式

我们先来一个非正常的实现方案:从组织架构里,有 3 个角色,分别是经理、组长、工程师,那么我们就按角色去实现一番。

Manager 为经理类,经理下有多个组长 leaders。

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
/**
* 经理
*/
class Manager {

private String name;
private List<Leader> leaders;

public Manager(String name) {
this.name = name;
this.leaders = new LinkedList<>();
}

public void add(Leader leader) {
this.leaders.add(leader);
}

public void remove(Leader leader) {
this.leaders.remove(leader);
}

public void display(int index) {
for (int i = 0; i < index; i++) {
System.out.print("----");
}
System.out.println("经理:" + this.name);
leaders.forEach(leader -> {
leader.display(index+1);
});
}

}

Leader 为组长类,组长下有多个工程师 engineers。

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
/**
* 组长
*/
class Leader {

private String name;
private List<Engineer> engineers;

public Leader(String name) {
this.name = name;
this.engineers = new LinkedList<>();
}

public void add(Engineer engineer) {
this.engineers.add(engineer);
}

public void remove(Engineer engineer) {
this.engineers.remove(engineer);
}

public void display(int index) {
for (int i = 0; i < index; i++) {
System.out.print("----");
}
System.out.println("组长:" + this.name);
engineers.forEach(engineer -> {
engineer.display(index + 1);
});
}
}

Engineer 为工程师类,工程师没有下属。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 工程师
*/
class Engineer {

private String name;

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

public void display(int index) {
for (int i = 0; i < index; i++) {
System.out.print("----");
}
System.out.println("工程师:" + this.name);
}

}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class NoCompositeTest {

public static void main(String[] args) {
Manager manager = new Manager("大熊");
Leader leader = new Leader("中熊");
Engineer engineer1= new Engineer("小熊1");
Engineer engineer2 = new Engineer("小熊2");

manager.add(leader);
leader.add(engineer1);
leader.add(engineer2);

manager.display(0);
}

}

打印结果:
经理:大熊
----组长:中熊
--------工程师:小熊1
--------工程师:小熊2

这份代码看完之后,有什么想法?是不是感觉代码有点冗余?经理和组长的代码几乎一致,而工程师类和经理类、组长类也有共同点,唯一的区别就是工程师没有下属,因此没有对下属的增删操作方法。

安全模式

通过上面一层思考,这 3 个角色有相通性,我们可以抽象出一个 Employee 类,把 3 个角色共同的特性放到 Employee2 类中,经理和组长合并共用一个类,因为在这个例子里,这 2 个角色完全一样的。下面看代码。

Employee2 抽象类,它有这 3 个角色共有的特性,名称设置获取以及显示数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class Employee2 {

private String name;

public String getName() {
return name;
}

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

public abstract void display(int index);

}

Leader2 领导类,把上面的经理类和组长类都合并到这个领导类,因为他们都是领导层。

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
class Leader2 extends Employee2 {

private List<Employee2> employees;

public Leader2(String name) {
this.setName(name);
this.employees = new ArrayList<>();
}

public void add(Employee2 employee) {
this.employees.add(employee);
}

public void remove(Employee2 employee) {
this.employees.remove(employee);
}

@Override
public void display(int index) {
for(int i = 0; i < index; i++) {
System.out.print("----");
}
System.out.println("领导:" + this.getName());
this.employees.forEach(employee -> {
employee.display(index + 1);
});
}
}

Engineer2 工程师类,工程师类比较简单,因为名称设置获取在抽象类 Employee2 有了,所以就只需实现显示数据的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Engineer2 extends Employee2 {

public Engineer2(String name) {
this.setName(name);
}

@Override
public void display(int index) {
for(int i = 0; i < index; i++) {
System.out.print("----");
}
System.out.println("工程师:" + this.getName());
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CompositeTest {

public static void main(String[] args) {
// 安全模式
Leader2 leader1 = new Leader2("大熊");
Leader2 leader2 = new Leader2("中熊");
Engineer2 engineer1 = new Engineer2("小熊1");
Engineer2 engineer2 = new Engineer2("小熊2");

leader1.add(leader2);
leader2.add(engineer1);
leader2.add(engineer2);

leader1.display(0);
}

}

打印结果:
领导:大熊
----领导:中熊
--------工程师:小熊1
--------工程师:小熊2

看下运行结果和上面是一致的,这份代码比第一份代码有更好的封装性,也更符合面向对象的编程方式,经理和组长被合并成 Leader2,也就是咱们今天讲的组合模式,Leader2 为组合对象。上面讲的是安全模式,安全模式指的是抽象类 Employee2 只提供了 3 个角色中共有的特性,安全是相对透明模式所说的,因为这里领导类 Leader2 和工程师类 Engineer2 都只提供了自己能提供的方法,Engineer2 不会有多余的方法,而透明模式则不是。下面讲讲透明模式。

透明模式

透明模式把组合对象(即领导类)使用的方法放到抽象类中。而因为工程师没有下属,则不具体实现对应的方法。代码如下。

Employee3 抽象类,将组合对象的属性 employees 和 方法 add()、 remove() 都放到这个类里面。

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
abstract class Employee3 {

private String name;
private List<Employee3> employees;

public String getName() {
return name;
}

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

public List<Employee3> getEmployees() {
return employees;
}

public void setEmployees(List<Employee3> employees) {
this.employees = employees;
}

public abstract void add(Employee3 employee);

public abstract void remove(Employee3 employee);

public abstract void display(int index);

}

Leader3 领导类,具体实现 Employee3 提供的所有方法。

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
class Leader3 extends Employee3 {

public Leader3(String name) {
this.setName(name);
this.setEmployees(new ArrayList<>());
}

@Override
public void add(Employee3 employee) {
this.getEmployees().add(employee);
}

@Override
public void remove(Employee3 employee) {
this.getEmployees().remove(employee);
}

@Override
public void display(int index) {
for(int i = 0; i < index; i++) {
System.out.print("----");
}
System.out.println("领导:" + this.getName());
this.getEmployees().forEach(employee -> {
employee.display(index + 1);
});
}
}

Engineer3 工程师类,只具体实现 Employee3 中的 display() 方法,add() 和 remove() 方法不是工程师具备的,所以留空,不做具体实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Engineer3 extends Employee3 {

public Engineer3(String name) {
this.setName(name);
}

@Override
public void add(Employee3 employee) {
// 没有下属
}

@Override
public void remove(Employee3 employee) {
// 没有下属
}

@Override
public void display(int index) {
for(int i = 0; i < index; i++) {
System.out.print("----");
}
System.out.println("工程师:" + this.getName());
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CompositeTest {

public static void main(String[] args) {
// 透明模式
Leader3 leader3 = new Leader3("大熊");
Leader3 leader31 = new Leader3("中熊");
Engineer3 engineer31 = new Engineer3("小熊1");
Engineer3 engineer32 = new Engineer3("小熊2");

leader3.add(leader31);
leader31.add(engineer31);
leader31.add(engineer32);

leader3.display(0);

}

打印结果:
领导:大熊
----领导:中熊
--------工程师:小熊1
--------工程师:小熊2
}

安全模式把 3 个角色的共同点抽象到 Employee2 中,透明模式则把 3 个角色中的领导者(组合对象)的内容抽象到 Employee3 中。透明模式有些不好的地方在于工程师也有领导者的下属对象和相应的方法,其实工程师并没有这些功能。安全模式把领导者和工程师分开,每个对象都只提供自己具有的功能,这样子在使用的时候也就更安全。

总结

我们根据 IT 组织架构,从简单的每个角色对应一个类的实现,再到抽象出每个角色共同的功能、组合领导类的安全模式,接着再到抽象起来领导类(组合)所有功能的透明模式,分析了组合模式的完整过程,也讲了安全模式和透明模式的差异。组合模式让对象更加有层次,将对象的划分更加清晰,特别是树形结构的层次,利用组合模式会更加简化。

推荐阅读

结构型模式:适配器模式

行为型模式:访问者模式

行为型模式:解释器模式

公众号后台回复『大礼包』获取 Java、Python、IOS 等教程
加个人微信备注『教程』获取架构师、机器学习等教程

LieBrother

结构型模式:桥接模式

发表于 2019-04-22   |     |   阅读次数

文章首发:
结构型模式:桥接模式

桥

七大结构型模式之二:桥接模式。

简介

姓名 :桥接模式

英文名 :Bridge Pattern

价值观 :解耦靠我

个人介绍 :
Decouple an abstraction from its implementation so that the two can vary independently.
将抽象和实现解耦,使得两者可以独立地变化。
(来自《设计模式之禅》)

你要的故事

现在手机二分天下,安卓手机和苹果手机目前占有率高居 98.45%,其中安卓手机占有率为 70.21%,苹果手机占有率为 28.24%,如下图所示。

最新手机系统市场份额
(数据从 netmarketshare 来)

因为有这 2 个系统,所以很多软件商都不得不开发 2 个系统的 APP。我们就拿这个案例来讲,目前手机有安卓手机和苹果手机,软件有谷歌浏览器和火狐浏览器,通过手机打开软件这一过程来讲讲桥接模式。

从个人介绍可见,需要抽象化和实现化,然后使用桥接模式将抽象和实现解耦。

抽象化:把一类对象共有的东西抽象到一个类里面,该类作为这类对象的基类。在这里我们可以抽象化的便是手机。

实现化:将接口或抽象类的未实现的方法进行实现。在这里我们可以实现化的就是软件。

将抽象和实现解耦:有了上面的抽象化和实现化,通过桥接模式来实现解耦。在这里,我们把打开软件 open() 放到软件实现中,而抽象的手机利用模板方法模式定义 openSoftware() 供手机子类去实现,手机子类也是调用软件的 open() 方法,并没有自己实现打开逻辑,也就是解耦了这个打开软件过程。

下面给出案例的代码。

Phone 手机抽象类代码。属性 system 代表系统名称,software 代表要打开的软件,openSoftware() 对外提供打开软件的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
abstract class Phone {

private String system;
private Software software;

public abstract void openSoftware();

public String getSystem() {
return system;
}

public void setSystem(String system) {
this.system = system;
}

public Software getSoftware() {
return software;
}

public void setSoftware(Software software) {
this.software = software;
}

}

AndroidPhone 安卓系统手机代码。

1
2
3
4
5
6
7
8
9
10
11
12
class AndroidPhone extends Phone {

public AndroidPhone(Software software){
this.setSystem("Android");
this.setSoftware(software);
}

@Override
public void openSoftware() {
this.getSoftware().open(this);
}
}

IOSPhone IOS 系统手机代码(也就是苹果手机)。

1
2
3
4
5
6
7
8
9
10
11
12
class IOSPhone extends Phone {

public IOSPhone(Software software) {
this.setSystem("IOS");
this.setSoftware(software);
}

@Override
public void openSoftware() {
this.getSoftware().open(this);
}
}

Software 软件接口代码。它有一个方法 open(),用于打开该软件。

1
2
3
interface Software {
void open(Phone phone);
}

Chrome 谷歌浏览器软件代码。

1
2
3
4
5
6
7
8
class Chrome implements Software {

@Override
public void open(Phone phone) {
System.out.println("打开 " + phone.getSystem() + " 手机的 Chrome 浏览器");
}

}

FireFox 火狐浏览器软件代码。

1
2
3
4
5
6
7
8
class FireFox implements Software {

@Override
public void open(Phone phone) {
System.out.println("打开 " + phone.getSystem() + " 手机的 Firefox 浏览器");
}

}

测试代码如下。

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

public static void main(String[] args) {
Software chrome = new Chrome();
Software firefox = new FireFox();

Phone androidPhone = new AndroidPhone(chrome);
androidPhone.openSoftware();

androidPhone.setSoftware(firefox);
androidPhone.openSoftware();

Phone iosPhone = new IOSPhone(chrome);
iosPhone.openSoftware();

iosPhone.setSoftware(firefox);
iosPhone.openSoftware();
}

}

打印结果:
打开 Android 手机的 Chrome 浏览器
打开 Android 手机的 Firefox 浏览器
打开 IOS 手机的 Chrome 浏览器
打开 IOS 手机的 Firefox 浏览器

桥接模式代码已经写完。为什么叫桥接模式呢?因为它将打开软件的具体实现放到了软件实现里面,而不是放在了手机,通过聚合方式去调用软件打开的方法,这就像一条桥一样连接手机和软件。

总结

桥接模式利用了聚合的优点去解决继承的缺点,使得抽象和实现进行分离解耦。正由于解耦,使得有更好的扩展性,加手机类型或者加软件都非常容易,也不会破坏原有的代码。

推荐阅读

结构型模式:适配器模式

行为型模式:访问者模式

行为型模式:解释器模式

公众号后台回复『大礼包』获取 Java、Python、IOS 等教程
加个人微信备注『教程』获取架构师、机器学习等教程

LieBrother

结构型模式:适配器模式

发表于 2019-04-17   |     |   阅读次数

文章首发:
结构型模式:适配器模式

夜

七大结构型模式之一:适配器模式。

简介

姓名 :适配器模式

英文名 :Adapter Pattern

价值观 :老媒人,牵线搭桥

个人介绍 :
Convert the interface of a class into another interface clients expect.Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
(来自《设计模式之禅》)

你要的故事

大家有买过港式的 Apple 产品么?在深圳的同学估计买过,毕竟港式的 Apple 产品基本比国内便宜 500 以上。我手机和平板都是在香港买的,买来后这充电器是没法直接充电的,因为港版的电子产品都是英式的插头,而咱们国内是中式的,所以用上港版电子产品的同学免不了要用上这么一个转换器:将英式的插孔转为中式的插孔,方可插入咱家里的插座充电。这个转换器就是今天想讲的适配器。

没见过的同学可以看看图片熟悉一下,下图右边为港版苹果手机充电器,插头比较大,左边为某品牌转换器,插头为中国家用标准形状。
英中转换器

下图为使用时的图片
使用中

在这描述一下这个场景。用港式插头要在国内充电,因为插头和插座大小对不上,所以需要加一个适配器,这个适配器充当插头和插座,它的插头可以插入国内标准的插座,它的插座可以插入港式标准的插头,这样子就可以用港式充电器在国内为手机充电。

下面用适配器模式代码实现这个场景。

首先需要找到被适配的对象是什么?在这里我们的被适配对象是英式充电器。

1
2
3
4
5
6
7
8
9
10
/**
* 英式充电器
*/
class BritishCharger {

public void chargeByBritishStandard(){
System.out.println("用英式充电器充电");
}

}

在这个场景的目的是什么?在中国为港式手机充电,因此目的是让英式充电器能够在中国标准的插座充电。

1
2
3
4
5
6
7
8
/**
* 使用中式插座充电
*/
interface Target {

void chargeByChineseStandard();

}

接下来是这个设计模式的主角:适配器。它需要连接中式插座以及英式充电器,在中间做适配功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 充电器适配器
*/
class ChargerAdapter implements Target {

private BritishCharger britishCharger;

public ChargerAdapter(BritishCharger britishCharger) {
this.britishCharger = britishCharger;
}

@Override
public void chargeByChineseStandard() {
System.out.println("使用中英式插头转换器");
britishCharger.chargeByBritishStandard();
}
}

上面是适配器模式的一个简单的例子,要学习适配器模式也可以看看 Java 的 IO 实现源码,里面是应用适配器模式的官方很好的代码。

总结

适配器很好的将 2 个无法关联的类结合起来,在中间起桥梁作用。另外新增适配器代码不会影响原来被适配者的正常使用,他们可以一起被使用。在工作中和外部系统对接的时候,大可能外部系统的数据格式和自己系统的数据格式并不相同,这时候就可以利用适配器模式来实现。

推荐阅读

行为型模式:访问者模式

行为型模式:解释器模式

行为型模式:备忘录模式

公众号后台回复『大礼包』获取 Java、Python、IOS 等教程
加个人微信备注『教程』获取架构师、机器学习等教程

LieBrother

行为型模式:访问者模式

发表于 2019-04-17   |     |   阅读次数

文章首发:
行为型模式:访问者模式

如画

十一大行为型模式之十一:访问者模式。

简介

姓名 :访问者模式
英文名 :Visitor Pattern
价值观 :来访者便是客,招待就是
个人介绍 :
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
(来自《设计模式之禅》)

你要的故事

先声明一下,下面故事全瞎编的。。。

我们是否还记得 N 年前反腐开始的时候,有一段时间提倡官员宴请吃饭只能几菜几汤,不能超出。我记得那会刚读大一,军事理论的老师说到这个问题,也发表了他的一些想法,他觉得这么做比较刻板。今天的故事就和宴请有关。现在中国企业发展越来越大,在社会中担任的责任也越来越大,政府也越来越重视企业,官员去参观企业是常有的事,而企业宴请官员也变得格外的常见。

故事的背景就是企业宴请各级官员。不同级别的官员宴请的菜式就不一样,每家企业的菜式丰富程度也不一样。我们这里的访问对象就用 Alibaba 和 Tencent 这 2 家公司,而访问者就用郭嘉领导人和省领导人做举例。这 2 家公司都跟喜来登酒店合作,Alibaba 合作方案是:宴请省级领导人及以下官员则十菜一汤,宴请郭嘉领导人及以上官员则十四菜两汤;Tencent 合作方案是:宴请省领导人及以下官员则八菜一汤,宴请郭嘉领导人及以上官员则十六菜两汤。

下面看看如何用访问者模式来实现上面的故事。

首先定义一个抽象类:企业。企业有一个共有的特性就是接受上级领导的访问。

1
2
3
4
5
6
7
8
/**
* 企业
*/
abstract class Company {

public abstract void accept(Vistor vistor);

}

上面故事我们举例了 2 家企业,分别是 Alibaba 和 Tencent,这里实现这 2 家公司的宴请方案,并实现接待访问者方法。

Alibaba 宴请郭嘉领导人及以上官员是十四菜两汤,宴请省领导及以下是十菜一汤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Alibaba 企业
*/
class AlibabaCompany extends Company {

@Override
public void accept(Vistor vistor) {
vistor.visit(this);
}

public String entertainBelowProvincialLeader(String leader) {
return "Alibaba 接待" + leader + ":十菜一汤";
}

public String entertainAboveNationalLeader(String leader) {
return "Alibaba 接待" + leader + ":十四菜两汤";
}

}

Tencent 宴请郭嘉领导人及以上是十六菜两汤,宴请省领导及以下是八菜一汤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Tencent 企业
*/
class TencentCompany extends Company {

@Override
public void accept(Vistor vistor) {
vistor.visit(this);
}

public String entertainBelowProvincialLeader(String leader) {
return "Tencent 接待" + leader + ":八菜一汤";
}

public String entertainAboveNationalLeader(String leader) {
return "Tencent 接待" + leader + ":十六菜两汤";
}
}

这里定义访问者接口,访问者接口有 2 个方法,分别是访问 Alibaba 企业和访问 Tencent 企业。

1
2
3
4
5
6
7
8
9
10
/**
* 访问者接口
*/
interface Vistor {

void visit(AlibabaCompany alibabaCompany);

void visit(TencentCompany tencentCompany);

}

上面故事中有 2 个访问者,一个是郭嘉领导人,另一个是省领导人,因为不同企业对应不同访问者有不同的宴请方案,所以这里访问企业是需要调用对应企业的宴请方式。

省领导人访问企业时,需要调用企业对省领导及以下官员的宴请方案,为entertainBelowProvincialLeader()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 省领导访问
*/
class ProvincialLeaderVistor implements Vistor {

@Override
public void visit(AlibabaCompany alibabaCompany) {
System.out.println(alibabaCompany.entertainBelowProvincialLeader("省领导"));
}

@Override
public void visit(TencentCompany tencentCompany) {
System.out.println(tencentCompany.entertainBelowProvincialLeader("省领导"));
}
}

郭嘉领导人访问企业时,需要调用企业对郭嘉领导人的宴请方案,为entertainAboveNationalLeader()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 郭嘉领导访问
*/
class NationalLeaderVistor implements Vistor {

@Override
public void visit(AlibabaCompany alibabaCompany) {
System.out.println(alibabaCompany.entertainAboveNationalLeader("省领导"));
}

@Override
public void visit(TencentCompany tencentCompany) {
System.out.println(tencentCompany.entertainAboveNationalLeader("郭嘉领导"));
}
}

上面是访问者和被访问者的代码,因为企业是在喜来登酒店宴请领导人,所以这里还需要一个酒店,酒店里面有企业合作的名单,以及负责宴请各路领导的方法提供。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 酒店
*/
class Hotel {
private List<Company> companies = new ArrayList<>();

public void entertain(Vistor vistor) {
for (Company company : companies) {
company.accept(vistor);
}
}

public void add(Company company) {
companies.add(company);
}
}

下面提供测试代码,看看运行的结果怎样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class VisitorTest {

public static void main(String[] args) {
AlibabaCompany alibabaCompany = new AlibabaCompany();
TencentCompany tencentCompany = new TencentCompany();
ProvincialLeaderVistor provincialLeaderVistor = new ProvincialLeaderVistor();
NationalLeaderVistor nationalLeaderVistor = new NationalLeaderVistor();

Hotel xilaideng = new Hotel();
xilaideng.add(alibabaCompany);
xilaideng.add(tencentCompany);

xilaideng.entertain(provincialLeaderVistor);
xilaideng.entertain(nationalLeaderVistor);
}

}

打印结果:
Alibaba 接待省领导:十菜一汤
Tencent 接待省领导:八菜一汤
Alibaba 接待郭嘉领导:十四菜两汤
Tencent 接待郭嘉领导:十六菜两汤

完整的访问者模式代码已经呈现,花 1 分钟思考一番,理解整个代码后我们来看看下面的总结。

总结

访问者模式有比较好的扩展性,看看访问者代码,我们如果要新增一个访问者:市领导人,只需新增市领导人类,便可实现。当然也有它不好的地方,就是把被访问者暴露给访问者,使得访问者可以直接了解被访问者的所有东西。明白了优缺点,才能更好的在实际中运用,一般访问者模式运用于要求遍历多个不同的对象的场景。

推荐阅读

行为型模式:解释器模式

行为型模式:观察者模式

行为型模式:迭代器模式

设计模式系列文章持续更新中,欢迎关注公众号 LieBrother,一起交流学习。

公众号后台回复『大礼包』获取 Java、Python、IOS 等教程
加个人微信备注『教程』获取架构师、机器学习等教程

LieBrother

行为型模式:解释器模式

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

文章首发:
行为型模式:解释器模式

鲨鱼

十一大行为型模式之十:解释器模式。

简介

姓名 :解释器模式
英文名 :Interpreter Pattern
价值观 :不懂解释到你懂​
个人介绍 :
Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
(来自《设计模式之禅》)

你要的故事

解释器顾名思义就是对 2 个不同的表达方式进行转换,让本来不懂的内容解释成看得懂的。比如翻译官就是解释器,把英文翻译成中文,让我们明白外国人说什么。咱们工作中也有很多类似的场景,开发系统避免不了使用数据库,数据库有特定的语法,我们称为 SQL (Structured Query Language),而我们系统开发语言和 SQL 的语法不一样,这中间就需要做一层转换,像把 Java 语言中的 userDao.save(user) 变成 insert into user (name,age) values ('小明', 18),这一层转换也可以称为解释器。很多框架实现了这个功能,比如 Hibernate,我们称这些框架为 ORM。

今天,我们就来简单的实现 SQL 拼接解释器,通过参数组装成我们要的 SQL 语句。好多开发同学都吐槽工作天天在 CRUD,也就是只干增删改查的活,对于 SQL 我们经常用的也就是这 4 种语法:insert 语句、delete 语句、update 语句、select 语句。这 4 种语法各有不同,也即需要不同的解释器去解析。利用今天要讲的解释器模式,我们来实现一番。

解释器模式中,会有一个上下文类,这个类用于给解释器传递参数。这里我们 SQL 解释器需要的参数分别是

  1. tableName :数据库名
  2. params :修改时更新后的数据
  3. wheres :where 语句后的条件
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
class Context {
private String tableName;
private Map<String, Object> params = new HashMap<>();
private Map<String, Object> wheres = new HashMap<>();

public String getTableName() {
return tableName;
}

public void setTableName(String tableName) {
this.tableName = tableName;
}

public Map<String, Object> getParams() {
return params;
}

public void setParams(Map<String, Object> params) {
this.params = params;
}

public Map<String, Object> getWheres() {
return wheres;
}

public void setWheres(Map<String, Object> wheres) {
this.wheres = wheres;
}
}

解释器主角来了,定义 SQL 解释器抽象类,它有一个抽象方法 interpret,通过这个方法来把 context 中的参数解释成对应的 SQL 语句。

1
2
3
4
5
6
7
8
/**
* SQL 解释器
*/
abstract class SQLExpression {

public abstract String interpret(Context context);

}

我们上面说了 SQL 语句用的比较多的就是 4 种,每一种其实就是一个解释器,因为语法不一样,解释的逻辑也就不一样,我们就利用 SQLExpression 解释器抽象类,来实现 4 个具体的 SQL 解释器,分别如下:

Insert SQL 解释器代码实现:

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
/**
* Insert SQL 解释器
*/
class InsertSQLExpression extends SQLExpression {

@Override
public String interpret(Context context) {
StringBuilder insert = new StringBuilder();
insert.append("insert into ")
.append(context.getTableName());

// 解析 key value
StringBuilder keys = new StringBuilder();
StringBuilder values = new StringBuilder();
keys.append("(");
values.append("(");
for (String key : context.getParams().keySet()) {
keys.append(key).append(",");
values.append("'").append(context.getParams().get(key)).append("',");
}
keys = keys.replace(keys.length() - 1, keys.length(), ")");
values = values.replace(values.length() - 1, values.length(), ")");

// 拼接 keys values
insert.append(keys)
.append(" values ")
.append(values);

System.out.println("Insert SQL : " + insert.toString());
return insert.toString();
}
}

Update SQL 解释器代码实现:

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
/**
* Update SQL 解释器
*/
class UpdateSQLExpression extends SQLExpression {

@Override
public String interpret(Context context) {
StringBuilder update = new StringBuilder();
update.append("update ")
.append(context.getTableName())
.append(" set ");

StringBuilder values = new StringBuilder();
for (String key : context.getParams().keySet()) {
values.append(key)
.append(" = '")
.append(context.getParams().get(key))
.append("',");
}

StringBuilder wheres = new StringBuilder();
wheres.append(" 1 = 1 ");
for (String key : context.getWheres().keySet()) {
wheres.append(" and ")
.append(key)
.append(" = '")
.append(context.getWheres().get(key))
.append("'");
}

update.append(values.substring(0, values.length() - 1))
.append(" where ")
.append(wheres);

System.out.println("Update SQL : " + update.toString());
return update.toString();
}
}

Select SQL 解释器代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Select SQL 解释器
*/
class SelectSQLExpression extends SQLExpression {

@Override
public String interpret(Context context) {
StringBuilder select = new StringBuilder();
select.append("select * from ")
.append(context.getTableName())
.append(" where ")
.append(" 1 = 1 ");
for (String key : context.getWheres().keySet()) {
select.append(" and ")
.append(key)
.append(" = '")
.append(context.getWheres().get(key))
.append("'");
}
System.out.println("Select SQL : " + select.toString());
return select.toString();
}
}

Delete SQL 解释器代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Delete SQL 解释器
*/
class DeleteSQLExpression extends SQLExpression {

@Override
public String interpret(Context context) {
StringBuilder delete = new StringBuilder();
delete.append("delete from ")
.append(context.getTableName())
.append(" where ")
.append(" 1 = 1");
for (String key : context.getWheres().keySet()) {
delete.append(" and ")
.append(key)
.append(" = '")
.append(context.getWheres().get(key))
.append("'");
}
System.out.println("Delete SQL : " + delete.toString());

return delete.toString();
}
}

测试代码

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 InterpreterTest {
public static void main(String[] args) {
Context context = new Context();
context.setTableName("user");

// Insert SQL
Map<String, Object> params = new HashMap<>();
params.put("name", "小明");
params.put("job", "Java 工程师");
context.setParams(params);
SQLExpression sqlExpression = new InsertSQLExpression();
String sql = sqlExpression.interpret(context);

// Delete SQL
Map<String, Object> wheres = new HashMap<>();
wheres.put("name", "小明");
context.setParams(null);
context.setWheres(wheres);
sqlExpression = new DeleteSQLExpression();
sql = sqlExpression.interpret(context);

// Update SQL
params = new HashMap<>();
params.put("job", "Java 高级工程师");
wheres = new HashMap<>();
wheres.put("name", "小明");
context.setParams(params);
context.setWheres(wheres);
sqlExpression = new UpdateSQLExpression();
sql = sqlExpression.interpret(context);

// Select SQL
wheres = new HashMap<>();
wheres.put("name", "小明");
context.setParams(null);
context.setWheres(wheres);
sqlExpression = new SelectSQLExpression();
sql = sqlExpression.interpret(context);
}

}

打印结果:

Insert SQL : insert into user(name,job) values ('小明','Java 工程师')
Delete SQL : delete from user where 1 = 1 and name = '小明'
Update SQL : update user set job = 'Java 高级工程师' where 1 = 1 and name = '小明'
Select SQL : select * from user where 1 = 1 and name = '小明'

上面实现了整个解释器模式的代码,其实咱们在开发中,SQL 解析没有这么去实现,更多是用一个工具类把上面的各个 SQL 解释器的逻辑代码分别实现在不同方法中,如下代码所示。因为咱们可以预见的就这 4 种语法类型,基本上不用什么扩展,用一个工具类就足够了。

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

public static String insert(String tableName, Map<String, Object> params) {
StringBuilder insert = new StringBuilder();
insert.append("insert into ")
.append(tableName);

// 解析 key value
StringBuilder keys = new StringBuilder();
StringBuilder values = new StringBuilder();
keys.append("(");
values.append("(");
for (String key : params.keySet()) {
keys.append(key).append(",");
values.append("'").append(params.get(key)).append("',");
}
keys = keys.replace(keys.length() - 1, keys.length(), ")");
values = values.replace(values.length() - 1, values.length(), ")");

// 拼接 keys values
insert.append(keys)
.append(" values ")
.append(values);

System.out.println("Insert SQL : " + insert.toString());
return insert.toString();
}

public static String update(String tableName, Map<String, Object> params, Map<String, Object> wheres) {
StringBuilder update = new StringBuilder();
update.append("update ")
.append(tableName)
.append(" set ");

StringBuilder values = new StringBuilder();
for (String key : params.keySet()) {
values.append(key)
.append(" = '")
.append(params.get(key))
.append("',");
}

StringBuilder wheresStr = new StringBuilder();
wheresStr.append(" 1 = 1 ");
for (String key : wheres.keySet()) {
wheresStr.append(" and ")
.append(key)
.append(" = '")
.append(wheres.get(key))
.append("'");
}

update.append(values.substring(0, values.length() - 1))
.append(" where ")
.append(wheresStr);

System.out.println("Update SQL : " + update.toString());
return update.toString();
}

public static String select(String tableName, Map<String, Object> wheres) {
StringBuilder select = new StringBuilder();
select.append("select * from ")
.append(tableName)
.append(" where ")
.append(" 1 = 1 ");
for (String key : wheres.keySet()) {
select.append(" and ")
.append(key)
.append(" = '")
.append(wheres.get(key))
.append("'");
}
System.out.println("Select SQL : " + select.toString());
return select.toString();
}

public static String delete(String tableName, Map<String, Object> wheres) {
StringBuilder delete = new StringBuilder();
delete.append("delete from ")
.append(tableName)
.append(" where ")
.append(" 1 = 1");
for (String key : wheres.keySet()) {
delete.append(" and ")
.append(key)
.append(" = '")
.append(wheres.get(key))
.append("'");
}
System.out.println("Delete SQL : " + delete.toString());

return delete.toString();
}
}

总结

上面用解释器模式实现了 SQL 解释器,然后又指明了实际上咱们开发中大多数是直接一个 SQLUtil 工具类就搞定,并不是说解释器模式没用,想表达的观点是:解释器在工作中很少使用,工作中我们一般遵循的是能用就好策略,满足当前需求,加上一些易扩展性就足够了。解释器模式有比较大的扩展性,就如上面,再加上个建表语句 create table 只需要加一个 CreateTableSQLExpression 就可以轻松实现,不用去改动其他解释器代码。今天的解释器就到讲到这。觉得不错点个“在看”鼓励鼓励一下。

推荐阅读

行为型模式:备忘录模式

行为型模式:观察者模式

行为型模式:迭代器模式

设计模式系列文章持续更新中,欢迎关注公众号 LieBrother,一起交流学习。

LieBrother

1…456…24
LieBrother

LieBrother

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

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