做者簡介 joey 螞蟻金服·數據體驗技術團隊html
本文是typescript設計模式系列文章的最後一篇,介紹了最後5個對象行爲型的設計模式~java
定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知並被自動更新。git
觀察者模式包含如下角色:github
目標向觀察者發送關於改變的「詳細信息」,而無論它們須要與否。由目標維護觀察者。算法
// 場景:顧客點菜後,服務員記下顧客的信息,菜作好後廣播通知顧客領取
// 觀察者基類
class Observer {
take(msg: string): void {}
}
// 目標基類
class Subject {
set: Set<Observer> = new Set();
// 註冊回調
add(observer: Observer): void {
this.set.add(observer);
}
// 註銷回調
remove(observer: Observer): void {
this.set.delete(observer);
}
// 觸發全部已註冊的回調
notify(msg: string): void {
this.set.forEach(observer => {
observer.take(msg);
});
}
}
// 具體目標,服務員類
class Waiter extends Subject {
// 菜作完後通知全部註冊了的顧客
ready(): void {
this.notify('ready');
}
}
// 具體觀察者,顧客類
class Client extends Observer {
name: string;
// 初始化時將自身註冊到目標,以便接收通知
constructor(name: string, waiter: Waiter) {
super();
this.name = name;
waiter.add(this);
}
take(msg: string) {
console.log(`顧客 ${this.name} 收到了消息顯示狀態是<${msg}>, 到吧檯領取了菜`);
}
}
function observerPushDemo() {
const waiter = new Waiter();
// 顧客點菜後,等待服務員通知
const bob = new Client('Bob', waiter);
const mick = new Client('Mick', waiter);
// 菜準備好後,服務員廣播通知顧客能夠到吧檯領取了
waiter.ready();
}
複製代碼
目標除了「最小通知」外什麼也不送出,而在此以後由觀察者顯式地向目標詢問細節。觀察者裏維護了目標對象。typescript
// 場景:顧客點菜後,收到通知從服務員處詢問詳細信息
// 觀察者基類
class Observer {
take(subject: Subject): void {}
}
// 目標基類
class Subject {
set: Set<Observer> = new Set();
// 註冊回調
add(observer: Observer): void {
this.set.add(observer);
}
// 註銷回調
remove(observer: Observer): void {
this.set.delete(observer);
}
// 觸發全部已註冊的回調
notify(): void {
this.set.forEach(observer => {
observer.take(this);
});
}
}
// 具體目標,服務員類
class Waiter extends Subject {
status = 'doing';
// 與推模式的區別是,只發送通知,不發送詳細數據
ready(): void {
this.status = 'ready';
this.notify();
}
// 提供訪問詳細數據接口,讓觀察者訪問詳細數據
getStatus(): string {
return this.status;
}
}
// 具體觀察者,顧客類
class Client extends Observer {
name: string;
// 初始化時將自身註冊到目標,以便接收通知
constructor(name: string, waiter: Waiter) {
super();
this.name = name;
waiter.add(this);
}
// 與推模式的區別是,收到通知後,沒有數據傳入,須要從目標裏讀取
take(waiter: Waiter) {
const msg = waiter.getStatus();
console.log(`顧客 ${this.name} 收到通知,詢問服務員後發現狀態是 <${msg}> 後領取了菜`);
}
}
function observerPushDemo() {
const waiter = new Waiter();
// 顧客點菜
const bob = new Client('Bob', waiter);
const mick = new Client('Mick', waiter);
// 菜準備完後,服務員通知了下全部顧客狀態改變了,但沒有發送內容出去,須要顧客再詢問一下服務員才知道最新狀態
waiter.ready();
}
複製代碼
容許一個對象在其內部狀態改變時改變它的行爲。對象看起來彷佛修改了它的類。設計模式
狀態模式包含如下角色:bash
// 帳戶有幾種狀態:正常,透支,受限
// 帳戶類,表明狀態模式中的環境
class Account {
private name: string;
private state: State;
// 餘額
private balance = 0;
// 初始時爲正常狀態
constructor(name: string) {
this.name = name;
this.state = new NormalState(this);
console.log(`用戶 ${this.name} 開戶,餘額爲 ${this.balance}`);
console.log('--------');
}
getBalance(): number {
return this.balance;
}
setBalance(balance: number) {
this.balance = balance;
}
setState(state: State) {
this.state = state;
}
// 存款
deposit(amount: number) {
this.state.deposit(amount);
console.log(`存款 ${amount}`);
console.log(`餘額爲 ${this.balance}`);
console.log(`帳戶狀態爲 ${this.state.getName()}`);
console.log('--------');
}
// 取款
withdraw(amount: number) {
this.state.withdraw(amount);
console.log(`取款 ${amount}`);
console.log(`餘額爲 ${this.balance}`);
console.log(`帳戶狀態爲 ${this.state.getName()}`);
console.log('--------');
}
// 結算利息
computeInterest() {
this.state.computeInterest();
}
}
// 狀態抽象類
abstract class State {
private name: string;
protected acc: Account;
constructor(name: string) {
this.name = name;
}
getName() {
return this.name;
}
abstract deposit(amount: number);
abstract withdraw(amount: number);
abstract computeInterest();
abstract stateCheck();
}
// 正常狀態類
class NormalState extends State {
acc: Account;
constructor(acc: Account) {
super('正常');
this.acc = acc;
}
deposit(amount: number) {
this.acc.setBalance(this.acc.getBalance() + amount);
this.stateCheck();
}
withdraw(amount: number) {
this.acc.setBalance(this.acc.getBalance() - amount);
this.stateCheck();
}
computeInterest() {
console.log('正常狀態,無須支付利息');
}
// 狀態轉換
stateCheck() {
if (this.acc.getBalance() > -2000 && this.acc.getBalance() <= 0) {
this.acc.setState(new OverdraftState(this.acc));
} else if (this.acc.getBalance() == -2000) {
this.acc.setState(new RestrictedState(this.acc));
} else if (this.acc.getBalance() < -2000) {
console.log('操做受限');
}
}
}
// 透支狀態
class OverdraftState extends State {
acc: Account;
constructor(acc: Account) {
super('透支');
this.acc = acc;
}
deposit(amount: number) {
this.acc.setBalance(this.acc.getBalance() + amount);
this.stateCheck();
}
withdraw(amount: number) {
this.acc.setBalance(this.acc.getBalance() - amount);
this.stateCheck();
}
computeInterest() {
console.log('計算利息');
}
// 狀態轉換
stateCheck() {
if (this.acc.getBalance() > 0) {
this.acc.setState(new NormalState(this.acc));
} else if (this.acc.getBalance() == -2000) {
this.acc.setState(new RestrictedState(this.acc));
} else if (this.acc.getBalance() < -2000) {
console.log('操做受限');
}
}
}
// 受限狀態
class RestrictedState extends State {
acc: Account;
constructor(acc: Account) {
super('受限');
this.acc = acc;
}
deposit(amount: number) {
this.acc.setBalance(this.acc.getBalance() + amount);
this.stateCheck();
}
withdraw(ammount: number) {
console.log('帳號受限,取款失敗');
}
computeInterest() {
console.log('計算利息');
}
// 狀態轉換
stateCheck() {
if (this.acc.getBalance() > 0) {
this.acc.setState(new NormalState(this.acc));
} else if (this.acc.getBalance() > -2000) {
this.acc.setState(new OverdraftState(this.acc));
}
}
}
function stateDemo() {
const acc = new Account('Bob');
acc.deposit(1000);
acc.withdraw(2000);
acc.deposit(3000);
acc.withdraw(4000);
acc.withdraw(1000);
acc.computeInterest();
}
複製代碼
定義一系列的算法,把它們一個個封裝起來,而且使它們可相互替換。本模式使得算法可獨立於使用它的客戶而變化。數據結構
策略模式包含如下角色:框架
// 火車票類:環境類
class TrainTicket {
private price: number;
private discount: Discount;
constructor(price: number) {
this.price = price;
}
setDiscount(discount: Discount) {
this.discount = discount;
}
getPrice(): number {
return this.discount.calculate(this.price);
}
}
// 折扣接口
interface Discount {
calculate(price: number): number;
}
// 學生票折扣
class StudentDiscount implements Discount {
calculate(price: number): number {
console.log('學生票打7折');
return price * 0.7;
}
}
// 兒童票折扣
class ChildDiscount implements Discount {
calculate(price: number): number {
console.log('兒童票打5折');
return price * 0.5;
}
}
// 軍人票折扣
class SoldierDiscount implements Discount {
calculate(price: number): number {
console.log('軍人免票');
return 0;
}
}
function strategyDemo() {
const ticket: TrainTicket = new TrainTicket(100);
// 從環境中獲取到身份信息,而後根據身份信息獲取折扣策略
const discount: Discount = getIdentityDiscount();
// 注入折扣策略對象
ticket.setDiscount(discount);
// 根據策略對象獲取票價
console.log(ticket.getPrice());
}
複製代碼
定義一個操做中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟。
模板方法包含如下角色:
模板方法是基於繼承的一種模式。
下面是一個組件渲染的例子,模擬React組件渲染流程。
// 組件基類
class Component {
// 模板方法,把組件渲染的流程定義好
setup() {
this.componentWillMount();
this.doRender();
this.componentDidMount();
}
private doRender() {
// 作實際的渲染工做
}
componentWillMount() {}
componentDidMount() {}
}
class ComponentA extends Component {
componentWillMount() {
console.log('A組件即將被渲染');
}
componentDidMount() {
console.log('A組件渲染完成');
}
}
class ComponentB extends Component {
componentWillMount() {
console.log('B組件即將被渲染');
}
componentDidMount() {
console.log('B組件渲染完成');
}
}
// 渲染A和B組件,生命週期的流程都是相同的,已經在模板方法裏定義好了的
function templateMethodDemo() {
const compA = new ComponentA();
compA.setup();
const compB = new ComponentB();
compB.setup();
}
複製代碼
提供一個做用於某對象結構中的各元素的操做表示,它使咱們能夠在不改變各元素的類的前提下定義做用於這些元素的新操做。
訪問者模式包含如下角色:
一個公司有兩種員工,正式工和臨時工,他們有不一樣的工時和薪酬結算方法。
// 員工接口
interface Employee {
accept(handler: Department): void;
}
// 全職員工類
class FulltimeEmployee implements Employee {
private name = '';
// 全職員工按週薪計算薪酬
private weeklyWage = 0;
// 工做時長
private workTime = 0;
constructor(name: string, weeklyWage: number, workTime: number) {
this.name = name;
this.weeklyWage = weeklyWage;
this.workTime = workTime;
}
getName(): string {
return this.name;
}
getWeeklyWage(): number {
return this.weeklyWage;
}
getWorkTime(): number {
return this.workTime;
}
// 實現接口,調用訪問者處理全職員工的方法
accept(handler: Department) {
handler.visitFulltime(this);
}
}
// 臨時員工類
class ParttimeEmployee implements Employee {
private name = '';
// 臨時員工按時薪計算薪酬
private hourWage = 0;
// 工做時長
private workTime = 0;
constructor(name: string, hourWage: number, workTime: number) {
this.name = name;
this.hourWage = hourWage;
this.workTime = workTime;
}
getName(): string {
return this.name;
}
getHourWage(): number {
return this.hourWage;
}
getWorkTime(): number {
return this.workTime;
}
// 實現接口,調用訪問者處理臨時工的方法
accept(handler: Department) {
handler.visitParttime(this);
}
}
// 部門接口
interface Department {
visitFulltime(employee: FulltimeEmployee): void;
visitParttime(employee: ParttimeEmployee): void;
}
// 具體訪問者——財務部,結算薪酬實現部門接口
class FADepartment implements Department {
// 全職員工薪酬計算方式
visitFulltime(employee: FulltimeEmployee) {
const name: string = employee.getName();
let workTime: number = employee.getWorkTime();
let weekWage: number = employee.getWeeklyWage();
const WEEK_WORK_TIME = 40;
if (workTime > WEEK_WORK_TIME) {
// 計算加班工資
const OVER_TIME_WAGE = 100;
weekWage = weekWage + (workTime - WEEK_WORK_TIME) * OVER_TIME_WAGE;
} else if (workTime < WEEK_WORK_TIME) {
if (workTime < 0) {
workTime = 0;
}
// 扣款
const CUT_PAYMENT = 80;
weekWage = weekWage - (WEEK_WORK_TIME - workTime) * CUT_PAYMENT;
}
console.log(`正式員工 ${name} 實際工資爲:${weekWage}`);
}
// 臨時工薪酬計算方式
visitParttime(employee: ParttimeEmployee) {
const name = employee.getName();
const hourWage = employee.getHourWage();
const workTime = employee.getWorkTime();
console.log(`臨時工 ${name} 實際工資爲:${hourWage * workTime}`);
}
}
// 具體訪問者——人力資源部,結算工做時間,實現部門接口
class HRDepartment implements Department {
// 全職員工工做時間報告
visitFulltime(employee: FulltimeEmployee) {
const name: string = employee.getName();
let workTime: number = employee.getWorkTime();
// 實際工做時間報告
let report = `正式員工 ${name} 實際工做時間爲 ${workTime} 小時`;
const WEEK_WORK_TIME = 40;
if (workTime > WEEK_WORK_TIME) {
// 加班時間報告
report = `${report},加班 ${WEEK_WORK_TIME - workTime} 小時`;
} else if (workTime < WEEK_WORK_TIME) {
if (workTime < 0) {
workTime = 0;
}
// 請假時間報告
report = `${report},請假 ${WEEK_WORK_TIME - workTime} 小時`;
}
console.log(report);
}
// 臨時工工做時間報告
visitParttime(employee: ParttimeEmployee) {
const name: string = employee.getName();
const workTime: number = employee.getWorkTime();
console.log(`臨時工 ${name} 實際工做時間爲 ${workTime} 小時`);
}
}
// 員工集合類
class EmployeeList {
list: Array<Employee> = [];
add(employee: Employee) {
this.list.push(employee);
}
// 遍歷員工集合中的每個對象
accept(handler: Department) {
this.list.forEach((employee: Employee) => {
employee.accept(handler);
});
}
}
function visitorDemo() {
const list: EmployeeList = new EmployeeList();
const full1 = new FulltimeEmployee('Bob', 3000, 45);
const full2 = new FulltimeEmployee('Mikel', 2000, 35);
const full3 = new FulltimeEmployee('Joe', 4000, 40);
const part1 = new ParttimeEmployee('Lili', 80, 20);
const part2 = new ParttimeEmployee('Lucy', 60, 15);
list.add(full1);
list.add(full2);
list.add(full3);
list.add(part1);
list.add(part2);
// 財務部計算薪酬
const faHandler = new FADepartment();
list.accept(faHandler);
// 人力資源部出工做報告
const hrHandler = new HRDepartment();
list.accept(hrHandler);
}
複製代碼
本文介紹了最後5種對象行爲型模式,多謝你們對於系列文章的支持~對團隊感興趣的同窗能夠關注專欄或者發送簡歷至'tao.qit####alibaba-inc.com'.replace('####', '@'),歡迎有志之士加入~