面向對象程序設計比任何具體技術都重要。但是在使用種種模式的時候,也要防止過度設計,增加不必要的復雜性、浪費時間和經費。
1、使用接口降低程序的耦合性(AchIEving Loose Coupling with Interfaces)
雖然面向接口編程回比直接面向具體類編程增加一點點的復雜性,但是帶來的好處卻是巨大的。
1)、可以改變實現而不改變調用的代碼。這使我們可以重新實現程序的一部分,而不用去修改其它部分。
2)、可以自由的實現接口,並促進重用。
3)、必要的時候可以寫出一個簡單的測試實現,或者Stub實現。
2、盡量使用結合,而不是繼承(Prefer Object Composition to Concrete Inheritance)
結合提供了更大的靈活性,它可以在運行時改變程序的行為。Strategy和State模式都是基於這一理論的。
3、模版模式(The Template Method Design Pattern)
模版模式是直接類繼承的一種正確使用。但你知道一些算法的步驟,但是不清楚他們的具體操作就可以使用模版模式,將具體的操作留到以後實現。依賴倒轉 (IOC/Inversion of Control)就是模版模式的一種使用,它通過模版模式,讓框架代碼調用用戶自己的代碼,而不是正常的那種用戶代碼去掉用框架的代碼。模版模式特別適用 於框架設計。
4、策略模式(The Strategy Design Pattern)
策略模式比模版模式稍微復雜了一點,但是提供了更大的靈活性。但存在以下的情況時就應該使用策略模式:
1)、當算法的所有的步驟都可變,而不是僅僅有幾個的時候
2)、當實現具體步驟的類有一個特定的繼承層次的時候
3)、當實現具體步驟的類需要和其他類相關聯的時候
4)、當這些步驟需要在運行時改變的時候
5)、當算法特定步驟的實現會持續增加的時候
5、使用回調增加擴展性(Using Callbacks to AchIEve Extensibility)
回調是策略模式的一種特殊使用,他可以實現特殊的操作,而將錯誤處理、Logging放進框架中。如:
public interface RowCallbackHandler {void processRow(ResultSet rs) throws SQLException;}
public void query(String sql, RowCallbackHandler callbackHandler)
throws JdbcSqlException {
try {
.....
while (rs.next()) {
callbackHandler.processRow(rs);
}
....
} catch (SQLException ex) {
....
} finally {
....
}
}
回調的優點:
1)、可以集中進行錯誤處理和資源的獲取、釋放。這樣可以讓你實現更靈活、更強大的錯誤處理,還不增加工作量。
2)、於底層實現細節無關。
3)、一個工作流程,可以通過不同的實現,完成不同的工作。充分實現了代碼的重用。
回調的缺點:
1)、這種模式,不能讓你直接看到被調用的代碼。這可能讓代碼變得不容易理解和維護。
2)、必須創建一個回調的接受接口或類。
3)、不知道書上再寫什麼。
這個模式特別適用於回調接口非常簡單的情況,最好回調接口只有一個方法,這樣可以通過inner類的形式實現。
6、觀察者模式(The Observer Desing Pattern)
只有當系統中出現了需要了解工作流程的松散監聽者(Loosely coupled Listener)時,才需要使用這種模式。如果我們過度的使用了他,就會導致業務被事件(event)發布的代碼所淹沒。只有重要的工作流程才有可能需 要實現他。監聽者(Listener)必須能夠快速的返回,並且線程安全。不能及時放回的監聽者可能阻塞這個程序。
七、完善方法的參數(Consider Consolidating Method Parameters)
有些時候,我們需要將多個方法參數壓縮到一個對象中,這樣可以簡化代碼。最大的好處就是可以改變方法的參數,但是不用改變調用的代碼,並且能過簡單的實現方法的默認參數。缺點就是可能參數大量的小對象,這些對象將消耗堆的空間。
八、異常捕捉(Exception Handling – Checked or Unchecked Exceptions)
Java將異常分為:Checked exceptions,Unchecked(Runtime) exceptoins。Checked exceptions繼承與java.lang.Exception,不許聲明和捕捉;Runtime exceptions繼承與Java.lang.RuntimeException,不用生命,可以不捕捉。(C++,C#中的異常都相當於 Unchecked Exceptions)
雖然傳統的觀點認為盡量使用Checked Exception,不用Runtime Exception。但是作者不怎麼認為,理由如下:
1)、太多代碼
2)、難於閱讀
3)、無休止的包裝Exception
4)、易變得方法聲明
5)、導致不必要的關聯底層實現。
作者相信,當一個函數返回兩個可選返回值得一個的時候,就需要去判斷,最好編譯器能過強制判斷。那就是使用Checked Exception,其他的情況,作者認為Checked Exception被過分強調了。
注:Checked異常要比使用return codes強,它能夠強迫用戶去捕捉異常。但是Checked Exception不適合用於表示致命的錯誤,和調用者不需要去捕捉的異常。J2EE容器有責任去捕捉並記錄Runtime Exception。
每當需要決定使用哪一種異常的時候,都需要問自己下面的幾個問題:
1)、所用調用者都需要處理這個錯誤嗎?是否異常是作為方法的另一種返回值。
例子:processInvoice()
回答:使用Checked Exception,讓編譯器幫組我們做檢查。
2)、只有少數調用者需要處理這個錯誤嗎?
例子:JDO exceptinos
回答:使用Runtime Exception。讓用戶可以捕捉,也可以不捕捉。
3)、是非常嚴重的異常嗎?是不能恢復的異常嗎?
例子:因連接不上數據庫,導致的業務異常
回答:使用Runtime Exception。調用者除了通知用戶,不能做任何其他有用的事情。
4)、還不清楚?
回答:使用Runtime Exception。讓調用者自己決定是否捕捉。
注:使用Runtime Exception時,最好也聲明異常。
良好的異常捕捉實踐:保存原始異常,重寫getMessage()和printStackTrace()方法。
讓異常包含更多信息:
1)、如果程序需要針對不同的異常作出相應的反應,就需要定義一系列實現統一個接口的異常,在每個類中作特殊的處理。
2)、針對用戶的異常,最好定義一個getErrorCode()的方法,將實際的異常消息放在propertIEs中,這樣方便以後的處理。
3)、異常信息必須盡可能的詳細![WebApplicationContext failed to load config]就不是一個很好的消息,這個消息應該寫成[WebApplicationContext failed to load config from file /WEB-INF/applicationContext.XML': cannot instantiate class ‘com.foo.bar.Magic’ attempting to load bean element with name ‘too’ – check that this class has a public no arg constructor]。難度好像的確很大。
九、使用反射(Using Reflection)
反射可以讓程序在運行時加載、實例化、操作類。反射還可以強化很多設計模式,比如工廠模式(Factory),就沒有必要將類名寫在程序中,可以直接配置到文件中。
反射和Swithches(Reflection and Switches)
public void vetoableChange(PropertyChangeEvent e) throws PropertyVetoException {
if (e.getPropertyName() .equals ("email")) {
String email = (String) e.getNewValue();
validateEmail (email, e);
}
...
} else if (e.getPropertyName() .equals ("age")) {
int age = ((Integer) e.getNewValue()).intValue();
validateAge(age, e);
}
...
}
是一個正常的屬性檢查的代碼,但是如果我們需要增加或者刪除一個屬性,我們就需要修改這些判斷,這意味著我們需要重新測試。但是如果我們使用反射,就可以非常優雅的解決怎麼問題
public AbstractVetoableChangeListener() throws SecurityException {
Method[] methods = getClass() .getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i] .getName() .startsWith(VALIDATE_METHOD_PREFIX) &&
methods[i] .getParameterTypes() .length == 2 &&
PropertyChangeEvent.class.isAssignableFrom(methods[i].
getParameterTypes() )) {
// We've found a potential validator
Class[] exceptions = methods[i] .getExceptionTypes();
// We don't care about the return type, but we must ensure that
// the method throws only one checked exception, PropertyVetoException
if (exceptions.length == 1 &&
PropertyVetoException.class.isAssignableFrom(exceptions[0])) {
// We have a valid validator method
// Ensure it's Accessible (for example, it might be a method on an
// inner class)
methods[i].setAccessible(true);
String propertyName = Introspector.decapitalize(methods[i].getName().
substring(VALIDATE_METHOD_PREFIX.length()));
validationMethodHash.put(propertyName, methods[i]);
System.out.println(methods[i] + " is validator for property " +
propertyName);
}
}
}
}
public final void vetoableChange(PropertyChangeEvent e)
throws PropertyVetoException {
Method m = (Method) validationMethodHash.get(e.getPropertyName());
if (m != null) {
try {
Object val = e.getNewValue();
m.invoke(this, new Object[] { val, e });
} catch (IllegalAccessException ex) {
System.out.println("WARNING: can't validate. " +
"Validation method "' + m + "' isn't Accessible");
} catch (InvocationTargetException ex) {
// We don't need to catch runtime exceptions
if (ex.getTargetException() instanceof RuntimeException)
throw (RuntimeException) ex.getTargetException();
// Must be a PropertyVetoException if it's a checked exception
PropertyVetoException pex = (PropertyVetoException)
ex.getTargetException();
throw pex;
}
}
}
雖然,使用反射的代碼要比正常的代碼復雜了一點,但是他只需要測試一遍,就可以應對可能的修改,這樣才能說是框架性的代碼!反射是Java的核心API,所以必須掌握。
反射和工廠模式(Reflection and the Factory Design Pattern)
public Object getObject(String classname, Class requiredType)
throws FactoryException {
try {
Class clazz = Class.forName(classname);
Object o = clazz.newInstance();
if (! requiredType.isAssignableFrom(clazz))
throw new FactoryException("Class "' + classname +
"' not of required type " + requiredType);
// Configure the object...
return o;
} catch (ClassNotFoundException ex) {
throw new FactoryException("Couldn't load class "' + classname + ""', ex);
} catch (IllegalAccessException ex) {
throw new FactoryException("Couldn't construct class "' + classname + "': is the no arg constructor public?", ex);
} catch (InstantiationException ex) {
throw new FactoryException("Couldn't construct class "' + classname +
"': does it have a no arg constructor", ex);
}
}
使用: MyInterface mo = (MyInterface)
beanFactory.getObject("com.mycompany.mypackage.MyImplementation",
MyInterface.class);
使用反射時,一般都會導致我們不能確定代碼是否能夠正常被執行,這是就要求我們必須提供更詳細的錯誤信息,以方便以後的處理。
動態代理(Java 1.3 Dynamic ProxIEs)
第11章(Infrastructure and Application Implementation)會詳細介紹,是一個和AOP(ASPect OrIEnted Programming )相關的東西。
十、使用JavaBean取得靈活性(Using JavaBeans to AchIEve Flexibility)
使用JavaBeans可以讓對象很容易的使用代碼之外的配置。不過最好能過使用下面的這些類:
PropertyEditor
PropertyChangeListener
VetoableChangeListener
Introspector
十一、通過程序級的注冊避免單例模式(Avoid a Proliferation of Singletons by Using an Application Registry)
傳統的單例模式有很多的缺點,比如單例類將被硬編碼於代碼中,單例類必須自己配制,復雜的程序可能需要很多單例類,單例類不支持接口,單例類不能被繼承,不能被及時的更新狀態。所有的這一些都大大局限了單例類的使用。
不過通過程序上下文(application context)可以很好的解決這些問題,首先它可以是一個正常的Java類,這就避免了所有上面的問題。他只要在程序初始化時,取得然後在程序的其他地方直接使用。甚至還可以直接配置到JNDI中。
我覺得最大的優點就是他的可以動態配置性,這要比將他硬性寫在代碼中好太多。
十二、重構(Refactoring)
重構不應該僅僅局限於代碼的重構,錯誤消息,log,文檔都應該是重構的對象。