開閉原則
對修改關(guān)閉咽笼,對擴展開放顷编。
如何對擴展開放?
- 面向抽象編程剑刑,利用實現(xiàn)接口媳纬、繼承類的方式來進行擴展。
- 在擴展的同時滿足之前方法的可用叛甫。
依賴倒置原則
抽象不應(yīng)該依賴于細節(jié)层宫,細節(jié)應(yīng)該依賴抽象。(針對接口編程其监,不要針對實現(xiàn)編程)
如何實現(xiàn)依賴倒置萌腿?
- 面向抽象編程。
單一職責(zé)原則
不要存在多于一個導(dǎo)致類變更的原因抖苦。(一個類或者一個方法毁菱,盡可能的只做一件事情)
如何實現(xiàn)單一職責(zé)?
- 拆分步驟锌历,解耦贮庞。
接口隔離原則
用多個專門的接口,而不是使用單一的總接口究西,客戶端不應(yīng)該依賴他不需要的接口窗慎。
實現(xiàn)接口隔離應(yīng)該注意:
- 一個類對一個類的依賴應(yīng)該建立在最小的接口上。
- 建立單一的接口卤材,不要建立龐大臃腫的解耦遮斥。(注意適度)
- 要細化接口,接口中的方法盡量少扇丛。
迪米特原則(最少知道原則)
一個對象應(yīng)該對其他對象保持最少的了解术吗。(只和朋友交流,不和陌生人說話)
怎么做帆精?
- 如何做到其他的依賴(代理较屿?委派隧魄?)
里氏替換原則
一個軟件實體如果適用一個父類的話,那么一定適用于其子類隘蝎,所有引用父類的地方必須能透明的使用其子類的對象购啄,子類能夠替換父類對象,而程序邏輯不變末贾。
怎么做闸溃?
- 子類可以擴展父類功能,單不能改變父類原有功能拱撵。
- 子類可以實現(xiàn)父類的抽象方法辉川,但是不能覆蓋非抽象方法。
- 子類的方法實現(xiàn)父類的方法時(重載拴测、重寫乓旗、實現(xiàn)抽象),方法的入?yún)⒁鼘捤杉鳎鰠⒁鼑?yán)格屿愚。
合成復(fù)用原則
盡可能的使用對象組合,聚合而不是繼承的關(guān)系來達到軟件復(fù)用的目的务荆。(組合:has-a妆距。聚合:Contains-a。繼承:is-a)
設(shè)計原則總結(jié)
學(xué)習(xí)了設(shè)計模式函匕,才知道之前自己有多蠢娱据,編出的代碼又費勁又冗余,給后來的同時維護造成了很多麻煩盅惜。
1中剩、不知道什么是開閉原則,來了新業(yè)務(wù)就新增一段邏輯代碼抒寂,業(yè)務(wù)不段的適應(yīng)市場調(diào)整邏輯和參數(shù)结啼,自己就不斷的修改代碼,很傻很無聊屈芜。最后類越來越繁瑣郊愧,自己都看不下去了,別人更頭疼井佑。
現(xiàn)在想想如果新業(yè)務(wù)對原先邏輯又依賴糕珊,應(yīng)該寫一個子類繼承原來的類,在子類上擴展新業(yè)務(wù)的實現(xiàn)方法毅糟。
2、不知道什么是依賴倒置原則澜公,一上來就想編碼實現(xiàn)業(yè)務(wù)的具體要求姆另,沒有想過新業(yè)務(wù)和新業(yè)務(wù)之間喇肋、新業(yè)務(wù)和老業(yè)務(wù)之間的關(guān)聯(lián)關(guān)系。結(jié)果寫了很多重復(fù)的代碼迹辐,以后業(yè)務(wù)模式變化了蝶防,每個類都要修改一遍。
現(xiàn)在想想應(yīng)該在拿到需求之后明吩,先找下業(yè)務(wù)的共同的间学,抽象出接口或者父類出來,各自子類中實現(xiàn)不同的業(yè)務(wù)邏輯印荔,后面的維護就會變得容易很多低葫,代碼更容易閱讀。
3仍律、不知道什么是單一原則嘿悬,某些主要的類文件越來越大,功能越來越雜水泉,簡直能包羅萬象了善涨。
現(xiàn)在想想應(yīng)該不同業(yè)務(wù)不同功能的都單獨編一個類或方法。
4草则、不知道什么是接口隔離原則钢拧,類似不知道單一原則,把所有不同方法都放在一個接口中炕横,搞成了一個超級接口
現(xiàn)在想想接口也要分門別類源内,專門做同一類事情。
5看锉、不知道什么是迪米特法則姿锭,一個類中引用了很多無用的jar包,入?yún)⒑统蓡T變量中引入了不需要的實例對象伯铣。
現(xiàn)在想想應(yīng)該刪除用不到的jar包呻此,舍棄無關(guān)的引用。
6腔寡、不知道什么是里氏替換原則焚鲜,用不好繼承,懼怕用繼承放前,結(jié)果代碼寫的很臃腫和初級忿磅,沒一點逼格,編程功底一直無法再進一步凭语,一直停留再初級階段葱她。
現(xiàn)在想想應(yīng)該善用繼承,把繼承的規(guī)范用到極致似扔,這樣的代碼會更靈活和健壯吨些。
7搓谆、不知道什么是合成復(fù)用原則,就是因為之前6條法則做不好豪墅,沒有做到細化類/接口/方法泉手,到處都是緊耦合,搞得既沒法用繼承復(fù)用偶器,更不能用組合或聚合的原則去編碼斩萌。
工廠模式(創(chuàng)建型)
簡單工廠(產(chǎn)品的工廠)
switch case/ if else / new Instance() 等手段來實現(xiàn),后邊擴展的越多內(nèi)部越臃腫屏轰。
- 通過名稱判斷應(yīng)該創(chuàng)建哪個產(chǎn)品颊郎。
- 根據(jù)傳入的Class類型,直接通過反射調(diào)用構(gòu)造函數(shù)生成對象實例。
工廠方法(工廠的工廠)
主要是為了解決 簡單工廠 代碼的臃腫問題亭枷。 不再由單一的工廠類生產(chǎn)產(chǎn)品袭艺,而事故由工廠類的子類實現(xiàn)具體產(chǎn)品的創(chuàng)建。因此 每增加一類產(chǎn)品叨粘,只需要增加一個相應(yīng)工廠類的子類猾编。
抽象工廠(復(fù)雜的工廠)
主要為了解決產(chǎn)品組,產(chǎn)品等級之間復(fù)雜的關(guān)系升敲。一個產(chǎn)品可以有多個生產(chǎn)廠商答倡,一個生產(chǎn)廠商可以生產(chǎn)多個產(chǎn)品。
單例模式(創(chuàng)建型)
- 私有化構(gòu)造方法驴党。
- 懶加載瘪撇。
- 保證線程安全。
- 防止反射破壞單例港庄。
- 防止序列化倔既、反序列化破壞。
延伸: Spring是如何保證單例鹏氧?如何保證安全渤涌?如何實現(xiàn)問題的? 容器式單例,如何保證線程安全把还,如何保證不被反射以及序列化实蓬。
Spring是容器式單例。
IOC 容器使用 ConcurrentHashMap吊履,同時在操作IOC容器時也會使用synchronized加鎖安皱。
//存儲注冊信息的BeanDefinition
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
...
//注冊的過程中需要線程同步,以保證數(shù)據(jù)的一致性
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
Spring 默認(rèn)是懶加載艇炎?(getBean)酌伊。
Spring 單例能否被反射 序列化破壞取決于自己新建的類(Spring 本身不做控制)。
如何防止反射
首先要理解反射實例化對象也是基于構(gòu)造函數(shù)實現(xiàn)的缀踪,那么如果我們要阻止反射居砖,可以再構(gòu)造函數(shù)中進行判斷燕锥。如果實例不為null,直接返回實例對象,或者直接跑出異常悯蝉。
// 防止反射
private Test(){
if(object != null){
throw new Exception();
}
}
如何防止序列化
在序列化反序列化過程中,會進行一個判斷托慨。判斷類是否有readResolve()方法鼻由,有的話調(diào)用該方法,沒有使用newInstance()厚棵。
所以
private volatile static Test object;
// 防止反序列化
private Object readResolve(){
return object;
}
餓漢式
- 通過static 變量蕉世,或者static 靜態(tài)塊賦值靜態(tài)變量。
- 初始化就加載婆硬。效率高狠轻。
- 內(nèi)存消耗大,不適合大量使用彬犯。
懶漢式
使用時再賦值向楼。
節(jié)省了內(nèi)存空間。
出現(xiàn)了線程不安全問題(兩個線程同時創(chuàng)建對象谐区,造成出現(xiàn)兩個實例情況)湖蜕。
Synchronized關(guān)鍵字給方法加鎖解決。(鎖的粒度比較大宋列,效率不高昭抒,高并發(fā)使用會造成大量阻塞)。
-
減少鎖的粒度炼杖。(在對象為null需要進行初始化時再去加鎖灭返,還是會出現(xiàn)線程安全問題)。
private volatile static Test object; private Test(){ } private Object getInstance(){ if(object == null){ synchronized(Test.class){ // 雙重檢查鎖 if(object == null){ // 這里可能會發(fā)生 指令重排序問題 所以需要用volatile 關(guān)鍵字 object = new Test(); } } } return object; }
不安全問題可以使用雙重檢查鎖解決坤邪。(雙重檢查鎖熙含,會出現(xiàn)指令重排序問題需要用 volatile關(guān)鍵字來保證有序性,不夠優(yōu)雅~)
靜態(tài)內(nèi)部類
利用java語法的特點來實現(xiàn)單例罩扇。Java靜態(tài)屬性婆芦、靜態(tài)塊、靜態(tài)方法會在類初始化時就加載分配空間喂饥。而靜態(tài)內(nèi)部類則是在使用時候才會去加載分配內(nèi)存消约。
- 寫法優(yōu)雅,利用了Java本身語法的特點员帮,性能高或粮,避免了內(nèi)存浪費。
注冊式(枚舉式)
枚舉 官方定義不允許反射創(chuàng)建 捞高。枚舉式單例與餓漢式單例一樣氯材,不適合大量使用渣锦。
容器式單例
將每一個實例都緩存在容器中,使用唯一標(biāo)識獲取氢哮。
ThreadLoacl
單線程下能夠保證單例袋毙。
ThreadLocal底層也是一個map,key為當(dāng)前的線程冗尤。
源碼使用
Spring
-
AbstractFactoryBean
@Override public final T getObject() throws Exception { if (isSingleton()) { return (this.initialized ? this.singletonInstance : getEarlySingletonInstance()); } else { return createInstance(); } }
Mybatis
-
ErrorContext
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<>(); private ErrorContext() { } public static ErrorContext instance() { ErrorContext context = LOCAL.get(); if (context == null) { context = new ErrorContext(); LOCAL.set(context); } return context; }
原型模式(創(chuàng)建型)
拷貝創(chuàng)建對象听盖,不是基于構(gòu)造函數(shù)。
- 類初始化消耗資源較多裂七。
- new對象需要非常繁瑣的過程(數(shù)據(jù)準(zhǔn)備皆看、訪問權(quán)限)。
- 構(gòu)造函數(shù)比較復(fù)雜背零。
- 循環(huán)中生產(chǎn)大量對象腰吟。
淺克隆
淺克隆在做的時候都是將引用地址傳遞過去,并非是值傳遞徙瓶。cloneObjec.setAge(this.getAge());
深克隆
字節(jié)碼(輸入輸出流)毛雇、JSON
建造者模式(創(chuàng)建型)
建造者模式注重于創(chuàng)建過程,不同的創(chuàng)建過程產(chǎn)生不同的結(jié)果倍啥。
通過一個Builder類來實現(xiàn)創(chuàng)建過程禾乘,形成鏈?zhǔn)浇Y(jié)構(gòu)。適用于復(fù)雜的創(chuàng)建過程對象使用虽缕。
Builder 也可以作為一個內(nèi)部類存在始藕。
源碼中:StringBuilder、BeanDefinationBuilder氮趋、SqlSessionFactoryBuilder,
代理模式
靜態(tài)代理
能夠代理具體的對象伍派。
動態(tài)代理
jdk動態(tài)代理
基于接口實現(xiàn),需要傳遞目標(biāo)對象剩胁。
- 代理類需要實現(xiàn)InvocationHandler接口诉植。
- 目標(biāo)類必須實現(xiàn)一個固定的接口。
- 實際是通過method.invoke()調(diào)用目標(biāo)對象方法昵观。
cglib動態(tài)代理
動態(tài)代理本質(zhì)
super.h.invoke(); h 是什么晾腔?
public class Proxy implements Serializable {
protected InvocationHandler h;
protected Proxy(InvocationHandler var1) {
Objects.requireNonNull(var1);
this.h = var1;
}
// ....
public static Object newProxyInstance(ClassLoader var0, Class<?>[] var1, InvocationHandler var2) throws IllegalArgumentException {
Objects.requireNonNull(var2);
try {
return var6.newInstance(var2);
} catch (InstantiationException | IllegalAccessException var8) {
} catch (InvocationTargetException var9) {
} catch (NoSuchMethodException var10) {
}
}
}
jdk動態(tài)代理,通過輸入輸出流啊犬,生成新的代理對象(class類)灼擂,對象類中繼承了Proxy類,對象中所有方法均調(diào)用Proxy類中InvocationHandler屬性的invoke()方法觉至。
適配器模式(結(jié)構(gòu)型)
又叫變壓器模式剔应,他的功能是將一個類的接口變成客戶端所期望的另一種接口,從而使的原本因接口不匹配而無法在一起工作的兩個類能夠一起工作。
類適配器
對象適配器
接口適配器
橋接模式(結(jié)構(gòu)型)
享元模式(結(jié)構(gòu)型)
享元模式主要為了提升程序性能峻贮,通過緩存來避免大量創(chuàng)建重復(fù)的對象席怪。一種緩存池。
享元模式看主要是為了控制資源消耗纤控。
例如:數(shù)據(jù)庫連接池(創(chuàng)建連接需要嘗試去連接數(shù)據(jù)庫挂捻,比較耗時,可以使用享元模式直接緩存)船万,線程池细层。
特點
享元模式一般配合工廠模式(單例),來去使用唬涧。通過工廠對象來獲取、重置共享資源盛撑。
適用場景
常常應(yīng)用于系統(tǒng)底層的開發(fā)碎节,以便解決系統(tǒng)性能問題。
系統(tǒng)有大量相似的對象需要緩沖池的場景抵卫。
- 大量相似對象不代表經(jīng)常new的對象都應(yīng)該使用享元模式緩沖池狮荔。
- 比如訂單,每天都要有很多的訂單介粘,每次都需要重新生成新的訂單殖氏,那么訂單適合應(yīng)用享元模式么?
如果訂單生成有一部分重復(fù)的耗時操作姻采,建議使用享元模式來去避免雅采,后續(xù)訂單如何重置為初始狀態(tài),可以考慮克隆慨亲。
問題點
-
享元模式如何保證每次獲取的都是初始化的對象(被拿走使用后會有一部分的賦值婚瓜、修改操作)。
克隆后使用(一樣消耗內(nèi)存)刑棵?或者使用后重置巴刻?
享元模式還需要重點關(guān)注線程安全問題。
內(nèi)部狀態(tài)
一些內(nèi)部屬性蛉签,不需要隨環(huán)境的變化而變化胡陪。如數(shù)據(jù)庫連接池中連接地址,用戶名碍舍、密碼柠座。
外部狀態(tài)
同樣是屬性,但是會隨著外部變化而變化乒验,如連接池中連接是否在用愚隧,在被哪個線程使用。