[Go自学笔记]Go的委托模式

go-nsq里的委托模式

Go的接口设计是一绝,可以把接口看做一张设计图,实现结构体的时候只要根据“设计图”的标准进行“加工”,那么该结构体的实例便可用于符合设计要求的地方。就如工厂的工程师先画好零件的设计图(interface),然后根据设计图制作零件模具(struct),这样通过模具生产出来的零件(instances)都是符合设计标准,可以用到其他地方去的。

Go的接口设计更符合现实生产的工厂模型,也给程序设计带来了更多的灵活性。通过更换相同接口的不同结构体实例,我们可以在保证整体功能方向的情况下(输入与输出格式确定),更改具体功能的实现方式。

现实里的生产关系一般是各个不同的厂家,根据相同的标准进行生产,以达到产品通用的目的。例如对于TypeC接口的安卓手机而言,只要是TypeC接口的充电线与符合当地交流电标准的充电头,就可以为手机充电(当然具体功率会有差异)。

就拿笔者手上的小米11来说,其本身除了支持最高11V5A的55W充电,还兼容其他的充电协议,例如PD3.0(27W),QC3.0(18W),如果使用Go来实现,大概就是这样的:

先有标准的TypeC充电接口统一标准:

// TypeC标准的充电接口设计
type ChargeDelegate interface {OnCharge()
}

然后小米11根据该标准设计了充电接口:

type Mi11 struct {//...Charge ChargeDelegate //充电接口//...
}

很多充电器也是根据标准设计的,当然也包括小米11的原装充电器:

// 原装充电器
type originalCharge struct{}
func (c *originalCharge) OnCharge() {fmt.Printf("Charging in %d power\n", 5*11) //因为是原装的,走私有协议,所以有55w的满功率输出
}// 第三方PD充电器
type thirdPDCharge struct{}
func (c *thirdPDCharge) OnCharge() {fmt.Printf("Charging in %d power\n", 3*9) //因为是第三方,对接不了私有协议,所以最高只有27w
}

来模拟一下可能的使用情景:

func main() {phone := &Mi11{Charge: &originalCharge{}} //买了新手机,附带原装充电器phone.Charge.OnCharge()                   //使用原装充电器充电,发现充电很快,有55wphone.Charge = &thirdPDCharge{}           //出去出差了,发现没带充电器,去附近的OPPO专卖店买了一个phone.Charge.OnCharge()                   //回酒店充电,发现只有27w
}

结果是显而易见的,虽然充电功率不同,但都能给手机进行充电:

Charging in 55 power
Charging in 27 powerProcess finished with exit code 0

小米11的结构体也可以这样写,但好像不太明了:

type Mi11 struct {//...Charge interface{}//...
}

我们引入一个iPhone的充电协议和充电器:

// TypeC标准的充电接口设计
type appleChargeDelegate interface {OnCharge(mfiCertification bool) //MFI认证,并且是雷电接口
}// iPhone充电器
type iphoneCharge struct{}
func (c *iphoneCharge) OnCharge(mfiCertification bool) {fmt.Printf("Charging in %d power\n", 5*1)
}

然后让兄弟再出差一趟:


func main() {phone := &Mi11{Charge: &originalCharge{}} //买了新手机,附带原装充电器if c, ok := phone.Charge.(ChargeDelegate); ok {c.OnCharge() //原装充电ok}phone.Charge = &thirdPDCharge{} //出去出差了,发现没带充电器,去附近的vivo旗舰店买了一个if c, ok := phone.Charge.(ChargeDelegate); ok {c.OnCharge() //第三方的也ok,但功率比较低}phone.Charge = &iphoneCharge{} //从同事那儿借来一个苹果的充电器if c, ok := phone.Charge.(ChargeDelegate); ok {c.OnCharge()} else {fmt.Printf("Wrong charger for Mi11") //因为是Lightning接口,所以肯定是充不上的}
}

其中,c, ok := phone.Charge.(ChargeDelegate)通过断言方式判断phone.Charge是否符合ChargeDelegate的接口设计。

结果也没有太多悬念:

Charging in 55 power
Charging in 27 power
Wrong charger for Mi11Process finished with exit code 0

接下来我们看看nsq-go里面对Go委托模式的使用。

NSQ是一款分布式的消息队列,在官方的Go客户端nsq-go中,大量使用了go的委托模式。实际上,上面的代码参考了nsq-go的实现方式。

nsq-go里面的Message结构:

type Message struct {//...Delegate MessageDelegate//...
}type MessageDelegate interface {OnFinish(*Message)OnRequeue(m *Message, delay time.Duration, backoff bool)OnTouch(*Message)
}

只要实现了MessageDelegate接口,在消息初始化的时候指定Message.Delegate,就可以控制消息的行为。

官方提供了一种现成的实现,有需要的话用户也可以自己实现接口,然后换上,就像使用不同的充电器一样。

type connMessageDelegate struct {c *Conn
}func (d *connMessageDelegate) OnFinish(m *Message) { d.c.onMessageFinish(m) }
func (d *connMessageDelegate) OnRequeue(m *Message, t time.Duration, b bool) {d.c.onMessageRequeue(m, t, b)
}
func (d *connMessageDelegate) OnTouch(m *Message) { d.c.onMessageTouch(m) }

Conn的结构也有类似:

type Conn struct {//...delegate ConnDelegate//...
}

ConnDelegate接口的方法就多了,也和上面一样,官方提供了可以开箱即用的具体实现,不过这次是两种:consumerConnDelegateproducerConnDelegate,对应于Consumer和Producer对于连接不同的需求:

//producer.go:278
w.conn = NewConn(w.addr, &w.config, &producerConnDelegate{w})
//consumer.go:535
conn := NewConn(addr, &r.config, &consumerConnDelegate{r})

还有Consumer在对NSQLookupD返回的IP地址进行过滤,也使用了委托模式:

type Consumer struct {//...behaviorDelegate interface{}//...
}type DiscoveryFilter interface {Filter([]string) []string
}//consumer.go:463
func (r *Consumer) queryLookupd() {//...if discoveryFilter, ok := r.behaviorDelegate.(DiscoveryFilter); ok {nsqdAddrs = discoveryFilter.Filter(nsqdAddrs)}//...
}

作为Go的初学者,通过阅读nsq-go的源码对Go的委托模式有了更深刻的认识,感觉受益匪浅。Go的接口编程模式如同魔法一般,将写程序变成了搭积木,给我们带来了不同于传统面向对象语言的编程魅力。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部