06 ChainOfResponsibility responsibility chain mode

Posted by tomtomdotcom on Thu, 23 Dec 2021 04:39:33 +0100

Scenario 1

A website that can leave comments, send messages, etc. The message is input through the front end, then transmitted to the back end, then entered into the database, and then displayed on the website. If the text contains a web page script, it will destroy our web page; If sensitive words are included, it will have a more serious impact on the website and may even violate laws and regulations. Therefore, we need to filter the messages and replace the problematic words filtered.

We define a message class:

package org.garen.cor;

/**
 * news
 */
public class Msg {
    private String name;
    private String msg;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "Msg{" +
                "name='" + name + '\'' +
                ", msg='" + msg + '\'' +
                '}';
    }
}

The simplest treatment

Test class:

package org.garen.cor;

public class Main {

    public static void main(String[] args) {
        Msg msg = new Msg();
        msg.setMsg("hello everyone:),<script>,Welcome to visit www.garen.org,Everybody is 996");

        // Process web script
        String r = msg.getMsg();
        r = r.replace("<", "[");
        r = r.replace(">", "]");
        msg.setMsg(r);
        System.out.println(r);

        // Dealing with sensitive words
        r = r.replace("996", "955");
        msg.setMsg(r);
        System.out.println(r);
    }

}

Operation results:

Hello, everyone:), [script], welcome to www.garen.com Org, everyone is 996
Hello, everyone:), [script], welcome to www.garen.com Org, everyone is 955

This method is simple, but it is not conducive to expansion. If there are new conditions to deal with, modify the code.
Using design patterns is to complicate simple programs and encapsulate where changes will occur. The program is more complex, but it improves the scalability.

Package change

Message filtering interface:

package org.garen.cor;

/**
 * Message filtering interface
 */
public interface MsgFilter {
    void doFilter(Msg m);
}

Web script filter:

package org.garen.cor;

/**
 * Web script filter
 */
public class HTMLFilter implements MsgFilter{
    @Override
    public void doFilter(Msg m) {
        String s = m.getMsg();
        s = s.replace("<", "[");
        s = s.replace(">", "]");
        m.setMsg(s);
    }
}

Sensitive word filter:

package org.garen.cor;

/**
 * Sensitive word filter
 */
public class SensitiveFilter implements MsgFilter{
    @Override
    public void doFilter(Msg m) {
        String s = m.getMsg();
        s = s.replace("996", "955");
        m.setMsg(s);
    }
}

Test class:

package org.garen.cor;

public class Main {

    public static void main(String[] args) {
        Msg msg = new Msg();
        msg.setMsg("hello everyone:),<script>,Welcome to visit www.garen.org,Everybody is 996");

        // Process web script
        MsgFilter htmlFilter = new HTMLFilter();
        htmlFilter.doFilter(msg);
        // Dealing with sensitive words
        MsgFilter sensitiveFilter = new SensitiveFilter();
        sensitiveFilter.doFilter(msg);
        // Print message
		System.out.println(msg);
    }

}

Operation results:

Msg{name = 'null', msg = 'Hello:), [script], welcome to www.garen.org, everyone is 955'}

In this way, if there are new conditions to deal with, you only need to add a new filter and modify the test class.

Add filter to collection

Test class:

package org.garen.cor;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        Msg msg = new Msg();
        msg.setMsg("hello everyone:),<script>,Welcome to visit www.garen.org,Everybody is 996");

        // Filter set
        List<MsgFilter> filters = new ArrayList<>();
        filters.add(new HTMLFilter());
        filters.add(new SensitiveFilter());
        // add new filter ...

        // Traverse the collection for filtering
        for (MsgFilter filter : filters) {
            filter.doFilter(msg);
        }
    }

}

Operation results:

Msg{name = 'null', msg = 'Hello:), [script], welcome to www.garen.org, everyone is 955'}

We add the filter to the collection, and then traverse the collection for filtering.
If there are new conditions to deal with, just add a new filter to the filter set.
Traverse the filter set and process it in turn. Does it look like a chain?

Filter chains

We continue to transform the code, create a filter chain class, and provide methods such as adding, deleting and filtering.
Filter chains:

package org.garen.cor;

import java.util.ArrayList;
import java.util.List;

public class MsgFilterChain {
    List<MsgFilter> filters = new ArrayList<>();

    public void add(MsgFilter filter) {
        filters.add(filter);
    }

    // delete omit

    public void doFilter(Msg msg) {
        for (MsgFilter filter : filters) {
            filter.doFilter(msg);
        }
    }

}

Test class:

package org.garen.cor;

public class Main {

    public static void main(String[] args) {
        Msg msg = new Msg();
        msg.setMsg("hello everyone:),<script>,Welcome to visit www.garen.org,Everybody is 996");
        
        MsgFilterChain fc = new MsgFilterChain();
        fc.add(new HTMLFilter());
        fc.add(new SensitiveFilter());

        fc.doFilter(msg);

        System.out.println(msg);
    }

}

Operation results:

Msg{name = 'null', msg = 'Hello:), [script], welcome to www.garen.org, everyone is 955'}

What's the benefit? It looks like moving the filter collection and traversal code in the test class to an MsgFilterChain class.

Modified add method

If we modify the add method, it will be different.
add method of filter chain class:

public MsgFilterChain add(MsgFilter filter) {
        filters.add(filter);
        return this;
    }

Add filter code of test class:

MsgFilterChain fc = new MsgFilterChain();
fc.add(new HTMLFilter()).add(new SensitiveFilter());

When a new filter is added, it will add continues to form a chain.

Add another chain

Smiley face filter:

package org.garen.cor;

public class FaceFilter implements MsgFilter{
    @Override
    public void doFilter(Msg m) {
        String s = m.getMsg();
        s = s.replace(":)", "^v^");
        m.setMsg(s);
    }
}

url Filter:

package org.garen.cor;

public class UrlFilter implements MsgFilter{
    @Override
    public void doFilter(Msg m) {
        String s = m.getMsg();
        s = s.replace("www.garen.org", "http://www.garen.org");
        m.setMsg(s);
    }
}

Test class:

package org.garen.cor;

public class Main {

    public static void main(String[] args) {
        Msg msg = new Msg();
        msg.setMsg("hello everyone:),<script>,Welcome to visit www.garen.org,Everybody is 996");

        MsgFilterChain fc = new MsgFilterChain();
        fc.add(new HTMLFilter()).add(new SensitiveFilter());

        // Newly added chain 2
        MsgFilterChain fc2 = new MsgFilterChain();
        fc2.add(new FaceFilter()).add(new UrlFilter());

        fc.doFilter(msg);
        fc2.doFilter(msg);  // Treatment of chain 2

        System.out.println(msg);
    }

}

Operation results:

Msg{name = 'null', msg = 'hello v,[script], welcome to visit http://www.garen.org , everyone is 955 '}

Connect the two chains

Modify MsgFilterChain to also implement MsgFilter interface, FC Add (FC2) connects the two chains

Filter chains:

package org.garen.cor;

import java.util.ArrayList;
import java.util.List;

public class MsgFilterChain implements MsgFilter{
    List<MsgFilter> filters = new ArrayList<>();

    public MsgFilterChain add(MsgFilter filter) {
        filters.add(filter);
        return this;
    }

    // delete omit

    @Override
    public void doFilter(Msg msg) {
        for (MsgFilter filter : filters) {
            filter.doFilter(msg);
        }
    }

}

Test class:

package org.garen.cor;

public class Main {

    public static void main(String[] args) {
        Msg msg = new Msg();
        msg.setMsg("hello everyone:),<script>,Welcome to visit www.garen.org,Everybody is 996");

        MsgFilterChain fc = new MsgFilterChain();
        fc.add(new HTMLFilter()).add(new SensitiveFilter());

        // Newly added chain 2
        MsgFilterChain fc2 = new MsgFilterChain();
        fc2.add(new FaceFilter()).add(new UrlFilter());

        // Chain up
        fc.add(fc2);

        // The two chains are handled together
        fc.doFilter(msg);

        System.out.println(msg);
    }

}

Operation results:

Msg{name = 'null', msg = 'hello v,[script], welcome to visit http://www.garen.org , everyone is 955 '}

Scenario 2

If I want to control whether to continue after executing a filter, what should I do?
Modify MsgFilter and change the return value to boolean type

package org.garen.cor;

/**
 * Message filtering interface
 */
public interface MsgFilter {
    boolean doFilter(Msg m);
}

Modify all filter implementation classes, and each filter returns true / false according to its own logical judgment. Here we will not write logical judgment. The sensitive word filter returns false and others return true.

Web filter:

package org.garen.cor;

/**
 * Web script filter
 */
public class HTMLFilter implements MsgFilter{
    @Override
    public boolean doFilter(Msg m) {
        String s = m.getMsg();
        s = s.replace("<", "[");
        s = s.replace(">", "]");
        m.setMsg(s);
        return true;
    }
}

Sensitive word filter:

package org.garen.cor;

/**
 * Sensitive word filter
 */
public class SensitiveFilter implements MsgFilter{
    @Override
    public boolean doFilter(Msg m) {
        String s = m.getMsg();
        s = s.replace("996", "955");
        m.setMsg(s);
        return false;
    }
}

Smiley face filter:

package org.garen.cor;

public class FaceFilter implements MsgFilter{
    @Override
    public boolean doFilter(Msg m) {
        String s = m.getMsg();
        s = s.replace(":)", "^v^");
        m.setMsg(s);
        return true;
    }
}

url Filter:

package org.garen.cor;

public class UrlFilter implements MsgFilter{
    @Override
    public boolean doFilter(Msg m) {
        String s = m.getMsg();
        s = s.replace("www.garen.org", "http://www.garen.org");
        m.setMsg(s);
        return true;
    }
}

Chain class also implements the interface, and doFilter should also be changed to return Boolean type. At the same time, if the filter returns false, execution will not continue.
Chain type:

package org.garen.cor;

import java.util.ArrayList;
import java.util.List;

public class MsgFilterChain implements MsgFilter{
    List<MsgFilter> filters = new ArrayList<>();

    public MsgFilterChain add(MsgFilter filter) {
        filters.add(filter);
        return this;
    }

    // delete omit

    @Override
    public boolean doFilter(Msg msg) {
        for (MsgFilter filter : filters) {
            if(!filter.doFilter(msg)) return false;
        }
        return true;
    }

}

The test class remains unchanged, and the test results are as follows:

Msg{name = 'null', msg = 'Hello:), [script], welcome to www.garen.org, everyone is 955'}

It ends after the sensitive word filter is executed.

Filter simulating Servlet

requirement:
request execution order: filter1,filter2
response execution sequence: filter2,filter1

Request response object:

package org.garen.cor2;

public class Request {
    String str;
}
package org.garen.cor2;

public class Response {
    String str;
}

Filter interface:

package org.garen.cor2;

public interface Filter {
    void doFilter(Request request, Response response, FilterChain filterChain);
}

filter:

package org.garen.cor2;

public class HTMLFilter implements Filter{

    @Override
    public void doFilter(Request request, Response response, FilterChain filterChain) {
        System.out.println("HTMLFilter request: " + request.str);
        filterChain.chain(request, response);
        System.out.println("HTMLFilter response: " + response.str);
    }
}
package org.garen.cor2;

public class UrlFilter implements Filter{
    @Override
    public void doFilter(Request request, Response response, FilterChain filterChain) {
        System.out.println("UrlFilter request: " + request.str);
        filterChain.chain(request, response);
        System.out.println("UrlFilter response: " + response.str);
    }
}

Chain:

package org.garen.cor2;

import java.util.ArrayList;
import java.util.List;

public class FilterChain{

    List<Filter> filters = new ArrayList<>();

    public FilterChain add(Filter filter) {
        filters.add(filter);
        return this;
    }

    int i = 0;
    public void chain(Request request, Response response) {
        if (i == filters.size()) return ;
        Filter filter = filters.get(i);
        i++;
        filter.doFilter(request, response, this);
    }
}

Test class:

package org.garen.cor2;

public class Main {
    public static void main(String[] args) {
        // Request response object
        Request request = new Request();
        request.str = "REQUEST";
        Response response = new Response();
        response.str = "RESPONSE";
        // Add chain
        FilterChain filterChain = new FilterChain();
        filterChain.add(new HTMLFilter());
        filterChain.add(new UrlFilter());
        // implement
        filterChain.chain(request, response);
    }

}

Operation results:

HTMLFilter request: REQUEST
UrlFilter request: REQUEST
UrlFilter response: RESPONSE
HTMLFilter response: RESPONSE

Topics: Java Design Pattern