命令模式实践--设计模式

大学的时候对设计模式走入了误区,总以为那是没什么用的东西,看了局限自己的思维,让自己无法发挥自己的灵性天马行空的编码。

这实在是个错误,是随着代码慢慢码多了,原来觉得没什么问题的代码,耦合越来越严重。才寻求解决办法。

命令模式最初看的别人文章的时候觉得没什么用,因为每个人的理解都不一样。

有的人的重点在于JAVA没有回调函数,所以把命令模式当作当作JAVA的回调函数。

有的人的重点在于命令模式可以方便的进行DO和UNDO。

我的初步理解是,经典的命令模式是为了对行为执行者和行为实现者解耦。在很多种不同行为的情况下,可以很方便的添加行为实现者而不修改已有的代码。

经典命令模式的实例

经典命令模式包括抽象命令(Command),具体命令(ConcreteCommand),接收者(Receiver),调用者(Invoker)和客户端(Client)

以经典的服务器上传作为模式例子

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
package main

import (
"fmt"
"net"
)

//Command
type Command interface {
Do() error
}

//ConcreteCommand
type UploadCommand struct {
cmd byte
buf []byte
s *Server
}

func NewUploadCommand(s *Server) (c *UploadCommand) {
c = &UploadCommand{}
c.s = s
return
}

//execute
func (this *UploadCommand) Do() (err error) {
return this.s.Upload(this)
}

//Reciver
type Server struct {
addr string
conn net.Conn
}

func NewServer(addr string) (s *Server, err error) {
s = &Server{}
s.addr = addr
s.conn, err = net.Dial("tcp", addr)
if err != nil {
return
}
return
}

//action1
func (this *Server) Upload(c *UploadCommand) error {
_, err := this.conn.Write(c.buf)
return err
}

//Invoker
func main() {
s, err := NewServer("127.0.0.1:80")
if err != nil {
fmt.Printf("conn failed %s\n", err)
return
}
c := Command(NewUploadCommand(s))
err = c.Do()
if err != nil {
fmt.Printf("command failed %s\n", err)
return
}
}

Server就是命令的接收方Reciver

UploadCommand就是具体命令ConcreteCommand,他调用Server的具体action:Upload函数来执行命令

Main函数是命令的执行者,持有了命令,调用命令的execute:Do函数

当后续需要扩展Download,Move等指令时,就只需要实现DownloadCommand,MoveCommand。并且相对应的调用Server的Download,Move函数

我自己的实际使用的命令模式

以上的命令模式是根据经典命令模式去写的,实际使用中不会那么标准

在我自己的代码中,早就用了命令模式而不自知

在经典的命令模式中,命令是在Reciever这一端去实现的,而命令和Reciver其实是强耦合的、

所以我认为把命令的实现放在命令本身中没什么关系

命令不再直接调用Reciever端的相应实现,而是把Reciever当做环境。实现的时候直接引用该环境。

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
package main

import (
"fmt"
"net"
)

//Command
type Commmand interface {
Do() error
}

//ConcreteCommand
type UploadCommand struct {
cmd byte
buf []byte
s *Server
}

func NewUploadCommand(s *Server) (c *UploadCommand) {
c = &UploadCommand{}
c.s = s
return
}

//execute
func (this *UploadCommand) Do() (err error) {
conn := this.s.GetConn()
_, err = conn.Write([]byte{this.cmd})
return
}

//Reciver
type Server struct {
addr string
conn net.Conn
}

func NewServer(addr string) (s *Server, err error) {
s = &Server{}
s.addr = addr
s.conn, err = net.Dial("tcp", addr)
if err != nil {
return
}
return
}

//action
func (this *Server) GetConn() net.Conn {
return this.conn
}

//Invoker
func main() {
s, err := NewServer("127.0.0.1:80")
if err != nil {
fmt.Printf("conn failed %s\n", err)
return
}
c := NewUploadCommand(s)
err = c.Do()
if err != nil {
fmt.Printf("command failed %s\n", err)
return
}
}

在我的实际代码中,UploadCommand直接实现了需要的操作。而把Server的conn作为环境传入。

这样做的好处在于,新加其他命令的时候,Server的代码完全不需要改变

总结

看别人博客有一句话说的很好,大意是:

学设计模式并不完全是学设计模式的优点,因为同一个场景可能有多种适合的设计模式,不应该去强行套路。而是应该掌握各种设计模式的缺点,用最适合这种场景的设计模式。

这种命令模式的修改,是我自己想的。

这种改变的缺点我可能暂时还没有看到,希望可以指出。