設計模式(Design Patterns) ——可復用面向對象軟體的基礎設計模式(Design pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的,設...
設計模式(Design Patterns)
——可復用面向對象軟體的基礎
設計模式(Design pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使代碼編製真正工程化,設計模式是軟體工程的基石,如同大廈的一塊塊磚石一樣。項目中合理的運用設計模式可以完美的解決很多問題,每種模式在現在中都有相應的原理來與之對應,每一個模式描述了一個在我們周圍不斷重覆發生的問題,以及該問題的核心解決方案,這也是它能被廣泛應用的原因。本章系Java之美[從菜鳥到高手演變]系列之設計模式,我們會以理論與實踐相結合的方式來進行本章的學習,希望廣大程式愛好者,學好設計模式,做一個優秀的軟體工程師!
一、設計模式的分類
總體來說設計模式分為三大類:
創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。
其實還有兩類:併發型模式和線程池模式。
二、設計模式的六大原則
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)
原則是儘量使用合成/聚合的方式,而不是使用繼承。
以上參考 http://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html
三、工廠模式,抽象工廠模式,單例模式
1、工廠模式
實體類的抽象介面
public interface Sender {
public void Send();
}
實體類的實現一
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!");
}
}
工廠實體類
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;
}
}
工廠測試類
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produce("ss");
sender.Send();
}
}
二、抽象工廠
在上面的例子上把工廠類創建一個抽象工廠,在實現工廠時對應相應的實體類。這樣就可以實現擴展的時候不用修改原來的代碼,並對應了閉包原則。
在提供一個介面:
- public interface Provider {
- public Sender produce();
- }
兩個工廠類:
- public class SendMailFactory implements Provider {
- @Override
- public Sender produce(){
- return new MailSender();
- }
- }
- public class SendSmsFactory implements Provider{
- @Override
- public Sender produce() {
- return new SmsSender();
- }
- }
測試類:
public class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
四、單例模式
線程安全的單例模式實現有幾種思路,個人認為第2種方案最優雅:、餓漢式、藉助內部類、普通加鎖解決、雙重檢測,但要註意寫法,如果單體模式繼續擴展為N元單體模式,那就是對象池模式了1、餓漢式單例
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return INSTANCE;
}
}
2、藉助內部類
屬於懶漢式單例,因為Java機制規定,內部類SingletonHolder只有在getInstance()方法第一次調用的時候才會被載入(實現了lazy),而且其載入過程是線程安全的。內部類載入的時候實例化一次instance。
public class Singleton {
private Singleton() { }
private static class SingletonHolder {
private final static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
3、普通加鎖解決
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
雖然解決了線程安全問題,但是每個線程調用getInstance都要加鎖,我們想要只在第一次調用getInstance時加鎖,請看下麵的雙重檢測方案
4、雙重檢測,但要註意寫法
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if(instance == null) {
synchronzied(Singleton.class) {
Singleton temp = instance;
if(temp == null) {
temp = new Singleton();
instance = temp
}
}
}
return instance;
}
}
由於指令重排序問題,所以不可以直接寫成下麵這樣:
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if(instance == null) {
synchronzied(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
但是如果instance實例變數用volatile修飾就可以了,volatile修飾的話就可以確保instance = new Singleton();對應的指令不會重排序,如下的單例代碼也是線程安全的:
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if(instance == null) {
synchronzied(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}