結構型設計模式在公司專案中的運用實踐,超級實用
關於設計模式的一些實戰總結 -- 常見的結構型設計模式
在設計模式裡面,有一種叫做介面卡的設計模式 Adapter Design Pattern ,這類介面卡模式通常應用於做不同介面之間的適配和調整,常見的應用場景例如:
對一些不同實現的介面做統一整合,對一些介面的設計“缺陷”做一定的補救措施。
舉個栗子來說,假設某個業務場景裡面的遇到了一個人臉識別的功能:
公司內部接入了多個第三方的認證介面,具體的介面設計如下:
public interface IFaceRecognitionService { /** * 人臉識別 * * @param userId * @return */ Boolean recognition(Integer userId); }
對應的人臉認證介面實現如下:
public class AFaceRecognitionService implements IFaceRecognitionService { @Override public Boolean recognition(Integer userId) { System.out.println("this is AliFaceRecognitionService"); return true; } }
public class BFaceRecognitionService implements IFaceRecognitionService { @Override public Boolean recognition(Integer userId) { System.out.println("this is B FaceRecognitionService"); return true; } }
然後此時我們就有兩類的認證介面,假設後邊的業務愈發擴充套件,接入的第三方介面越來越多,這時候可以設計出一個靈活的介面卡來進行程式碼的相容:
public class FaceRecognitionAdaptorService implements IFaceRecognitionService { private IFaceRecognitionService faceRecognitionService; public FaceRecognitionAdaptorService(IFaceRecognitionService faceRecognitionService){ this.faceRecognitionService = faceRecognitionService; } public FaceRecognitionAdaptorService(){ } public IFaceRecognitionService select(int type){ if(type==1){ this.faceRecognitionService = new AFaceRecognitionService(); }else{ this.faceRecognitionService = new BFaceRecognitionService(); } return this.faceRecognitionService; } @Override public Boolean recognition(Integer userId) { return faceRecognitionService.recognition(userId); } public static void main(String[] args) { FaceRecognitionAdaptorService faceRecognitionAdaptorService = new FaceRecognitionAdaptorService(); faceRecognitionAdaptorService.select(1).recognition(1001); faceRecognitionAdaptorService.select(2).recognition(1002); } }
雖然說demo很簡單,但是從程式碼的後期維護角度來說,我們可以得出以下兩點經驗:
-
使用了介面卡模式其實有時候可以讓兩個獨立的類各自發展,隔離他們之間的依賴,每當有類發生變化的時候只會影響到對應的類和介面卡內部的程式碼,耦合程度可以大大降低。
-
而且在實際工作中,如果需要對系統的功能進行維護,可以通過採用介面卡模式的方式來進行適配,從而減少對原先程式碼的侵入性。
代理模式的應用
什麼是代理模式?
簡單來講就是在不改變原始類(或叫被代理類)程式碼的情況下,通過引入代理類來給原始類附加功能。我們通過一個簡單的例子來學習一下代理模式:
一個使用者登入的介面程式碼如下所示:
public class UserService implements IUserService{ @Override public void login() { System.out.println("UserService login..."); } }
可以結合代理模式,使用jdk代理的方式來進行介面的時長統計:
public class MetricsProxy { public Object createProxy(Object proxyObj){ Class<?>[] interfaces = proxyObj.getClass().getInterfaces(); DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(interfaces); return Proxy.newProxyInstance(proxyObj.getClass().getClassLoader(), interfaces, dynamicProxyHandler); } private class DynamicProxyHandler implements InvocationHandler { private Object proxiedObject; public DynamicProxyHandler(Object proxiedObject) { this.proxiedObject = proxiedObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long begin = System.currentTimeMillis(); Object result = method.invoke(proxiedObject, args); String apiName = proxiedObject.getClass().getName() + ":" + method.getName(); long end = System.currentTimeMillis(); System.out.println("介面耗時:"+(end - begin)); return result; } } public static void main(String[] args) { MetricsProxy metricsProxy = new MetricsProxy(); IUserService userService = (IUserService) metricsProxy.createProxy(new UserService()); userService.login(); } }
除了上述的這種簡單場景之外,實際上在我們工作中經常應用的rpc服務框架也有代理模式的影子。
例如說我們常用的dubbo框架,在進行遠端化的服務呼叫過程中就是使用了代理模式的方式進行設計,使得使用者端在呼叫介面的時候不需要去對底層的一些網路通訊,資料編碼做過多深入的瞭解。
為了更好地演示理解這個應用,下邊我將通過一個實際案例來進行介紹:
首先是服務端程式碼:
public class RpcServer { public void export(Object service,int port){ if(service==null || port<0 || port>65535){ throw new RuntimeException("param is error"); } try { ServerSocket serverSocket = new ServerSocket(port); while (true){ final Socket socket = serverSocket.accept(); new Thread(new Runnable() { @Override public void run() { try { ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); String methodName = objectInputStream.readUTF(); Class<?>[] parameterTypes = (Class<?>[]) objectInputStream.readObject(); Object[] args = (Object[]) objectInputStream.readObject(); Method method = service.getClass().getMethod(methodName,parameterTypes); Object result = method.invoke(service,args); output.writeObject(result); } catch (IOException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } }).start(); } } catch (IOException e) { e.printStackTrace(); } } }
客戶端程式碼
public class RpcClient { public <T> T refer(final Class<T> interfaceClass, final String host, final int port) throws Exception { if(interfaceClass==null){ throw new RuntimeException("interfaceClass is null"); }else if (host==null){ throw new RuntimeException("host is null"); }else if (port<0 || port>65535){ throw new RuntimeException("port is invalid "); } return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Socket socket = new Socket(host,port); OutputStream outputStream = socket.getOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeUTF(method.getName()); objectOutputStream.writeObject(method.getParameterTypes()); objectOutputStream.writeObject(args); ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); try { Object result = input.readObject(); if (result instanceof Throwable) { throw (Throwable) result; } return result; } finally { input.close(); } } }); } }
接著是一些測試使用的模擬服務,程式碼如下所示:
public interface IUserService { String test(); } public class UserServiceImpl implements IUserService { @Override public String test() { System.out.println("this is test"); return "success"; } }
藉助了使用代理模式設計的服務呼叫方和服務提供方,這裡通過建立了兩端的demo案例進行模擬:
首先是服務端程式碼:
public class ServerDemo { public static void main(String[] args) { RpcServer rpcServer = new RpcServer(); IUserService userService = new UserServiceImpl(); rpcServer.export(userService,9090); } }
接著是客戶端程式碼:
public class ClientDemo { public static void main(String[] args) throws Exception { RpcClient rpcClient = new RpcClient(); IUserService iUserService = rpcClient.refer(IUserService.class,"localhost",9090); iUserService.test(); } }
本文總結
1.介面卡模式是用來做適配,它將不相容的介面轉換為可相容的介面,讓原本由於介面不相容而不能一起工作的類可以一起工作。而且在對於系統維護的時候,介面卡模式還可以作為一種用於補償機制的設計模式來供開發者選用。
2.代理模式主要是在不改變原有類設計的基礎上邊通過引入相應的代理類來給原始類做擴充套件功能。常見的代理有劃分為靜態代理和動態代理兩類。但是由於靜態代理的可擴充套件性不好,因此實際工作中更多的場景會考慮使用動態代理的設計思路。比較常見的動態代理實現技術有cglib和jdk兩類技術。然而使用JDK實現的動態代理不能完成繼承式的動態代理,如果遇到這樣的場景,可以使用cglib來實現繼承式的動態代理。
3.介面卡模式和代理模式兩者都有點”包裝(Wrapper)“資料的味道,其實這也是他們之間的一些共同性。如果要用他們的共性來劃分,其實這兩類設計模式可以統一稱呼為結構型設計模