大学的时候对设计模式走入了误区,总以为那是没什么用的东西,看了局限自己的思维,让自己无法发挥自己的灵性天马行空的编码。
这实在是个错误,是随着代码慢慢码多了,原来觉得没什么问题的代码,耦合越来越严重。才寻求解决办法。
命令模式最初看的别人文章的时候觉得没什么用,因为每个人的理解都不一样。
有的人的重点在于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的代码完全不需要改变
总结
看别人博客有一句话说的很好,大意是:
学设计模式并不完全是学设计模式的优点,因为同一个场景可能有多种适合的设计模式,不应该去强行套路。而是应该掌握各种设计模式的缺点,用最适合这种场景的设计模式。
这种命令模式的修改,是我自己想的。
这种改变的缺点我可能暂时还没有看到,希望可以指出。