结构型设计模式在公司项目中的运用实践

关于设计模式的一些实战总结 -- 常见的结构型设计模式


在设计模式里面,有一种叫做适配器的设计模式 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. 使用了适配器模式其实有时候可以让两个独立的类各自发展,隔离他们之间的依赖,每当有类发生变化的时候只会影响到对应的类和适配器内部的代码,耦合程度可以大大降低。


  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)“数据的味道,其实这也是他们之间的一些共同性。如果要用他们的共性来划分,其实这两类设计模式可以统一称呼为结构型设计模式。


END

上一篇:谈谈目前网络工程师的待遇问题


下一篇:Spring Boot遇到的某些问题