Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

目前对观察订阅者模式的一些体会和应用场景 #102

Open
SimpleCodeCX opened this issue Nov 17, 2018 · 1 comment
Open

目前对观察订阅者模式的一些体会和应用场景 #102

SimpleCodeCX opened this issue Nov 17, 2018 · 1 comment

Comments

@SimpleCodeCX
Copy link

目前对观察订阅者模式的一些体会和应用场景

1、监听

以前在用vue重构我的项目时,自己写一个类似rxjs的简单版SimpleObservable,用于实现header title等的监听,因为在我的项目中,不同页面的header只有title文字不一样,比如page1显示的titile是'page1',于是我把header做成了一个组件,而每个页面也是一个组件,因此通过SimpleObservable就可以实现组件与组件间的间接单向通信,即观察者(observer)监听被观察者(observable),被观察者触发观察者。

(备注:以下用ts语法,可以通过tsc编译生成对应js,再运行)

大致代码如下:

demo1

SimpleObservable.ts
class SimpleObservable {
  private observer: Array<Function>;
  constructor() {
    this.observer = [];
  }
  subscribe(_observer: Function) {
    this.observer.push(_observer);
    let _index = this.observer.length - 1;
    let that = this;
    return {
      _index: _index,
      unsubscribe: function () {
        console.log("before:unsubscribe:", that.observer);
        that.observer.splice(this._index, 1);
        console.log("after  :unsubscribe:", that.observer);
      }
    };
  }
  next(_data: any) {
    for (let i = 0, len = this.observer.length; i < len; i++) {
      this.observer[i](_data);
    }
  }
}
export default SimpleObservable;
demo1.ts
import SimpleObservable from './SimpleObservable';
let subscriptions=[];
let headerTitleObservable = new SimpleObservable();
let headerTitle_subscriptsion = headerTitleObservable 
      .subscribe(_title => {
        console.log(`the header title is change to${_title }`);
      });
subscriptions.push(headerTitle_subscriptsion );

// 触发
this.headerTitleObservable.next('page1');

// 在组件生命周期结束时,清除所有observers
subscriptions.forEach(subscription => {
      subscription.unsubscribe();
    });

2、解除两个类的强耦合状态

使用观察订阅者模式,有时候能解除两个类的强耦合状态,比如,在我的项目中,我写了一个http拦截器httpInterceptor,用来过滤每一次http请求和响应,比如统一加access_token header,统一出错处理啊等等,我的httpInterceptor其中的一个功能,就是从服务器返回的响应头中检测登录状态是否已经失效或access_token是否已经过期,当检测到登录失效时,要在页面中弹出一个登录modal框(loginModalComponent),提醒用户重新登录。大致的代码如下:

(备注:以下采用ts语法,为了使理解起来更加容易,没有使用ts的依赖注入等特性)

未使用观察者模式:

demo2

demo2.ts
/**
 * 这里模拟了一个登录modal组件
 */
class LoginModalComponent {
  constructor() { }
  open() {
    console.log('##检测到access_token已经过期,打开login模态框##');
    console.log('请输入账号和密码,点击登录');
  }
}

/**
 * 这里模拟了一个http拦截器
 */
class HttpInterceptor {
  loginModalComponent: LoginModalComponent = new LoginModalComponent();;
  constructor() { }
  handleHttpRes() {
    if (this.checkLoginInvalid) {
      this.loginModalComponent.open();
    }
  }
  // 检查登录是否失效
  checkLoginInvalid() {
    return true;
  }
}

let httpInterceptor = new HttpInterceptor();
httpInterceptor.handleHttpRes();

以上示例代码是未使用观察者模式的情况下,需要在HttpInterceptor里new LoginModalComponent,从而导致HttpInterceptor和LoginModalComponent处于强耦合的状态,但是这样不符合HttpInterceptor拦截器的思想,因为HttpInterceptor拦截器是不应该和组件相关的东西进行耦合的,因此,采用观察者模式进行改进.

代码如下:

demo3

demo3.ts
class SimpleObservable {
  private observer: Array<Function>;
  constructor() {
    this.observer = [];
  }
  subscribe(_observer: Function) {
    this.observer.push(_observer);
    let _index = this.observer.length - 1;
    let that = this;
    return {
      _index: _index,
      unsubscribe: function () {
        console.log("before:unsubscribe:", that.observer);
        that.observer.splice(this._index, 1);
        console.log("after  :unsubscribe:", that.observer);
      }
    };
  }
  next(_data: any) {
    for (let i = 0, len = this.observer.length; i < len; i++) {
      this.observer[i](_data);
    }
  }
}
/**
 * 这里模拟了一个登录modal组件
 */
class LoginModalComponent {
  constructor() { }
  open() {
    console.log('##检测到access_token已经过期,打开login模态框##');
    console.log('请输入账号和密码,点击登录');
  }
}

/**
 * 通过一个service来作为中间者,从而解除HttpInterceptor和LoginModalComponent强耦合状态。
 */
class HttpStatusService {
  login_invalid_observable = new SimpleObservable();
  constructor() { }
  getLoginInvalidObservable() {
    return this.login_invalid_observable;
  }
  triggerLoginInvalidObservable() {
    this.login_invalid_observable.next('access_token is invalid.')
  }
}

/**
 * 创建一个HttpStatusService的实例,全局可用,实际的做法是采用ts的依赖注入
 */
let httpStatusService: HttpStatusService = new HttpStatusService();
/**
 * 这里模拟了一个http拦截器
 */
class HttpInterceptor {
  constructor() { }
  handleHttpRes() {
    if (this.checkLoginInvalid) {
      httpStatusService.triggerLoginInvalidObservable();
    }
  }
  // 模拟一个登录失败的状态,实际的情况是,拦截到http请求的服务器响应信息
  checkLoginInvalid() {
    return true;
  }
}


/**
 * AppComponent,一个页面跟组件
 */
class AppComponent {
  loginModalComponent: LoginModalComponent = new LoginModalComponent();
  subscriptions: Array<any> = [];
  constructor() {
    this.init();
  }
  init() {
    let subscription1 = httpStatusService.getLoginInvalidObservable().subscribe((err_msg) => {
      this.loginModalComponent.open();
    });
    this.subscriptions.push(subscription1);
  }
  onDestroy() {
    this.subscriptions.forEach(subscription => {
      subscription.unsubscribe();
    });
  }
}

let appComponent = new AppComponent();
/**
 * 此时appComponent已经在监听httpStatusService里的login_invalid_observable
 */
let httpInterceptor = new HttpInterceptor();
httpInterceptor.handleHttpRes();

appComponent.onDestroy();
@tangmingxiang
Copy link

tangmingxiang commented Aug 20, 2022

class SimpleObservable 中的代码可能有一点 bug,我先 注册(subscribe) 一系列函数,如果我之后把这一系列函数中先注册的函数,先注销(unscribble) 掉的话,那么后注册的函数就无法再注销掉了,因为 observer 数组的长度在注销的时候会发生变化,可以这样修改一下:

1. subscribe() 中将 that.observer.splice(this._index, 1);  改为 that.observer.splice(this._index, 1, null);
2.  next(_data) 中 this.observer[i](_data); 改为 this.observer[i] && this.observer[i](_data);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants