設計模式總結乾貨1
1.面向物件的三大特性
1) 封裝 (Encapsulation)
隱藏物件的具體實現細節,通過共有方法暴露物件的功能。內部結構可以自由修改,同時可對成員進行更加精確的控制 (比如在setter方法中加值合法判斷)
2) 繼承 (Inheritance)
使用已經存在的類作為基礎類(父類),在此基礎上建立新類(子類), 子類既可複用父類的功能,也能進行擴充套件,從而實現程式碼複用。 另外,Java不能像C++那樣同時繼承多個父類,只能 樹形的繼承 , 比如:Man -> Human -> Animal,或通過 介面和內部類 實現多繼承。
另外,關於繼承還需注意以下幾點:
1.子類擁有父類非private的屬性與方法
2.構造方法只能呼叫,不能實現,子類預設呼叫父類的無參構造方法,
如果父類沒有無參的構造方法,需要使用super顯式呼叫!
3.慎用繼承,要考慮是否需要從子類向父類進行向上轉型!
3) 多型 (Polymorphism)
定義 :一個類例項的相同方法在不同的情形下有不同的表現形式
分為以下兩種:
編譯時多型 (OverLoading)—— 方法過載
執行時多型 (OverRidding)—— 繼承 + 方法重寫 + 向上轉型 (父類引用指向子類物件)
執行時多型 (動態繫結,new後面什麼型別,動態型別就是什麼型別)
示例如下:
C++程式碼
class Animal() { fun show() { println("動物") }}
class People:Animal() { fun show() { println("人類") }}
//下述程式碼列印結果:人類
Animal animal = new People()
animal.show()
2.類與類間的關係
口訣 : 雞溼衣冠劇組 (繼承,實現,依賴,關聯,聚合,組合)
繼承和實現就不說了,後面四個只是 語意層次 的區別
兩個類的相關程度, 依賴 < 關聯 < 聚合 < 組合
依次的UML類圖示記:
繼承/泛化 (Generalization): 子類
父類

實現 (Realization):實現類
介面

依賴 (Dependency): 不持有引用 ,具體表現:區域性變數,函式引數,
返回值使用
依賴類,比如大佬依賴於遞茶小弟;

關聯 (Association): 持有引用 ,具體表現:成員變數, 箭頭指向被關聯類,可雙向,一對多或多對多:

聚合 (Aggregation):成員變數, 關聯 是處於 同一層次 的,而 聚合 則 是 整體和區域性 層次的,比如: 社團 和 小弟 ,另外即使沒有了社團, 小弟們依舊可以到別的地方搞事情。

組合 (Composition):與聚合類似,程度更加強烈,共生死, 組合類 負責 被組合類 的生命週期,比如: 社團 和 大佬 ,如果沒了社團, 大佬也就就不能存在了。

3.面向物件七大原則
單一職責原則 (Single Responsibility Principle)
每一個類應該專注於做一件事情 。 即:高內聚,低耦合。
開閉原則 (Open Close Principle)
一個物件 對擴充套件開放 , 對修改關閉 。即:對類的改動是通過增加程式碼進行的,而不是修改現有程式碼。
里氏替換原則 (Liskov Substitution Principle)
在任何父類出現的地方都可以用它的子類來替代。
依賴倒置原則 (Dependence Inversion Principle)
要依賴於抽象 , 不要依賴於具體實現 。
介面隔離原則 (Interface Segregation Principle)
應當為客戶端提供 儘可能小的單獨的介面 ,而不是提供大的總的介面。
迪米特原則 (Law Of Demeter)
一個物件應當儘量少地與其他物件之間發生相互作用,使得系統功能模組相對獨立。
組合 / 聚合複用原則 (Composite/Aggregate Reuse Principle)
儘量使用 組合/聚合 的方式,而 不是使用繼承 。
23種設計模式
建立型 (5種):主要用於處理物件的建立,例項化物件:
單例 , 建造者 , 原型 , 工廠方法 , 抽象工廠
結構型 (7種):處理類或物件間的組合
介面卡 , 裝飾者 , 結合 , 橋接 , 外觀 , 享元 , 代理
行為型 (11種):描述類或物件怎樣進行互動和職責分配
策略 , 觀察者 , 迭代器 , 命令 , 備忘錄 , 中介者 , 直譯器 , 訪問者 , 責任鏈 , 狀態 , 模板方法
一. 單例模式(Singleton Pattern)
作用 :保證 類在記憶體中 的 物件唯一性 。
適用場景 :
1.避免建立多個例項浪費資源
2.避免多個例項因多次呼叫而出現錯誤
3.一般寫工具類,執行緒池,快取,資料庫會用到。
套路 (三個要點):
1.不允許在類外new物件 —— 構造方法私有化
2.在類中建立物件 —— 通過new在本類中建立一個例項
3.對外提供獲取該例項的方法 —— 定義公有方法返回建立的例項
餓漢與懶漢的區別
前者在 類裝載時 就例項化,後者只有在 第一次被使用 時才例項化。 (餓漢的優點是避免執行緒同步問題,缺點是即使沒用到這個例項還是會載入) (懶漢的優點是實現了懶載入,但需要解決執行緒安全問題!)
7種單例套路 :
1) 餓漢式 ,沒有實現懶載入~
Java程式碼
public class Singleton() {
private static Singleton instance = new Singleton();
private Singleton(){ }
public static Singleton getInstance() {
return instance;
}
}
//獲取單例物件
Singleton mSingleton = Singleton.getInstance();
2) 懶漢式
雖然達到了懶載入,但是卻存線上程安全問題,比如有兩個執行緒都剛好執行完if(instance == null),接著準備執行instance = new Singleton() 語句,這樣的結果會導致我們 例項化了兩個Singleton物件 ,為了解決執行緒不安全問題,可以對getInstance()方法加鎖。
Java程式碼
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
private static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
3) 懶漢式加鎖版
為getInstance方法加鎖雖然保證了執行緒安全,但是每次執行getInstance() 都需要同步,而例項化物件只需要執行一次就夠了,後面獲取該示例, 應該直接return就好了,方法同步效率太低,一種改進後的寫法是: synchronized (Singleton.class) { instance = new Singleton(); } 但是,這樣寫依然是執行緒不安全的,如果你還是想用懶漢式的話,推薦 雙重檢查鎖定 (DCL,Double Check Lock)。
Java程式碼
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
private static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
4) 懶漢式雙重校驗鎖 (DCL)
程式碼中進行了兩次if檢查,這樣就可以保證執行緒安全,初始化一次後,後面再次訪問時,if檢查,直接return 例項化物件。volatile是1.5後引入的,volatile關鍵字會遮蔽Java虛擬機器所做的一些程式碼優化,會導
致系統執行效率降低,而更好的寫法是使用靜態內部類來實現單例!
Java程式碼
public class Singleton{
private static volatile Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) instance = new Singleton();
}
}
return instance;
}
}
5) 靜態內部類實現單例 (推薦)
和餓漢式類似,都是通過類載入機制來保證初始化例項的時候只有一個執行緒,從而避免執行緒安全問題,餓漢式的Singleton類被載入時,就會例項化,而靜態內部類這種,當Singleton類被載入時,不會立即例項化,呼叫getInstance() 方法才會裝載SingletonHolder類,從而完成Singleton的例項化。
Java程式碼
public class Singleton {
private Singleton() { }
private static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton()
}
}
6) 列舉實現單例
INSTANCE即為SingletonEnum型別的引用,得到它就可以呼叫
列舉中的方法。既避免了執行緒安全問題,還能防止反序列化
重新建立新的物件,但是失去了類的一些特性,而且沒有延時載入。
Java程式碼
public enum SingletonEnum {
INSTANCE;
private Singleton instance;
SingletonEnum() {
instance = new Singleton();
}
public Singleton getInstance() {
return instance;
}
}
//呼叫方式
SingletonEnum.INSTANCE.method();
7) 容器實現單例
將多種單例型別注入到一個統一的管理類中,在使用時根據key獲取物件
對應型別的物件。這種方式使得我們可以管理多種型別的單例,並且在使
用時可以通過統一的介面進行獲取操作,降低了使用者的使用成本,也對用
戶隱藏了具體實現,降低了耦合度。
Java程式碼
public class SingletonManager {
private static Map<String,Object> objMap = new HashMap<String,Object>();
private Singleton(){ }
public static void registerService(String key,Object instance) {
if(!objMap.containsKey(key)) {
objMap.put(key,instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}
二. 建造者模式(Builder Pattern)
將 複雜物件的構建與表示分離 開來,使得 同樣的構建過程 可以 建立不同的表示 ,缺點是可能產生 多餘的建立者與構建過程物件 ,消耗記憶體, 不適用於內部建造順序不穩定,變化複雜的物件 ,可能導致需要 建立很多 具體的建造者來實現這些變化。
例子:玩遊戲建立角色時的自定義,不同的搭配生成不同的角色。
四個角色與UML類圖


示例程式碼 :
Java程式碼
//產品類
class Character {
private String sex;
private String face;
private String clothes;
void setSex(String sex) { this.sex = sex;}
void setFace(String face) { this.face = face; }
void setClothes(String clothes) { this.clothes = clothes;}
String showMsg() { return "你建立了一個穿著 " + clothes + " 一副 " + face + " 的" + sex + "ヾ(≧▽≦*)o 戳菊狂笑~"; }
}
//抽象Builder介面
interface Builder {
void setSex(String sex);
void setFace(String face);
void setClothes(String clothes);
Character build();
}
//Builder介面實現類
class ConcreteBuilder implements Builder {
private Character mCharacter = new Character();
@Override public void setSex(String sex) { mCharacter.setSex(sex); }
@Override public void setFace(String face) { mCharacter.setFace(face); }
@Override public void setClothes(String clothes) { mCharacter.setClothes(clothes); }
@Override public Character build() {return mCharacter;}
}
//裝配過程類
class Director {
private Builder mBuilder = null;
Director(Builder builder) { this.mBuilder = builder; }
Character createCharacter(String sex, String face, String clothes) {
this.mBuilder.setSex(sex);
this.mBuilder.setFace(face);
this.mBuilder.setClothes(clothes);
return mBuilder.build();
}
}
//客戶端呼叫類
public class Game {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
Character character = director.createCharacter("基佬","硬漢臉","死庫水");
System.out.println(character.showMsg());
}
}
輸出結果 :

三. 原型模式(Prototype Pattern)
下面兩種場景可以考慮使用原型模式:
1.當初始化類物件需要消耗非常多資源,或者說要進行繁瑣
的資料準備或者許可權,如果想 簡化建立 ,可以使用原型模式。
2.一個物件提供給其他物件訪問,而各個呼叫者可能都需要
修改物件的值,可以考慮使用原型模式克隆多個物件供呼叫者
使用( 保護性拷貝 )
三個角色與UML類圖


Java中 == 與equals的區別
== , 基本資料型別 (int,long等),比較 儲存的值 是否相等
對比的是 引用型別 ,比較的是 所指物件地址 是否相等
equals ,不能用於比較基本資料型別,如果**沒對equals()方法進行重寫 ,比較的是 指向的物件地址 ,如果想要比較物件內容, 需要自行重寫**方法,做相應的判斷!!!!String調equals是可以判斷內容是否一樣,是因為對equals()方法進行了重寫,具體可參見原始碼!
克隆需要滿足的三個條件
1. x.clone()!=x ,即 不是同一物件
2. x.clone().getClass == x.getClass() ,即 物件型別一致
3.如果物件obj的equals()方法定義恰當的話,那麼
obj.clone().equals(obj) == true 應當是成立的。(推薦,不強制)
Java中如何使用
Prototype原型類(想被克隆的類) 實現Cloneable介面 , 重寫clone() 方法。
Java程式碼
ConcretePrototype cp1 = new ConcretePrototype();
ConcretePrototype cp2 = (ConcretePrototype)cp1.clone();
需注意 :
1.執行克隆方法, 不會呼叫構造方法
2.克隆會生成的新的 物件成員,但指向的卻是同一個記憶體地址 !
3.克隆前後 資料型別一致 !
4.克隆的時候,類中 基本資料型別 的屬性會 新建 ,但是 引用型別 的
只會生成個新的引用變數 ,引用變數的地址依舊指向同一個記憶體地址!
實現深拷貝的兩種套路 :
這種 只新建基本型別資料,不新建引用型別資料 ,稱為 淺拷貝 ,如果連引用型別資料也新建的話,則稱為 深拷貝 。
兩個套路:
1. 引用型別也實現Cloneable介面 ,如果屬性的型別也是物件,那麼需要一直遞迴的克隆下去
2. 序列化 ,屬性的型別是引用型別的話,需要實現 Serializable介面 ,然後自己寫個方法來在裡面**完成物件轉二進位制流與二進位制流轉物件**的方法,然後返回克隆後的物件!
具體程式碼見: ofollow,noindex">3.Prototype Pattern
四.工廠方法模式(Factory Method Pattern)
關於三種工廠模式,其實理解起來非常簡單,只是把物件的建立放到一個特定的類中,相比起我們直接new物件,這種套路會寫多幾個類,但是卻擁有更好的擴充套件性,而且當建立的物件發生改變,可以減少一定的修改量。(想想你在專案中有一個類在多處都new了,現在需要對這個類的構造方法,或者相關引數做些修改,你需要找到每個new這個類的地方進行修改,而如果你把工作都丟給一個工廠類,你可能只需要修改這個類) 另外,簡介下這幾種工廠模式的區別:
簡單工廠模式 :最簡單的直接把new物件丟到一個工廠類中;
工廠方法模式 :對工廠類進行抽象,實現具體工廠類以建立不同物件;
抽象工廠模式 :當工廠需要建立多種相互關聯或依賴的物件,有兩個名
產品等級結構 與 產品族 ,具體是什麼的自己看~
簡單工廠模式的三個角色 :

程式碼示例 :
Java程式碼
abstract class Tea {
public abstract void 加奶茶();
public abstract void 加料();
}
class YeGuoTea extends Tea{
@Override public void 加奶茶() { System.out.println("加了一把奶茶");}
@Override public void 加料() {System.out.println("加了一把椰果");}
}
class ZhenZhuTea extends Tea{
@Override public void 加奶茶() { System.out.println("加了一把奶茶");}
@Override public void 加料() {System.out.println("加了一把珍珠");}
}
public class Me {
public static Tea makeTea(int type) {
System.out.println("==============");
Tea tea = type == 0 ? new ZhenZhuTea() : new YeGuoTea();
tea.加奶茶();
tea.加料();
return tea;
}
}
public class Store {
public static void main(String[] args) {
for (int i = 0;i < 3;i++) {
Tea tea = Me.makeTea(buyTea()); //小豬製作奶茶
}
}
/* 模擬使用者下單,0代表要珍珠奶茶,1代表要椰果奶茶 */
private static int buyTea() {
return new Random().nextInt(2);
}
}
輸出結果 :

工廠方法模式(靜態工廠) :
其實就是在簡單工廠模式基礎上,把工廠建立不同產品的 內部邏輯 抽取出來,生成一個 抽象工廠 ,再建立具體工廠類,生產不同的產品。
UML類圖

程式碼示例 :
Java程式碼
//工廠介面/抽象類
abstract class MakeTea {
abstract Tea 小豬帶特效的奶茶製作工藝();
}
//工廠實現類1
class ZhenZhuMakeTea extends MakeTea {
@Override
Tea 小豬帶特效的奶茶製作工藝() {
System.out.println("====== 珍珠小弟炮製港式珍珠奶茶 ======");
Tea tea = new ZhenZhuTea();
tea.加奶();
tea.加茶();
tea.加料();
tea.打包();
return tea;
}
}
//工廠實現類2
class YeGuoMakeTea extends MakeTea {
@Override
Tea 小豬帶特效的奶茶製作工藝() {
System.out.println("====== 椰果小弟炮製日式椰果奶茶 ======");
Tea tea = new YeGuoTea();
tea.加奶();
tea.加茶();
tea.加料();
tea.打包();
return tea;
}
}
//客戶端呼叫
public class StoreS {
public static void main(String[] args) {
//初始化兩個小弟
ZhenZhuMakeTea zhenzhu = new ZhenZhuMakeTea();
YeGuoMakeTea yeguo = new YeGuoMakeTea();
for (int i = 0;i < 3;i++) {
Tea tea = buyTea() == 0 ? zhenzhu.小豬帶特效的奶茶製作工藝()
: yeguo.小豬帶特效的奶茶製作工藝();
}
}
/* 模擬使用者下單,0代表要珍珠奶茶,1代表要椰果奶茶 */
private static int buyTea() {
return new Random().nextInt(2);
}
}
輸出結果 :
