(八)代理模式

133 阅读4分钟

@TOC

代理模式

  • 介绍
  • 演示
  • 场景
  • 总结

代理模式 介绍

  • 使用者无权访问目标对象

  • 中间加代理,通过代理做授权和控制

概念

为其他对象提供一种代理以控制对这个对象的访问。在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。 在这里插入图片描述

示例

  • 科学上网,访问github.com
  • 明星经纪人,假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的

另外,想一下你通过链家的中介买房子,算不算代理模式?—— 不算,此时是三方关系,买家、中介、卖家,合同上都要签字的。而上面的明星经纪人却是两方关系,你和经纪人联系就好了,你不用跟明星直接联系。

代理模式 演示

常见的 UML 类图是

在这里插入图片描述

简化之后 在这里插入图片描述

代码描述

class ReadImg {
    constructor(fileName) {
        this.fileName = fileName
        this.loadFromDisk()  // 初始化即从硬盘中加载
    }
    display() {
        console.log(`display...` + this.fileName)
    }
    loadFromDisk() {
        console.log(`loading... ` + this.fileName)
    }
}

class ProxyImg {
    constructor(fileName) {
        this.realImg = new ReadImg(fileName)
    }
    display() {
        this.realImg.display()
    }
}

// 测试代码
let proxImg = new ProxyImg('1.png')
proxImg.display()

关键在于两者都必须用display这一个 API 名字,就像你用不用科学上网,访问youtube.com的网站都是一样的。

代理模式 场景

不一定会严格按照 UML 类图来实现,但是这种设计思想是能体现出来的。

网页事件代理

将目标元素的点击时间,代理到父元素上,以实现某种功能,很常用。

<div id="div1">
    <a href="#">a1</a>
    <a href="#">a2</a>
    <a href="#">a3</a>
    <a href="#">a4</a>
</div>
<button>点击增加一个 a 标签</button>

<script>
    var div1 = document.getElementById('div1')
    div1.addEventListener('click', function (e) {
        var target = e.target
        if (e.nodeName === 'A') {
            alert(target.innerHTML)
        }
    })
</script>

jQuery $.proxy

$('#div1').click(function () {
    // this 符合期望
    $(this).addClass('red')
})
$('#div1').click(function () {
    setTimeout(function () {
        // this 不符合期望
        $(this).addClass('red')
    }, 1000);
});

// 可以用如下方式解决
$('#div1').click(function () {
    var _this = this
    setTimeout(function () {
        // _this 符合期望
        $(_this).addClass('red')
    }, 1000);
});

// 但推荐使用 $.proxy 解决,这样就少定义一个变量
$('#div1').click(function () {
    setTimeout($.proxy(function () {
        // this 符合期望
        $(this).addClass('red')
    }, this), 1000);
});

总结一下,$.proxy(fn, obj)返回的就是fn的代理,这个代理和fn的功能一样,只不过将this设置成了我们期望值。虽然看似简单、常用的功能,但是用代理模式设计的。

ES6 Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。就是一个属性的代理器。

// 明星
let star = {
    name: '张XX',
    age: 25,
    phone: '13910733521'
}

// 经纪人
let agent = new Proxy(star, {
    get: function (target, key) {
        if (key === 'phone') {
            // 返回经纪人自己的手机号
            return '18611112222'
        }
        if (key === 'price') {
            // 明星不报价,经纪人报价
            return 120000
        }
        return target[key]
    },
    set: function (target, key, val) {
        if (key === 'customPrice') {
            if (val < 100000) {
                // 最低 10w
                throw new Error('价格太低')
            } else {
                target[key] = val
                return true
            }
        }
    }
})

// 主办方
console.log(agent.name)
console.log(agent.age)
console.log(agent.phone)
console.log(agent.price)

// 想自己提供报价(砍价,或者高价争抢)
agent.customPrice = 150000
// agent.customPrice = 90000  // 报错:价格太低
console.log('customPrice', agent.customPrice)

代理模式 总结

  • 什么是代理模式
  • 关键:和目标接口一致,不能改变接口
  • 前端使用场景

对比:

  • vs 适配器模式:
    • 适配器模式:提供一个不同的接口(如不同版本的插头)
    • 代理模式:提供一模一样的接口,用不用代理,访问 facebook 的网址都一样
  • vs 装饰器模式
    • 装饰器模式:扩展功能,原有功能不变且可直接使用;使用方也可直接使用目标接口。
    • 代理模式:显示原有功能,提供给使用方的是经过限制或者阉割之后的功能;使用方不可直接使用目标接口。

设计模式验证:

  • 将代理类和目标类进行分离,用代理隔离开目标类和使用者
  • 符合开放封闭原则