嗨 大家好 我是一路爬坡的阿肥
昨天因為老毛病發作想說去看中醫
結果脖子後面被札了十幾針
還被喬到發出喀喀喀的聲音
有種比發作時還要可怕的感覺
工讀生 A、B 各別負責寫個股相關資料的 API 串接類別,以及回傳結果的資料處理類別。這兩個類別在外觀模式中扮演 client 需要面對的兩個複雜子系統。
StockAPI.ts - 台灣證交所 API 串接
export class StockAPI {
protected baseURL: string = 'https://www.twse.com.tw/exchangeReport/STOCK_DAY';
protected stockId: number;
constructor(stockId: number) {
this.stockId = stockId;
}
//待會建立的Facade會調用的方法
public async getStockData(): Promise<IStockData> {
let url = new URL(this.baseURL),
params = { response: 'json', stockNo: this.stockId };
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));
try {
const resp = await fetch(url.href);
let theJson: IStockData = fakeJson;
theJson = await resp.json();
return theJson;
} catch (error) {
return fakeJson;
}
}
//... 之後可能會再擴充功能,形成複雜的類別
}
StockProcessor.ts - 處理 API 回傳的結果給 client
export class StockProcessor {
protected reqResult: IStockData;
public setReqResult(reqResult: IStockData) {
//...
}
public checkStat() {
//...
}
private getFieldIndex(fieldName: T_FieldName): number {
//...
}
private getNumber(str: string): number {
//...
}
private getValueList(index: number): number[] {
//...
}
private getExtrmeValueIndex(list: number[], type: T_QueryType): number[] {
//...
}
private getResultDataList(indexList: number[]): Array<IStockInfo> {
//...
}
//待會建立的Facade會調用的方法
public queryData(fieldName: T_FieldName, queryType: T_QueryType) {
let fieldIndex = this.getFieldIndex(fieldName);
let valueList: number[] = this.getValueList(fieldIndex);
let targetIndexList: number[] = this.getExtrmeValueIndex(valueList, queryType);
let resultList = this.getResultDataList(targetIndexList);
return resultList ? resultList : [];
}
//... 之後可能會再擴充功能,形成複雜的類別
}
接著再以外觀模式來做這兩個複雜類別的使用接口,讓 client 可以透過更簡易的方式調用。
首先先建立外觀類別的建構子參數的介面,其中宣告了兩個複雜類別,表示這個外觀會使用到這兩個類別來實作細節。
interface I_StockFacade {
stockId?: number;
stockAPI?: StockAPI;
stockProcessor?: StockProcessor;
}
接著建立外觀類別。我們在 constructor 做了點彈性的設計 ─ 如果只傳入 stockId 的話,就由外觀自己建立複雜類別的實體;否則由外部傳入來建立。
export class StockFacade {
protected stockAPI: StockAPI;
protected stockProcessor: StockProcessor;
constructor(param: I_StockFacade) {
if (param.stockId) {
this.stockAPI = new StockAPI(param.stockId);
this.stockProcessor = new StockProcessor();
} else {
this.stockAPI = param.stockAPI;
this.stockProcessor = param.stockProcessor;
}
}
// client調用這個方法,就可以取得經處理過的資料
public async getData(fieldName: T_FieldName, type: T_QueryType) {
let result: IStockInfo[] = [];
let reqResult = await this.stockAPI.getStockData();
this.stockProcessor.setReqResult(reqResult);
result = this.stockProcessor.queryData(fieldName, type);
return result;
}
}
前端的程式很簡單。建立一個表單,輸入相關欄位,按送出就能查詢。
我們看一下剛剛建立的 Facade 怎麼使用。
async function handleSubmit(e) {
e.preventDefault();
if (!options.fieldName || !options.queryType || !options.stockId) {
alert('請輸入完整選項');
return false;
}
// 建立facade的實體
let stockFacade = new StockFacade({ stockId: options.stockId });
// 只要調用getData就能取到資料
let data: IStockInfo[] = await stockFacade.getData(options.fieldName, options.queryType);
if (data) setData(data);
}
看到畫面後就可以輸入台股的代碼、選擇篩選的欄位,以及選最大或最小值。不過因為 CORS 的關係,目前沒有辦法取得 API 回來的真實資料。所以阿肥先以台積電 2019 年 9 月的資料作為結果回傳。阿肥之後有時間再來改進這部分,讓 demo 可以更真實喔。
阿肥相信只要開發過中型以上的專案,大部分都有用過外觀模式的概念實作過。
像是以前用 redux 做狀態管理時,我們通常會再寫個類別或是模組,來統整 API 的串接和處理 redux 的資料邏輯;甚至再大型專案中,要引入多個專案來實作一個功能時,外觀模式就是一個協助 client 作簡易接口的方式。
所以外觀模式算是相當實用也蠻重要的模式,希望大家看完都會有收穫喔!
今天的程式實作會在 github 的 packages/src/day20-strusctural.facade.code