設計模式之不變模式(Immutable Pattern)分析
不變模式(immutable pattern)</SPAN>
一個類的內部狀態創建后,在整個生命期間都不會發生變化時,就是不變類。這種使用不變類的做法叫做不變模式。
不變模式有兩種形式:一種是弱不變模式,另一種是強不變模式。
弱不變模式:</SPAN>
一個類的實例的狀態是不可變化的,但是這個類的引用的實例具有可能會變化的狀態。這樣的類符合弱不變模式的定義。要實現弱不變模式,一個類必須滿足如下條件:
第一,對象沒有任何方法會修改對象的狀態,當對象的構造函數對對象的狀態初始化之后,對象的狀態便不再改變。
第二,所有的屬性都應當是私有的,以防客戶端對象直接修改任何的內部狀態。
第三,這個對象所引用的對象如果是可變對象的話,必須設法限制外界對這個對象的訪問,以防止對這些對象的修改。如果可能應該盡量在不變對象的內部來初始化。
弱不變模式的缺點是:
一個弱不變對象引用的實例變量可以是可變對象,可能會通過外界修改父對象的狀態,這是一個顯著的缺點。可以在初始化可變對象時,先進行clone。
代碼演示:
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代碼 <A title=復制代碼 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="/misc/goto?guid=4958346284570877790"><IMG alt=復制代碼 src="/images/icon_copy.gif"></A> <A title=收藏這段代碼 onclick="code_favorites_do_favorite(this);return false;" href="/misc/goto?guid=5033824541631065302"><IMG class=star alt=收藏代碼 src="/images/icon_star.png"><IMG style="DISPLAY: none" class=spinner src="/images/spinner.gif"></A></DIV></DIV>
<OL class=dp-j>
- @author Peter Wei
*/ public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
- 弱不變模式
- @author Peter Wei
*/ public class WeakImmutable {
// 屬性私有,滿足條件2 private int state; // 屬性私有,滿足條件2 private User user;
private Integer age;
public WeakImmutable(int state, User user, Integer age) {
this.state = state; this.user = user; this.age = age;
}
public int getState() {
return this.state;
}
public User getUser() {
return this.user;
}
public Integer getAge() {
return this.age;
}
public void setState() {
// 對象沒有任何方法修改對象的狀態,滿足條件1 // do nothing.
}
public static void main(String[] args) {
int state = 0; User u = new User(); Integer age = 100; u.setName("yes"); WeakImmutable weak = new WeakImmutable(state, u, age); System.out.println("原始值:" + weak.getState() + "," + weak.getUser().getName() + "," + weak.getAge()); // 修改引用后 state = 5; // User由于是可變對象引用,所以有影響 u.setName("no"); age = 200; System.out.println("修改引用后:" + weak.getState() + "," + weak.getUser().getName() + "," + weak.getAge());
} }</PRE>結果:可以看到user的名字會改變。
原始值:0,yes,100
修改引用后:0,no,100
我們再引伸一個不可變類的例子:</SPAN>
在時間截止時,我們需要一一檢查隊列成員是不是vip,如果是可以去USA.假設是多線程環境,并且users數組是多線程共享,那么另外的線程通過users去修改users[n],這時就會把users[n]繞過時間檢查而去USA.
<DIV class=dp-highlighter> <DIV class=bar> <DIV class=tools>Java代碼 <A title=復制代碼 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="/misc/goto?guid=4958346284570877790"><IMG alt=復制代碼 src="/images/icon_copy.gif"></A> <A title=收藏這段代碼 onclick="code_favorites_do_favorite(this);return false;" href="/misc/goto?guid=5033824541631065302"><IMG class=star alt=收藏代碼 src="/images/icon_star.png"><IMG style="DISPLAY: none" class=spinner src="/images/spinner.gif"></A></DIV></DIV> <OL class=dp-j>- /**
- * 不變模式之clone
- *
- * @author Peter Wei
- *
- */
- public class WeakImmutableClone {
- public static void main(String[] args) {
- User[] users = new User[3];
- users[0] = new User();
- users[0].setName("peterwei");
- users[1] = new User();
- users[1].setName("Tomssssss");
- users[2] = new User();
- users[2].setName("peterwei88");
- time4Check();
- /*
- * 時間到,我們需要一一檢查隊列成員是不是vip,如果是可以去USA.假設是多線程環境,并且users數組是多線程共享,
- * 那么另外的線程通過users去修改users[n],這時就會把users[n]繞過時間檢查而去USA.
- */
- goUSA(users);
- }
- public static void goUSA(User[] users) {
- // User[] tmp = new User[users.length];
- // System.arraycopy(users, 0, tmp, 0, users.length);
- for (User u : users) {
- if (checkVip(u)) {
- System.out.println("You can go!");
- } else {
- System.out.println("go away!");
- }
- }
- }
- public static boolean checkVip(User user) {
- if (user.getName().startsWith("peterwei")) {
- return true;
- }
- return false;
- }
- public static void time4Check() {
- // 假設時間期限到,要檢查上萬人以上的隊列。
- }
- } </OL></DIV><PRE style="DISPLAY: none" class=java title="設計模式之不變模式(Immutable Pattern)分析" name="code" pre_index="1" source_url="
- 不變模式之clone
- @author Peter Wei
*/ public class WeakImmutableClone {
public static void main(String[] args) {
User[] users = new User[3]; users[0] = new User(); users[0].setName("peterwei"); users[1] = new User(); users[1].setName("Tomssssss"); users[2] = new User(); users[2].setName("peterwei88"); time4Check(); /* * 時間到,我們需要一一檢查隊列成員是不是vip,如果是可以去USA.假設是多線程環境,并且users數組是多線程共享, * 那么另外的線程通過users去修改users[n],這時就會把users[n]繞過時間檢查而去USA. */ goUSA(users);
}
public static void goUSA(User[] users) {
// User[] tmp = new User[users.length]; // System.arraycopy(users, 0, tmp, 0, users.length); for (User u : users) { if (checkVip(u)) { System.out.println("You can go!"); } else { System.out.println("go away!"); } }
}
public static boolean checkVip(User user) {
if (user.getName().startsWith("peterwei")) { return true; } return false;
}
public static void time4Check() {
// 假設時間期限到,要檢查上萬人以上的隊列。
} }</PRE>
解決方法:</SPAN>
在事務處理及數據大批量入庫的多線程環境中,應該也會有類似的問題。所以對于這樣的傳入參數及上例中的不變對象引用可變對象,我們可以將其在相關構造函數及方法中復制為本地變量(數組),及使用它的深度clone,阻止相關數據與外部線程的聯系。
<DIV class=dp-highlighter> <DIV class=bar> <DIV class=tools>Java代碼 <A title=復制代碼 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="/misc/goto?guid=4958346284570877790"><IMG alt=復制代碼 src="/images/icon_copy.gif"></A> <A title=收藏這段代碼 onclick="code_favorites_do_favorite(this);return false;" href="/misc/goto?guid=5033824541631065302"><IMG class=star alt=收藏代碼 src="/images/icon_star.png"><IMG style="DISPLAY: none" class=spinner src="/images/spinner.gif"></A></DIV></DIV> <OL class=dp-j>- public static void goUSA(User[] users) {
- User[] tmp = new User[users.length];
- System.arraycopy(users, 0, tmp, 0, users.length);
- for (User u : tmp) {
- if (checkVip(u)) {
- System.out.println("You can go!");
- } else {
- System.out.println("go away!");
- }
- }
- } </OL></DIV><PRE style="DISPLAY: none" class=java title="設計模式之不變模式(Immutable Pattern)分析" name="code" pre_index="2" source_url="
- String a = "123" ;
- String a1 = "123" ;
- String a2 = "123" ;
- String a3 = "1234" ; </OL></DIV><PRE style="DISPLAY: none" class=java title="設計模式之不變模式(Immutable Pattern)分析" name="code" pre_index="3" source_url="
User[] tmp = new User[users.length];
System.arraycopy(users, 0, tmp, 0, users.length);
for (User u : tmp) {
if (checkVip(u)) {
System.out.println("You can go!");
} else {
System.out.println("go away!");
}
}
}</PRE>
強不變模式:</SPAN>
一個類的實例的狀態不會改變,同時它的子類的實例也具有不可變化的狀態。這樣的類符合強不變模式。要實現強不變模式,一個類必須首先滿足弱不變模式所要求的所有條件,并且還要滿足下面條件之一:
第一,所考慮的類所有的方法都應當是final,這樣這個類的子類不能夠置換掉此類的方法。
第二,這個類本身就是final的,那么這個類就不可能會有子類,從而也就不可能有被子類修改的問題。
不變模式在Java中的應用</SPAN>
如String類
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代碼 <A title=復制代碼 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="/misc/goto?guid=4958346284570877790"><IMG alt=復制代碼 src="/images/icon_copy.gif"></A> <A title=收藏這段代碼 onclick="code_favorites_do_favorite(this);return false;" href="/misc/goto?guid=5033824541631065302"><IMG class=star alt=收藏代碼 src="/images/icon_star.png"><IMG style="DISPLAY: none" class=spinner src="/images/spinner.gif"></A></DIV></DIV>
<OL class=dp-j>
String a1 = "123" ;
String a2 = "123" ;
String a3 = "1234" ;</PRE><BR>java虛擬機只會創建一個字符串實例,a,a1,a2對象共享一個值。遇到不同的字符串,java虛擬機會再創建一個String對象,如a3。如果程序所處理的字串有頻繁的內容變化,就不宜使用String類型,而應當使用StringBuffer類型,如果需要對字串做大量的循環查詢,也不宜使用String類型,應當考慮使用byte或char數組. <BR><BR>其它不變類: <BR>The Integer,String, Float, Double, Byte, Long, Short, Boolean, and Character classes are all examples of an immutable class. By definition, you may not alter the value of an immutable object after its construction.In Java, a class such as Integer acts as a simple wrapper around its primitive counterpart -- in this case, int. The wrappers found in java.lang allow us to treat the primitives as if they were objects. So, for example, you could not put an int into a Vector without wrapping it。 <BR><BR><SPAN style="FONT-SIZE: large"><SPAN style="COLOR: green"><FONT size=4>優缺點:</FONT></SPAN></SPAN> <BR>不變模式可增強對象的健壯性。不變模式允許多個對象共享某一對象,降低了對該對象進行并發訪問時的同步化開銷。唯一缺點是一旦需要修改一個不變對象的狀態,就只好創建一個新的同類對象,在需要頻繁修改不變對象的環境里,會有大量的不變對象作為中間結果被創建出來,再被Java的垃圾收集器收走,這是一種資源的浪費。 <BR><BR><SPAN style="FONT-SIZE: large"><SPAN style="COLOR: green"><FONT size=4>總結:</FONT></SPAN></SPAN> <BR><SPAN style="COLOR: green">不變模式的核心就是對象不變,從而引伸出對象復用共享的思想。</SPAN>如無狀態的單例模式,享元(Flyweight)模式及原型模式(Prototype)都可以認為是不變模式的應用。其它如線程池,緩存等的實現也一定程度上是使用不變模式。還有EJB的Stateless Session Bean(無狀態會話bean),Spring對Service層、Dao層bean的默認單例實現,我認為都是沿用了不變模式中共享的思想。