目錄
爲什麼需要設計模式
設計模式是軟件設計中常見問題的通用可重用的解決方案,與語言無關。通過引入設計模式,可以更好的提高代碼複用性、靈活性、擴展性。
程序設計原則
程序設計也需要遵循很多原則,開閉原則就是說對擴展開放,對修改關閉。里氏代換原則,任何基類可以出現的地方,子類一定可以出現。依賴倒轉原則、接口隔離原則、迪米特法則、合成複用原則。
設計模式的分類
創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行爲型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。
單例模式
- 概念
- 要點
- 優缺點
- 適用場景
- 單例模式要素
- 實例
概念:單例模式是一種軟件設計模式,使用單例模式可以確保系統中一個類只有一個實例,即一個類只有一個對象實例(借鑑一下百度百科解釋)
要點:
① 使用單例模式的某個類只能有一個實例(單例,單例肯定有且僅有一個實例對象)
② 該類必須自己創建實例對象(因爲構造方法私有化,不能new對象,只能自己創建)
③ 必須自行的向整個系統提供創建的這個實例對象(提供一個static方法向外部系統提供這個對象的實例)
優缺點:
優點① 提供唯一實例的訪問控制 (實例控制)
② 避免了頻繁的創建和銷燬對象,提供系統性能,同時只有一個對象,避免了內存空間的浪費,節約系統資源;
③ 類控制實例化過程,所以可以比較靈活的更改實例過程(靈活性)
缺點① 沒有抽象層,不易於擴展
② 單例類職責過多,違背"單一職責原則"
③ 濫用單例會造成系統崩潰
適用場景: (對象只需要實例化一次的時候)
單例模式只允許創建一個對象,因此節省內存,加快對象訪問速度,因此對象需要被公用的場合適合使用,如多個模塊使用同一個數據源連接對象等等。如:
1.需要頻繁實例化然後銷燬的對象。
2.創建對象時耗時過多或者耗資源過多,但又經常用到的對象。
3.有狀態的工具類對象。
4.頻繁訪問數據庫或文件的對象。
以下都是單例模式的經典使用場景:
1.資源共享的情況下,避免由於資源操作時導致的性能或損耗等。如上述中的日誌文件,應用配置。
2.控制資源的情況下,方便資源之間的互相通信。如線程池等。
應用場景舉例:
1.外部資源:每臺計算機有若干個打印機,但只能有一個PrinterSpooler,以避免兩個打印作業同時輸出到打印機。內部資源:大多數軟件都有一個(或多個)屬性文件存放系統配置,這樣的系統應該有一個對象管理這些屬性文件
2. Windows的Task Manager(任務管理器)就是很典型的單例模式(這個很熟悉吧),想想看,是不是呢,你能打開兩個windows task manager嗎? 不信你自己試試看哦~
3. windows的Recycle Bin(回收站)也是典型的單例應用。在整個系統運行過程中,回收站一直維護着僅有的一個實例。
4. 網站的計數器,一般也是採用單例模式實現,否則難以同步。
5. 應用程序的日誌應用,一般都何用單例模式實現,這一般是由於共享的日誌文件一直處於打開狀態,因爲只能有一個實例去操作,否則內容不好追加。
6. Web應用的配置對象的讀取,一般也應用單例模式,這個是由於配置文件是共享的資源。
7. 數據庫連接池的設計一般也是採用單例模式,因爲數據庫連接是一種數據庫資源。數據庫軟件系統中使用數據庫連接池,主要是節省打開或者關閉數據庫連接所引起的效率損耗,這種效率上的損耗還是非常昂貴的,因爲何用單例模式來維護,就可以大大降低這種損耗。
8. 多線程的線程池的設計一般也是採用單例模式,這是由於線程池要方便對池中的線程進行控制。
9. 操作系統的文件系統,也是大的單例模式實現的具體例子,一個操作系統只能有一個文件系統。
單例模式要素:
a.私有構造方法
b.私有靜態引用指向自己實例
c.以自己實例爲返回值的公有靜態方法
實例1:
/**
* 餓漢單例模式
*/
public class HungrySingleton {
//定義私有的靜態實例對象,只能通過當前類提供的方法獲取
private static final HungrySingleton instance = new HungrySingleton();
//私有的構造方法,禁止被實例化和繼承
private HungrySingleton() {
}
//提供一個靜態方法來獲取當前實例對象
public static HungrySingleton getInstance() {
return instance;
}
}
優點:解決了多線程訪問設計到的內存可見性,達到線程安全的目的;
缺點:涉及到加載問題,如果系統中存在大量懶漢單例模式,那麼可能很多類在被實例化之前就已經初始化了,從而造成資源浪費,降低系統性能。
實例2:
/**
* 懶漢模式又叫延遲模式
*/
public class LazySingleton {
//定義類靜態屬性
private static LazySingleton instance;
//私有化構造方法
private LazySingleton() {
}
//向外部提供一個獲取單例的接口
public LazySingleton getInstance() {
if (instance == null) {
return new LazySingleton();
}
return instance;
}
}
優點:保證了對象的單一實例,節省內存空間
缺點:存在線程安全問題,線程不安全
實例3 (V-C-L模式)
/**
* 考慮多線程併發解決的有一種安全單例模式
* 設計技術:volatile關鍵字
* synchronized同步鎖
* 雙重檢查
* 建議:儘量在適用synchronized時候,避免直接鎖整個方法,如果這樣做的話,將導致額外的性能開銷;
*/
public class SafeSingleton {
//1、定義一個靜態屬性
private volatile static SafeSingleton instance;
//2、構造方法私有化
private SafeSingleton() {
}
//對外部開放唯一實例接口
public SafeSingleton getInstance() {
if (instance == null) { // 第1次檢查
synchronized (SafeSingleton.class) { //加鎖
if (instance == null) { // 第2次檢查
return new SafeSingleton();
}
} //釋放鎖
}
return instance;
}
}
優點:既實現了單一實例原則也保障了線程安全問題,同時也儘量的避免了不必要的性能開銷,加上volatile關鍵字避免了指令重排序造成的不良影響。
實例4:holder模式
public class HolderDemo {
//保證:唯一性,線程安全;性能好;懶加載
public HolderDemo() {
}
private static class Holder {
private static HolderDemo instanse = new HolderDemo();
}
private static HolderDemo getInstance() {
return Holder.instanse;
}
}
實例5:枚舉模式
public class SingletonDemo {
private enum EnumHolder {
//可以理解爲常量,類加載的時候被執行,且被執行一次,所屬類型爲所在類【SingletonDemo】
INSTANSE;
private SingletonDemo instance;
EnumHolder() {
this.instance = new SingletonDemo();
}
private SingletonDemo getInstance() {
return instance;
}
}
private static SingletonDemo getInstance() {
return EnumHolder.INSTANSE.instance;
}
}
工廠模式
- 簡單工廠模式(靜態工廠)
- 工廠模式
- 抽象工廠模式
簡單工廠模式
目的:定義創建類的接口。
組成部分:
1)工廠角色 ---> 主要是用於生產實例對象工廠類
2) 抽象產品 ---> 一般是一個接口或者抽象類,用於被具體產品的實現或者繼承
3) 具體產品 ---> 具體的產品,相對於我們自己new的具體實例對象
代碼展示:
工廠角色部分代碼
/** * 這是電腦的抽象工廠 */ public class ComputerFactory { public Computer production(Class clazz) { Computer computer = null; try { //此處通過類加載器來實現簡單的類型獲取 computer = (Computer) Class.forName(clazz.getName()).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return computer; } }
抽象產品類代碼展示
/** * 一個簡單的電腦產品抽象類 */ public abstract class Computer { public abstract void produceName(); }
具體產品代碼展示
public class DellComputer extends Computer{ public void produceName() { System.out.println("生產戴爾筆記本電腦......"); } }
public class ThinkPadComputer extends Computer { public void produceName() { System.out.println("生產聯想筆記本電腦......"); } }
測試類
public class SimpleTest { public static void main(String[] args) { // 獲取電腦工廠 ComputerFactory computerFactory = new ComputerFactory(); // 生產戴爾筆記本電腦對象 Computer dellComputer = computerFactory.production(DellComputer.class); dellComputer.produceName(); // 獲得聯繫筆記本電腦對象 Computer thinkPadComputer = computerFactory.production(ThinkPadComputer.class); thinkPadComputer.produceName(); } }
優點:工程類有必要的判斷,可以實現對象的實例;
確定:只能適用於少量的對象實例,並且額不易於擴展,違背了開閉原則;
PS:何爲開閉原則,是指面向對象設計的六大基本原則之一,即:一個軟件實體應當遵循對外開放,對內代碼修改關閉;就是說軟件實體儘量在不許改源代碼的前提下實現在程序的擴展;
工廠模式
實質:定義創建類的接口,具體的實例交給其子類去覺得實例化那個類,使類的實例化得到了延遲.
組成部分:
1) 抽象工廠角色 ---> 工廠的核心,定義做什麼的接口或者抽象類,具體怎麼做,就是其子類實現工廠決定
2) 具體工廠角色 ---> 實現抽象工廠,定義怎麼做
3) 抽象產品角色 ---> 具體實體產品的接口或抽象類
4) 具體產品角色 ---> 實現抽象角色
代碼展示:
抽象工廠部分
/** * 這是電腦的抽象工廠 */ public interface ComputerFactory { Computer production(); }
具體工廠
/** * 戴爾廠商實現抽象工廠接口去生產戴爾筆記本電腦 */ public class DellFactory implements ComputerFactory { public Computer production() { return new DellComputer(); } }
/** * 聯想廠商實現抽象工廠接口用於生產聯想筆記本電腦 */ public class ThinkPadFactory implements ComputerFactory { public Computer production() { return new ThinkPadComputer(); } }
抽象產品接口/具體產品接口 (同上簡單工廠)
測試代碼類
public static void main(String[] args) { // 獲取戴爾廠商 ComputerFactory dellFactory = new DellFactory(); // 生產戴爾筆記本電腦 Computer dellComputer = dellFactory.production(); dellComputer.produceName(); // 獲得聯繫廠商 ComputerFactory thinkPadFactory = new ThinkPadFactory(); // 生產聯想筆記本電腦 Computer thinkPadComputer = thinkPadFactory.production(); thinkPadComputer.produceName(); }
優點:向客戶隱藏了對象實例的這一細節,具體的實例對象由工廠來創建,客戶只需要
創建對應的工廠即可,即實現了系統良好的可擴展性也滿足"開閉原則";
缺定:當我們需要新的產品的時候,那麼就需要重新新建產品工廠並且實現抽象接口,如果
系統中需要非常多的不同類別的產品,那麼我們就需要新建很多工廠,這麼的話無形間增加
工作量並且也增大了維修成本,同時更多的類需要加載和編譯,爲系統帶來了額外的開銷。
抽象工廠模式
概念:
向客戶端提供一個接口,在客戶端不必指明特定產品類型的前提下能夠創建多個產品族的產品對象.
PS:產品族是 同類型的產品對象的集合。(比如商務車和跑車就屬於不同產品族羣,)
商務車族羣可以有奔馳、大衆;跑車族羣也可以有大衆,法拉第;
組成部分:
1) 系統中有多個產品族,而系統一次只可能消費其中一族產品。
2) 同屬於同一個產品族的產品以其使用。
來看看抽象工廠模式的各個角色(和工廠方法的如出一轍):
1) 抽象工廠角色: 這是工廠方法模式的核心,它與應用程序無關。是具體工廠角色必須實現的接口或者必須繼承的父類。在java中它由抽象類或者接口來實現。
2) 具體工廠角色:它含有和具體業務邏輯有關的代碼。由應用程序調用以創建對應的具體產品的對象。在java中它由具體的類來實現。
3) 抽象產品角色:它是具體產品繼承的父類或者是實現的接口。在java中一般有抽象類或者接口來實現。
4) 具體產品角色:具體工廠角色所創建的對象就是此角色的實例。在java中由具體的類來實現代碼展示:
抽象工廠
/** * 一個抽象工廠接口,主要目的是定義生產各個類型的電腦 * 這個的每個類型就相當於我們抽象工廠模式所說的產品族 * 這裏一個三個產品族. */ public interface AbstractFactory { //生產軍用電腦 MilitaryComputer getMilitary(); //生產民用電腦 CivilComputer getCivil(); //商用電腦 BusinessComputer getBusiness(); }
具體工廠
/** * 聯想品牌的電腦工廠,實現生產各類型的聯想牌子筆記本電腦 */ public class ThinkPadComputerFactory implements AbstractFactory { public MilitaryComputer getMilitary() { return new ThinkPadMilitaryComputer(); } public CivilComputer getCivil() { return new ThinkPadCivilComputer(); } public BusinessComputer getBusiness() { return new ThinkPadBusinessComputer(); } }
/**
* 戴爾品牌的電腦工廠,實現生產各類型的戴爾牌子筆記本電腦
*/
public class DellComputerFactory implements AbstractFactory{
public MilitaryComputer getMilitary() {
return new DellMilitaryComputer();
}
public CivilComputer getCivil() {
return new DellCivilComputer();
}
public BusinessComputer getBusiness() {
return new DellBusinessComputer();
}
}
抽象產品
/** * 定義商用電腦抽象類 */ public abstract class BusinessComputer { public abstract void produceName(); }
/**
* 定義民用電腦抽象類
*/
public abstract class CivilComputer{
public abstract void produceName();
}
/** * 定義軍用電腦抽象類 */ public abstract class MilitaryComputer { public abstract void produceName(); }
具體產品
public class DellBusinessComputer extends BusinessComputer {
public void produceName() {
System.out.println("戴爾商用筆記本電腦");
}
}
public class DellCivilComputer extends CivilComputer { public void produceName() { System.out.println("戴爾民用筆記本電腦"); } }
public class DellMilitaryComputer extends MilitaryComputer {
public void produceName() {
System.out.println("戴爾軍用筆記本電腦");
}
}
public class ThinkPadBusinessComputer extends BusinessComputer {
public void produceName() {
System.out.println("聯想商用筆記本電腦");
}
}
public class ThinkPadCivilComputer extends CivilComputer {
public void produceName() {
System.out.println("聯想民用筆記本電腦");
}
}
public class ThinkPadMilitaryComputer extends MilitaryComputer {
public void produceName() {
System.out.println("聯想軍用筆記本電腦");
}
}
測試類
public class AbstractTest {
public static void main(String[] args) {
// 獲得戴爾工廠
AbstractFactory dellComputerFactory = new DellComputerFactory();
// 獲得戴爾民用筆記本電腦
CivilComputer dellCivilComputer = dellComputerFactory.getCivil();
//獲得戴爾軍用筆記本電腦
MilitaryComputer dellMilitaryComputer = dellComputerFactory.getMilitary();
//獲得戴爾商用筆記本電腦
BusinessComputer dellBusinessComputer = dellComputerFactory.getBusiness();
dellCivilComputer.produceName();
dellMilitaryComputer.produceName();
dellBusinessComputer.produceName();
System.out.println(" ");
//獲取聯想工廠
AbstractFactory thinkPadComputerFactory = new ThinkPadComputerFactory();
//生產聯想民用筆記本電腦實例
CivilComputer thinkPadCivilComputer = thinkPadComputerFactory.getCivil();
//生產聯想軍用筆記本電腦
MilitaryComputer thinkPadMilitaryComputer = thinkPadComputerFactory.getMilitary();
//生產聯想商用筆記本電腦
BusinessComputer thinkPadBusinessComputer = thinkPadComputerFactory.getBusiness();
thinkPadCivilComputer.produceName();
thinkPadMilitaryComputer.produceName();
thinkPadBusinessComputer.produceName();
}
控制檯輸出結果 :戴爾民用筆記本電腦
戴爾軍用筆記本電腦
戴爾商用筆記本電腦
聯想民用筆記本電腦
聯想軍用筆記本電腦
聯想商用筆記本電腦
優缺點
優點:
1)封裝性。每個產品的實現類不是高層模塊要關心的,它要關心的是接口,是抽象,它不關心對象是如何創建出來的,這都由工廠類負責的,只要知道工廠類是誰,我就能創建一個需要的對象,省時省力。
2)產品族內的約束爲非公開狀態。例如生產男女比例的問題上,猜想女媧娘娘肯定有自己的打算,那麼在抽象工廠模式中,這些約束都在工廠內裏面實現的。
缺點:
1) 產品族擴展比較困難,也就說我們如果在增加一個"科研用的電腦"產品族類,那麼我們需要修改
AbstractFactory 以及它的實現類
ThinkPadComputerFactory
DellComputerFactory
同時還需要新增: ScientificComputer 、ThinkPadScientificComputer 、 DellScientificComputer
2) 如果我們僅僅是增加一個產品品牌,比如說,增加一個appleFactory來生產各種類型的筆記本電腦,那麼就非常簡單,只需要新增幾個簡單的類
AppleComputerFactory 、AppleMilitaryComputer 、AppleBussionComputer 、 AppleCivilComputer
AppleBusinessComputer
而不用修改其他代碼;;;特簡單方便,同時也實現代碼的降耦合。
總結來說就是:抽象工廠模式橫向擴展容易,縱向擴展比較麻煩.
橫座標:相當於我們的不同品牌廠商(dell、thinkPad、apple)
縱座標:相當於我們的電腦的不同用途(busines、civil、military)
每個縱座標就是一個具體工廠,橫座標的個數就是工廠生產的產品實例