sping原始碼系列---【2-七大設計原則和23種設計模式】
1. OOA、OOD、OOP、OOPL的涵義。
-
OOA(全稱:
Object Oriented Analysis
):面向物件分析。 -
OOD(全稱:
Object Oriented Design
):面向物件設計。 -
OOP(全稱:
Object Oriented Programming
):面向物件程式設計。 -
OOPL(全稱:
Object Oriented Programming Language
):面向物件程式語言。
2.面向物件的七大設計原則。
這 7 種設計原則是軟體設計模式必須儘量遵循的原則,各種原則要求的側重點不同。其中,【開閉原
則】是總綱,它告訴我們要【對擴充套件開放,對修改關閉】;【⾥⽒替換原則】告訴我們【不要破壞繼承
體系】;【依賴倒置原則】告訴我們要【⾯向接⼝程式設計】;【單⼀職責原則】告訴我們實現【類】要
【職責單⼀】,也就是如何定義⼀個類,如何去實現類的封裝;【接⼝隔離原則】告訴我們在設計【接
⼝】的時候要【精簡單⼀】;【迪⽶特法則】告訴我們要【降低耦合度】;【合成復⽤原則】告訴我們
要【優先使⽤組合或者聚合關係復⽤,少⽤繼承關係復⽤】。
設計原則名稱 | 定義 | 使用頻率 |
---|---|---|
單⼀職責原則(Single Responsibility Principle, SRP) | ⼀個類只負責⼀個功能領域中的相應職責 | ★★★★☆ |
開閉原則(Open-Closed Principle, OCP) | 軟體實體應對擴充套件開放,⽽對修改關閉 |
★★★★★ |
⾥⽒代換原則(Liskov Substitution Principle, LSP) | 所有引⽤基類物件的地⽅能夠透明地使⽤其⼦類的物件 | ★★★★★ |
依賴倒轉原則(Dependence Inversion Principle, DIP) | 抽象不應該依賴於細節,細節應該依賴於抽象 | ★★★★★ |
接⼝隔離原則(Interface Segregation Principle, ISP) | 使⽤多個專⻔的接⼝,⽽不使⽤單⼀的 總接⼝ | ★★☆☆☆ |
合成復⽤原則(Composite Reuse Principle, CRP) | 儘量使⽤物件組合,⽽不是繼承來達到復⽤的⽬的 | ★★★★☆ |
迪⽶特法則(Law of Demeter, LoD) | ⼀個軟體實體應當儘可能少地與其他實體發⽣相互作⽤ | ★★★☆☆ |
3.23種設計模式。
什麼是設計模式?
-
設計模式(Design pattern)代表了最佳的實踐,通常被有經驗的⾯向物件的軟體開發⼈員所採⽤。
-
設計模式是軟體開發⼈員在軟體開發過程中⾯臨的⼀般問題的解決⽅案。這些解決⽅案是眾多軟體開發⼈員經過相當⻓的⼀段時間的試驗和錯誤總結出來的。
-
設計模式是⼀套被反覆使⽤、多數⼈知曉的、經過分類編⽬的、程式碼設計經驗的總結。使⽤設計模式是為了可重⽤程式碼、讓程式碼更容易被他⼈理解、保證程式碼可靠性。
-
設計模式不是⼀種⽅法和技術,⽽是⼀種思想。
-
設計模式和具體的語⾔⽆關,學習設計模式就是要建⽴⾯向物件的思想,儘可能的⾯向接⼝程式設計,低耦合,⾼內聚,使設計的程式可復⽤。
-
學習設計模式能夠促進對⾯向物件思想的理解,反之亦然。它們相輔相成。
設計模式的分類
總體來說,設計模式按照功能分為三類23種:
-
建立型(5種) : ⼯⼚模式、抽象⼯⼚模式、單例模式(重點)、原型模式、構建者模式 。
-
結構型(7種): 介面卡模式、裝飾模式、代理模式(重點) 、外觀模式、橋接模式、組合模式、享元模式
-
⾏為型(11種): 模板⽅法模式、策略模式 、觀察者模式、中介者模式、狀態模式、 責任鏈模式 、命令模式、迭代器模式、訪問者模式、直譯器模式、備忘錄模式。
4.單例模式中的三種保證執行緒安全的寫法。
雙重檢查鎖(DCL)
public class DoubleCheckLockSingleton {
private static volatile DoubleCheckLockSingleton instance;
private DoubleCheckLockSingleton() {
if(instance != null){
// gun
}
public static DoubleCheckLockSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckLockSingleton.class) {
if (instance == null) {
instance = new DoubleCheckLockSingleton();
}
}
}
return instance;
}
public void tellEveryone() {
System.out.println("This is a DoubleCheckLockSingleton " +this.hashCode());
}
private Object readResolve() {
}
}
注意:
volatile關鍵字在此處起了什麼作⽤?
為何要執⾏兩次 instance == null 判斷?
靜態內部類
public class StaticInnerHolderSingleton {
// 靜態內部類
private static class SingletonHolder {
private static final StaticInnerHolderSingleton INSTANCE =
new StaticInnerHolderSingleton();
}
private StaticInnerHolderSingleton() {}
public static StaticInnerHolderSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
public void tellEveryone() {
System.out.println("This is a StaticInnerHolderSingleton" +this.hashCode());
}
}
這種⽅式是通過什麼機制保證執行緒安全性與延遲載入的?(注意,這是Java單例的兩⼤要點,必須
保證)
列舉
public enum EnumSingleton {
INSTANCE;
public void tellEveryone() {
System.out.println("This is an EnumSingleton " + this.hashCode());
}
}
Java列舉的本質是什麼?
這種⽅式⼜是通過什麼機制保證執行緒安全性與延遲載入的?
5.寫出兩種攻擊單例模式的方式的名稱(如何破壞一個單例?)
反射攻擊
public class SingletonAttack {
public static void main(String[] args) throws Exception {
reflectionAttack();
}
public static void reflectionAttack() throws Exception {
//通過反射,獲取單例類的私有構造器
Constructor constructor =
DoubleCheckLockSingleton.class.getDeclaredConstructor();
//設定私有成員的暴⼒破解
constructor.setAccessible(true);
// 通過反射去建立單例類的多個不同的例項
DoubleCheckLockSingleton s1 = (DoubleCheckLockSingleton)constructor.newInstance();
// 通過反射去建立單例類的多個不同的例項
DoubleCheckLockSingleton s2 = (DoubleCheckLockSingleton)constructor.newInstance();
s1.tellEveryone();
s2.tellEveryone();
System.out.println(s1 == s2);
}
}
執⾏結果如下:
This is a DoubleCheckLockSingleton 1368884364
This is a DoubleCheckLockSingleton 401625763
false
這種⽅法⾮常簡單暴⼒,通過反射侵⼊單例類的私有構造⽅法並強制執⾏,使之產⽣多個不同的例項,
這樣單例就被破壞了。
序列化攻擊
這種攻擊⽅式只對實現了Serializable接⼝的單例有效,但偏偏有些單例就是必須序列化的。現在假設
DoubleCheckLockSingleton類已經實現了該接⼝,上程式碼:
public class SingletonAttack {
public static void main(String[] args) throws Exception {
serializationAttack();
}
public static void serializationAttack() throws Exception {
// 物件序列化流去對物件進⾏操作
ObjectOutputStream outputStream = new ObjectOutputStream(new
FileOutputStream("serFile"));
//通過單例程式碼獲取⼀個物件
DoubleCheckLockSingleton s1 = DoubleCheckLockSingleton.getInstance();
//將單例物件,通過序列化流,序列化到⽂件中
outputStream.writeObject(s1);
// 通過序列化流,將⽂件中序列化的物件資訊讀取到記憶體中
ObjectInputStream inputStream = new ObjectInputStream(new
FileInputStream(new File("serFile")));
//通過序列化流,去建立物件
DoubleCheckLockSingleton s2 = (DoubleCheckLockSingleton)inputStream.readObject();
s1.tellEveryone();
s2.tellEveryone();
System.out.println(s1 == s2);
}
}
執⾏結果如下:
This is a DoubleCheckLockSingleton 777874839
This is a DoubleCheckLockSingleton 254413710
false
為什麼會發⽣這種事?⻓話短說,在 ObjectInputStream.readObject() ⽅法執⾏時,其內部⽅法 readOrdinaryObject ()中有這樣⼀句話:
//其中desc是類描述符
obj = desc.isInstantiable() ? desc.newInstance() : null;
也就是說,如果⼀個實現了Serializable/Externalizable接⼝的類可以在運⾏時例項化,那麼就調⽤newInstance()⽅法,使⽤其預設構造⽅法反射建立新的物件例項,⾃然也就破壞了單例性。要防禦序列化攻擊,就得將instance宣告為transient,並且在單例中加⼊以下語句:
private Object readResolve() {
return instance;
}
這是因為在上述readOrdinaryObject()⽅法中,會通過衛語句 desc.hasReadResolveMethod() 檢查類
中是否存在名為readResolve()的⽅法,如果有,就執⾏ desc.invokeReadResolve(obj) 調⽤該⽅
法。readResolve()會⽤⾃定義的反序列化邏輯覆蓋預設實現,因此強制它返回instance本身,就可以防
⽌產⽣新的例項。
6.volatile可以解決的問題
-
防止重排序
-
實現可見性
-
保證原子性