設計模式(Design Patterns)
——可複用面向對象軟件的基礎
設計模式(Design pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使代碼編制真正工程化,設計模式是軟件工程的基石,如同大廈的一塊塊磚石一樣。項目中合理的運用設計模式可以完美的解決很多問題,每種模式在現在中都有相應的原理來與之對應,每一個模式描述了一個在我們周圍不斷重複發生的問題,以及該問題的核心解決方案,這也是它能被廣泛應用的原因。
一、設計模式的分類
總體來說設計模式分爲三大類:
- 創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
- 結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
- 行爲型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。
- 剩餘還有兩類:併發型模式和線程池模式。
二、設計模式的六大原則
1、開閉原則(Open Close Principle)
開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。所以一句話概括就是:爲了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類,後面的具體設計中我們會提到這點。
2、里氏代換原則(Liskov Substitution Principle)
里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承複用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行爲。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。—— From Baidu 百科
3、依賴倒轉原則(Dependence Inversion Principle)
這個是開閉原則的基礎,具體內容:真對接口編程,依賴於抽象而不依賴於具體。
4、接口隔離原則(Interface Segregation Principle)
這個原則的意思是:使用多個隔離的接口,比使用單個接口要好。還是一個降低類之間的耦合度的意思,從這兒我們看出,其實設計模式就是一個軟件的設計思想,從大型軟件架構出發,爲了升級和維護方便。所以上文中多次出現:降低依賴,降低耦合。
5、迪米特法則(最少知道原則)(Demeter Principle)
爲什麼叫最少知道原則,就是說:一個實體應當儘量少的與其他實體之間發生相互作用,使得系統功能模塊相對獨立。
6、合成複用原則(Composite Reuse Principle)
原則是儘量使用合成/聚合的方式,而不是使用繼承。
三、Java的23種設計模式
從這一塊開始,我們詳細介紹Java中23種設計模式的概念,應用場景等情況,並結合他們的特點及設計模式的原則進行分析。
1、工廠方法模式(Factory Method)
工廠方法模式分爲三種:
1.1 普通工廠模式
就是建立一個工廠類,對實現了同一接口的一些類進行實例的創建。首先看下關係圖:
舉例如下:(我們舉一個發送郵件和短信的例子)
1) 創建二者的共同接口:
public interface Sender {
public void Send();
}
2) 創建產品的實現類
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
4) 建立工廠類
public class SendFactory {
public Sender produce(String type) {
if ("mail".equals(type)) {
return new MailSender();
} else if ("sms".equals(type)) {
return new SmsSender();
} else {
System.out.println("請輸入正確的類型!");
return null;
}
}
}
5) 測試
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produce("sms");
sender.Send();
}
}
1.2 多個工廠方法模式
對普通工廠方法模式進行改進,在普通工廠方法模式中,如果傳遞的字符串出錯,則不能正確創建對象,而多個工廠方法模式是提供多個工廠方法,分別創建對象。關係圖:
改動SendFactory類,如下:
public class SendFactory {
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
測試:
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
1.3 靜態工廠方法模式
將上面的多個工廠方法模式裏的方法置爲靜態的,不需要創建實例,直接調用即可。
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
public class FactoryTest {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.Send();
}
}
1.4 工廠模式總結
總體來說,工廠模式適合:凡是出現了大量的產品需要創建,並且具有共同的接口時,可以通過工廠方法模式進行創建。在以上的三種模式中,第一種如果傳入的字符串有誤,不能正確創建對象,第三種相對於第二種,不需要實例化工廠類,所以,大多數情況下,我們會選用第三種——靜態工廠方法模式。
2、抽象工廠模式(Abstract Factory)
工廠方法模式有一個“缺陷”就是,類的創建依賴工廠類,也就是說,如果想要拓展程序,必須對工廠類進行修改,這違背了閉包原則,所以,從設計角度考慮,有一定的問題,如何解決?就用到抽象工廠模式,創建多個工廠類,這樣一旦需要增加新的功能,直接增加新的工廠類就可以了,不需要修改之前的代碼。因爲抽象工廠不太好理解,我們先看看圖,然後就和代碼,就比較容易理解。
2.1 抽象工廠模式實例
1) 產品類需實現的接口
public interface Sender {
public void Send();
}
2) 兩個產品的實現類
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
3) 工廠類需實現的接口
public interface Provider {
public Sender produce();
}
4) 兩個工廠的實現類
public class SendMailFactory implements Provider {
@Override
public Sender produce(){
return new MailSender();
}
}
public class SendSmsFactory implements Provider{
@Override
public Sender produce() {
return new SmsSender();
}
}
5) 測試
public class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
2.1 抽象工廠模式總結
其實這個模式的好處就是,如果你現在想增加一個功能:發及時信息,則只需做一個實現類,實現Sender
接口,同時做一個工廠類,實現Provider
接口,就OK了,無需去改動現成的代碼。這樣做,拓展性較好!
3、單例模式(Singleton)
單例對象(Singleton)是一種常用的設計模式。在Java應用中,單例對象能保證在一個JVM中,該對象只有一個實例存在。這樣的模式有幾個好處:
- 1、某些類創建比較頻繁,對於一些大型的對象,這是一筆很大的系統開銷。
- 2、省去了new操作符,降低了系統內存的使用頻率,減輕GC壓力。
- 3、有些類如交易所的核心交易引擎,控制着交易流程,如果該類可以創建多個的話,系統完全亂了。(比如一個軍隊出現了多個司令員同時指揮,肯定會亂成一團),所以只有使用單例模式,才能保證核心交易服務器獨立控制整個流程。
首先我們寫兩個簡單的單例類:
3.1 餓漢式
public class Singleton {
private Singleton() {//私有的構造方法
}
private static Singleton singleton = new Singleton();//私有的靜態的對象
public static Singleton getInstance(){//公共的靜態的獲取對象的方法
return singleton;
}
}
餓漢式是天生線程安全的。
3.2 懶漢式
public class Singleton {
/* 持有私有靜態實例,防止被引用,此處賦值爲null,目的是實現延遲加載 */
private static Singleton instance = null;
/* 私有構造方法,防止被實例化 */
private Singleton() {
}
/* 靜態工程方法,創建實例 */
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
/* 如果該對象被用於序列化,可以保證對象在序列化前後保持一致 */
public Object readResolve() {
return instance;
}
}
4、建造者模式(Builder)
工廠類模式提供的是創建單個類的模式,而建造者模式則是將各種產品集中起來進行管理,用來創建複合對象,所謂複合對象就是指某個類具有不同的屬性,其實建造者模式就是前面抽象工廠模式和最後的Test結合起來得到的。我們看一下代碼:
還和前面一樣,一個Sender
接口,兩個實現類MailSender
和SmsSender
。
4.1 建造者模式實例
public class Builder {
private List<Sender> list = new ArrayList<Sender>();
public void produceMailSender(int count){
for(int i=0; i<count; i++){
list.add(new MailSender());
}
}
public void produceSmsSender(int count){
for(int i=0; i<count; i++){
list.add(new SmsSender());
}
}
}
測試
public class Test {
public static void main(String[] args) {
Builder builder = new Builder();
builder.produceMailSender(10);
}
}
4.2 建造者模式總結
從這點看出,建造者模式將很多功能集成到一個類裏,這個類可以創造出比較複雜的東西。所以與工程模式的區別就是:工廠模式關注的是創建單個產品,而建造者模式則關注創建符合對象,多個部分。因此,是選擇工廠模式還是建造者模式,依實際情況而定。
5、原型模式(Prototype)
原型模式雖然是創建型的模式,但是與工程模式沒有關係,從名字即可看出,該模式的思想就是將一個對象作爲原型,對其進行復制、克隆,產生一個和原對象類似的新對象。本小結會通過對象的複製,進行講解。在Java中,複製對象是通過clone()
實現的,先創建一個原型類:
public class Prototype implements Cloneable {
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
}
很簡單,一個原型類,只需要實現Cloneable
接口,覆寫clone
方法,此處clone
方法可以改成任意的名稱,因爲Cloneable
接口是個空接口,你可以任意定義實現類的方法名,如cloneA
或者cloneB
,因爲此處的重點是super.clone()
這句話,super.clone()
調用的是Object
的clone()
方法,而在Object
類中,clone()
是native
的,具體怎麼實現,我會在另一篇文章中,關於解讀Java中本地方法的調用,此處不再深究。在這兒,我將結合對象的淺複製和深複製來說一下,首先需要了解對象深、淺複製的概念:
- 淺複製:將一個對象複製後,基本數據類型的變量都會重新創建,而引用類型,指向的還是原對象所指向的。
- 深複製:將一個對象複製後,不論是基本數據類型還有引用類型,都是重新創建的。簡單來說,就是深複製進行了完全徹底的複製,而淺複製不徹底。
此處,寫一個深淺複製的例子:
public class Prototype implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private String string;
private SerializableObject obj;
/* 淺複製 */
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
/* 深複製 */
public Object deepClone() throws IOException, ClassNotFoundException {
/* 寫入當前對象的二進制流 */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
/* 讀出二進制流產生的新對象 */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public SerializableObject getObj() {
return obj;
}
public void setObj(SerializableObject obj) {
this.obj = obj;
}
}
class SerializableObject implements Serializable {
private static final long serialVersionUID = 1L;
}