不变模式
最近老有人问我不变模式,我其实也理解得不深,于是花了一些时间进行学习总结,分析了一下不变模式(immutable pattern),和大家一起分享。说得不对的地方欢迎拍砖,谢绝谩骂。姐妹篇:精通有状态vs无状态(Stateful vs Stateless).http://www.iteye.com/topic/960532
不变模式(immutable pattern)
一个类的内部状态创建后,在整个生命期间都不会发生变化时,就是不变类。这种使用不变类的做法叫做不变模式。
不变模式有两种形式:一种是弱不变模式,另一种是强不变模式。
弱不变模式:
一个类的实例的状态是不可变化的,但是这个类的引用的实例具有可能会变化的状态。这样的类符合弱不变模式的定义。要实现弱不变模式,一个类必须满足如下条件:
第一,对象没有任何方法会修改对象的状态,当对象的构造函数对对象的状态初始化之后,对象的状态便不再改变。
第二,所有的属性都应当是私有的,以防客户端对象直接修改任何的内部状态。
第三,这个对象所引用的对象如果是可变对象的话,必须设法限制外界对这个对象的访问,以防止对这些对象的修改。如果可能应该尽量在不变对象的内部来初始化。
弱不变模式的缺点是:
一个弱不变对象引用的实例变量可以是可变对象,可能会通过外界修改父对象的状态,这是一个显著的缺点。可以在初始化可变对象时,先进行clone。
代码演示:
/** * @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()); } }
结果:可以看到user的名字会改变。
原始值:0,yes,100
修改引用后:0,no,100
我们再引伸一个不可变类的例子:
在时间截止时,我们需要一一检查队列成员是不是vip,如果是可以去USA.假设是多线程环境,并且users数组是多线程共享,那么另外的线程通过users去修改users[n],这时就会把users[n]绕过时间检查而去USA.
/** * 不变模式之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() { // 假设时间期限到,要检查上万人以上的队列。 } }
解决方法:
在事务处理及数据大批量入库的多线程环境中,应该也会有类似的问题。所以对于这样的传入参数及上例中的不变对象引用可变对象,我们可以将其在相关构造函数及方法中复制为本地变量(数组),及使用它的深度clone,阻止相关数据与外部线程的联系。
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!"); } } }
强不变模式:
一个类的实例的状态不会改变,同时它的子类的实例也具有不可变化的状态。这样的类符合强不变模式。要实现强不变模式,一个类必须首先满足弱不变模式所要求的所有条件,并且还要满足下面条件之一:
第一,所考虑的类所有的方法都应当是final,这样这个类的子类不能够置换掉此类的方法。
第二,这个类本身就是final的,那么这个类就不可能会有子类,从而也就不可能有被子类修改的问题。
不变模式在Java中的应用
如String类
原文地址:http://www.iteye.com/topic/959751