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

原创内容,转载请注明出处

Posted by Weakyon Blog on August 13, 2017

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

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

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

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

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

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

1 经典命令模式的实例

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

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

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函数

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

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

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

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

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

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

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的代码完全不需要改变

3 总结

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

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

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

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

13 Aug 2017