2020-04-24design princeple

一,什么是設(shè)計(jì)

  • 按哪一種思路或者標(biāo)準(zhǔn)來(lái)實(shí)現(xiàn)功能
  • 功能相同,可以有不同的設(shè)計(jì)方式
  • 需求如果不斷變化,設(shè)計(jì)的作用才能體現(xiàn)出來(lái)

二,SOLID 五大設(shè)計(jì)原則

首字母 指代 概念
S 單一職責(zé)原則 單一功能原則認(rèn)為對(duì)象應(yīng)該僅具有單一功能 的概念
O 開放封閉原則 開閉原則認(rèn)為 軟件體應(yīng)該是對(duì)外開放的,但是對(duì)于修改封閉的概念
L 里式替換原則 里式替換原則認(rèn)為程序中的對(duì)象應(yīng)該是可以在不改變程序正確性的前提下 被它的子類替換 的概念
I 接口隔離原則 接口隔離原則認(rèn)為 多個(gè)特定的客戶端接口要好于一個(gè)寬泛用途的接口 的概念
D 依賴反轉(zhuǎn)原則 依賴反轉(zhuǎn)原則認(rèn)為一個(gè)方法應(yīng)該遵循 依賴于抽象而不是一個(gè)實(shí)例 的概念 依賴注入是該原則實(shí)現(xiàn)的一種方式

1.O開放封閉原則

  • Open Closed Principle
  • 對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉
  • 增加需求時(shí),擴(kuò)展新代碼,而非修改已有代碼
  • 開閉原則是設(shè)計(jì)模式中的總原則
  • 對(duì)近期可能會(huì)變化并且如果有變化但改動(dòng)量巨大的地方要增加擴(kuò)展點(diǎn),擴(kuò)展點(diǎn)過(guò)多會(huì)降低可讀性

不好的設(shè)計(jì)

class Product{
  constructor(name,price){
    this.name=name
    this.price=price
  }
  cost(customer){
    switch(customer.rank){
      case 'member':
          return this.price*.8
      case 'vip':
          return this.price*.6
      default:
        return this.price
    }
  }
}
class Customer{
  constructor(rank){
    this.rank=rank
  }
}
let product=new Product('筆記本電腦',1000)
let member=new Customer('member')
let vip=new Customer('vip')
let guest=new Customer('guest')
console.log(product.cost(member))
console.log(product.cost(vip))
console.log(product.cost(guest))

好的設(shè)計(jì)

class Product{
  constructor(name,price){
    this.name=name
    this.price=price
  }
  cost(customer){
    return this.price*customer.discount
  }
}
class Customer{
  constructor(rank,discount=1){
    this.rank=rank
    this.discount=discount
  }
}
let product=new Product('筆記本電腦',1000)
let member=new Customer('member',.8)
let vip=new Customer('vip',.6)
let guest=new Customer('guest')
let superVip=new Customer('superVip',.4)
console.log(product.cost(member))
console.log(product.cost(vip))
console.log(product.cost(guest))
console.log(product.cost(superVip))



  • 多態(tài)是一個(gè)功能,它的實(shí)現(xiàn)是要靠繼承的,沒(méi)有繼承就沒(méi)有多態(tài)

2. S 單一職責(zé)原則

  • Single responsibility principle
  • 一個(gè)類或者模塊只負(fù)責(zé)完成一個(gè)職責(zé),如果功能特別復(fù)雜就進(jìn)行拆分
  • 單一職責(zé)可以降低類的復(fù)雜性,提高代碼可讀性、可維護(hù)性
  • 當(dāng)類代碼行數(shù)過(guò)多、方法過(guò)多、功能太多、職責(zé)復(fù)雜的時(shí)候就要對(duì)類進(jìn)行拆分了
  • 拆分不能過(guò)度,如果拆分過(guò)度會(huì)損失內(nèi)聚性和維護(hù)性

3. L里氏替換原則

  • Liskkov Substitution Principle
  • 所有引用基類的地方必須能透明的使用其他子類的對(duì)象
  • 子類能替換父類,使用者可能根本就不需要知道父類還是子類,反之則不行
  • 里式替換原則是開閉原則的實(shí)現(xiàn)基礎(chǔ),程序設(shè)計(jì)的時(shí)候盡量使用基類定義及引用,運(yùn)行時(shí)再?zèng)Q定使用哪個(gè)子類
  • 里式替換原則可以提高代碼的復(fù)用性,提高代碼的可擴(kuò)展性,也增加了耦合性
  • 相對(duì)于多態(tài),這個(gè)原則講的是類如何設(shè)計(jì),子類如果違反了父類的功能則表示違反了里式替換原則


    里氏替換原則

好的設(shè)計(jì)

class Drink{
  //抽象類abstract
  getPrice(){}
}
class CocaCola extends Drink{
  getPrice(){
    return 3
  }
}
class Sprite extends Drink{
  getPrice(){
    return 3
  }
}
class Fanta extends Drink{
  getPrice(){
    return 5
  }
}

class Customer{
  drink(AbstrackDrink){
    console.log(`花費(fèi)${AbstrackDrink.getPrice()}`)
  }
}
let c1=new Customer()
c1.drink(new CocaCola())

不好的設(shè)計(jì)

class Drink{
  //抽象類abstract
  getPrice(){}
}
class CocaCola extends Drink{
  getPrice(){
    return '我是一瓶可口可樂(lè)' //子類違反了父類的功能和規(guī)定
  }
}
class Sprite extends Drink{
  getPrice(){
    return 3
  }
}
class Fanta extends Drink{
  getPrice(){
    return 5
  }
}

class Customer{
  drink(AbstrackDrink){
    console.log(`花費(fèi)${AbstrackDrink.getPrice()}`)
  }
}
let c1=new Customer()
c1.drink(new CocaCola())

4.D 依賴倒置原則

  • Dependence Inversion Principle
  • 依賴倒置原則的原始定義為:高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴其抽象;抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象(High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions)。
  • 核心思想是:要面向接口編程,不要面向?qū)崿F(xiàn)編程
依賴倒置原則的主要作用如下:
  1. 依賴倒置原則可以降低類間的耦合性。
  2. 依賴倒置原則可以提高系統(tǒng)的穩(wěn)定性。
  3. 依賴倒置原則可以減少并行開發(fā)引起的風(fēng)險(xiǎn)。
  4. 依賴倒置原則可以提高代碼的可讀性和可維護(hù)性
依賴倒置原則的實(shí)現(xiàn)方法

依賴倒置原則的目的是通過(guò)要面向接口的編程來(lái)降低類間的耦合性,所以我們?cè)趯?shí)際編程中只要遵循以下4點(diǎn),就能在項(xiàng)目中滿足這個(gè)規(guī)則。

  1. 每個(gè)類盡量提供接口或抽象類,或者兩者都具備。
  2. 變量的聲明類型盡量是接口或者是抽象類。
  3. 任何類都不應(yīng)該從具體類派生。
  4. 使用繼承時(shí)盡量遵循里氏替換原則
    依賴倒置描述鏈接
購(gòu)物圖例
package principle;
public class DIPtest
{
    public static void main(String[] args)
    {
        Customer wang=new Customer();
        System.out.println("顧客購(gòu)買以下商品:"); 
        wang.shopping(new ShaoguanShop()); 
        wang.shopping(new WuyuanShop());
    }
}
//商店
interface Shop
{
    public String sell(); //賣
}
//韶關(guān)網(wǎng)店
class ShaoguanShop implements Shop
{
    public String sell()
    {
        return "韶關(guān)土特產(chǎn):香菇、木耳……"; 
    } 
}
//婺源網(wǎng)店
class WuyuanShop implements Shop
{
    public String sell()
    {
        return "婺源土特產(chǎn):綠茶、酒糟魚……"; 
    }
} 
//顧客
class Customer
{
    public void shopping(Shop shop)
    {
        //購(gòu)物
        System.out.println(shop.sell()); 
    }
}
依賴倒置原則
interface Girlfriend{
  age:number
  height:number
  cook():void
}
class LinChiling implements Girlfriend{
  age:number=35
  height:number=178
  cook(){
    console.log('泡面')
  }
}
class HanMeimei implements Girlfriend{
  age:number=35
  height:number=178
  cook(){
    console.log('泡面')
  }
}
class SingleDog{
  constructor(public girlfriend:Girlfriend){

  }
}
let dog1=new SingleDog(new LinChiling())
let dog2=new SingleDog(new HanMeimei())

5 接口隔離原則

  • Interface Segregation Principle
  • 保持接口的單一獨(dú)立,避免出現(xiàn)胖接口
  • 客戶端不應(yīng)該依賴它不需要的接口,類之間的依賴關(guān)系應(yīng)該建立在最小的接口上
  • 接口盡量細(xì)化,而且接口中的方法盡量的少
  • 類似于單一職責(zé)原則,更關(guān)注接口


    image.png
interface Runing{
  run():void
}
interface Flying{
  fly():void
}
interface Swimming{
  swim():void
}
class Automobile implements Runing,Flying,Swimming{
  run(){}
  fly(){}
  swim(){}
}

6 迪米特法則

  • Law of Demeter, LOD
  • 有時(shí)候也叫做最少知識(shí)原則
  • 一個(gè)軟件實(shí)體應(yīng)當(dāng)盡少地與其他實(shí)體發(fā)生相互作用
  • 迪米特法則的初衷在于降低類之間的耦合
  • 類定義時(shí)盡量要實(shí)現(xiàn)內(nèi)聚,少用public修飾符,盡量使用private、protected等
  • 迪米特法則的定義是:只與你的直接朋友交談,不跟“陌生人”說(shuō)話(Talk only to your immediate friends and not to strangers)。其含義是:如果兩個(gè)軟件實(shí)體無(wú)須直接通信,那么就不應(yīng)當(dāng)發(fā)生直接的相互調(diào)用,可以通過(guò)第三方轉(zhuǎn)發(fā)該調(diào)用。其目的是降低類之間的耦合度,提高模塊的相對(duì)獨(dú)立性。
  • 迪米特法則中的“朋友”是指:當(dāng)前對(duì)象本身、當(dāng)前對(duì)象的成員對(duì)象、當(dāng)前對(duì)象所創(chuàng)建的對(duì)象、當(dāng)前對(duì)象的方法參數(shù)等,這些對(duì)象同當(dāng)前對(duì)象存在關(guān)聯(lián)、聚合或組合關(guān)系,可以直接訪問(wèn)這些對(duì)象的方法

迪米特法則的優(yōu)點(diǎn)

  1. 迪米特法則要求限制軟件實(shí)體之間通信的寬度和深度,正確使用迪米特法則將有以下兩個(gè)優(yōu)點(diǎn)。
  2. 降低了類之間的耦合度,提高了模塊的相對(duì)獨(dú)立性。
  3. 由于親合度降低,從而提高了類的可復(fù)用率和系統(tǒng)的擴(kuò)展性。

缺點(diǎn)

但是,過(guò)度使用迪米特法則會(huì)使系統(tǒng)產(chǎn)生大量的中介類,從而增加系統(tǒng)的復(fù)雜性,使模塊之間的通信效率降低。所以,在釆用迪米特法則時(shí)需要反復(fù)權(quán)衡,確保高內(nèi)聚和低耦合的同時(shí),保證系統(tǒng)的結(jié)構(gòu)清晰。

迪米特法則的實(shí)現(xiàn)方法

從迪米特法則的定義和特點(diǎn)可知,它強(qiáng)調(diào)以下兩點(diǎn):
1.從依賴者的角度來(lái)說(shuō),只依賴應(yīng)該依賴的對(duì)象。

  1. 從被依賴者的角度說(shuō),只暴露應(yīng)該暴露的方法。

所以,在運(yùn)用迪米特法則時(shí)要注意以下 6 點(diǎn)。

  1. 在類的劃分上,應(yīng)該創(chuàng)建弱耦合的類。類與類之間的耦合越弱,就越有利于實(shí)現(xiàn)可復(fù)用的目標(biāo)。
  2. 在類的結(jié)構(gòu)設(shè)計(jì)上,盡量降低類成員的訪問(wèn)權(quán)限。
  3. 在類的設(shè)計(jì)上,優(yōu)先考慮將一個(gè)類設(shè)置成不變類。
  4. 在對(duì)其他類的引用上,將引用其他對(duì)象的次數(shù)降到最低。
  5. 不暴露類的屬性成員,而應(yīng)該提供相應(yīng)的訪問(wèn)器(set 和 get 方法)。
  6. 謹(jǐn)慎使用序列化(Serializable)功能。

示例1

class Salesman{
  constructor(public name:string){

  }
  sale(){
    console.log(this.name+'銷售中。。。。。。。。。。')
  }
}
class SaleManager{
  public salesmen:Array<Salesman>=[new Salesman('張三'),new Salesman('李四')]
  sale(){
    this.salesmen.forEach(salesman=>salesman.sale())
  }
}
class CEO{
  private saleManager:SaleManager=new SaleManager()
  sale(){
    this.saleManager.sale()
  }
}
let ceo=new CEO()
ceo.sale()

示例2 明星與經(jīng)紀(jì)人的關(guān)系實(shí)例

分析:明星由于全身心投入藝術(shù),所以許多日常事務(wù)由經(jīng)紀(jì)人負(fù)責(zé)處理,如與粉絲的見(jiàn)面會(huì),與媒體公司的業(yè)務(wù)洽淡等。這里的經(jīng)紀(jì)人是明星的朋友,而粉絲和媒體公司是陌生人,所以適合使用迪米特法則


image.png
package principle;
public class LoDtest
{
    public static void main(String[] args)
    {
        Agent agent=new Agent();
        agent.setStar(new Star("林心如"));
        agent.setFans(new Fans("粉絲韓丞"));
        agent.setCompany(new Company("中國(guó)傳媒有限公司"));
        agent.meeting();
        agent.business();
    }
}
//經(jīng)紀(jì)人
class Agent
{
    private Star myStar;
    private Fans myFans;
    private Company myCompany;
    public void setStar(Star myStar)
    {
        this.myStar=myStar;
    }
    public void setFans(Fans myFans)
    {
        this.myFans=myFans;
    }
    public void setCompany(Company myCompany)
    {
        this.myCompany=myCompany;
    }
    public void meeting()
    {
        System.out.println(myFans.getName()+"與明星"+myStar.getName()+"見(jiàn)面了。");
    }
    public void business()
    {
        System.out.println(myCompany.getName()+"與明星"+myStar.getName()+"洽淡業(yè)務(wù)。");
    }
}
//明星
class Star
{
    private String name;
    Star(String name)
    {
        this.name=name;
    }
    public String getName()
    {
        return name;
    }
}
//粉絲
class Fans
{
    private String name;
    Fans(String name)
    {
        this.name=name;
    }
    public String getName()
    {
        return name;
    }
}
//媒體公司
class Company
{
    private String name;
    Company(String name)
    {
        this.name=name;
    }
    public String getName()
    {
        return name;
    }
}

7.合成復(fù)用原則

1.類的關(guān)系

  • 類之間有三種基本關(guān)系,分別是關(guān)聯(lián)(聚合和組合)、泛化和依賴
  • 如果一個(gè)類單向依賴另一個(gè)類,那么它們之間就是單向關(guān)聯(lián)。如果彼此依賴,則為相互依賴,即雙向關(guān)聯(lián)
  • 關(guān)聯(lián)關(guān)系包括兩種特例:聚合和組合
    • 聚合,用來(lái)表示整體和部分的關(guān)系或者擁有關(guān)系,代表部分的對(duì)象可能會(huì)被整體擁有,但不一定會(huì)隨著整體的消亡而銷毀,比如班級(jí)和學(xué)生
    • 合成或者說(shuō)組合要比聚合關(guān)系強(qiáng)的多,部分和整體的生命周期是一致的,比如人和器官之間


      圖示

      2.合成復(fù)用原則

  • 合成復(fù)用原則是通過(guò)將已有的對(duì)象納入到新對(duì)象中,作為新對(duì)象的成員對(duì)象來(lái)實(shí)現(xiàn)的
  • 新對(duì)象可以調(diào)用已有的對(duì)象的功能,從而達(dá)到復(fù)用
  • 原則是盡量首先使用組合/聚合方式,而不是繼承(繼承耦合性太強(qiáng))
  • 專業(yè)人做專業(yè)事
  • 合成復(fù)用原則(Composite Reuse Principle,CRP)又叫組合/聚合復(fù)用原則(Composition/Aggregate Reuse Principle,CARP)。它要求在軟件復(fù)用時(shí),要盡量先使用組合或者聚合等關(guān)聯(lián)關(guān)系來(lái)實(shí)現(xiàn),其次才考慮使用繼承關(guān)系來(lái)實(shí)現(xiàn)
  • 如果要使用繼承關(guān)系,則必須嚴(yán)格遵循里氏替換原則。合成復(fù)用原則同里氏替換原則相輔相成的,兩者都是開閉原則的具體實(shí)現(xiàn)規(guī)范

合成復(fù)用原則的重要性

通常類的復(fù)用分為繼承復(fù)用和合成復(fù)用兩種
繼承復(fù)用雖然有簡(jiǎn)單和易實(shí)現(xiàn)的優(yōu)點(diǎn),但它也存在以下缺點(diǎn)。

  1. 繼承復(fù)用破壞了類的封裝性。因?yàn)槔^承會(huì)將父類的實(shí)現(xiàn)細(xì)節(jié)暴露給子類,父類對(duì)子類是透明的,所以這種復(fù)用又稱為“白箱”復(fù)用。
  2. 子類與父類的耦合度高。父類的實(shí)現(xiàn)的任何改變都會(huì)導(dǎo)致子類的實(shí)現(xiàn)發(fā)生變化,這不利于類的擴(kuò)展與維護(hù)。
  3. 它限制了復(fù)用的靈活性。從父類繼承而來(lái)的實(shí)現(xiàn)是靜態(tài)的,在編譯時(shí)已經(jīng)定義,所以在運(yùn)行時(shí)不可能發(fā)生變化。

采用組合或聚合復(fù)用時(shí),可以將已有對(duì)象納入新對(duì)象中,使之成為新對(duì)象的一部分,新對(duì)象可以調(diào)用已有對(duì)象的功能,它有以下優(yōu)點(diǎn)。
1.它維持了類的封裝性。因?yàn)槌煞謱?duì)象的內(nèi)部細(xì)節(jié)是新對(duì)象看不見(jiàn)的,所以這種復(fù)用又稱為“黑箱”復(fù)用。
2.新舊類之間的耦合度低。這種復(fù)用所需的依賴較少,新對(duì)象存取成分對(duì)象的唯一方法是通過(guò)成分對(duì)象的接口。
3.復(fù)用的靈活性高。這種復(fù)用可以在運(yùn)行時(shí)動(dòng)態(tài)進(jìn)行,新對(duì)象可以動(dòng)態(tài)地引用與成分對(duì)象類型相同的對(duì)象。

合成復(fù)用原則的實(shí)現(xiàn)方法

合成復(fù)用原則是通過(guò)將已有的對(duì)象納入新對(duì)象中,作為新對(duì)象的成員對(duì)象來(lái)實(shí)現(xiàn)的,新對(duì)象可以調(diào)用已有對(duì)象的功能,從而達(dá)到復(fù)用

//盡量使用組合或者聚合,而不是使用繼承
class Cooker{
  cook(){

  }
}
class Person{
  private cooker:Cooker
  cook(){
    this.cooker.cook()
  }
}

【例1】汽車分類管理程序。

分析:汽車按“動(dòng)力源”劃分可分為汽油汽車、電動(dòng)汽車等;按“顏色”劃分可分為白色汽車、黑色汽車和紅色汽車等。如果同時(shí)考慮這兩種分類,其組合就很多。圖 1 所示是用繼淨(jìng):關(guān)系實(shí)現(xiàn)的汽車分類的類圖


圖1

從圖 1 可以看出用繼承關(guān)系實(shí)現(xiàn)會(huì)產(chǎn)生很多子類,而且增加新的“動(dòng)力源”或者增加新的“顏色”都要修改源代碼,這違背了開閉原則,顯然不可取。但如果改用組合關(guān)系實(shí)現(xiàn)就能很好地解決以上問(wèn)題,其類圖如圖 2 所示。


圖2

8.總結(jié)

  • 開閉原則是核心,對(duì)修改關(guān)閉對(duì)擴(kuò)展開放是軟件設(shè)計(jì)的基石
  • 單一職責(zé)要求我們?cè)O(shè)計(jì)接口和模塊功能的時(shí)間盡量保證單一性和原子性,修改一條不影響全局和其他模塊
  • 里氏替換原則和依賴倒置原則要求面向接口和抽象編程,不要依賴具體實(shí)現(xiàn),否則實(shí)現(xiàn)一改,上層調(diào)用者就要對(duì)應(yīng)修改

tips:如何寫出好代碼

  • 可維護(hù)性,bug是否好改
  • 可讀性,是否容易看懂
  • 可擴(kuò)展性 是否可以添加新功能
  • 靈活性 添加新功能是否容易,老方法和接口是否容易復(fù)用
  • 簡(jiǎn)潔性,代碼是否簡(jiǎn)單清晰
  • 可復(fù)用性 相同的代碼不要寫兩遍
  • 可測(cè)試性 是否方便寫單元測(cè)試和集成測(cè)試

23種設(shè)計(jì)模式

一. 創(chuàng)建型
  • 工廠模式(工廠方法模式、抽象工廠模式、簡(jiǎn)單工廠模式)、建造者模式、單例模式
  • 原型模式
二. 結(jié)構(gòu)型
  • 代理模式、橋接模式、裝飾器模式、適配器模式
  • 外觀模式、組合模式、享元模式
三.行為型

-觀察者模式、模版方法模式、策略模式、職責(zé)鏈模式、迭代器模式、狀態(tài)模式

  • 訪問(wèn)者模式、 備忘錄模式、命令模式、解釋器模式、中介者模式
設(shè)計(jì)模式之間的關(guān)系.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容