今天有人問我怎么增強一個類的功能。博客剛好沒東西,今天就講講增強類。
增強的手段有三種類型:
1、繼承或者實現接口:特點是被增強對象不能變,增強的內容不能變。
2、裝飾着模式:特點是被增強對象可變,但增強內容不可變。
3、動態代理:特點是被增強對象可變,增強內容可變。
下面是三種對a對象進行增強的手段:
繼承:AA類繼承a對象的類型:A類,然后重寫fun1()方法,其中重寫的fun1()方法就是被增強的方法。但是,繼承必須要知道a對象的真實類型,然后才能去繼承。如果我們不知道a對象的確切類型,而只知道a對象是IA接口的實現類對象,那么就無法使用繼承來增強a對象了;
裝飾者模式:AA類去實現a對象相同的接口:IA接口,還需要給AA類傳遞a對象,然后在AA類中所有的方法實現都是通過代理a對象的相同方法完成的,只有fun1()方法在代理a對象相同方法的前后添加了一些內容,這就是對fun1()方法進行了增強;
動態代理:動態代理與裝飾者模式比較相似,而且是通過反射來完成的。
詳解:
1、繼承,java是面向對象的語言,繼承機制使他的可擴展性大大增強,我們可以通過繼承方式對現有類進行擴展增強。子類繼承父類之后可以獲得父類所有的公共方法,子類可以進行重寫等操作,這種方式簡便易學,但是隨之而來的是代碼的耦合性大大的增強,不利於后期的維護,所以對於繼承這種方法,謹慎使用。
2、裝飾者設計模式,設計模式是java編程的重要組成部分,確切的說不僅僅是Java,幾乎所有的高級編程語言都多多少少會涉及到編程模式。當然想要完全理解設計模式,單純學習一門語言是完全不夠的。百度百科中的解釋為:在不必改變原類文件和使用繼承的情況下,動態地擴展一個對象的功能。它是通過創建一個包裝對象,也就是裝飾來包裹真實的對象。java的IO流就是標准的裝飾者模式,在這里我們拿出java中的兩個類做解釋InputStream和BufferedInputStream,兩個類分別為字節輸入流和字節緩沖輸入流,簡單來說緩沖流就是字節流的強化版,使用的模式就是裝飾者模式,我們可以看到BufferedInputStream的構造方法中BufferedInputStream(InputStream in) 字節流就是作為參數輸入,這樣緩沖流中可以通過輸入的字節流對象來調用字節流中的所有的方法,同時不會妨礙緩沖流的私有方法,這就是比較經典的裝飾者強化。
下面舉個例子說明:
需求:處理GET請求參數編碼問題,需要在Filter中放行時,把request對象給“調包”了,也就是讓目標Servlet使用我們“調包”之后的request對象。這說明我們需要保證“調包”之后的request對象中所有方法都要與“調包”之前一樣可以使用,並且getParameter()方法還要有能力返回轉碼之后的參數。這可能讓你想起了“繼承”,但是這里不能用繼承,而是“裝飾者模式(Decorator Pattern)”!
解決方法:對request對象進行增強的條件,剛好符合裝飾者模式的特點!因為我們不知道request對象的具體類型,但我們知道request是HttpServletRequest接口的實現類。這說明我們寫一個類EncodingRequest,去實現HttpServletRequest接口,然后再把原來的request傳遞給EncodingRequest類!在EncodingRequest中對HttpServletRequest接口中的所有方法的實現都是通過代理原來的request對象來完成的,只有對getParameter()方法添加了增強代碼!
JavaEE已經給我們提供了一個HttpServletRequestWrapper類,它就是HttpServletRequest的包裝類,但它沒做任何的增強!你可能會說,寫一個裝飾類,但不做增強,其目的是什么呢?使用這個裝飾類的對象,和使用原有的request有什么分別呢?HttpServletRequestWrapper類雖然是HttpServletRequest的裝飾類,但它不是用來直接使用的,而是用來讓我們去繼承的!當我們想寫一個裝飾類時,還要對所有不需要增強的方法做一次實現是很心煩的事情,但如果你去繼承HttpServletRequestWrapper類,那么就只需要重寫需要增強的方法即可了。
EncodingRequest代碼:
/** * <p>類名稱 EncodingRequest </p> * <p>類描述 裝飾者模式: * 增強request的getParameter方法,使其編碼格式為utf-8 * </p> * @author 裴健 * @date 2017年3月22日 下午1:53:06 */ public class EncodingRequest extends HttpServletRequestWrapper{ private HttpServletRequest request; public EncodingRequest(HttpServletRequest request) { super(request); this.request=request; } @Override public String getParameter(String name) { String value = request.getParameter(name); try { value=new String(value.getBytes("iso-8859-1"),"utf-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } return value; } }
這里是將get請求的request對象對getParameter()方法增強了,直接將“iso-8859-1”的編碼改為“utf-8”的編碼。
3、代理模式,代理類模式理解起來會比裝飾者設計模式更加難,因為這個涉及到反射的原理。學習代理模式最終是學習AOP(面向切面編程),它與裝飾者模式有點相似,它比裝飾者模式還要靈活!
下面舉個例子說明:
需求:(1)定義一個服務員接口
// 服務員 public interface Waiter { // 服務 public void serve(); }
(2)一個男服務員的類並實現服務員接口。男服務員有個方法是服務。
public class ManWaiter implements Waiter { public void serve() { System.out.println("服務中..."); } }
現在想讓男服務員在服務的時候有禮貌,需要在服務的方法前后加上禮貌用語。此時會想到修改代碼實現。如果每次增強一個方法都去修改源代碼,通俗點說,顯得太low了。這里就需要動態代理增強這個“服務員”。
解決代碼:
public class Demo { @Test public void fun1() { Waiter manWaiter = new ManWaiter();//目標對象 /* * 給出三個參數,來創建方法,得到代理對象 */ ClassLoader loader = this.getClass().getClassLoader(); Class[] interfaces = {Waiter.class}; InvocationHandler h = new WaiterInvocationHandler(manWaiter);//參數manWaiter表示目標對象 // 得到代理對象,代理對象就是在目標對象的基礎上進行了增強的對象! Waiter waiterProxy = (Waiter)Proxy.newProxyInstance(loader, interfaces, h); waiterProxy.serve();//前面添加“您好”, 后面添加“再見” } } class WaiterInvocationHandler implements InvocationHandler { private Waiter waiter;//目標對象 public WaiterInvocationHandler(Waiter waiter) { this.waiter = waiter; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("您好!"); this.waiter.serve();//調用目標對象的目標方法 System.out.println("再見!"); return null; } }