關於設計
- 按照哪一種思路或標準來實現功能;
- 功能相同,可以有不同的設計方案來實現;
- 伴隨需求增加,設計作用才體現出來(版本迭代,需求變化);
設計哲學(Unix/Linux)
- 小即是美;
- 讓每個程式做好一件事;
- 讓每個程式都稱為過濾器(
gulp的pipe、webpack的loader
); - 快速建立原型(快速釋出,獲取使用者需求,再迭代);
- 捨棄高效率而取可移值性(取捨,寧可放棄效率高的也要選擇可複用的);
- 採用純文字來儲存資料(取捨,寧可放棄便於計算機閱讀的方式也要選擇方便人讀的格式);
- 充分利用軟體的槓桿效應(軟體複用);
- 使用shell指令碼來提高槓杆效應和移值性;
- 避免強制性的使用者介面(使用命令列,高效)(使用者介面佔用記憶體,也會存在安全問題);
小準則
- 允許使用者定製環境;
- 儘量使作業系統核心小而輕量化(包含最核心的API,核心是核心,工具是工具,外掛是外掛);
- 使用小寫字母並儘量簡單(list - ls);
- 沉默是金(沒有就不輸出);
- 各部分之和大於整體(部分之間不影響);
- 尋求90%的解決方案(28 定律:20% 成本解決 80% 的需求,剩下 20% 的需求想要解決需要花更多時間不值得去做);
1.演示:沉默是金 + 讓每個程式都稱為過濾器
ls
ls | grep .json
ls | grep .json | grep 'package' // package.json
ls | grep .json | grep 'package1' // 什麼都不顯示(沉默是金)
複製程式碼
五大設計原則-SOLID
- S(single)-單一職責原則- 一個程式只做好一件事;如果功能過於複雜就拆分開,每個部分保持獨立;
- O(open-close)-開放封閉原則- 對擴充套件開放,對修改封閉;增加需求時,擴充套件新程式碼,而非修改已有程式碼;
- L(Liskov)-里氏置換原則-子類能覆蓋父類;父類能出現的地方子類就能出現;
- I(Interface)-介面獨立原則-保持介面的單一獨立,避免出現”胖介面“;js中沒有介面(typescript除外),使用少;
- D(Dependence)-依賴倒置原則-程式設計依賴抽象介面,不要依賴具體實現;使用方只關注介面而不關注具體類的實現;
示例 promise
- 單一職責原則:每個 then 中的邏輯製作好一件事
- 開放封閉原則:如果新增需求,擴充套件
then
// 0.0.1/loadImg.js
function loadImg(src) {
let promise = new Promise(function (resolve, reject) {
let img = document.createElement('img');
img.onload = function () {
resolve(img);
}
img.onerror = function () {
reject('圖片載入失敗');
}
img.src = src;
});
return promise;
}
let imgUrl = 'https://raw.githubusercontent.com/ruizhengyun/images/master/cover/ruizhengyun.cn_.png';
loadImg(imgUrl).then(function (img) {
console.log(`width: ${img.width}`);
return img;
}).then(function (img) {
console.log(`height: ${img.height}`);
}).catch(function (ex) {
console.log(ex);
});
複製程式碼
設計到模式(設計是設計,模式是模式)
- 設計-原則-思路-心法;
- 模式-模板-做事-招式;
如何學設計模式
- 明白設計的用意;
- 通過經典應用體會它的整正使用場景;
- 自己編碼時多思考,多模仿(刻意訓練);
面試題
示例1:叫車
1.需求
- 叫車時,可以打專車或快車。任何車都有車牌號和名稱
- 不同車價格不同,快車每公里1元,專車每公里2元
- 行程開始時,顯示車輛資訊
- 行程結束時,顯示叫車餘額(假定行程為5公里)
2.UML類圖
3.編碼
// 0.0.1/car.js
class Car {
constructor(num, name) {
this.num = num
this.name = name
}
}
class Kuaiche extends Car {
constructor(num, name, price) {
super(num, name)
this.price = price
}
}
class Zhuanche extends Car {
constructor(num, name, price) {
super(num, name)
this.price = price
}
}
class Trip {
constructor(car){
this.car = car
}
start() {
console.log(`行程開始,名稱${this.car.name},車牌號:${this.car.num}`)
}
end() {
console.log(`行程結束,價格:${this.car.price * 5}`)
}
}
// 例項
let k1 = new Kuaiche('浙A Z1001', '大眾', 1);
let t1 = new Trip(k1);
t1.start();
t1.end();
console.log('---------')
let z1 = new Zhuanche('浙A Z0001', '賓士', 3);
let t2 = new Trip(z1);
t2.start();
t2.end();
複製程式碼
示例2:停車
1.需求
- 某停車場,分3層,每層100車位
- 每個車位都能監控到車輛的駛入與離開
- 車輛駛入前,顯示每層的空餘車位數量
- 車輛駛入時,攝像頭可識別車牌號和時間
- 車輛離開時,出口顯示器顯示車牌號和停車時長
2.UML類圖
3.編碼
// 0.0.1/park-car.js
const random = (start, end) => {
return Math.floor(Math.random() * (end - start + 1)) + start;
}
const timestampToTime = timestamp => {
const date = new Date(timestamp);
const Y = date.getFullYear() + '-';
const month = date.getMonth() + 1;
const M = (month < 10 ? ('0' + month) : month) + '-';
const D = date.getDate() + ' ';
const h = date.getHours() + ':';
const m = date.getMinutes() + ':';
const s = date.getSeconds();
return Y + M + D + h + m + s;
}
// 車
class Car {
constructor(num) {
this.num = num
}
}
// 攝像頭
class Camera {
shot(car) {
return {
num: car.num,
inTime: Date.now()
}
}
}
// 出口螢幕
class Screen {
show(car, inTime) {
console.log(`車牌號 ${car.num},停車時間 ${Date.now() - inTime} 毫秒`);
}
}
// 停車場
class Park {
constructor(floors) {
this.floors = floors || []
this.camera = new Camera()
this.screen = new Screen()
this.carList = {} // 儲存攝像頭拍攝返回的車輛資訊
}
in(car) {
// 通過攝像頭獲取資訊
const info = this.camera.shot(car);
const i = random(1, 100);
const j = random(1, 3);
const place = this.floors[j].places[i]; // 第0層某個隨機車位
place.in()
info.place = place
// 記錄某車牌的資訊
this.carList[car.num] = info; // { num, inTime, place }
// console.log(`車牌號${info.num} 在 ${timestampToTime(info.inTime)} 駛入`);
}
out(car) {
// 獲取資訊
const { place, inTime } = this.carList[car.num];
place.out()
// 顯示時間
this.screen.show(car, inTime);
// console.log(`車牌號${car.num} 在 ${timestampToTime(Date.now())} 駛出`);
// 清空記錄
delete this.carList[car.num];
}
emptyFloorsNum() { // 計算每層車位剩餘多少
return this.floors
.map(floor => `${floor.index} 層還有 ${floor.emptyPlacesNum()} 個空車位`)
.join('\n')
}
}
// 層
class Floor {
constructor(index, places) {
this.index = index
this.places = places || []
}
emptyPlacesNum() { // 計算每層車位剩餘多少
let num = 0
this.places.forEach(place => {
if (place.empty) {
num += 1
}
});
return num
}
}
// 車位
class Place {
constructor() {
this.empty = true
}
in() {
this.empty = false
}
out() {
this.empty = true
}
}
// 測試-----------
// 初始化停車場
const floors = [];
for (let i = 1; i < 4; i++) {
const places = []
for (let j = 1; j < 101; j++) {
places[j] = new Place()
}
floors[i] = new Floor(i, places)
}
const park = new Park(floors);
// 初始化車輛
const car1 = new Car(1001);
const car2 = new Car(1002);
const car3 = new Car(1003);
console.log('第1輛車進入,當前停車庫停車情況');
console.log(park.emptyFloorsNum());
park.in(car1);
console.log('第2輛車進入,當前停車庫停車情況');
console.log(park.emptyFloorsNum());
park.in(car2);
console.log('第1輛車離開');
park.out(car1);
console.log('第2輛車離開');
park.out(car2);
console.log('第3輛車進入,當前停車庫停車情況')
console.log(park.emptyFloorsNum());
park.in(car3);
console.log('第3輛車離開');
park.out(car3);
複製程式碼