代理模式是一种结构型设计模式,它通过引入一个代理对象来控制对另一个对象的访问。这种模式可以在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
1. 在Golang中实现代理模式
在Go语言中实现代理模式,我们可以定义一个接口,该接口规定了代理和被代理对象共同遵循的方法。然后,创建一个代理结构体,它实现了这个接口,并包含对被代理对象的引用。在代理结构体的方法中,可以添加额外的逻辑,比如访问控制、日志记录等,然后调用被代理对象的相应方法。 如下:
type Animal interface {
Call()
}
type Dog struct{}
func (d *Dog) Call() {
fmt.Println("wangwang")
}
type AnimalProxy struct {
animal Animal
}
func (a *AnimalProxy) Call() {
fmt.Println("方法调用之前的逻辑")
a.animal.Call()
fmt.Println("方法调用之后的逻辑")
}
func TestProxyOne(t *testing.T) {
dog := &Dog{}
proxy := &AnimalProxy{animal: dog}
proxy.Call()
}
只需要这样一个操作 我们就可以实现go的代理模式了。
但是这样的实现要求我们必须有一个接口才可以。如果我们要代理的对象,它没有接口呢? 比如我们没有这个Animal接口,我们如何代理Dog对象呢?
那就没有办法了,只能像JAVA一样,用继承实现代理模式了.... 诶?我继承呢?
2. 对没有接口的对象进行代理
Go鼓励使用组合来复用代码,并通过接口来实现多态性,而不是使用传统的类继承。这种方式使得代码更加模块化,减少了类继承可能带来的复杂性和紧密耦合。所以它使用组合的方式替代了继承,我们只能用组合来实现代理,如下:
type Dog struct{}
func (d *Dog) Call() {
fmt.Println("wangwang")
}
type AnimalProxy struct {
Dog
}
func (a *AnimalProxy) Call() {
fmt.Println("方法调用之前的逻辑")
a.Dog.Call()
fmt.Println("方法调用之后的逻辑")
}
func TestProxyOne(t *testing.T) {
proxy := &AnimalProxy{}
proxy.Call()
}
但是这种方法缺点有一堆,比如没有多态,以前所有使用Dog的地方都得手改成AnimalProxy。比如这个代理无法复用,只能代理Dog。比如每代理一个方法都得实现这个方法。那么有没有更好的解决方案呢?
答案是 有! 我们可以使用反射来进行一点优化
3. 用反射实现代理模式
type ReflectProxy struct {
animal reflect.Value
}
func (p *ReflectProxy) Call() {
_ = p.CallMethod("Call")
}
func (p *ReflectProxy) CallMethod(methodName string, args ...interface{}) []reflect.Value {
method := p.animal.MethodByName(methodName)
argsV := make([]reflect.Value, len(args))
for i, arg := range args {
argsV[i] = reflect.ValueOf(arg)
}
fmt.Println("方法调用之前的逻辑")
values := method.Call(argsV)
fmt.Println("方法调用之后的逻辑")
return values
}
func TestProxyOne(t *testing.T) {
proxy := &ReflectProxy{animal: reflect.ValueOf(&Dog{})}
proxy.Call()
}
通过反射实现的代理,让我们可以不用将Dog对象写死,也可以简化一下代理方法的实现,一些通用逻辑,可以写在CallMethod里面(这样貌似我们可以写个工具自动生成了,在定义好通用逻辑之后)。但是它依旧有着问题,比如,无法实现如同继承的多态一样的无缝代理。我们需要将所有用到Dog的地方换成ReflectProxy才可以实现代理。那么还有别的办法吗?
Go是一个面向静态类型的语言,我们无法像通过替换掉对象的指针来让它调用我们的代理方法,实际上,这样操作的结果是它依然调用的是它一开始定义的类型的方法。
也许有其他的更好的方法,不过我就不知道了。