緣起
最近因為要開發中介軟體。但是寫了好幾個版本,都感覺不怎麼好。比起一些大廠的實現,感覺在程式碼架構的設計,java語言的基礎知識上面還是掌握的不夠牢靠。於是打算重讀一些比較經典的書籍。《Effective Java》就是其中一本。
第一章
主要講解了物件的建立和銷燬。多用工廠和建造者模式,儘量避免冗長的構造方法。其中對於單例的建立,提到了有關在單例序列化/反序列化的時候兩個個陷阱:
- 在通過私有構造器,例項化公有final靜態域的反序列化的時候如果是僅僅是繼承Serializable介面不是不夠的。
public class User implements Serializable {
private static final long serialVersionUID = -672817526808710807L;
private static final User user = new User();
private String age;
private String name;
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private User() {
this.age = "3";
this.name = "java";
}
public static User getInstance() {
return user;
}
private Object readResolve() {
return user;
}
}
public class SerializableDemo {
@SuppressWarnings("resource")
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/Users/tiny/Desktop/test.txt"));
out.writeObject(User.getInstance());
FileInputStream fis = new FileInputStream(new File("/Users/xujianxing/Desktop/test.txt"));
ObjectInputStream ois = new ObjectInputStream(fis);
User u = (User) ois.readObject();
System.out.println(u.getName());
if(User.getInstance()==u){
System.out.println("equals");
}
else{
System.out.println("not equals");
}
}
}複製程式碼
輸出not equals。
將User物件,稍稍改造:
package demo;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = -672817526808710807L;
private static final User user = new User();
private String age;
private String name;
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private User() {
this.age = "3";
this.name = "java";
}
public static User getInstance() {
return user;
}
private Object readResolve() {
return user;
}
}
package demo;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = -672817526808710807L;
private static final User user = new User();
private String age;
private String name;
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private User() {
this.age = "3";
this.name = "java";
}
public static User getInstance() {
return user;
}
private Object readResolve() {
return user;
}
}複製程式碼
增加一個readResolve
方法。
- 使用反射。通過
AccessibleObject.setAccessible
強制呼叫私有構造器。就像這樣:
public class SerializableDemo {
@SuppressWarnings("resource")
public static void main(String[] args)
throws InvocationTargetException, InstantiationException, IllegalAccessException, IllegalArgumentException {
Class<?> clazz = User.getInstance().getClass();
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor.getName());
constructor.setAccessible(true); // AccessibleObject
System.out.println(constructor.newInstance(null));
System.out.println(User.getInstance());
}
}
}複製程式碼
如果要抵禦這種攻擊。可以修改構造器,但在構造器中丟擲異常不是一個好的實踐。並且會破壞final域。
關於建造者模式
在某些情況下,可以利用建造者模式來大幅優化業務程式碼,使得業務程式碼看起來也十分優雅,並且具有很強的擴充套件性。比如有一個攝影師系統。攝影師的積分規則如下:
- 每個訂單+10分;
- 使用者加片:每加一張+1分;
- 使用者評價:五星+5分,四星+1分,三星+0分,二星-5分,一星-1;
- 上傳獎懲積分:拍攝日當天上傳,每單+10分。
- 第二天上傳,每單+5分。第三天上傳,每單+0分。第四天上傳,每單-5分。以此類推,每延後一天,每單-5分。第20天之後上傳,永久取消認證攝影師資格。
- 攝影師競標,未被使用者選中,補償2分。
- 攝影師作品管理上傳一套加1分。
如果直接在業務系統裡面計算程式碼,一但業務改變,那麼業務中的程式碼也會改變,這樣就違背了“對擴充套件開放,對修改關閉"的原則。使用建造者模式可以減少對業務系統的入侵。
public class PointRule {
//積分變動記錄
private Multimap<String, String> myMultimap =null;
private int changeTotal;
public int getChangeTotal() {
return changeTotal;
}
public void setChangeTotal(int changeTotal) {
this.changeTotal = changeTotal;
}
private PointRule(int changeTotal,Multimap<String, String> record) {
this.changeTotal = changeTotal;
this.record=record;
}
private static final int PLUS_NEW_ORDER = 10;
private static final int PLUS_ADD_SHEET = 1;
private static final int PLUS_COMMENT_FIVE = 5;
private static final int PLUS_COMMENT_FOUR = 10;
private static final int PLUS_UPLOAD_CURRENT_DAY = 10;
private static final int PLUS_UPLOAD_SECOND_DAY = 5;
private static final int PLUS_BID_FAIL = 2;
private static final int PLUS_UPLOAD_WORK = 1;
private static final int REDUCE_UPLOAD_DELAY = -5;
public static class Builder {
private int total = 0;
Multimap<String, String> myMultimap = ArrayListMultimap.create();
public static Builder getInstance() {
return new Builder();
}
public Builder plusNewOrder(String orderId) {
total += PLUS_NEW_ORDER;
this.record.put("orderId", "plusNewOrder");
return this;
}
public Builder plusAddSheet(String orderId) {
total += PLUS_ADD_SHEET;
this.record.put("orderId", "plusAddSheet");
return this;
}
public Builder plusCommentFive(String orderId) {
total += PLUS_COMMENT_FIVE;
this.record.put("orderId", "plusCommentFive");
return this;
}
public Builder plusCommentFour() {
total += PLUS_COMMENT_FOUR;
this.record.put("orderId", "plusCommentFour");
return this;
}
public Builder plusUploadCurrentDay() {
total += PLUS_UPLOAD_CURRENT_DAY;
this.record.put("orderId", "plusUploadCurrentDay");
return this;
}
public Builder plusUploadSecondDay() {
total += PLUS_UPLOAD_SECOND_DAY;
this.record.put("orderId", "plusUploadSecondDay");
return this;
}
public Builder plusBidFail(String orderId) {
total += PLUS_BID_FAIL;
this.record.put("orderId", "plusBidFail");
return this;
}
public Builder plusUploadWork(String orderId) {
total += PLUS_UPLOAD_WORK;
this.record.put("orderId", "plusUploadWork");
return this;
}
public Builder reduceUploadDelay(String orderId) {
total += REDUCE_UPLOAD_DELAY;
this.record.put("orderId", "reduceUploadDelay");
return this;
}
public PointRule build() {
PointRule pointRule = new PointRule(this.total,this.record);
return pointRule;
}
}
}複製程式碼
假設有一個Service:
public class PhotographerService {
public void userConfirmSheetOrder(){
//使用者收片的邏輯。
PointRule.Builder buidler=PointRule.Builde.getInstance();
if(當天收片){
buidler.plusCurrentDay();
}
if(修了10張片){
(for int i=0; i<10;i++){
buidler.plusAddSheet();
}
//其他的業務邏輯
最後得到這次要改變的分數:
PointRule rule=buidler.builder();
//如果要將積分變動的記錄入庫。那麼可以迴圈PointRule 的map物件。
}複製程式碼
對於積分變動記錄,可以使用迭代器模式。當讓這個例子只是一個舉例說明。比起直接在service裡面算積分,是不是會好很多呢?