Abstract Factory Pattern은 관련된 객체군을 생성하기 위한 일관적인 인터페이스를 제공하는 패턴이다.
// AbstractProduct - Button
interface Button {
void paint();
}
// ConcreteProduct - WindowsButton
class WindowsButton implements Button {
@Override
public void paint() {
System.out.println("Windows 스타일의 버튼을 그립니다.");
}
}
// ConcreteProduct - MacButton
class MacButton implements Button {
@Override
public void paint() {
System.out.println("Mac 스타일의 버튼을 그립니다.");
}
}
// AbstractProduct - Checkbox
interface Checkbox {
void paint();
}
// ConcreteProduct - WindowsCheckbox
class WindowsCheckbox implements Checkbox {
@Override
public void paint() {
System.out.println("Windows 스타일의 체크박스를 그립니다.");
}
}
// ConcreteProduct - MacCheckbox
class MacCheckbox implements Checkbox {
@Override
public void paint() {
System.out.println("Mac 스타일의 체크박스를 그립니다.");
}
}
// AbstractFactory
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
// ConcreteFactory - WindowsFactory
class WindowsFactory implements GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public Checkbox createCheckbox() {
return new WindowsCheckbox();
}
}
// ConcreteFactory - MacFactory
class MacFactory implements GUIFactory {
@Override
public Button createButton() {
return new MacButton();
}
@Override
public Checkbox createCheckbox() {
return new MacCheckbox();
}
}
// Client
class Application {
private Button button;
private Checkbox checkbox;
public Application(GUIFactory factory) {
button = factory.createButton();
checkbox = factory.createCheckbox();
}
public void paint() {
button.paint();
checkbox.paint();
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
GUIFactory factory;
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("win")) {
factory = new WindowsFactory();
} else {
factory = new MacFactory();
}
Application app = new Application(factory);
app.paint();
}
}
위의 예시를 통해 추상 팩토리 패턴의 장단점을 살펴보자.
Application Class를 확인해보자.
WindowsFactory나 MacFactory와 같은 구체적인 클래스 없이 인터페이스만 받아서 객체를 생성할 수 있게 된다.
Button과 같이, 하나의 AbstractProduct가 정의된다면, ConcreteProduct는 이 AbstractProduct를 구현하여야 하므로 Product 사이의 일관성이 지켜진다.
다시, Application Class를 확인해보자.
Application은 특정한 ConcreteFactory에 의존하지 않고,
AbstractFactory와 그에 의해 만들어지는 AbstractProduct군에 의존하고 있다.
따라서 WindowsFactory에 의해 생성된 팩토리를 넘겨주다가
MacFactory에 의해 생성된 팩토리를 넘겨준다 하여도, 추가적인 코드 수정이 필요없이 쉽게 대체가 가능해진다.
단일 책임 원칙은 객체가 오직 하나의 책임만 가진다는 원칙이다.
개방 폐쇄 원칙은 확장에는 열려있고, 수정에는 닫혀있도록 하는 원칙이다.
새로운 Product를 Factory에 추가하고자 할 때는 기존의 모든 ConcreteFactory 또한 수정해주어야 하므로, 새로운 Product를 제공하는 데에는 제약이 있다.
이러한 장단점을 고려해볼 때, 제공하는 객체군은 변하지만(예시 : Windows, Mac, Linux), 그 안에 들어있는 Product 자체는 변화가 크게 없는 경우, Abstract Factory Pattern의 도입을 고려해볼 수 있겠다.
Factory Method 패턴은 객체 생성을 서브클래스에 위임하는 패턴이다. 이 패턴을 사용하면 객체 생성의 유연성을 높이고, 코드의 재사용성과 유지보수성을 향상시킬 수 있다.
// Product
interface Logger {
void log(String message);
}
// ConcreteProduct
class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("Console: " + message);
}
}
class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("File: " + message);
}
}
// Creator
abstract class LoggerFactory {
public void logMessage(String message) {
Logger logger = createLogger();
logger.log(message);
}
// Factory Method
protected abstract Logger createLogger();
}
// ConcreteCreator
class ConsoleLoggerFactory extends LoggerFactory {
@Override
protected Logger createLogger() {
return new ConsoleLogger();
}
}
class FileLoggerFactory extends LoggerFactory {
@Override
protected Logger createLogger() {
return new FileLogger();
}
}
// Client code
public class Main {
public static void main(String[] args) {
LoggerFactory factory = new ConsoleLoggerFactory();
factory.logMessage("Hello, Factory Method!");
factory = new FileLoggerFactory();
factory.logMessage("Hello, Factory Method!");
}
}
LoggerFactory라는 Creator의 동작을 살펴보자면,
이 Creator가 실제로 어떤 ConcreteProduct를 생성할 지에 대해서는, 이 LoggerFactory를 상속받아 구현될 ConcreteCreator(ConsoleLoggerFactory, FileLoggerFactory)에 위임한다.
LoggerFactory에서는 생성된 Product를 어떻게 사용할지에 대해서만 관심을 가지고 있다.
logMessage라는 메서드는 Logger라는 Product에 있는 log라는 메서드를 실행시키도록 한다.
Product의 생성과 그것의 이용에 대한 관심을 잘 분리하고 있다.
위의 예시를 통해 팩토리 메서드 패턴의 장단점을 살펴보자.
LoggerFactory를 확인해보자.
ConsoleLogger나 FileLogger와 같은 구체적인 클래스 없이 인터페이스만 받아서 객체를 생성할 수 있게 된다.
단일 책임 원칙은 객체가 오직 하나의 책임만 가진다는 원칙이다.
개방 폐쇄 원칙은 확장에는 열려있고, 수정에는 닫혀있도록 하는 원칙이다.
Product마다 그에 해당하는 Creator 서브클래스를 만들어야 하므로 클래스 수가 과도하게 늘어날 수가 있다.
간단한 객체를 만드는 데에도 Factory Method를 적용하면 불필요한 복잡성이 발생할 수 있다
Abstract Factory와 다르게, 관련된 여러 Product를 한번에 생성하는 데에는 적합하지 않고, 하나의 Product에 대해 여러 ConcreteProduct를 생성할 때에만 유리하다.