This is the fifth day of my participation in the August Wen Challenge.More challenges in August

The creative design pattern, as we have learned, addresses the creation of objects, encapsulates the complex creation process, and decouples the object creation code from its usage code.

Today we begin with structural design patterns, which summarize some classical structures of classes or objects grouped together to solve problems in specific application scenarios.

Without further ado, let’s start with the agent model.

The principle of analytic

Proxy mode:

  • A proxy is provided for other objects to control access to this object.

  • Add functionality to the original class by introducing a proxy class without changing the original class code.

Let’s take an example to show how the proxy pattern works:

Logs about key user operations are required to facilitate future system maintenance and troubleshooting.

You need to create a database table for log records, including the fields ID, IP_ADDR, user_name, operation, and create_date, corresponding to the log ID, IP address, user name, operation, and operation time respectively.

Let’s simulate the code in UserController:

public class UserController {
    / / login
    public void login(String username, String password){
        // The value of the parameter in the log is obtained
        Log log = new Log();
        log.setIpAddr("192.168.1.1");
        log.setUserName("Black");
        log.setOperation("Method name: login" + "[Parameter 1, type:"+ username.getClass() + "Values." + username
                + "Parameter 2, type:" + password.getClass() + "Values." + username);
        log.setCreateDate(new Date());

        // omit the database code.// Omit the login procedure code. }/ / register
    public void register(String username, String password){
        Log log = new Log();
        log.setIpAddr("192.168.1.1");
        log.setUserName("Xiao pang");
        log.setOperation("Method name: register" + "[Parameter 1, type:"+ username.getClass() + "Values." + username
                + "Parameter 2, type:" + password.getClass() + "Values." + username);
        log.setCreateDate(new Date());

        // omit the database code.// Omit the login procedure code. }/ / logout. }Copy the code

Obviously, there’s something wrong with the way this is written. First, the log collection code intrudes into and is highly coupled to the business code. If you change the logic or parameters of the log collection in the future, the replacement cost can be significant. Second, the code that collects log information has nothing to do with the business code and should never have been placed in a class. Business classes are best served with a single responsibility and a focus on business processing.

To decouple non-business code from business code, the proxy pattern comes in handy. The proxy class UserControllerProxy implements the same interface, IUserController, as the original class UserController. The UserController class is only responsible for business functions. The proxy class UserControllerProxy is responsible for attaching additional logical code before and after the execution of the business code, and for executing the business code by delegating calls to the original class.

public interface IUserController {

    void login(String username, String password);

    void register(String username, String password);

    void logout(a);

}

 
public class UserController implements IUserController {

    @Override
    public void login(String username, String password) {
        // omit login logic...

    }

    @Override
    public void register(String username, String password) {
        // omit register logic...
    }

    @Override
    public void logout(a) {
        // omit logout logic...}}public class UserControllerProx implements IUserController {

    private UserController userController;

    public UserControllerProx(UserController userController) {
        this.userController = userController;
    }

    @Override
    public void login(String username, String password) {
        // The value of the parameter in the log is obtained
        Log log = new Log();
        log.setIpAddr("192.168.1.1");
        log.setUserName("Black");
        log.setOperation("Method name: login" + "[Parameter 1, type:"+ username.getClass() + "Values." + username
                + "Parameter 2, type:" + password.getClass() + "Values." + username);
        log.setCreateDate(new Date());
        / / to entrust
        userController.login(username, password);

    }

    @Override
    public void register(String userName, String password) {
        / / similar to login
    }

    @Override
    public void logout(a) {
        / / similar to login}}Copy the code

Examples:

// Because the primitive class and the proxy class implement the same interface, it is based on interface rather than implementation programming
// Replace the UserController class object with the UserControllerProxy class object without too much code change
IUserController  userController = new UserControllerProx(new UserController());
Copy the code

In the above code, the proxy class and the original class need to implement the same interface. However, if the original class does not define an interface, and the original class code comes from a third-party library, we cannot directly modify the original class to define a new interface. At this point, we can use inheritance.

public class UserControllerProx extends UserController {
    @Override
    public void login(String username, String password) {
        // The value of the parameter in the log is obtained
        Log log = new Log();
        log.setIpAddr("192.168.1.1");
        log.setUserName("Black");
        log.setOperation("Method name: login" + "[Parameter 1, type:"+ username.getClass() + "Values." + username
                + "Parameter 2, type:" + password.getClass() + "Values." + username);
        log.setCreateDate(new Date());
        super.login(username, password);
    }

    @Override
    public void register(String username, String password) {
        / / similar to login...
        super.register(username, password);
    }

    @Override
    public void logout(a) {
        / / similar to login...
        super.logout(); }}Copy the code

Use:

UserController userController = new UserControllerProxy();
Copy the code

A dynamic proxy

There are still a few problems with the above code. First, we need to re-implement all the methods in the original class in the proxy class, and attach similar code logic to each method. Second, if we have more than one class with additional functionality to add, we need to create a proxy class for each class, resulting in a doubling of the number of classes. And there is a lot of “duplicate” code in each proxy class.

For this problem, we can use dynamic proxy to solve.

Dynamic proxy: Instead of writing a proxy class for each primitive class in advance, a proxy class corresponding to the primitive class is created dynamically at runtime and replaced by the proxy class in the system.

public class LogCollectorProx {

    public Object createProxy(Object proxiedObject){ Class<? >[] interfaces = proxiedObject.getClass().getInterfaces(); DynamicProxyHandler handler =new DynamicProxyHandler(proxiedObject);
        return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);
    }

    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 {
            Log log = new Log();
            log.setIpAddr("192.168.1.1");
            log.setUserName("Black");
            log.setOperation("Method name:"+ method.getName()); // More parameter information can be obtained by reflection here
            log.setCreateDate(new Date());
            // omit database operation...

            Object result = method.invoke(proxiedObject, args);
            returnresult; }}}Copy the code

Examples:

LogCollectorProx prox = new LogCollectorProx();
IUserController userController = (IUserController) prox.createProxy(new UserController());
Copy the code

In fact, the underlying implementation principle of Spring AOP is based on dynamic proxies.

Application scenarios

The proxy pattern is often used to develop non-functional requirements in business systems, such as monitoring, statistics, authentication, limiting traffic, transactions, and idempotence. We decoupled these additional functions from the business functions and placed them in a proxy class that allowed programmers to focus only on the business side of development. In addition, proxy mode can also be used in RPC, caching, and other application scenarios.