The application and practice of structural design mode in the company's projects, super practical

Practical summary of design patterns -- common structural design patterns

In the design pattern, there is a design pattern called Adapter Design Pattern. This kind of adapter pattern is usually used to adapt and adjust different interfaces. Common application scenarios are as follows:

Some interfaces of different implementations are integrated, and some remedial measures are taken for the design "defects" of some interfaces.

For example, suppose a business scenario meets the function of face recognition:

Multiple third-party authentication interfaces are connected in the company. The specific interface design is as follows:

public interface IFaceRecognitionService {


    /**
     * Face recognition
     *
     * @param userId
     * @return
     */
    Boolean recognition(Integer userId);
}

 

The corresponding face authentication interface is implemented as follows:

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;
    }
}

 

Then we have two types of authentication interfaces. If the business behind is expanding and there are more and more third-party interfaces, a flexible adapter can be designed for code compatibility:

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);
    }
}

 

Although demo is very simple, from the perspective of code later maintenance, we can draw the following two experiences:

  1. In fact, the use of adapter pattern can sometimes allow two independent classes to develop independently and isolate their dependence. Whenever there is a class change, it will only affect the corresponding class and the code within the adapter, and the coupling degree can be greatly reduced.

  2. And in the actual work, if you need to maintain the function of the system, you can use adapter mode to adapt, so as to reduce the invasion of the original code.

Application of agent mode

What is agent mode?

In short, it is to add functions to the original class by introducing the proxy class without changing the code of the original class (or the proxy class). Let's take a simple example to learn about the agent mode:

The interface code of a user login is as follows:

public class UserService implements IUserService{


    @Override
    public void login() {
        System.out.println("UserService login...");
    }
}

 

You can use the jdk proxy mode to perform interface time statistics in combination with the proxy mode:

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("Interface time consumption:"+(end - begin));
            return result;
        }
    }


    public static void main(String[] args) {
        MetricsProxy metricsProxy = new MetricsProxy();
        IUserService userService = (IUserService) metricsProxy.createProxy(new UserService());
        userService.login();
    }
}

 

In addition to the above simple scenario, the rpc service framework that we often use in our work also has the shadow of the proxy pattern.
For example, our commonly used dubbo framework is designed in the way of proxy mode in the process of remote service invocation, so that the client does not need to know more about the underlying network communication and data coding when invoking the interface.
In order to better demonstrate and understand this application, I will introduce it through a practical case:

First, the server code:

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();
        }


    }

}

 

Client code

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();
                }
            }
        });
    }
}

 

Then there are some simulation services used in the test, the code is as follows:

public interface IUserService {

    String test();
}


public class UserServiceImpl implements  IUserService {

    @Override
    public String test() {
        System.out.println("this is test");
        return "success";
    }
}

 

With the help of the service callers and service providers designed with the proxy pattern, the demo cases at both ends are established to simulate:
First, the server code:

public class ServerDemo {

    public static void main(String[] args) {
        RpcServer rpcServer = new RpcServer();
        IUserService userService = new UserServiceImpl();
        rpcServer.export(userService,9090);
    }
}

 

Then the client code:

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();

    }
}

 

Summary of this paper

1. Adapter mode is used for adaptation. It converts incompatible interfaces into compatible interfaces, so that classes that cannot work together due to incompatible interfaces can work together. In addition, the adapter pattern can also be used as a design pattern for compensation mechanism for developers to choose when maintaining the system.

2. The agent mode is to extend the original class by introducing the corresponding agent class without changing the original class design. Common agents are divided into static agents and dynamic agents. However, due to the poor scalability of static agents, more scenarios in actual work will consider the design idea of using dynamic agents. Cglib and JDK are two kinds of common dynamic proxy technologies. However, the dynamic agent implemented by JDK can not complete the inherited dynamic agent. If such a scenario is encountered, cglib can be used to implement the inherited dynamic agent.

3. Both the adapter mode and the agent mode have the taste of "Wrapper" data. In fact, this is something in common between them. If we want to use their commonness to divide, in fact, these two kinds of design patterns can be called structural design patterns.

Tags: Java socket JDK Dubbo network

Posted on Sun, 17 May 2020 22:52:41 -0400 by trmbne2000