回顾
适配器模式使用了==继承==的方法,达到了兼容的目的,或者使用注入的办法也能够达到兼容的目的。适配器模式就是==兼容==
装饰器模式
- 为了某个实现类在不修改原始类的基础上进行动态的覆盖或者增加方法。
- 该实现保持跟原有类的继承关系
- 采用装饰模式
- 装饰器模式实际上是一种非常特殊的适配器模式
==装饰器模式和适配器模式对比==
装饰器模式 | 适配器模式 |
---|---|
是一种非常特别的适配器模式 | 可以不保留层级关系 |
装饰者和被装饰者都要实现同一个接口 主要目是为了:扩展,依旧保留 OOP 关系 | 适配者和被适配者没有必然的层级关系 通常采用代理或者继承的形式进行包装 |
满足 is - a 的关系 | 满足 has - a 的关系 |
注重覆盖、扩展 | 注重兼容、转换 |
Spring中 装饰器模式的类:
- Decorator
- wrapper
装饰器代码:
Member.class
java
@Data
public class Member {
private String userName;
private String password;
private String mid;
private String info;
}
@Data
public class Member {
private String userName;
private String password;
private String mid;
private String info;
}
1
2
3
4
5
6
7
2
3
4
5
6
7
ResultMsg.class
java
@Data
public class ResultMsg {
private String code;
private String msg;
private Object data;
public ResultMsg(String code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
@Data
public class ResultMsg {
private String code;
private String msg;
private Object data;
public ResultMsg(String code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
SigninService.class
SigninService.class
java
@Deprecated // 过时
public interface SigninService {
ResultMsg regist(String userName, String password);
ResultMsg login(String userName, String password);
}
@Deprecated // 过时
public interface SigninService {
ResultMsg regist(String userName, String password);
ResultMsg login(String userName, String password);
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
SigninServiceImpl.class
java
public class SigninServiceImpl implements SigninService {
/***
* 注册方法
* @param userName
* @param password
* @return
*/
public ResultMsg regist(String userName, String password) {
return new ResultMsg("200", "注册成功", new Member());
}
/***
* 登录方法
* @param userName
* @param password
* @return
*/
public ResultMsg login(String userName, String password) {
return null;
}
}
public class SigninServiceImpl implements SigninService {
/***
* 注册方法
* @param userName
* @param password
* @return
*/
public ResultMsg regist(String userName, String password) {
return new ResultMsg("200", "注册成功", new Member());
}
/***
* 登录方法
* @param userName
* @param password
* @return
*/
public ResultMsg login(String userName, String password) {
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SigninForThirdService.class
java
public interface SigninForThirdService extends SigninService {
public ResultMsg loginForQQ(String openId);
public ResultMsg logForWeChat(String openId);
public ResultMsg loginForToken(String token);
public ResultMsg loginForTelephone(String telephone, String code);
public ResultMsg loginForRegist(String userName, String password);
}
public interface SigninForThirdService extends SigninService {
public ResultMsg loginForQQ(String openId);
public ResultMsg logForWeChat(String openId);
public ResultMsg loginForToken(String token);
public ResultMsg loginForTelephone(String telephone, String code);
public ResultMsg loginForRegist(String userName, String password);
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
SigninForThirdServiceImpl.class
java
public class SigninForThirdServiceImpl implements SigninForThirdService {
private SigninService signinService;
public SigninForThirdServiceImpl(SigninService signinService) {
this.signinService = signinService;
}
public ResultMsg regist(String userName, String password) {
return signinService.regist(userName, password);
}
public ResultMsg login(String userName, String password) {
return signinService.login(userName, password);
}
public ResultMsg loginForQQ(String openId) {
// 1. openId 是全局唯一的,我们可以把它当作一个用户名(加长)
// 2. 密码默认为 QQ_EMPTY
// 3. 注册(在原有的系统里边创建一个用户)
// 4. 调用原来的登陆方法
return this.loginForRegist(openId, null);
}
public ResultMsg logForWeChat(String openId) {
return null;
}
public ResultMsg loginForToken(String token) {
// 通过 token 拿到用户信息,然后再重新登录一次
return null;
}
public ResultMsg loginForTelephone(String telephone, String code) {
//
return null;
}
public ResultMsg loginForRegist(String userName, String password) {
this.regist(userName, password);
return this.login(userName, password);
}
}
public class SigninForThirdServiceImpl implements SigninForThirdService {
private SigninService signinService;
public SigninForThirdServiceImpl(SigninService signinService) {
this.signinService = signinService;
}
public ResultMsg regist(String userName, String password) {
return signinService.regist(userName, password);
}
public ResultMsg login(String userName, String password) {
return signinService.login(userName, password);
}
public ResultMsg loginForQQ(String openId) {
// 1. openId 是全局唯一的,我们可以把它当作一个用户名(加长)
// 2. 密码默认为 QQ_EMPTY
// 3. 注册(在原有的系统里边创建一个用户)
// 4. 调用原来的登陆方法
return this.loginForRegist(openId, null);
}
public ResultMsg logForWeChat(String openId) {
return null;
}
public ResultMsg loginForToken(String token) {
// 通过 token 拿到用户信息,然后再重新登录一次
return null;
}
public ResultMsg loginForTelephone(String telephone, String code) {
//
return null;
}
public ResultMsg loginForRegist(String userName, String password) {
this.regist(userName, password);
return this.login(userName, password);
}
}
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
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
SignninTest.class
java
public class SignninTest {
public static void main(String[] args) {
// 原来的功能依旧对外开放,依旧保留
// 新的功能同样地也可以使用
SigninForThirdService signinForThirdService = new SigninForThirdServiceImpl(new SigninServiceImpl());
}
}
public class SignninTest {
public static void main(String[] args) {
// 原来的功能依旧对外开放,依旧保留
// 新的功能同样地也可以使用
SigninForThirdService signinForThirdService = new SigninForThirdServiceImpl(new SigninServiceImpl());
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
DecotratorTest.class
java
public class DecotratorTest {
public static void main(String[] args) {
// 为了某个实现类在不修改原始类的基础上进行动态的覆盖或者增加方法。
// 该实现保持跟原有类的继承关系
// 采用装饰模式
// 装饰器模式实际上是一种非常特殊的适配器模式
// 虽然 DAtaInputStream 功能更强大
// DataInputStream 同样去实现 InputStrem
InputStream in = null;
DataInputStream fis = new DataInputStream(in);
}
}
public class DecotratorTest {
public static void main(String[] args) {
// 为了某个实现类在不修改原始类的基础上进行动态的覆盖或者增加方法。
// 该实现保持跟原有类的继承关系
// 采用装饰模式
// 装饰器模式实际上是一种非常特殊的适配器模式
// 虽然 DAtaInputStream 功能更强大
// DataInputStream 同样去实现 InputStrem
InputStream in = null;
DataInputStream fis = new DataInputStream(in);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
==assets/装饰器模式中类图.png==
观察者模式
发布者(Publish)和订阅者(Subscribe)
订阅者一直在观察发布者
订阅方是观察者模式,目的:解耦。
观察模式通常会跟代理模式混合使用。
监听器都是用的观察者模式
==assets/观察者模式Lily照片.png==
Event.class
java
public class Event {
// 事件源 ( set 方法, 只有同一个包下可以调用)
private Object source;
// 通知目标
private Object target;
// 回调
private Method callback;
// 触发 ( set 方法, 只有同一个包下可以调用)
private String trigger;
// 时间
private Date time;
public Event(Object target, Method callback) {
this.target = target;
this.callback = callback;
}
public Object getSource() {
return source;
}
void setSource(Object source) {
this.source = source;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public Method getCallback() {
return callback;
}
public void setCallback(Method callback) {
this.callback = callback;
}
public String getTrigger() {
return trigger;
}
Event setTrigger(String trigger) {
this.trigger = trigger;
return this;
}
public Date getTime() {
return time;
}
Event setTime(Date time) {
this.time = time;
return this;
}
@Override
public String toString() {
return "Event{" + "\n" +
"\tsource=" + source + ",\n" +
"\ttarget=" + target + ",\n" +
"\tcallback=" + callback + ",\n" +
"\ttrigger='" + trigger + '\'' + ",\n" +
"\ttime=" + time + ",\n" +
'}';
}
}
public class Event {
// 事件源 ( set 方法, 只有同一个包下可以调用)
private Object source;
// 通知目标
private Object target;
// 回调
private Method callback;
// 触发 ( set 方法, 只有同一个包下可以调用)
private String trigger;
// 时间
private Date time;
public Event(Object target, Method callback) {
this.target = target;
this.callback = callback;
}
public Object getSource() {
return source;
}
void setSource(Object source) {
this.source = source;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public Method getCallback() {
return callback;
}
public void setCallback(Method callback) {
this.callback = callback;
}
public String getTrigger() {
return trigger;
}
Event setTrigger(String trigger) {
this.trigger = trigger;
return this;
}
public Date getTime() {
return time;
}
Event setTime(Date time) {
this.time = time;
return this;
}
@Override
public String toString() {
return "Event{" + "\n" +
"\tsource=" + source + ",\n" +
"\ttarget=" + target + ",\n" +
"\tcallback=" + callback + ",\n" +
"\ttrigger='" + trigger + '\'' + ",\n" +
"\ttime=" + time + ",\n" +
'}';
}
}
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
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
EventListener.class
java
/**
* 事件的注册和监听
* <br>Darian
**/
public class EventListener {
protected Map<Enum, Event> events = new HashMap<Enum, Event>();
public void addListener(Enum eventType, Object target, Method callback){
// 注册时间
// 用反射调用这个方法
events.put(eventType, new Event(target, callback));
}
private void trigger(Event e){
e.setSource(this);
e.setTime(new Date());
try {
e.getCallback().invoke(e.getTarget(),e);
} catch (Exception e1) {
e1.printStackTrace();
}
}
protected void trigger(Enum call){
if( !this.events.containsKey(call)){
return ;
}
trigger(this.events.get(call).setTrigger(call.toString()));
}
}
/**
* 事件的注册和监听
* <br>Darian
**/
public class EventListener {
protected Map<Enum, Event> events = new HashMap<Enum, Event>();
public void addListener(Enum eventType, Object target, Method callback){
// 注册时间
// 用反射调用这个方法
events.put(eventType, new Event(target, callback));
}
private void trigger(Event e){
e.setSource(this);
e.setTime(new Date());
try {
e.getCallback().invoke(e.getTarget(),e);
} catch (Exception e1) {
e1.printStackTrace();
}
}
protected void trigger(Enum call){
if( !this.events.containsKey(call)){
return ;
}
trigger(this.events.get(call).setTrigger(call.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
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
Observer.class
java
public class Observer {
public void advice(Event e) {
System.out.println("=======触发事件,打印日志=========\n" + e);
/**
* input
* input.addListener("click", function(){
* })
**/
}
}
public class Observer {
public void advice(Event e) {
System.out.println("=======触发事件,打印日志=========\n" + e);
/**
* input
* input.addListener("click", function(){
* })
**/
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
Subject.class
java
public class Subject extends EventListener {
// 通常说的话,采用动态代理实现监控,避免代码侵入
public void add(){
System.out.println("调用一个添加的方法");
trigger(SubjectEventType.ON_ADD);
}
public void remore(){
System.out.println("调用删除的方法");
trigger(SubjectEventType.ON_REMOVE);
}
}
public class Subject extends EventListener {
// 通常说的话,采用动态代理实现监控,避免代码侵入
public void add(){
System.out.println("调用一个添加的方法");
trigger(SubjectEventType.ON_ADD);
}
public void remore(){
System.out.println("调用删除的方法");
trigger(SubjectEventType.ON_REMOVE);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
SubjectEventType.class
java
public enum SubjectEventType {
ON_ADD,
ON_REMOVE,
ON_EDIT,
ON_QUERY
}
public enum SubjectEventType {
ON_ADD,
ON_REMOVE,
ON_EDIT,
ON_QUERY
}
1
2
3
4
5
6
2
3
4
5
6
ObserverTest.class
java
public class ObserverTest {
public static void main(String[] args) {
try {
// 观察者
Observer observer = new Observer();
Method advice = observer.getClass().getMethod("advice", new Class<?>[]{Event.class});
// 这里写 Lily
Subject subject = new Subject();
subject.addListener(SubjectEventType.ON_ADD, observer, advice);
subject.addListener(SubjectEventType.ON_REMOVE, observer,advice);
subject.add();
subject.remore();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ObserverTest {
public static void main(String[] args) {
try {
// 观察者
Observer observer = new Observer();
Method advice = observer.getClass().getMethod("advice", new Class<?>[]{Event.class});
// 这里写 Lily
Subject subject = new Subject();
subject.addListener(SubjectEventType.ON_ADD, observer, advice);
subject.addListener(SubjectEventType.ON_REMOVE, observer,advice);
subject.add();
subject.remore();
} catch (Exception e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
==assets/观察者模式EventListener类图.png==
Mourse 鼠标的点击事件
Mouse.class
java
/**
* 如果有过 Swing 开发经验的话,就会有一种似曾相识的感觉
* <br>Darian
**/
public class Mouse extends EventListener {
public void click() {
System.out.println("鼠标单机");
this.trigger(MouseEventType.ON_CLICK);
}
public void doubleClick() {
System.out.println("鼠标双击");
this.trigger(MouseEventType.ON_DOUBLE_CLICK);
}
public void up() {
System.out.println("鼠标弹起");
this.trigger(MouseEventType.ON_UP);
}
public void down() {
System.out.println("鼠标按下");
this.trigger(MouseEventType.ON_DOWN);
}
public void wheel() {
System.out.println("鼠标滚动");
this.trigger(MouseEventType.ON_WHEEL);
}
public void move() {
System.out.println("鼠标移动");
this.trigger(MouseEventType.ON_MOVE);
}
public void over() {
System.out.println("鼠标悬停");
this.trigger(MouseEventType.ON_OVER);
}
}
/**
* 如果有过 Swing 开发经验的话,就会有一种似曾相识的感觉
* <br>Darian
**/
public class Mouse extends EventListener {
public void click() {
System.out.println("鼠标单机");
this.trigger(MouseEventType.ON_CLICK);
}
public void doubleClick() {
System.out.println("鼠标双击");
this.trigger(MouseEventType.ON_DOUBLE_CLICK);
}
public void up() {
System.out.println("鼠标弹起");
this.trigger(MouseEventType.ON_UP);
}
public void down() {
System.out.println("鼠标按下");
this.trigger(MouseEventType.ON_DOWN);
}
public void wheel() {
System.out.println("鼠标滚动");
this.trigger(MouseEventType.ON_WHEEL);
}
public void move() {
System.out.println("鼠标移动");
this.trigger(MouseEventType.ON_MOVE);
}
public void over() {
System.out.println("鼠标悬停");
this.trigger(MouseEventType.ON_OVER);
}
}
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
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
MouseEventCallBack.class
java
/**
* 观察者
*
* 回调响应的逻辑,由自己实现
* <br>Darian
**/
public class MouseEventCallBack {
public void onClick(Event e) {
System.out.println(">>>>>>>>>>>这是鼠标单击以后要执行的逻辑:开始");
System.out.println("===========触发了鼠标的单击事件============\n" + e);
System.out.println(">>>>>>>>>>>这是鼠标单击以后要执行的逻辑:结束");
}
public void onDoubleClick(Event e) {
System.out.println("===========触发了鼠标的双击事件============\n" + e);
}
public void onUp(Event e) {
System.out.println("===========触发了鼠标的弹起事件============\n" + e);
}
public void onDown(Event e) {
System.out.println("===========触发了鼠标的按下事件============\n" + e);
}
public void onWheel(Event e) {
System.out.println("===========触发了鼠标的滚动事件============\n" + e);
}
public void onMove(Event e) {
System.out.println("===========触发了鼠标的移动事件============\n" + e);
}
public void onOver(Event e) {
System.out.println("===========触发了鼠标的悬停事件============\n" + e);
}
}
/**
* 观察者
*
* 回调响应的逻辑,由自己实现
* <br>Darian
**/
public class MouseEventCallBack {
public void onClick(Event e) {
System.out.println(">>>>>>>>>>>这是鼠标单击以后要执行的逻辑:开始");
System.out.println("===========触发了鼠标的单击事件============\n" + e);
System.out.println(">>>>>>>>>>>这是鼠标单击以后要执行的逻辑:结束");
}
public void onDoubleClick(Event e) {
System.out.println("===========触发了鼠标的双击事件============\n" + e);
}
public void onUp(Event e) {
System.out.println("===========触发了鼠标的弹起事件============\n" + e);
}
public void onDown(Event e) {
System.out.println("===========触发了鼠标的按下事件============\n" + e);
}
public void onWheel(Event e) {
System.out.println("===========触发了鼠标的滚动事件============\n" + e);
}
public void onMove(Event e) {
System.out.println("===========触发了鼠标的移动事件============\n" + e);
}
public void onOver(Event e) {
System.out.println("===========触发了鼠标的悬停事件============\n" + e);
}
}
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
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
MouseEventType.class
java
public enum MouseEventType {
ON_CLICK,
ON_DOUBLE_CLICK,
ON_UP,
ON_DOWN,
ON_WHEEL,
ON_MOVE,
ON_OVER;
}
public enum MouseEventType {
ON_CLICK,
ON_DOUBLE_CLICK,
ON_UP,
ON_DOWN,
ON_WHEEL,
ON_MOVE,
ON_OVER;
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
MouseTest.class
java
public class MouseTest {
public static void main(String[] args) {
/***
* var input = document.getElementById("username");
* input.addListener("click", function(){
* alert("鼠标点击了这个文本框 ")
* });
*
* 给一个回调的逻辑
*/
// 观察者和被观察者之间没有必然的联系
// 注册的时候,才产生联系
// 解耦
try {
MouseEventCallBack callBack = new MouseEventCallBack();
Method onClick = MouseEventCallBack.class.getMethod("onClick", Event.class);
// 人为地调用鼠标的单击事件
Mouse mouse = new Mouse();
mouse.addListener(MouseEventType.ON_CLICK, callBack, onClick);
mouse.click();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class MouseTest {
public static void main(String[] args) {
/***
* var input = document.getElementById("username");
* input.addListener("click", function(){
* alert("鼠标点击了这个文本框 ")
* });
*
* 给一个回调的逻辑
*/
// 观察者和被观察者之间没有必然的联系
// 注册的时候,才产生联系
// 解耦
try {
MouseEventCallBack callBack = new MouseEventCallBack();
Method onClick = MouseEventCallBack.class.getMethod("onClick", Event.class);
// 人为地调用鼠标的单击事件
Mouse mouse = new Mouse();
mouse.addListener(MouseEventType.ON_CLICK, callBack, onClick);
mouse.click();
} catch (Exception e) {
e.printStackTrace();
}
}
}
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
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
控制台输出
verilog
Connected to the target VM, address: '127.0.0.1:7419', transport: 'socket'
调用一个添加的方法
Disconnected from the target VM, address: '127.0.0.1:7419', transport: 'socket'
=======触发事件,打印日志=========
Event{
source=com.darian.pattern.observer.subject.Subject@3419866c,
target=com.darian.pattern.observer.subject.Observer@63e31ee,
callback=public void com.darian.pattern.observer.subject.Observer.advice(com.darian.pattern.observer.core.Event),
trigger='ON_ADD',
time=Sat Oct 06 03:01:46 CST 2018,
}
调用删除的方法
=======触发事件,打印日志=========
Event{
source=com.darian.pattern.observer.subject.Subject@3419866c,
target=com.darian.pattern.observer.subject.Observer@63e31ee,
callback=public void com.darian.pattern.observer.subject.Observer.advice(com.darian.pattern.observer.core.Event),
trigger='ON_REMOVE',
time=Sat Oct 06 03:01:46 CST 2018,
}
Connected to the target VM, address: '127.0.0.1:7419', transport: 'socket'
调用一个添加的方法
Disconnected from the target VM, address: '127.0.0.1:7419', transport: 'socket'
=======触发事件,打印日志=========
Event{
source=com.darian.pattern.observer.subject.Subject@3419866c,
target=com.darian.pattern.observer.subject.Observer@63e31ee,
callback=public void com.darian.pattern.observer.subject.Observer.advice(com.darian.pattern.observer.core.Event),
trigger='ON_ADD',
time=Sat Oct 06 03:01:46 CST 2018,
}
调用删除的方法
=======触发事件,打印日志=========
Event{
source=com.darian.pattern.observer.subject.Subject@3419866c,
target=com.darian.pattern.observer.subject.Observer@63e31ee,
callback=public void com.darian.pattern.observer.subject.Observer.advice(com.darian.pattern.observer.core.Event),
trigger='ON_REMOVE',
time=Sat Oct 06 03:01:46 CST 2018,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20