์ฑ…์ž„ ์—ฐ์‡„ ํŒจํ„ด์€ ๋ฌด์—‡์ผ๊นŒ?

์œ„ํ‚ค์— ์‹ค๋ฆฐ ์˜ˆ์‹œ

@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);
    }
}
  • ๋‚ด๊ฐ€ ์ฒ˜๋ฆฌ๋ชปํ•˜๋ฉด ๋‹ค์Œ์œผ๋กœ ๋„˜๊ฒจ
  • ๋‚ด๊ฐ€ ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ๋‚ด๊ฐ€ ํ•˜๊ณ  ๋!

Reference