一,什么是設(shè)計(jì)
- 按哪一種思路或者標(biāo)準(zhǔn)來實(shí)現(xiàn)功能
- 功能相同摹芙,可以有不同的設(shè)計(jì)方式
- 需求如果不斷變化灼狰,設(shè)計(jì)的作用才能體現(xiàn)出來
二,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)過多會(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)是要靠繼承的挣轨,沒有繼承就沒有多態(tài)
2. S 單一職責(zé)原則
- Single responsibility principle
- 一個(gè)類或者模塊只負(fù)責(zé)完成一個(gè)職責(zé),如果功能特別復(fù)雜就進(jìn)行拆分
- 單一職責(zé)可以降低類的復(fù)雜性轩猩,提高代碼可讀性卷扮、可維護(hù)性
- 當(dāng)類代碼行數(shù)過多、方法過多均践、功能太多晤锹、職責(zé)復(fù)雜的時(shí)候就要對(duì)類進(jìn)行拆分了
- 拆分不能過度,如果拆分過度會(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 '我是一瓶可口可樂' //子類違反了父類的功能和規(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)編程
依賴倒置原則的主要作用如下:
- 依賴倒置原則可以降低類間的耦合性剪况。
- 依賴倒置原則可以提高系統(tǒng)的穩(wěn)定性教沾。
- 依賴倒置原則可以減少并行開發(fā)引起的風(fēng)險(xiǎn)。
- 依賴倒置原則可以提高代碼的可讀性和可維護(hù)性
依賴倒置原則的實(shí)現(xiàn)方法
依賴倒置原則的目的是通過要面向接口的編程來降低類間的耦合性译断,所以我們?cè)趯?shí)際編程中只要遵循以下4點(diǎn)详囤,就能在項(xiàng)目中滿足這個(gè)規(guī)則。
- 每個(gè)類盡量提供接口或抽象類镐作,或者兩者都具備藏姐。
- 變量的聲明類型盡量是接口或者是抽象類。
- 任何類都不應(yīng)該從具體類派生该贾。
- 使用繼承時(shí)盡量遵循里氏替換原則
依賴倒置描述鏈接
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)注接口
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等
- 迪米特法則的定義是:只與你的直接朋友交談举庶,不跟“陌生人”說話(Talk only to your immediate friends and not to strangers)。其含義是:如果兩個(gè)軟件實(shí)體無須直接通信揩抡,那么就不應(yīng)當(dāng)發(fā)生直接的相互調(diào)用户侥,可以通過第三方轉(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)系副瀑,可以直接訪問這些對(duì)象的方法
迪米特法則的優(yōu)點(diǎn)
- 迪米特法則要求限制軟件實(shí)體之間通信的寬度和深度,正確使用迪米特法則將有以下兩個(gè)優(yōu)點(diǎn)曹体。
- 降低了類之間的耦合度俗扇,提高了模塊的相對(duì)獨(dú)立性。
- 由于親合度降低箕别,從而提高了類的可復(fù)用率和系統(tǒng)的擴(kuò)展性铜幽。
缺點(diǎn)
但是滞谢,過度使用迪米特法則會(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.從依賴者的角度來說护蝶,只依賴應(yīng)該依賴的對(duì)象。
- 從被依賴者的角度說翩迈,只暴露應(yīng)該暴露的方法持灰。
所以,在運(yùn)用迪米特法則時(shí)要注意以下 6 點(diǎn)负饲。
- 在類的劃分上堤魁,應(yīng)該創(chuàng)建弱耦合的類。類與類之間的耦合越弱返十,就越有利于實(shí)現(xiàn)可復(fù)用的目標(biāo)妥泉。
- 在類的結(jié)構(gòu)設(shè)計(jì)上,盡量降低類成員的訪問權(quán)限洞坑。
- 在類的設(shè)計(jì)上盲链,優(yōu)先考慮將一個(gè)類設(shè)置成不變類。
- 在對(duì)其他類的引用上检诗,將引用其他對(duì)象的次數(shù)降到最低匈仗。
- 不暴露類的屬性成員瓢剿,而應(yīng)該提供相應(yīng)的訪問器(set 和 get 方法)逢慌。
- 謹(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é)處理隔盛,如與粉絲的見面會(huì),與媒體公司的業(yè)務(wù)洽淡等拾稳。這里的經(jīng)紀(jì)人是明星的朋友吮炕,而粉絲和媒體公司是陌生人,所以適合使用迪米特法則
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()+"見面了访得。");
}
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)系包括兩種特例:聚合和組合
- 聚合,用來表示整體和部分的關(guān)系或者擁有關(guān)系浆兰,代表部分的對(duì)象可能會(huì)被整體擁有磕仅,但不一定會(huì)隨著整體的消亡而銷毀,比如班級(jí)和學(xué)生
-
合成或者說組合要比聚合關(guān)系強(qiáng)的多簸呈,部分和整體的生命周期是一致的榕订,比如人和器官之間
2.合成復(fù)用原則
- 合成復(fù)用原則是通過將已有的對(duì)象納入到新對(duì)象中,作為新對(duì)象的成員對(duì)象來實(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)系來實(shí)現(xiàn)族壳,其次才考慮使用繼承關(guān)系來實(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)。
- 繼承復(fù)用破壞了類的封裝性拢操。因?yàn)槔^承會(huì)將父類的實(shí)現(xiàn)細(xì)節(jié)暴露給子類锦亦,父類對(duì)子類是透明的,所以這種復(fù)用又稱為“白箱”復(fù)用令境。
- 子類與父類的耦合度高杠园。父類的實(shí)現(xiàn)的任何改變都會(huì)導(dǎo)致子類的實(shí)現(xiàn)發(fā)生變化,這不利于類的擴(kuò)展與維護(hù)舔庶。
- 它限制了復(fù)用的靈活性抛蚁。從父類繼承而來的實(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ì)象看不見的犬钢,所以這種復(fù)用又稱為“黑箱”復(fù)用。
2.新舊類之間的耦合度低思灰。這種復(fù)用所需的依賴較少玷犹,新對(duì)象存取成分對(duì)象的唯一方法是通過成分對(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ù)用原則是通過將已有的對(duì)象納入新對(duì)象中油湖,作為新對(duì)象的成員對(duì)象來實(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 可以看出用繼承關(guān)系實(shí)現(xiàn)會(huì)產(chǎn)生很多子類,而且增加新的“動(dòng)力源”或者增加新的“顏色”都要修改源代碼蹦误,這違背了開閉原則劫拢,顯然不可取。但如果改用組合關(guān)系實(shí)現(xiàn)就能很好地解決以上問題强胰,其類圖如圖 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)模式
- 訪問者模式藤树、 備忘錄模式谷醉、命令模式嚷闭、解釋器模式薪鹦、中介者模式