Go 学设计模式–怕把核心代码改乱,记得用代理模式

大家好,这里是每周都陪你进步的网管~

其实也不是每周啦,上周阳了~实在是进步不动了...... 这周咱们继续之前搁置了一段时间的设计模式系列。

上一次咱们分享的是职责链模式,在文章最后提到了一下装饰器模式,两者虽然结构上类似但在用途上还是有区别的,而装饰器模式本身算是代理模式的一个特殊应用,所以这篇文章我们就先来学习一下代理模式的构成和用法,后面再来学习装饰器。

老看我文章的同学可能发现了,虽然教材上是把设计模式分成了建造型、结构型、行为型三大块展开的,但是我没有按照这个大纲来铺开内容,而是更注重延续性一点,力求尽量能做到由一种模式引出关联的另外一种模式。所以在学完 "流程开发的三个利器"— 模版、策略和职责链三个行为型模式后,我们先把其他行为型的模式放一放,先来学习两个结构型的模式 — 代理和装饰器。

什么是代理模式

代理模式是一种结构型设计模式。其中代理控制着对于原对象的访问,并允许在将请求提交给原对象的前后进行一些处理,从而增强原对象的逻辑处理。

上面的代理者我们一般叫做代理对象或者直接叫做代理-- Proxy,进行逻辑处理的原对象通常被称作服务对象,代理要跟服务对象实现相同的接口,才能让客户端傻傻分不清自己使用的到底是代理还是真正的服务对象,这样一来代理就能在客户端察觉不到的情况下对服务对象的处理逻辑进行增强。

什么叫对处理逻辑进行增强?或者换一种说法,叫对核心功能添加增强功能?举个例子来说,处理客户端查询用户订单信息的 API Handler 就是核心处理逻辑,增强逻辑就是我们需要在查询订单信息之前,验证请求是否是有效用户、记录请求的参数和返回的响应数据等等。

看了上面代理模式的解释,你可能还是觉得有点宽泛,下面咱们写一个简单的代码示例,这个过程中你差不多就会发现:“诶,原来这就是代理模式啊,我之前写代码的时候早就用过了~!” 下面我们一起开下这个例子吧。

代理模式使用演示

假设有一个代表小汽车的 Car 类型

type Car struct{}

小汽车要的主要行为就是可以让人驾驶,所以 Car 需要实现一个代表驾驶行为的接口(interface)Vehicle,该接口只有一个方法 Drive ()。

"本文使用的完整可运行源码
去公众号「网管叨bi叨」发送【设计模式】即可领取"
type Vehicle interface {
    Drive()
}

type Car struct{}

func (*Car) Drive() {
    fmt.Println("Car is being driven")
}

Car 的结构体指针通过实现 Drive () 方法实现了 Vehicle 接口。

现在我们只要实例化一个 Car 的实例,在实例上面调用 Drive () 方法就能让车开起来,不过如果我们的驾驶员现在还是个未成年,那么在地球的大部分国家都是不允许开车的,如果在开车时要加一个驾驶员的年龄限制,我们该怎么办呢?

给 Car 结构体加一个 Age 字段显然是不合理的,因为我们要表示的驾驶员的年龄而不是车的车龄。同理驾驶员年龄的判断我们也不应该加在 Car 实现的 Drive () 方法里,这样会导致每个实现 Vehicle 接口的类型都要在自己的 Drive () 方法里加上类似的判断。

这个时候通常的做法是,加一个表示驾驶员的类型 Driver。

type Driver struct {
    Age int
}

然后再来一个包装 Driver 和 Vehicle 类型的包装类型。

"本文使用的完整可运行源码
去公众号「网管叨bi叨」发送【设计模式】即可领取"
type CarProxy struct {
    vehicle    Vehicle
    driver *Driver
}

func NewCarProxy(driver *Driver) *CarProxy {
    return &CarProxy{&Car{}, driver}
}

这样的话我们接可以通过,用包装类型代理 vehicle 属性的 Drive () 行为时,给它加上驾驶员的年龄限制。

func (*CarProxy) Drive() {
    if c.driver.Age >= 16 {
        c.vehicle.Drive()
    } else {
        fmt.Println("Driver too young!")
    }
}

我相信这个编程技巧大家在平时开发中都用过,这个其实就是代理模式。

现在我们通过代理模式给 Car 类型的 Drive () 行为扩充了检查驾驶员的行为,下面我们执行一下程序试试效果。

"本文使用的完整可运行源码
去公众号「网管叨bi叨」发送【设计模式】即可领取"
func main() {
 car := NewCarProxy(&Driver{12})
 car.Drive() // 输出 Driver too young!
 car2 := NewCarProxy(&Driver{22})
 car2.Drive() // 输出 Car is being driven
}

正如执行后的结果所示,我们不必为服务对象 -- Car 类型添加任何属性和方法。相反,我们只是在其上面的代理层把客户端 Drive () 方法的调用委托(英文术语叫 delegate)给了其 vehicle 属性的 Drive 方法,并在之前添加了年龄检查行为,从而达到我们想要的效果。

看完例子后,相信大家都理解了写代码时怎么使用代理模式,下面我们从代码走出来,再更清晰的描述下代理模式它的整体结构。

看清代理模式

根据上面一开始的描述和后面的代码例子,我们总结出来,参与代理模式的一共有四种角色:客户端、服务接口、服务类和代理类,他们之间的关系用 UML 类图表示如下:

80ec9f03-df99-4e00-b635-54d35f1dd4d1

代理模式--UML 类图

上面 UML 类图一共有四个角色,这四个角色在代理模式中的职责分别是。

服务接口 (Ser­vice Inter­face) 声明了服务类要实现的接口。服务类的业务处理逻辑就是实现在这里定义的接口方法中,代理类也必须遵循该接口才能伪装成服务对象。

服务 (Ser­vice) 类,就是上面说的,提供实际业务逻辑的原对象。

代理 (Proxy) 类包含一个服务对象作为成员变量。代理完成其任务 (例如延迟初始化、记录日志、 访问控制和缓存等)后面会将请求传递给服务对象。通常情况下,代理会对其服务对象的整个生命周期进行管理,来增强服务对象,这样与核心业务逻辑不相关的增强逻辑就可以由代理来实现

客户端 (Client) 通过统一接口与服务或代理进行交互,所以可在一切需要服务对象的代码中使用服务对象的代理,客户端完全不会感知到。

代理模式延伸

在代理模式中,通过让代理类实现跟服务类相同的接口,从而把代理类伪装成了服务类,客户端请求代理时,代理再把请求委派给其持有的真实服务类,在委派的过程中我们就可以添加增强逻辑。

如果我们把代理类当成服务对象再给代理类加个代理,代理的代理再加代理,那么就变成了另外一种设计模式--装饰器模式啦,其实装饰器模式本身就是代理模式的一个特殊应用,关于装饰器的内容,我们放到后面进行学习。

本文来自微信公众号:网管叨 bi 叨 (ID:kevin_tech),作者:卡尔文_

订阅评论
提醒
guest的头像

0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x