面向對象之七大基本原則(javaScript)

面向對象編程有本身的特性與原則,若是對於面向對象有一些瞭解的話,面向對象三大特徵,封裝、繼承、多態,若是對面向對這三個概念不太瞭解,請參考面向對象之三個基本特徵(javaScript)java

單一職責

若是咱們在編寫程序的時候,一類或者一個方法裏面包含了太多方法,對於代碼的可讀性來講,無非是一場災難,對於咱們來講。因此爲了解決這個問題,出現了單一職責。編程

什麼是單一職責

單一職責:又稱單一功能原則,面向對象五個基本原則(SOLID)之一。它規定一個類應該只有一個發生變化的緣由。(節選自百度百科)segmentfault

按照上面說的,就是對一個類而言,應該僅有一個引發它變化的緣由。換句話說,一個類的功能要單一,只作與它相關的事情。在類的設計過程當中要按職責進行設計,彼此保持正交,互不干涉。設計模式

單一職責的好處
  1. 類的複雜性下降,實現什麼職責都有清晰明確的定義
  2. 可讀性提升,複雜性下降,那固然可讀性提升了
  3. 可維護性提升,可讀性提升,那固然更容易維護了
  4. 變動引發的風險下降,變動是必不可少的,若是接口的單一職責作得好,一個接口修改只對相應的實現類有影響,對其餘的接口無影響,這對系統的擴展性、維護性都有很是大的幫助。
實例
class ShoppinCar {
    constructor(){
        this.goods = [];
    }
    addGoods(good){
        this.goods = [good];
    }
    getGoodsList(){
        return this.goods;
    }
}
class Settlement {
    constructor(){
        this.result = 0; 
    }
    calculatePrice(list,key){
        let allPrice = 0;
        list.forEach((el) => {
            allPrice += el[key];
        })
        this.result = allPrice;
    }
    getAllPrice(){
        return this.result;
    }
}

用上面的代碼來講ShoppinCar類存在兩個方法addGoodsgetGoodsList,分別是添加商品和獲取商品列表。Settlement類中存在兩個方法calculatePricegetAllPrice分別作的事情是計算價錢與獲取總價錢。ShoppinCarSettlement都是在作本身的事情。添加商品與計算價格,雖然在業務上是相互依賴的,可是在代碼中分別用兩個類,然他們本身作本身的事情。其中任何一個類更改不會對另外一個類進行更改。框架

開閉原則

在一個類中暴露出去的方法,若這個方法變動了,則會產生很大的後果,可能致使其餘依賴於這個方法且有不須要變動的業務形成大面積癱瘓。爲了解決這個問題,能夠單獨再寫一個方法,若這個方法與這個類中的其餘方法相互依賴。函數

解決辦法:工具

  1. 把其中依賴的代碼copy一份到新的類中。
  2. 在新類中引用舊類中的方法。

兩種方法都不是最好的解決方案。單元測試

第一種方法會致使代碼大量的重複,第二種方法會致使類與類之間互相依賴。測試

什麼是開閉原則

開閉原則:「軟件中的對象(類,模塊,函數等等)應該對於擴展是開放的,可是對於修改是封閉的」,這意味着一個實體是容許在不改變它的源代碼的前提下變動它的行爲。(節選自百度百科)this

開閉原則對擴展開放,對修改關閉,並不意味着不作任何修改,底層模塊的變動,必然要有高層模塊進行耦合,不然就是一個孤立無心義的代碼片斷。開閉原則是一個最基本的原則,另外六個原則都是開閉原則的具體形態,是指導設計的工具和方法,而開閉原則纔是精神領袖.

開閉原則好處
  1. 開閉原則有利於進行單元測試
  2. 開閉原則能夠提升複用性
  3. 開閉原則能夠提升可維護性
  4. 面向對象開發的要求
實例
class Drag {
    down(){
        //  ...
    }   
    move(){
        //  ...
        // 對拖拽沒有作任何限制能夠隨意拖拽
    }   
    up(){
        //  ...
    }  
}
class LimitDrag extends Drag {
    move(){
        //  ...
        //  重寫該方法對拖拽進行限制處理
    }
}

LimitDrag中重寫了move方法,若修改了能夠知足兩種需求,一種是限制型拖拽,一種是不限制型拖拽,任何一個更改了另一個仍是能夠正常運行。

里氏替換

每一個開發人員在使用別人的組件時,只需知道組件的對外裸露的接口,那就是它所有行爲的集合,至於內部究竟是怎麼實現的,沒法知道,也無須知道。因此,對於使用者而言,它只能經過接口實現本身的預期,若是組件接口提供的行爲與使用者的預期不符,錯誤便產生了。里氏替換原則就是在設計時避免出現派生類與基類不一致的行爲。

什麼是里氏替換

里氏替換原則:OCP做爲OO的高層原則,主張使用「抽象(Abstraction)」和「多態(Polymorphism)」將設計中的靜態結構改成動態結構,維持設計的封閉性。「抽象」是語言提供的功能。「多態」由繼承語義實現。(節選自百度百科)

里氏替換好處
  1. 代碼共享,減小建立類的工做量,每一個子類都擁有父類的方法和屬性
  2. 提升代碼的重用性
  3. 子類能夠形似父類,可是又異於父類。
  4. 提升代碼的可擴展性,實現父類的方法就能夠了。許多開源框架的擴展接口都是經過繼承父類來完成。
  5. 提升產品或項目的開放性
實例
//  抽象槍類
class AbstractGun {
    shoot(){
        throw "Abstract methods cannot be called";
    }
}
//  步槍
class Rifle extends AbstractGun {
    shoot(){
        console.log("步槍射擊...");
    }
}
//  狙擊槍
class AUG extends Rifle {
    zoomOut(){
        console.log("經過放大鏡觀察");
    }
    shoot(){
        console.log("AUG射擊...");
    }
}
//  士兵
class Soldier {
    constructor(){
        this.gun = null;
    }
    setGun(gun){
        this.gun = gun;
    }
    killEnemy(){
        if(!this.gun){
            throw "須要給我一把槍";
            return;
        }
        console.log("士兵開始射擊...");
        this.gun.shoot();
    }
}
//  狙擊手
class Snipper extends Soldier {
    killEnemy(aug){
        if(!this.gun){
            throw "須要給我一把槍";
            return;
        }
        this.gun.zoomOut();
        this.gun.shoot();
    }
}
let soldier = new Soldier();
soldier.setGun(new Rifle());
soldier.killEnemy();

let snipper = new Snipper();
//  分配狙擊槍
snipper.setGun(new AUG());
snipper.killEnemy();

snipper.setGun(new Rifle());
// snipper.killEnemy();  //  this.gun.zoomOut is not a function

從上述代碼中能夠看出,子類和父類之間關係,子類方法必定是等於或大於父類的方法。子類可以出現的父類不必定能出現,可是父類出現的地方子類必定可以出現。

依賴倒置

若是方法與方法之間或類與類之間,存在太多的依賴關係會致使代碼可讀性以及可維護性不好。依賴倒置原則可以很好的解決這些問題。

什麼是依賴倒置

依賴倒置原則:程序要依賴於抽象接口,不要依賴於具體實現。簡單的說就是要求對抽象進行編程,不要對實現進行編程,這樣就下降了客戶與實現模塊間的耦合。(節選自百度百科)

  1. 高層模塊不該該依賴低層模塊,二者都應該依賴其抽象
  2. 抽象不該該依賴細節
  3. 細節應該依賴抽象
依賴倒置好處
  1. 經過依賴於接口,隔離了具體實現類
  2. 低一層的變更並不會致使高一層的變更
  3. 提升了代碼的容錯性、擴展性和易於維護
實例
//  抽象槍類
class AbstractGun {
    shoot(){
        throw "Abstract methods cannot be called";
    }
}
//  步槍
class Rifle extends AbstractGun {
    shoot(){
        console.log("步槍射擊...");
    }
}
//  狙擊槍
class AUG extends AbstractGun {
    shoot(){
        console.log("AUG射擊...");
    }
}

從上面的代碼能夠看出,步槍與狙擊槍的shoot所有都是依賴於AbstractGun抽象的槍類,上述編程知足了依賴倒置原則。

接口隔離

什麼是接口隔離

接口隔離:客戶端不該該依賴它不須要的接口;一個類對另外一個類的依賴應該創建在最小的接口上。(節選自百度百科)

接口隔離原則與單一職責原則的審視角度不相同。單一職責原則要求是類和接口的職責單一,注重的職責,這是業務邏輯上的劃分。接口隔離原則要求接口的方法儘可能少。

接口隔離好處
  1. 避免接口污染
  2. 提升靈活性
  3. 提供定製服務
  4. 實現高內聚
實例
function mix(...mixins) {
  class Mix {}
  for (let mixin of mixins) {
    copyProperties(Mix, mixin);
    copyProperties(Mix.prototype, mixin.prototype);
  }
  return Mix;
}
function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if ( key !== "constructor"&& key !== "prototype"&& key !== "name") {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}
class Behavior {
    eat(){
        throw "Abstract methods cannot be used";
    }   
    call(){
        throw "Abstract methods cannot be used";
    }
}
class Action {
    climbTree(){
        throw "Abstract methods cannot be used";
    }
}
class Dog extends Behavior{
    eat(food){
        console.log(`狗正在吃${food}`);
    }
    hungry(){
        console.log("汪汪汪,我餓了")
    }
}
const CatMin = mix(Behavior,Action);
class Cat extends CatMin{
    eat(food){
        console.log(`貓正在吃${food}`);
    }
    hungry(){
        console.log("喵喵喵,我餓了")
    }
    climbTree(){
        console.log("爬樹很開心哦~")
    }
}
let dog = new Dog();
dog.eat("骨頭");
dog.hungry();
let cat = new Cat();
cat.eat("魚");
cat.hungry();
cat.climbTree();

你們必定要好好分析一下上面的代碼,共有兩個抽象類,分別對應不一樣的行爲,CatDog類擁有共同的行爲,可是Cat又擁有其本身單獨的行爲,使用抽象(即接口)繼承其方法,使用接口隔離使其完成各自的工做,各司其職。

迪米特法則

迪米特法則:最少知識原則(Least Knowledge Principle 簡寫LKP),就是說一個對象應當對其餘對象有儘量少的瞭解,不和陌生人說話。英文簡寫爲: LoD.(節選自百度百科)

迪米特法則的作法觀念就是類間解耦,弱耦合,只有弱耦合了之後,類的複用率才能夠提升。一個類應該對其餘對象保持最少的瞭解。通俗來說,就是一個類對本身依賴的類知道的越少越好。由於類與類之間的關係越密切,耦合度越大,當一個類發生改變時,對另外一個類的影響也越大。

迪米特法則好處
  1. 減小對象之間的耦合性
實例
class ISystem {
    close(){
        throw "Abstract methods cannot be used";
    }
}
class System extends ISystem{
    saveCurrentTask(){
        console.log("saveCurrentTask")
    }
    closeService(){
        console.log("closeService")
    }
    closeScreen(){
        console.log("closeScreen")
    }
    closePower(){
        console.log("closePower")
    }
    close(){
        this.saveCurrentTask();
        this.closeService();
        this.closeScreen();
        this.closePower();
    }
}
class IContainer{
    sendCloseCommand(){
        throw "Abstract methods cannot be used";
    }
}
class Container extends IContainer{
    constructor(){
        super()
        this.system = new System();
    }
    sendCloseCommand(){
        this.system.close();
    }
}
class Person extends IContainer{
    constructor(){
        super();
        this.container = new Container();
    }
    clickCloseButton(){
       this.container.sendCloseCommand();
    }
}
let person = new Person();
person.clickCloseButton();

上面代碼中Container做爲媒介,其調用類不知道其內部是如何實現,用戶去觸發按鈕,Container把消息通知給計算機,計算機去執行相對應的命令。

組合/聚合複用原則

聚合(Aggregation)表示一種弱的‘擁有’關係,體現的是A對象能夠包含B對象但B對象不是A對象的一部分。

合成(Composition)則是一種強的'擁有'關係,體現了嚴格的部分和總體關係,部分和總體的生命週期同樣。

組合/聚合:是經過得到其餘對象的引用,在運行時刻動態定義的,也就是在一個對象中保存其餘對象的屬性,這種方式要求對象有良好定義的接口,而且這個接口也不常常發生改變,並且對象只能經過接口來訪問,這樣咱們並不破壞封裝性,因此只要類型一致,運行時還能夠經過一個對象替換另一個對象。

優先使用對象的合成/聚合將有助於你保持每一個類被封裝,並被集中在單個任務上,這樣類和類繼承層次會保持較小規模,並且不太可能增加爲不可控制的龐然大物。

組合/聚合複用原則好處
  1. 新的實現較爲容易,由於超類的大部分功能可經過繼承關係自動進入子類;
  2. 修改或擴展繼承而來的實現較爲容易。
實例
function mix(...mixins) {
  class Mix {}
  for (let mixin of mixins) {
    copyProperties(Mix, mixin);
    copyProperties(Mix.prototype, mixin.prototype);
  }
  return Mix;
}
function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if ( key !== "constructor"&& key !== "prototype"&& key !== "name") {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}
class Savings {
    saveMoney(){
        console.log("存錢");
    }
    withdrawMoney(){
        console.log("取錢");
    }
}
class Credit {
    overdraft(){
        console.log("透支")
    }
}
const CarMin = mix(Savings,Credit);
class UserCar extends CarMin {
    constructor(num,carUserName){
        super();
        console.log()
        this.carNum = num;
        this.carUserName = carUserName;
    }
    getCarNum(){
        return this.carNum;
    }
    getCarUserName(){
        return this.carUserName;
    }
}
let myCar = new UserCar(123456798,"Aaron");
console.log(myCar.getCarNum());
console.log(myCar.getCarUserName());
myCar.saveMoney();
myCar.withdrawMoney();
myCar.overdraft();

總結

這些原則在設計模式中體現的淋淋盡致,設計模式就是實現了這些原則,從而達到了代碼複用、加強了系統的擴展性。因此設計模式被不少人奉爲經典。咱們能夠經過好好的研究設計模式,來慢慢的體會這些設計原則。

相關文章
相關標籤/搜索