设计模式之-代理模式

1.代理实现懒加载1

javascript 复制代码
 		var myImage = (function(){
            var img = document.createElement('img');
            document.body.appendChild(img);
            return {
                setSrc:function(src){
                    img.src=src;
                }
            }
        })();
        var proxyImage = (function(){
            // 创建预加载图片
            var img=new Image();
            img.onload = function(){
                // 预加载完成后,设置真实图片
                myImage.setSrc(this.src)
            }
            return {
                setSrc:function(src){
                    // 先显示loading图片
                    myImage.setSrc('./loading.png');
                    img.src=src;
                }
            }
        })();
        // 使用示例
        proxyImage.setSrc('https://fastly.picsum.photos/id/784/200/300.jpg?hmac=LIWlcHgxQH79XHKNji8Jin_KakntjYyd9VXyckNYFbE')

代理实现懒加载2,class的写法

javascript 复制代码
		class MyImage {
            constructor() {
                this.img = document.createElement('img');
                document.body.appendChild(this.img);
            }
            setSrc(src) {
                this.img.src = src;
            }
        }

        class ProxyImage {
            constructor(realImage) {
                this.img = realImage;
                this.cacheImage = null; // 预加载的图片对象
            }
            
            setSrc(src) {
                // 先显示loading图片
                this.img.setSrc('./loading.png');
                
                // 创建预加载图片
                this.cacheImage = new Image();
                this.cacheImage.onload = () => {
                    // 预加载完成后,设置真实图片
                    this.img.setSrc(src);
                };
                
                this.cacheImage.src = src;
            }
        }

        // 使用示例
        const img = new MyImage();
        const proxyImg = new ProxyImage(img);
        proxyImg.setSrc('https://fastly.picsum.photos/id/784/200/300.jpg?hmac=LIWlcHgxQH79XHKNji8Jin_KakntjYyd9VXyckNYFbE')

代理引发的思考:也许有人会有疑惑,不过是实现一个小小的图片懒加载,即使不引用任何模式也能办到,那么引入代理模式的好处究竟在哪里呢?下面我们先抛开代理,编写一个更常见的图片预加载函数,不使用代理的与加载图片函数实现如下:

javascript 复制代码
		var myImage = (function(){
            var imgNode = document.createElement('img');
            document.body.appendChild(imgNode);
            var img=new Image();
            img.onload = function(){
                // 预加载完成后,设置真实图片
                imgNode.src=img.src
            }
            return {
                setSrc:function(src){
                    imgNode.src='./loading.png';
                    img.src=src
                }
            }
        })();
        // 使用示例
        myImage.setSrc('https://fastly.picsum.photos/id/784/200/300.jpg?hmac=LIWlcHgxQH79XHKNji8Jin_KakntjYyd9VXyckNYFbE')

代理的意义

1.为了说明代理的意义,下面我们引入一个面向对象设计的原则--单一职责原则。

2.单一职责原则指的是,就是一个类(通常也包括对象和函数等)而言,应该仅有一个引起他变化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起他变化的原因可能有多个。面向对象设计鼓励将行为分布到细粒度的对象当中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计,当变化发生时,设计可能会遭到意外的破坏。

3.职责被定义为"引起变化的原因"。上面代码中的MyImage对象除了负责给img节点设置src外,还要负责预加载图片。我们在处理其中一个职责的时候,有可能因为其强耦合性影响另外一个职责的实现。

4.另外,在面向对象的程序设计中,大多数情况下,若违反其他任何原则,同时将违反开放-封闭原则。如果我们只是从网络上获取一些体积很小的图片,或者5年后网速快到根本不在需要预加载了,我们可能希望把预加载图片的这段代码从MyImage对象里删掉。这时候就不得不改动MyImage对象了。

5.实际上,我们需要的只是给img节点设置src,预加载图片只是一个锦上添花的功能。如果能把这个操作放到另一个对象里面,自然是一个很好的方法。于是代理的作用就体现出来了,代理负责预加载图片,预加载的操作完成后,把请求重新交给本体MyImage.

6.纵观整个程序,我们并没有改变或者增加MyImage的接口,但是通过代理对象,实际上给系统添加了新的行为。这是符合开放-封闭原则的。给img节点设置src和图片预加载这两个功能,被隔离在两个对象里,他们可以各自变化而不影响对方。何况就算有一天我们不再需要预加载,那么只需要改成请求本体而不是请求代理对象即可。

代理和本体接口的一致性

1.如果有一天我们不再需要预加载了,那么就不在需要代理对象,可以选择直接请求本体。其中关键是代理对象和本地都对外提供了setSrc方法,在客户看来,代理对象和本体是一致的,代理接手请求的过程对于用户来说是透明的,用户并不清楚代理和本体的区别,那么这么做有两个好处

(1)用户可以放心的请求代理,他只关心是否能得到想要的结果

(2)在任何使用本体的地方都可以替换成使用代理

2.在Java等语言中,代理和本体都需要显式地实现同一个接口,一方面接口保证了他们会拥有同样的方法,另一方面,面向接口编程迎合依赖倒置原则,通过接口进行向上转型,从而避开编译器的类型检查,代理和本体将来可以被替换使用

3.在javascript这种动态类型语言中,我们有时候通过鸭子类型来检测代理和本体是否都实现setSrc方法,另外大多数时候甚至干脆不做检测,全部依赖程序员的自觉性,这对于程序的健壮性是有影响的。不过对于一门快速开发的脚本语言,这些影响还是在可以接受的范围内,而且我们也习惯了没有接口的世界。

4.另外值得一提的是,如果代理对象和本体对象都为一个函数(函数也是对象),而函数必然都能被执行,则可以认为它们具有一致的"接口",代码如下:

javascript 复制代码
 		var myImage = (function(){
            var img = document.createElement('img');
            document.body.appendChild(img);
            return  function(src){
                    img.src=src;
                }
            
        })();
        var proxyImage = (function(){
            // 创建预加载图片
            var img=new Image();
            img.onload = function(){
                // 预加载完成后,设置真实图片
                myImage(this.src)
            }
            return  function(src){
                    // 先显示loading图片
                    myImage('./loading.png');
                    img.src=src;
                }
            
        })();
        // 使用示例
        proxyImage('https://fastly.picsum.photos/id/784/200/300.jpg?hmac=LIWlcHgxQH79XHKNji8Jin_KakntjYyd9VXyckNYFbE')

2.ts版本的demo

typescript 复制代码
interface IService {
  operation(): void;
}
// 提供真实服务的类
class Service implements IService {
  operation(): void {
    console.log("Service operation called");
  }
}
class MyProxy implements IService {
  // 代理类中包含一个真实服务的引用
  private realService: Service;

  constructor(se: Service) {
    this.realService = se;
  }

  // 对外界的访问进行一个过滤
  checkAccess(): boolean {
    // 这边可以进行一些访问控制的操作
    // ...

    return true;
  }

  // 实现接口中定义的方法
  operation(): void {
    // 这边需要对外界的访问进行一个过滤
    if (this.checkAccess()) {
      // 经过访问控制后,可以调用真实服务的方法
      this.realService.operation();
    }
  }
}
// 使用
const realService = new Service();
const proxy = new MyProxy(realService);

// 外界要访问服务的时候,是和代理类打交道的
proxy.operation();  

3.前端中的代理模式-- ES6新增的Proxy构造方法

javascript 复制代码
// 创建一个要被代理的对象
// 这是真实的对象
const target = {
  name: "John",
  age: 30,
};

// 定义代理的行为
// 代理对象就会对外界的访问需求进行过滤
const handler = {
  // 拦截对象属性的读取
  get(target, prop, receiver) {
    console.log(`[GET]: ${prop}`);
    return Reflect.get(target, prop, receiver);
  },
  // 拦截对象属性的设置
  set(target, prop, value, receiver) {
    console.log(`[SET]: ${prop} = ${value}`);
    return Reflect.set(target, prop, value, receiver);
  },
};

// 使用Proxy构造函数创建代理对象
// new Proxy 会返回一个对象,该对象就是针对真实对象的代理对象
const proxy = new Proxy(target, handler);

// 测试代理的行为
// 之后就通过代理对象来访问真实对象的成员
console.log(proxy.name); // 输出:[GET]: name 以及 John
proxy.age = 31; // 输出:[SET]: age = 31

非原创,来源渡一谢杰老师和javascript设计模式与开发实践 作者曾探,简单记录,一起学起来吧

相关推荐
会员果汁2 小时前
13.设计模式-适配器模式
设计模式·适配器模式
GISer_Jing16 小时前
AI:多智能体协作与记忆管理
人工智能·设计模式·aigc
雨中飘荡的记忆18 小时前
责任链模式实战应用:从理论到生产实践
设计模式
沛沛老爹20 小时前
Web开发者进阶AI:Agent技能设计模式之迭代分析与上下文聚合实战
前端·人工智能·设计模式
Geoking.21 小时前
【设计模式】装饰者模式详解
设计模式·装饰器模式
vx-bot5556661 天前
企业微信接口在自动化工作流中的关键角色与设计模式
设计模式·自动化·企业微信
Yu_Lijing1 天前
基于C++的《Head First设计模式》笔记——工厂模式
c++·笔记·设计模式
HL_风神2 天前
设计原则之迪米特
c++·学习·设计模式
HL_风神2 天前
设计原则之合成复用
c++·学习·设计模式
Aeside12 天前
揭秘 Nginx 百万并发基石:Reactor 架构与 Epoll 底层原理
后端·设计模式