Chain of Responsibility Pattern
객체 지향 디자인 패턴 중 하나로, 요청을 처리하는 객체들을 연결하여 처리의 책임을 분산시키는 패턴입니다.
이 패턴은 일종의 체인으로 구성되어 있으며, 하나의 요청을 받은 객체가 해당 요청을 처리하지 못할 경우,
이를 다음 객체로 전달하여 계속해서 요청 처리를 시도합니다.
이러한 과정에서 요청을 처리할 객체를 찾을 때까지 요청이 계속해서 전달되며, 이를 통해 요청 처리의 책임이 분산됩니다.
이 패턴은 보안 검사, 로깅 등의 작업에서 유용하게 사용될 수 있습니다.
예를 들어, 여러 개의 보안 검사를 수행하는 시스템에서, 각 검사를 담당하는 객체들을 체인으로 연결하여 요청이 전달되는 과정에서 필요한 보안 검사를 수행할 수 있습니다. 또한, 이 패턴은 객체 간 결합도를 낮추고, 유연성과 확장성을 높이는 데에도 도움이 됩니다.
이 패턴을 구현하기 위해서는, 요청 처리 객체들이 각각의 인터페이스를 구현하고, 요청 처리에 대한 메소드를 구현해야 합니다.
또한, 객체 간의 연결 관계를 설정할 수 있는 방법이 필요하며, 일반적으로는 체인 형태의 구조를 가진 리스트나 연결 리스트를 사용합니다.
여러 객체들이 연결되어 있는 형태이기 때문에, 요청 처리 과정에서 루프를 돌지 않도록 주의해야 합니다. 특히, 요청 처리 객체가 너무 많아지면, 체인의 끝까지 요청이 전달되지 못하거나, 처리 속도가 느려지는 등의 문제가 발생할 수 있으므로, 적절한 객체 수를 유지해야 합니다.
예제1)
#include <iostream>
#include <string>
class Validator {
public:
virtual void setNext(Validator* next) = 0;
virtual void validate(std::string data) = 0;
};
class LengthValidator : public Validator {
public:
void setNext(Validator* next) override {
this->next = next;
}
void validate(std::string data) override {
if (data.length() > 10) {
std::cout << "Data too long!" << std::endl;
return;
}
if (next != nullptr) {
next->validate(data);
}
}
private:
Validator* next = nullptr;
};
class SymbolValidator : public Validator {
public:
void setNext(Validator* next) override {
this->next = next;
}
void validate(std::string data) override {
if (data.find_first_of("!@#$%^&*()") != std::string::npos) {
std::cout << "Data contains forbidden symbols!" << std::endl;
return;
}
if (next != nullptr) {
next->validate(data);
}
}
private:
Validator* next = nullptr;
};
class NumberValidator : public Validator {
public:
void setNext(Validator* next) override {
this->next = next;
}
void validate(std::string data) override {
if (data.find_first_of("0123456789") != std::string::npos) {
std::cout << "Data contains numbers!" << std::endl;
return;
}
if (next != nullptr) {
next->validate(data);
}
}
private:
Validator* next = nullptr;
};
int main() {
Validator* validator = new LengthValidator();
validator->setNext(new SymbolValidator());
validator->setNext(new NumberValidator());
validator->validate("test123");
validator->validate("test123!!!"); // Should fail length and symbol validation
validator->validate("testtesttest"); // Should fail length validation
validator->validate("testtesttest123"); // Should pass all validations
return 0;
}
이 예제 코드는 문자열의 길이, 특수문자, 숫자 등을 검사하는 간단한 예제입니다.
각 검사를 담당하는 클래스를 구현하고, 각 클래스들을 연결하여 체인을 만들고, 체인 상에서 검사를 수행합니다
인터페이스 : setNext(Validator* next), validate(std::string data)
객체 간의 연결 관계 : 연결 리스트
예제2) 파일 업로드 시 보안 검사를 수행하는 시스템
#include <iostream>
#include <string>
#include <vector>
class SecurityCheck {
public:
virtual void check(const std::string& file) = 0;
};
class FileExtensionCheck : public SecurityCheck {
public:
void check(const std::string& file) override {
if (file.find(".exe") != std::string::npos ||
file.find(".dll") != std::string::npos ||
file.find(".bat") != std::string::npos) {
std::cout << "File extension not allowed!" << std::endl;
return;
}
std::cout << "File extension check passed." << std::endl;
}
};
class FileSizeCheck : public SecurityCheck {
public:
void check(const std::string& file) override {
if (file.length() > 1024 * 1024) {
std::cout << "File size too big!" << std::endl;
return;
}
std::cout << "File size check passed." << std::endl;
}
};
class VirusScanCheck : public SecurityCheck {
public:
void check(const std::string& file) override {
std::cout << "Scanning for viruses..." << std::endl;
// perform virus scan here
std::cout << "Virus scan check passed." << std::endl;
}
};
class SecurityCheckSystem {
public:
void addCheck(SecurityCheck* check) {
checks.push_back(check);
}
void runChecks(const std::string& file) {
for (SecurityCheck* check : checks) {
check->check(file);
}
}
private:
std::vector<SecurityCheck*> checks;
};
int main() {
SecurityCheckSystem system;
system.addCheck(new FileExtensionCheck());
system.addCheck(new FileSizeCheck());
system.addCheck(new VirusScanCheck());
std::string filename = "example.txt";
system.runChecks(filename);
return 0;
}
이 예제 코드는 파일 확장자, 파일 크기, 바이러스 검사를 수행하는 간단한 보안 검사 시스템입니다.
각 검사를 수행하는 클래스를 구현하고, 이를 SecurityCheck 인터페이스로 추상화합니다.
그리고 SecurityCheckSystem 클래스에서는 이러한 검사들을 추가하고, 파일에 대해 이러한 검사들을 수행합니다.
이때, SecurityCheckSystem 클래스는 SecurityCheck 인터페이스를 사용하여 다형성을 지원합니다.
예제3) 로깅 시스템
#include <iostream>
#include <string>
#include <vector>
// Abstract base class for loggers
class Logger {
public:
virtual ~Logger() {}
virtual void log(const std::string& message, int level) = 0;
};
// Console logger
class ConsoleLogger : public Logger {
public:
void log(const std::string& message, int level) override {
std::cout << "Console logger: " << message << " (level " << level << ")" << std::endl;
}
};
// File logger
class FileLogger : public Logger {
public:
FileLogger(const std::string& filename) : filename(filename) {}
void log(const std::string& message, int level) override {
std::cout << "File logger: " << message << " (level " << level << ") -> " << filename << std::endl;
// write message to file here
}
private:
std::string filename;
};
// Email logger
class EmailLogger : public Logger {
public:
EmailLogger(const std::string& email) : email(email) {}
void log(const std::string& message, int level) override {
std::cout << "Email logger: " << message << " (level " << level << ") -> " << email << std::endl;
// send email here
}
private:
std::string email;
};
// Chain of responsibility class
class LoggerChain {
public:
LoggerChain(Logger* logger) : logger(logger) {}
void addLogger(Logger* newLogger) {
if (nextLogger) {
nextLogger->addLogger(newLogger);
} else {
nextLogger = new LoggerChain(newLogger);
}
}
void log(const std::string& message, int level) {
if (level <= logLevel) {
logger->log(message, level);
}
if (nextLogger) {
nextLogger->log(message, level);
}
}
void setLogLevel(int level) {
logLevel = level;
}
private:
Logger* logger;
LoggerChain* nextLogger = nullptr;
int logLevel = 0;
};
int main() {
// Create loggers
Logger* consoleLogger = new ConsoleLogger();
Logger* fileLogger = new FileLogger("log.txt");
Logger* emailLogger = new EmailLogger("admin@example.com");
// Create chain of responsibility
LoggerChain chain(consoleLogger);
chain.addLogger(fileLogger);
chain.addLogger(emailLogger);
// Set log level
chain.setLogLevel(2);
// Log messages
chain.log("This is a debug message", 1);
chain.log("This is an informational message", 2);
chain.log("This is a warning message", 3);
return 0;
}
이 예제 코드는 로그를 출력하는 Logger 추상 클래스와 이를 구현하는 ConsoleLogger, FileLogger, EmailLogger 클래스를 정의합니다. 그리고 LoggerChain 클래스에서는 이러한 로그를 처리하는 체인을 구성합니다.
LoggerChain 클래스는 Logger 객체를 멤버로 가지며, 체인의 다음 로거를 가리키는 포인터를 가집니다.
addLogger 함수에서는 새로운 로거를 추가하고, log 함수에서는 현재 로거에서 로그를 출력하고, setLogLevel 함수는 로그 레벨을 설정하며, main 함수에서는 각 로거를 생성하고 LoggerChain 클래스를 사용하여 로그 메시지를 출력합니다.
Console logger: This is an informational message (level 2)
File logger: This is an informational message (level 2) -> log.txt
Email logger: This is an informational message (level 2) -> admin@example.com
File logger: This is a warning message (level 3) -> log.txt
Email logger: This is a warning message (level 3) -> admin@example.com
이 예제에서는 로그 레벨이 2로 설정되어 있으므로, 레벨이 2 이하인 로그 메시지만 출력됩니다.
또한, LoggerChain 클래스를 사용하여 로그를 처리하는 체인을 구성하여, 여러 로거를 연결하여 로그 메시지를 출력할 수 있습니다.
+ Improvement
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
// Abstract base class for loggers
class Logger {
public:
virtual ~Logger() {}
virtual void log(const std::string& message, int level) = 0;
};
// Console logger
class ConsoleLogger : public Logger {
public:
void log(const std::string& message, int level) override {
if (level <= m_level) {
std::cout << "Console logger: " << message << std::endl;
}
}
void setLevel(int level) { m_level = level; }
private:
int m_level = 1;
};
// File logger
class FileLogger : public Logger {
public:
FileLogger(const std::string& filename) : m_filename(filename) {}
void log(const std::string& message, int level) override {
if (level <= m_level) {
std::ofstream file(m_filename, std::ios_base::app);
file << "File logger: " << message << " -> " << m_filename << std::endl;
}
}
void setLevel(int level) { m_level = level; }
private:
std::string m_filename;
int m_level = 2;
};
// Email logger
class EmailLogger : public Logger {
public:
EmailLogger(const std::string& email) : m_email(email) {}
void log(const std::string& message, int level) override {
if (level <= m_level) {
std::cout << "Email logger: " << message << " -> " << m_email << std::endl;
}
}
void setLevel(int level) { m_level = level; }
private:
std::string m_email;
int m_level = 3;
};
// Logger chain
class LoggerChain {
public:
void addLogger(Logger* logger) {
m_loggers.push_back(logger);
}
void log(const std::string& message, int level) {
for (auto logger : m_loggers) {
logger->log(message, level);
}
}
private:
std::vector<Logger*> m_loggers;
};
// Logging system
class LoggingSystem {
public:
LoggingSystem() {
m_loggerChain.addLogger(&m_consoleLogger);
m_loggerChain.addLogger(&m_fileLogger);
m_loggerChain.addLogger(&m_emailLogger);
}
void setLogLevel(int level) {
m_consoleLogger.setLevel(level);
m_fileLogger.setLevel(level);
m_emailLogger.setLevel(level);
}
void log(const std::string& message, int level) {
m_loggerChain.log(message, level);
}
private:
ConsoleLogger m_consoleLogger;
FileLogger m_fileLogger{"log.txt"};
EmailLogger m_emailLogger{"admin@example.com"};
LoggerChain m_loggerChain;
};
// Main function
int main() {
LoggingSystem loggingSystem;
loggingSystem.setLogLevel(2);
loggingSystem.log("This is an informational message", 2);
loggingSystem.log("This is a warning message", 3);
return 0;
}