關於設計模式的一些實戰總結 – 常見的結構型設計模式
在設計模式裏面,有一種叫做適配器的設計模式 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很簡單,但是從代碼的後期維護角度來說,我們可以得出以下兩點經驗:
1.使用了適配器模式其實有時候可以讓兩個獨立的類各自發展,隔離他們之間的依賴,每當有類發生變化的時候只會影響到對應的類和適配器內部的代碼,耦合程度可以大大降低。
代理模式的應用
什麼是代理模式?
簡單來講就是在不改變原始類(或叫被代理類)代碼的情況下,通過引入代理類來給原始類附加功能。我們通過一個簡單的例子來學習一下代理模式:
一個用戶登錄的接口代碼如下所示:
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)“數據的味道,其實這也是他們之間的一些共同性。如果要用他們的共性來劃分,其實這兩類設計模式可以統一稱呼爲結構型設計模式。