1. 程式人生 > >Spring實現兩種設計模式:工廠模式和單態模式

Spring實現兩種設計模式:工廠模式和單態模式

工廠模式可將Java 物件的呼叫者從被呼叫者的實現邏輯中分離出來,呼叫者只需關心被呼叫者必須滿足的規則(介面) ,而不必關心例項的具體實現過程。這是面向介面程式設計的優勢,能提高程式的解耦,避免所有的類以硬編碼方式耦合在一起。

        如果所有的類直接耦合,極易形成"骨牌效應",假如B 類呼叫了A 類,一旦A 類需要修改,則B 類也需要修改;假如C 類呼叫了B 類,則C 類也需要修改......依次類推,從而導致整個系統都需要改寫。造成"牽一髮而動全身",而系統重構的代價是相當高的。

        Spring 倡導”面向介面程式設計“,可以避免上述的問題,使設計良好的架構可保證系統重構的工作被封閉在重構的層內,絕不會影響其他層。

        Spring 容器是例項化和管理全部bean 的工廠,Spring 預設將所有的bean 設定成單態模式,無須自己完成單態模式,即對所有相同id 的bean 請求都將返回同一個共享例項。因此,單態模式可大大降低Java 物件在建立和銷燬時的系統開銷。

一. 單態模式的回顧

        單態模式限制了類例項的建立,但採用這種模式設計的類,可以保證僅有一個例項,並可提供訪問該例項的全域性訪問點。J2EE應用的大量元件,都需要保證一個類只有一個例項。比如資料庫引擎訪問點只能有一個。

        更多的時候,為了提高效能,程式應儘量減少Java 物件的建立和銷燬時的開銷。使用單態模式可避免Java 類被多次例項化,讓相同類的全部例項共享同一記憶體區。

        為了防止單態模式的類被多次例項化,應將類的構造器設成私有,這樣就保證了只能通過靜態方法獲得類例項。而該靜態方法則保證每次返回的例項都是同一個,這就需將該類的例項設定成類屬性,由於該屬性需要被靜態方法訪問,因此該屬性應設成靜態屬性。

下面給出單態模式的示例程式碼:

package ppp;
//單態模式測試類
public class SingletonTest {
	//該類的一個普通屬性
	int value;
	//使用靜態屬性儲存該類的一個例項
	private static SingletonTest instance;
	//構造器私有化,避免該類被多次例項化
	private SingletonTest(){
		System.out.println("正在執行構造器...");
	}
	//提供靜態方法返回該類例項 
	public static SingletonTest getInstance(){
		//例項化類例項前,先檢查該例項是否存在
		if(instance == null){
			//如果不存在,則新建一個例項
			instance = new SingletonTest();
		}
		//返回該類的成員變數:該類的例項 
		return instance;	
	}
	//以下提供對普通屬性value的getter和setter方法
    public int getValue(){
    	return value;
    }

    public void setValue(int values){
    	this.value = values;	
    }
    public static void main(String args[]){
    	SingletonTest t1 = SingletonTest.getInstance();
    	SingletonTest t2 = SingletonTest.getInstance();
    	t2.setValue(9);
    	System.out.println(t1 == t2);
    }
}

        從程式最後的列印結果可以看出,該類的兩個例項完全相同。這證明單態模式類的全部例項是同一共享例項。程式裡雖然獲得了類的兩個例項,但實際上只執行一次構造器,因為對於單態模式的類,無論有多少次的建立例項請求,都只執行一次構造器。

二. 工廠模式的回顧

        工廠模式是根據呼叫資料返回某個類的一個例項,此類可以是多個類的某一個類。通常,這些類滿足共同的規則(介面)或父類。呼叫者只關心工廠生產的例項是否滿足某種規範,即實現的某個介面是否可供自己正常呼叫(呼叫者僅僅使用)。該模式給物件之間作出了清晰的角色劃分,降低程式的耦合。

        介面產生的全部例項通常用於實現相同介面,接口裡定義了全部例項共同擁有的方法,這些方法在不同的實現類中實現的方式不同。從而使程式呼叫者無須關心方法的具體實現,降低了系統異構的代價。

下面是工廠模式的示例程式碼:

package ppp;

//Person介面定義
public interface Person {
	public String sayHello(String name);
	public String sayGoodbye(String name);
}

該介面定義了Person規範,規範要求實現該介面的類必須具有以下兩個的方法:能打招呼,能告別。
package ppp;
//American類實現Person介面
public class American implements Person {
	public String sayHello(String name){
		return name+",hello";
	}
	public String sayGoodbye(String name)
	{
		return name+",goodbye";
	}
}

下面是Person類的另一個實現類:Chinese

package ppp;  
//Chinese類實現Person介面
public class Chinese implements Person {
	public String sayHello(String name){
		return name+",您好";
	}
	public String sayGoodbye(String name)
	{
		return name+",下次再見";
	}
}

然後再看Person工廠的程式碼:

package ppp;

public class PersonFactory {
	public Person getPerson(String ethnic)
	{
		if(ethnic.equalsIgnoreCase("chin"))
		{
			return new Chinese();
		}else{
			return new American();		
		}
	}
}

以上是最簡單的工廠模式框架,其主程式如下:
package ppp;

public class FactoryTest {
	public static void main(String[] args){
		//建立PersonFactory例項 ,獲得工廠例項 
		PersonFactory pf = new PersonFactory();
		//定義介面Person例項,面向介面程式設計 
		Person p = null;
		//使用工廠獲得person例項
	    p = pf.getPerson("chin");
	    //下面呼叫Person介面方法
	    System.out.println(p.sayHello("wawa"));
	    System.out.println(p.sayGoodbye("wawa"));
	    //使用工廠獲得Person的另一個例項
	    p = pf.getPerson("ame");
	    //再次呼叫Person介面的方法
	    System.out.println(p.sayHello("wawa"));
	    System.out.println(p.sayGoodbye("wawa"));
	}
}

由此可看出,主程式從Person 介面的具體類中解耦出來,而且程式呼叫者無須關心Person 的例項化過程,主程式僅僅與工廠服務定位結合在一起,可獲得所有工廠能產生的例項。具體類的變化,介面無須發生任何改變,呼叫者程式程式碼部分也無須發生任何改動。
下面是Spring 對這兩種模式的實現。

三. Spring 對單態與工廠模式的實現

        隨著Spring 提供工廠模式的實現,在使用Spring 時,無須自己提供工廠類。因為Spring容器是最大的工廠,而且是個功能超強的工廠。Spring 使用配置檔案管理所有的bean ,其配置檔案中bean 由Spring 工廠負責生成和管理。

下面是關於兩個例項的配置檔案:

<!--下面是xml檔案的檔案頭-->
<?xml version = "1.0" encoding = "gb2312"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
   "http://www.springsource.org/dtd/spring-beans.dtd">
<!--beans是Spring配置檔案的根元素-->
<beans>
<!--定義第一個bean,該bean的id是chinese-->
<bean id = "chinese" class = "ppp.Chinese"/>
<!--定義第二個bean,該bean的id是American-->
<bean id = "american" class = "ppp.American"/>
</beans>

主程式部分如下:
package ppp;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class SpringTest {
	public static void main(String[] args) {
		//例項化Spring容器
		ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
		//定義Person介面例項
		Person p = null;
		//通過Spring上下文獲得Chinese例項
		p = (Person)ctx.getBean("chinese");
		//執行chinese例項的方法
		System.out.println(p.sayHello("wawa"));
		System.out.println(p.sayGoodbye("wawa"));
		
		p = (Person)ctx.getBean("american");
		System.out.println(p.sayHello("wawa"));
		System.out.println(p.sayGoodbye("wawa"));
	}
}

       使用Spring 時,即使沒有工廠類PersonFactory ,程式一樣可以使用工廠模式, Spring完全可以提供所有工廠模式的功能。

       下面對主程式部分進行簡單的修改:   

  
package ppp;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class SpringTest{
	 public static void main(String[] args){
		 //例項化Spring容器
		 ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
		 //定義p1介面的例項p1
		 Person p1 = null;
		 //通過Spring上下文獲得Chinese例項
		 p1 = (Person)ctx.getBean("Chinese");
		 //定義p2介面的例項p2
		 Person p2 = null;
		 p2 = (Person)ctx.getBean("Chinese");
		 System.out.println(p1 == p2);
	 }
}

 程式的執行結果是:true       

表明Spring對接受容器管理的全部的bean,預設採用單態模式管理,建議不要隨意更改bean的行為方式。因為從效能上講,單態的bean比非單態的bean效能更為優秀。

        仔細檢查上面的程式碼就會發現如下的特點;

        (1)除測試部分的主程式外,程式碼並未出現Spring的特定類和介面。

        (2)呼叫者的程式碼,也就是測試用的主程式部分,僅僅面向Person的介面程式設計,而無需知道實現類的具體名稱。同時,通過修改配置檔案來徹底切換底層的具體實現類。

        (3)由於廠無需多個例項,因此工廠應該採用單態模式設計,其中Spring上下文也就是Spring工廠,已被設計成單態。

          Spring工廠模式,不僅提供了建立bean的功能,還提供了對bean的生命週期的管理。最重要的是還以管理bean和bean之間的依賴關係