Design Pattern, Chain of Responsibility

2022. 2. 19. 23:36ETC/Design Patterns

 

Object Behavioral Pattern

Chain of Responsibility Pattern

 

-----------------    INDEX     -----------------

 

Chain of Responsibility Pattern ?

Structure

Sample Code: Java

특징

관련 패턴

 

----------------------------------------------

 

 

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

- GoF Design Patterns

 

책임 연쇄 패턴은요청을 수신 받는 객체들을 체인처럼 묶어 처리 기회를 주어 연쇄적으로 처리하는 패턴입니다.

 

익숙한 개념인데 위의 설명을 읽으면 좀 더 헷갈리는 것 같아요.

위의 표현을 풀어말하자면, 하나의 요청이 있을 때 이 요청들의 상태에 따라 처리하는 방식이 여러개일 수 있습니다.

 

특정 조건에 맞는 객체들만 실행하면 되는데 어떤 객체가 조건을 만족시킬지 모르기 때문에

모든 객체들을 묶어 조건에 해당하는지 체크하면서 다음 객체로 넘기는 것입니다.

 

여기서 요청을 처리하는 객체를 Handler라고 합니다.

Handler가 해당 요청을 실행할 수도 있고 다음 Handler로 넘길 수도 있으며, 혹은 두 개를 동시에 할 수도 있습니다.

Handler의 개념을 더 자세히 느끼기 위해 아래 예시를 들어볼게요.

 

 

실생활 예시

예를 들어 한 어플리케이션의 고객센터에 전화를 들어볼게요.

어플리케이션에 실행 오류가 생겨서 이 문제를 전화를 통해 고치고 싶어서, 고객센터에 전화를 겁니다.

 

가장 먼저, 자동응답기가 응답합니다. 자동 응답기에서 1부터 4까지 선택사항을 주고 해당하는 선택을 하라고 합니다. 

하지만 문의하고자 하는 선택사항이 없어 기타를 누르니 상담사에게로 전화가 돌아갑니다.

 

두 번째로, 상담사가 전화를 받았지만 어플리케이션에 대한 기술적인 도움을 받지 못합니다.

그래서 마지막으로 기술자에게로 전화가 돌아갑니다.

 

세 번째로, 기술자가 전화를 받았는데 어플리케이션의 문제점에 대해 자세한 솔루션을 주었고 실제로 해결이 되었어요.

 

무엇인가 체인처럼 연결되는 흐름이 보이나요?

여기서 요청을 처리하는 Handler는 세가지가 있습니다. 자동응답기, 상담사, 기술자가 바로 요청을 수신받는 Handler가 됩니다. 각 Handler에서 요청을 처리할 수 있는지의 여부를 따지고 불가능하면 다음 Handler로 넘기는 과정을 거칩니다.

 

자동응답기 -> 상담사 -> 기술자는 체인처럼 연결되어 해당 객체에서 처리되지 않으면 다음으로 넘깁니다.

이렇게 요청 수신기가 조건을 통해 요청을 처리할지 판단하여 다음 객체로 넘길지 말지 결정하는 방식이 책임 연쇄 패턴입니다.

 

 

 

Structure

 

GoF Design Pattern - Chain of Responsibility

 

Handler

- 요청을 처리하기 위한 인터페이스를 정의합니다.
- (옵션) 다음 객체로 연결하는 successor link(이하 후속 링크)를 구현합니다.

 

ConcreteHandler

- ConcreteHandler가 요청을 처리할 수 있는 경우 요청을 처리하고, 그렇지 않은 경우 요청을 후속 객체(successor)에게 전달합니다.

- 자신이 담당하는 요청을 처리합니다.

- 후속 객체에 접근할 수 있습니다.

 

 

Client

- 체인의 시작점 ConcreteHandler 객체에 요청을 보냅니다.

 

 

 

Interface

여기서 중요한 부분은, 모든 Handler가 동일한 인터페이스를 구현하는 것입니다.

각각의 ConcreteHandler는 execute 메소드가 있는 연결된 다음 Handler에만 신경 써야 합니다.

 

이렇게 해서 런타임에 체인을 구성할 수 있으며,

이것은 여러 ConcreteHandler 사이에 느슨한 결합만으로 다양한 Handler를 사용할 수 있습니다. 

 

 

 

Sample Code: Java

한 쇼핑몰에서 사용할 유저 인증 기능을 구현한다고 해봅시다. 

관리자 권한으로 들어갈 수 있는 페이지가 있어서 권한 제한이 필요합니다.

그래서 로그인 시, 관리자인지 일반 유저인지를 검증하는 로직이 필요합니다.

 

실제 로그인 상황을 떠올려보면 검증이 필요한 세 부분을 확인할 수 있어요.

가장 먼저, 사용자가 너무 많은 로그인 시도를 했을 때를 제한합니다.

그리고 나서, 존재하는 이메일인지의 여부와 존재한다면, 해당 이메일에 맞는 비밀번호인지를 확인합니다.

 

하지만, 모든 페이지마다 위의 검증 코드를 삽입한다면, 아주 무겁고 복잡한 코드가 될 거에요.

그래서 각각의 검증 과정을 각각의 미들웨어로 작성해볼게요.

이제부터 세 개의 미들웨어를 작성합니다.

 

1. ThrottlingMiddleware - 유저가 로그인 시도를 n회 이상할 때 제한합니다.

만약 n회 이상이 아니라면,

2. UserExistsMiddleware - 존재하는 유저의 이메일인지, 등록된 이메일에 부합하는 비밀번호인지를 판단합니다.

만약 이메일과 비밀번호가 일치한다면,

3. RoleCheckMiddleware - 해당 유저가 admin계정인지, 혹은 일반 user인지 확인합니다.

 

 

Handler

public abstract class Middleware {
    private Middleware next;

    public Middleware linkWith(Middleware next) {
        this.next = next;
        return next;
    }

    public abstract boolean check(String email, String password);

    protected boolean checkNext(String email, String password) {
        if (next == null) {
            return true;
        }
        return next.check(email, password);
    }
}

 

위에서 말했지만, Handler 인터페이스를 통일하는 것이 가장 중요합니다.

Handler 인터페이스를 확인해보면 next라는 속성을 가집니다. 

Chain을 만들 때의 핵심 기능으로, 다음 Handler의 참조를 가짐으로써 실행할 수 있는 고리가 생성되는 거죠.

 

linkWith 메서드를 통해 체인을 연결하고, check(handleRequest)로 체인을 통과할 요청을 처리합니다.

 

 

 

ConcreteHandler

public class ThrottlingMiddleware extends Middleware {
    private int limit;
    private int request = 0;

    public ThrottlingMiddleware(int limit) {
        this.limit = limit;
    }

    public boolean check(String email, String password) {
        request++;

        if (request > limit) {
            System.out.println("Request limit exceeded!");
            Thread.currentThread().stop();
        }
        return checkNext(email, password);
    }
}
public class UserExistsMiddleware extends Middleware {
    private Server server;

    public UserExistsMiddleware(Server server) {
        this.server = server;
    }

    public boolean check(String email, String password) {
        if (!server.hasEmail(email)) {
            System.out.println("This email is not registered!");
            return false;
        }
        if (!server.isValidPassword(email, password)) {
            System.out.println("Wrong password!");
            return false;
        }
        return checkNext(email, password);
    }
}
public class RoleCheckMiddleware extends Middleware {
    public boolean check(String email, String password) {
        if (email.equals("admin@example.com")) {
            System.out.println("Hello, admin!");
            return true;
        }
        System.out.println("Hello, user!");
        return checkNext(email, password);
    }
}

 

check 메서드에 공통적인 패턴이 있습니다.

요청을 처리할지를 Handler 객체 자체가 결정하고 처리합니다.

 

 

 

Client

public class Client {
    private static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    private static Server server;

    private static void init() {
        server = new Server();
        server.register("admin@example.com", "admin_pass");
        server.register("user@example.com", "user_pass");

        Middleware middleware = new ThrottlingMiddleware(2);
        middleware.linkWith(new UserExistsMiddleware(server))
                .linkWith(new RoleCheckMiddleware());

        server.setMiddleware(middleware);
    }

    public static void main(String[] args) throws IOException {
        init();

        boolean success;
        do {
            System.out.print("Enter email: ");
            String email = reader.readLine();
            System.out.print("Input password: ");
            String password = reader.readLine();
            success = server.logIn(email, password);
        } while (!success);
    }
}

 

 

Server

public class Server {
    private Map<String, String> users = new HashMap<>();
    private Middleware middleware;

    public void setMiddleware(Middleware middleware) {
        this.middleware = middleware;
    }

    public boolean logIn(String email, String password) {
        if (middleware.check(email, password)) {
            System.out.println("Authorization have been successful!");
            return true;
        }
        return false;
    }

    public void register(String email, String password) {
        users.put(email, password);
    }

    public boolean hasEmail(String email) {
        return users.containsKey(email);
    }

    public boolean isValidPassword(String email, String password) {
        return users.get(email).equals(password);
    }
}

 

체인처럼 연결된 구조가 보이나요? 

위의 코드를 실행하면 아래의 출력물을 볼 수 있습니다.

 

 

Output

Enter email: user@example.com
Input password: user_pass
Hello, user!
Authorization have been successful!

========== Admin Test ======

Enter email: admin@example.com
Input password: admin_pass
Hello, admin!
Authorization have been successful!

 

 

 

특징

✔️ 결합도 감소

Reduced coupling

객체가 요청을 "적절히" 처리한다는 것만 알면 되고, 어떤 객체가 처리하는지 알 필요가 없습니다.

모든 요청을 수신하는 객체를 참조하지 않아도 후속 객체에 단 한개만 참조하면 됩니다.

 

✔️ 유연성

flexibility

이 패턴은 객체 간에 책임을 분산해서 객체 책임의 유연성을 더합니다. 
런타임에 체인을 추가하거나 변경해서 요청 처리에 대한 책임을 추가하거나 변경할 수 있습니다. 

정적으로 handler를 전문화해서 서브클래스로 결합할 수 있습니다.


✔️ 수신 보장 X

Receipt isn't guaranteed

요청에는 명시적 수신자가 없을 수 있기 때문에, 요청이 처리될 것이라는 보장은 없습니다. 

요청이 처리되지 않고 체인 끝에서 떨어질 수 있습니다. 

체인이 제대로 구성되지 않은 경우에도 요청이 처리되지 않을 수 있습니다.

 

 

 

 

관련 패턴

C- Creational Patterns  |  S - Structural Patterns  |  B - Behavioral Patterns

 

 

S: Composite

체인 패턴의 구조는 복합적인 사슬 구조로 엮여 있고 내부 구조는 복합체 패턴과 유사합니다.

 

 

B: Command

핸들러를 통해 위임된 객체의 메서드를 호출할 때 명령 패턴이 같이 사용됩니다.

 

 

 

 

 

그럼 지금까지 Chain of Responsibility Pattern에 대해 알아보았습니다.

오타나 잘못된 내용이 있다면 댓글로 남겨주세요!

감사합니다 ☺️ 

 

 

 

모든 Design Patterns 모아보기

 

Design Patterns

안녕하세요. GoF 디자인 패턴을 정리하고자 합니다. 디자인 패턴은 일주일 전부터 공부를 시작했는데, 스스로 설명하듯 적는게 익히는데 도움이 클 것같아 정말 오랜만에 시리즈로 포스팅하려

gngsn.tistory.com

 

 

'ETC > Design Patterns' 카테고리의 다른 글

Design Pattern, Interpreter  (0) 2022.02.21
Design Pattern, Bridge  (0) 2022.02.20
Design Pattern, Flyweight  (0) 2022.02.18
Design Pattern, Prototype  (0) 2022.02.17
Design Pattern, Memento  (0) 2022.02.17