본문 바로가기
Design Pattern/행동 패턴(Behavioral patterns)

Chain of Responsibility Pattern, 책임 연쇄 패턴

by codeyaki 2024. 2. 27.
반응형

책임 연쇄 패턴이란?

요청을 사슬처럼 여러 독립적인 핸들러 객체를 순차적으로 지나치면서 각자 가지고 있는 책임을 해결하는 방법이다. 

마치 공장에서 각 장치별로 담당하는 부분만 처리하는 것과 같다.

대표적으로 스프링의 필터가 이러한 책임 연쇄 패턴을 이용하고 있다.

 

왜 사용해야 하는가?

  • 요청의 처리 순서를 제어할 수 있다.
  • 단일 책임 원칙을 지킬 수 있다. (작업하는 각각의 객체들의 책임 분리)
  • 개방/폐쇄 원칙을 지킬 수 있다. (새로운 핸들러추가시 기존 코드 수정하지 않는다.

언제 사용해야 하는가?

  • 요청 처리가 여러 단계로 이루어져야 하는 경우(하지만 요청 유형과 순서들을 미리 알 수 없는 경우)
  • 런타임시에 동적으로 핸들러를 관리해야 하는 경우
  • 핸들러를 특정 순서로 실행해야 하는 경우

구현 예시 (자바)

구현의 예시로 인증, 인가 체크를 하는 핸들러를 구현해보자.

먼저 기본 핸들러를 만들어 준다.

package com.example.designpattern.behavioral.cor.handler;

public abstract class Handler {
    private Handler next;

    public static Handler link(Handler first, Handler... chain) {
        Handler head = first;
        for (Handler nextInChain : chain) {
            head.next = nextInChain;
            head = nextInChain;
        }
        return first;
    }

    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);
    }
}
  • link메서드를 통해서 한 번에 핸들러들을 묶을 수 있도록 만든다.
  • check는 실제 객체들이 수행할 메서드로 abstract로 지정하여 각 핸들러에서 메서드를 구현하도록 한다.
  • checkNext에는 핸들러에 체인이 등록되어 있으면 다음 핸들러의 check메서드를 수행할 수 있도록 한다

먼저 인증 처리를 담당하는 핸들러를 만들어 보자

package com.example.designpattern.behavioral.cor.handler;

import com.example.designpattern.behavioral.cor.server.Server;

public class UserExistsHandler extends Handler {
    private Server server;

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

    @Override
    public boolean check(String email, String password) {
        if (!server.hasEmail(email)) {
            System.out.println("해당 이메일은 가입되지 않았습니다.");
            return false;
        }
        if (!server.isValidPassword(email, password)) {
            System.out.println("비밀번호가 틀렸습니다.");
            return false;
        }

        System.out.println("인증 성공");
        return checkNext(email, password);
    }
}
  • 로그인정보를 가지고 있는 서버를 통해서 해당 이메일과 패스워드가 적합한지 확인하는 과정이다.

다음으로 인가 체크를 담당하는 핸들러를 만들어 준다.

package com.example.designpattern.behavioral.cor.handler;

public class RoleCheckHandler extends Handler {
    @Override
    public boolean check(String email, String password) {
        if (email.equals("admin@example.com")) {
            System.out.println("반갑습니다, 어드민!");
            return true;
        }
        System.out.println("반갑습니다, 일반유저!");
        return checkNext(email, password);
    }
}

 

  • 이것도 유연하게 어드민계정을 등록하는 식으로 하면 좋겠지만 일단 하드코딩으로 넣어두었다. (이게 중점이 아니므로...)

 

이런 식으로 check 메서드를 목적에 알맞게 구현하고 마지막에 다음 핸들러를 호출하는 방식으로 핸들러를 구현하여 주면 된다.

 

마지막으로 이 핸들러들을 이용할 서버를 구현해 주자.

package com.example.designpattern.behavioral.cor.server;

import com.example.designpattern.behavioral.cor.handler.Handler;
import lombok.Setter;

import java.util.HashMap;
import java.util.Map;

public class Server {
    private Map<String, String> users = new HashMap<>();
    @Setter
    private Handler handler;

    public boolean logIn(String email, String password) {
        if (handler.check(email, password)) {
            System.out.println("인증 성공");
            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);
    }
    
}

 

클라이언트는 이제 간단하게 로그인 로직을 사용할 수 있다.

 

package com.example.designpattern.behavioral.cor;

import com.example.designpattern.behavioral.cor.handler.Handler;
import com.example.designpattern.behavioral.cor.handler.RoleCheckHandler;
import com.example.designpattern.behavioral.cor.handler.UserExistsHandler;
import com.example.designpattern.behavioral.cor.server.Server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class CorClient {
    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");

        Handler handler = Handler.link(
                new UserExistsHandler(server),
                new RoleCheckHandler()
        );

        server.setHandler(handler);
    }

    public static void main(String[] args) throws IOException {
        init();
        boolean success;
        do {
            System.out.print("이메일을 입력하시오: ");
            String email = reader.readLine();
            System.out.print("패스워드를 입력하시오: ");
            String password = reader.readLine();
            success = server.logIn(email, password);
        } while (!success);

    }
}

 

실행 결과

이메일을 입력하시오: awdw
패스워드를 입력하시오: qwads
해당 이메일은 가입되지 않았습니다.
이메일을 입력하시오: admin@example.com
패스워드를 입력하시오: wefw
비밀번호가 틀렸습니다.
이메일을 입력하시오: admin@example.com
패스워드를 입력하시오: admin_pass
인증 성공
반갑습니다, 어드민!
인증 성공
반응형