책임 연쇄 패턴은 무엇일까?
위키에 실린 예시
@FunctionalInterface
public interface Logger {
public enum LogLevel {
INFO, DEBUG, WARNING, ERROR, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;
public static LogLevel[] all() {
return values();
}
}
abstract void message(String msg, LogLevel severity);
default Logger appendNext(Logger nextLogger) {
return (msg, severity) -> {
message(msg, severity);
nextLogger.message(msg, severity);
};
}
static Logger writeLogger(LogLevel[] levels, Consumer<String> stringConsumer) {
EnumSet<LogLevel> set = EnumSet.copyOf(Arrays.asList(levels));
return (msg, severity) -> {
if (set.contains(severity)) {
stringConsumer.accept(msg);
}
};
}
static Logger consoleLogger(LogLevel... levels) {
return writeLogger(levels, msg -> System.err.println("Writing to console: " + msg));
}
static Logger emailLogger(LogLevel... levels) {
return writeLogger(levels, msg -> System.err.println("Sending via email: " + msg));
}
static Logger fileLogger(LogLevel... levels) {
return writeLogger(levels, msg -> System.err.println("Writing to Log File: " + msg));
}
}
class Runner {
public static void main(String[] args) {
// Build an immutable chain of responsibility
Logger logger = consoleLogger(LogLevel.all())
.appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))
.appendNext(fileLogger(LogLevel.WARNING, LogLevel.ERROR));
// Handled by consoleLogger since the console has a LogLevel of all
logger.message("Entering function ProcessOrder().", LogLevel.DEBUG);
logger.message("Order record retrieved.", LogLevel.INFO);
// Handled by consoleLogger and emailLogger since emailLogger implements Functional_Error & Functional_Message
logger.message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FUNCTIONAL_ERROR);
logger.message("Order Dispatched.", LogLevel.FUNCTIONAL_MESSAGE);
// Handled by consoleLogger and fileLogger since fileLogger implements Warning & Error
logger.message("Customer Address details missing in Branch DataBase.", LogLevel.WARNING);
logger.message("Customer Address details missing in Organization DataBase.", LogLevel.ERROR);
}
}- 이건 좀 어려우니 정리해서 보자.
Logger Class
public abstract class Logger {
private EnumSet<LogLevel> logLevels;
private Logger next;
public Logger(LogLevel[] levels) {
this.logLevels = EnumSet.copyOf(Arrays.asList(levels));
}
public Logger setNext(Logger next) {
this.next = next;
return this.next;
}
- 추상 클래스로 선언하여 공통된 동작을 지정함
logLevel이라는 enum set으로 값들을 집합으로 들고 있고next로 나다음의 로거를 가지고 있다.- 생성자에서
levels를 받는데, 이는 해당 로그에서 처리할 레벨을 정해준다.- 처리할 메시지들의 타입을 정해줌
setNext를 보니 Fluent Interface와 비슷하게 생겼다.- 근데 문제는 자기 자신을 반환하는게 아니고
next를 반환한다.- 응?
- fluent에서 한단계 더 나아갔네?
- 클라이언트 쪽에서는 자기를 반환하겠지 하고 생각하지만 그게 아닌 꼴
- 좋은 디자인인 것 같지는 않음
public final void message(String msg, LogLevel severity) {
if (logLevels.contains(severity)) {
log(msg);
}
if (this.next != null) {
this.next.message(msg, severity);
}
}
protected abstract void log(String msg);
} message함수는final이니 상속 불가다.- 메시지하고 로그 레벨 보내서 찍는 함수
- 내부적으로 로거가 처리가능한 레벨인지 확인하고 찍어줌
- 그리고 다음 로거에 전달해서 똑같이 찍어달라고 요청
log함수는 상속받는 클래스에서 구현
ConsoleLogger Class
public class ConsoleLogger extends Logger {
public ConsoleLogger(LogLevel[] levels) {
super(levels);
}
@Override
protected void log(String msg) {
System.err.println("Writing to console: " + msg);
}
}EmailLogger Class
public class EmailLogger extends Logger {
public EmailLogger(LogLevel[] levels) {
super(levels);
}
@Override
protected void log(String msg) {
System.err.println("Sending via email: " + msg);
}
}FileLogger Class
public class FileLogger extends Logger {
public FileLogger(LogLevel[] levels) {
super(levels);
}
@Override
protected void log(String msg) {
System.err.println("Writing to file: " + msg);
}
}enum LogLevel
public enum LogLevel {
INFO,
DEBUG,
WARNING,
ERROR,
FUNCTIONAL_MESSAGE,
FUNCTIONAL_ERROR;
public static LogLevel[] all() {
return values();
}
}실제로 사용해보기
Logger logger = new ConsoleLogger(LogLevel.all());
logger
.setNext(new EmailLogger(new LogLevel[]{LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR}))
.setNext(new FileLogger(new LogLevel[]{LogLevel.WARNING, LogLevel.ERROR}));
// ConsoleLogger에서 처리 -> 모든 로그레벨 처리 가능
logger.message("Entering function ProcessOrder().", LogLevel.DEBUG);
logger.message("Order record retrieved.", LogLevel.INFO);
// ConsoleLogger, EmailLogger에서 처리 가능
logger.message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FUNCTIONAL_ERROR);
logger.message("Order Dispatched.", LogLevel.FUNCTIONAL_MESSAGE);next로 메시지를 호출하게 해서 연쇄적으로 다음 친구를 호출할 수 있게 된다.

올바른 책임 연쇄 패턴 예시
public final class LogManager {
private static LogManager instance;
private ArrayList<Logger> loggers = new ArrayList<Logger>();
public static LogManager getInstance() {
if (instance == null) {
instance = new LogManager();
}
return instance;
}
public void addHandler(Logger logger) {
this.loggers.add(logger);
}
public void message(String msg, LogLevel severity) {
for (Logger logger : this.loggers) {
logger.message(msg, severity);
}
}
}- 왜 굳이 next 호출해줌?
- 이걸 다 관리하는 객체 하나만 있으면 되는데?
public abstract class Logger {
private EnumSet<LogLevel> logLevels;
public Logger(LogLevel[] levels) {
this.logLevels = EnumSet.copyOf(Arrays.asList(levels));
}
public final void message(String msg, LogLevel severity) {
if (logLevels.contains(severity)) {
log(msg);
}
}
protected abstracy void log(String msg);
}- next가 없다.
- 나는 그냥 로그하나 찍고 끝!
LogManager logManager = LogManager.getInstance();
logManager.addHandler(new ConsoleLogger(LogLevel.all()));
logManager.addHandler(new EmailLogger(new LogLevel[]{LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUINCTIONAL_ERROR}));
logManager.message("Entering function ProcessOrder().", LogLevel.DEBUG);- 이게 훨씬 간단..
- 굳이 “책임 연쇄 패턴” 이라는 것을 설명하기 위해 위의 예를 갖다둔게 잘못
- 디자인 패턴 잘못 배우면 위험하다의 단적인 예
- “연쇄”는 확인함. 근데 “책임”은 어디?
올바른 책임 연쇄 패턴
- 어떤 메시지를 처리할 수 있는 여러 개체가 있음
- 이 개체들은 차례대로 메시지를 처리할 수 있는 기회를 받음
- 만약 그 중 한 개체가 메시지를 처리하면 그것에 대한 책임을 짐
- 즉 다음 개체는 메시지를 처리할 기회를 받지 못함
- 그래서 “책임” 연쇄 패턴임
- iOS에서는 Responder chain이 그예라 할 수 있겠다.
위키피디아를 제대로 바꾸면..
public final void message(String msg, LogLevel severity) {
if (logLevels.contains(severity)) {
log(msg);
}
if (this.next != null) {
this.next.message(msg, severity);
}
}public final void message(String msg, LogLevel severity) {
if (logLevels.contains(severity)) {
log(msg);
} else if (this.next != null) {
this.next.message(msg, severity);
}
}- 내가 처리못하면 다음으로 넘겨
- 내가 할 수 있으면 내가 하고 끝!