抽象外觀類的單例化分析與改造

Liuwei-Sunny發表於2012-08-05

       有博友留言問抽象外觀類是否能設計為單例類?”,為了能夠更全面地回答這個問題,並且為大家在進行物件導向系統設計和實現時提供更多思路,加深對外觀模式和單例模式的理解,特寫此文。

       關於外觀模式的基本知識我在此就不介紹了,大家可以參考之前有關外觀模式的幾篇文章。本文所使用的示例程式碼如下(此處採用一種特殊的包含@Stereotype的註釋在原始碼中標註模式角色,目的是在逆向工程生成的UML類圖中能夠自動標註模式角色,該工具已開發完畢):

//@Stereotype Facade:Subsystem
class SubsystemA {
	public void method() {
		System.out.println("SubsystemA");
	}
}

//@Stereotype Facade:Subsystem
class SubsystemB {
	public void method() {
		System.out.println("SubsystemB");
	}
}

//@Stereotype Facade:Subsystem
class SubsystemC {
	public void method() {
		System.out.println("SubsystemC");
	}
}

//@Stereotype Facade:AbstractFacade
abstract class AFacade {
	private static AFacade instance = null;
	protected AFacade() {}
	public abstract void action();
	public static AFacade getInstance() {
		if (instance == null) {
			instance = (AFacade)XMLUtil.getBean();
		}
		return instance;
	}
}

//@Stereotype Facade:ConcreteFacade
class CFacade1 extends AFacade {
	public void action() {
		SubsystemA a = new SubsystemA();
		SubsystemB b = new SubsystemB();
		a.method();
		b.method();
	}
}

//@Stereotype Facade:ConcreteFacade
class CFacade2 extends AFacade {
	public void action() {
		SubsystemA a = new SubsystemA();
		SubsystemC c = new SubsystemC();
		a.method();
		c.method();
	}
}

//Client Class
class Client {
	public static void main(String args[]){
		AFacade af,af1;
		af = AFacade.getInstance();
		af1 = AFacade.getInstance();
		System.out.println(af == af1);
		af.action();
	}
}

       在上述程式碼中,我們將抽象外觀類設計為單例類(此處只能使用懶漢式單例,大家可以參考前面有關單例模式的文章對懶漢式單例進行進一步改進,解決多執行緒併發訪問的問題),在靜態工廠方法getInstance()中並沒有直接例項化抽象外觀類AFacade(抽象類不能使用new關鍵字直接例項化),而是通過程式碼“instance = (AFacade)XMLUtil.getBean();”(這句程式碼是關鍵)來獲取一個其子類(具體外觀類)的物件。在此處,XMLUtil是一個工具類,程式碼如下:

import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;

public class XMLUtil {
	//該方法用於從XML配置檔案中提取具體類類名,並返回一個例項物件
	public static Object getBean() {
		try {
			//建立文件物件
			DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = dFactory.newDocumentBuilder();
			Document doc;							
			doc = builder.parse(new File("config.xml")); 
		
			//獲取包含類名的文字節點
			NodeList nl = doc.getElementsByTagName("className");
            Node classNode=nl.item(0).getFirstChild();
            String cName=classNode.getNodeValue();
            
            //通過類名生成例項物件並將其返回
            Class c=Class.forName(cName);
	  	    Object obj=c.newInstance();
            return obj;
        }   
        catch(Exception e) {
           	e.printStackTrace();
           	return null;
        }
    }
}

        XMLUtil從XML配置檔案(config.xml)中讀取具體外觀類類名字串並反射生成一個外觀物件。我們將具體外觀類類名儲存在XML或properties檔案中,此處使用的是XML格式的配置檔案,程式碼如下(config.xml):

<?xml version="1.0"?>
<config>
	<className>CFacade1</className>
</config>

       編譯並執行程式,輸出結果如下:

true
SubsystemA
SubsystemB

           從輸出結果可以得知,在客戶類Client中,af和af1是兩個相同的物件,客戶類與具體外觀類解耦,沒有在客戶端直接指定具體外觀類。如果需要更換具體外觀類,只需修改配置檔案config.xml,例如:

<?xml version="1.0"?>
<config>
	<className>CFacade2</className>
</config>

       編譯並執行程式,輸出結果如下:

true
SubsystemB
SubsystemC

            通過對抽象外觀類進行單例化改造,既可以確保抽象外觀類是一個單例類,只能建立其子類的唯一物件,還可以保證系統的靈活性和可維護性,更換或者增加新的具體外觀類無須修改原始碼,符合開閉原則。

        在本文中,通過反射生成物件的方式來擴充套件單例類,解決了標準單例模式無法直接通過子類來擴充套件的問題,在需要設計單例類層次結構時可以考慮使用此設計方案,既可確保物件的唯一性又使得系統具有較好的可擴充套件性。

【作者:劉偉  http://blog.csdn.net/lovelion

相關文章