Abstract Factory
Abstract Factory 패턴이란?
Abstract Factory 패턴은 관련된 객체들의 집합을 생성할 수 있는 인터페이스를 제공합니다. 구체적인 클래스에 의존하지 않고, 여러 제품군에 속하는 객체들을 일관성 있게 생성할 수 있게 합니다. 주로 시스템이 여러 관련된 객체들의 집합을 필요로 할 때, 객체 생성의 추상화를 통해 코드의 유연성과 확장성을 높일 수 있습니다.
Abstract Factory 구조
- Client → AbstractFactory
- 클라이언트는 추상 팩토리AbstractFactory를 통해 제품 생성 메서드 (
CreateProductA()
,CreateProductB()
)를 호출합니다.
- 클라이언트는 추상 팩토리AbstractFactory를 통해 제품 생성 메서드 (
- AbstractFactory → ConcreteFactory
AbstractFactory
는 생성 요청을 구체적인 팩토리(ConcreteFactory1
,ConcreteFactory2
)에 위임합니다.- 각 구체 팩토리는 관련된 제품군을 생성하는 로직을 구현합니다.
- ConcreteFactory → AbstractProduct
- 구체적인 팩토리ConcreteFactory는 추상 제품(
AbstractProductA
,AbstractProductB
)의 구현체(ConcreteProductA1
,ConcreteProductB1
)를 생성하고 반환합니다. - 구체 팩토리는 자신이 담당하는 제품군만 생성합니다.
- 구체적인 팩토리ConcreteFactory는 추상 제품(
- AbstractProduct → ConcreteProduct
- 구체적인 제품(
ConcreteProductA1
,ConcreteProductB1
등)은 추상 제품(AbstractProductA
,AbstractProductB
)을 구현하여 다형성을 제공합니다.
- 구체적인 제품(
- Client → AbstractProduct
- 클라이언트는 추상 제품(
AbstractProductA
,AbstractProductB
)을 통해 반환된 제품 객체를 사용합니다. - 구체적인 제품 클래스에 직접 의존하지 않으므로, 제품의 구현이 변경되어도 클라이언트 코드는 영향을 받지 않습니다
- 클라이언트는 추상 제품(
Abstract Factory 패턴 적용
Abstract Factory 패턴의 필요성
도서 관리 시스템에서 여러 종류의 도서를 관리하고, 도서의 종류에 따라 서로 다른 방법으로 처리해야 할 때가 있습니다. 예를 들어, 일반 도서와 전자 도서는 서로 다른 처리 방식이 필요할 수 있습니다. 이렇게 도서의 종류에 따라 객체 생성 방식이 달라지는 상황에서 Abstract Factory 패턴을 사용하면 유연하게 시스템을 설계할 수 있습니다.
잘못된 처리
일반적으로 도서의 종류마다 객체를 명시적으로 생성하고, 객체 생성 시마다 조건문을 사용하여 객체의 구체적인 클래스를 선택하는 방식은 유지보수성에 문제가 발생할 수 있습니다.
public class BookStore
{
public void CreateBook(string type)
{
if (type == "ebook")
{
Console.WriteLine("Creating an E-Book.");
}
else if (type == "paperbook")
{
Console.WriteLine("Creating a Paper Book.");
}
else if (type == "audiobook")
{
Console.WriteLine("Creating an Audio Book.");
}
}
}
- 객체 생성의 결합도 : 객체 생성 로직이 특정 클래스에 의존하고 있어, 새로운 도서 종류가 추가될 때마다 조건문을 수정해야 합니다.
- 코드의 확장성 부족 : 새로운 도서 종류가 추가될 때마다 기존 코드를 수정해야 하므로, 코드의 확장성이 떨어집니다.
패턴 적용 예시
Abstract Factory 패턴을 적용하면, 객체 생성에 대한 책임을 팩토리 클래스에 위임하여 구체적인 클래스에 의존하지 않고, 제품군에 속하는 객체를 일관성 있게 생성할 수 있습니다.
// 제품 인터페이스: 책
public interface IBook
{
void Read();
}
// 구체적인 제품 클래스: 전자책
public class EBook : IBook
{
public void Read() => Console.WriteLine("Reading an E-Book.");
}
// 구체적인 제품 클래스: 종이책
public class PaperBook : IBook
{
public void Read() => Console.WriteLine("Reading a Paper Book.");
}
// 구체적인 제품 클래스: 오디오북
public class AudioBook : IBook
{
public void Read() => Console.WriteLine("Listening to an Audio Book.");
}
// 추상 팩토리 인터페이스
public interface IBookFactory
{
IBook CreateBook();
}
// 구체적인 팩토리 클래스: 전자책 팩토리
public class EBookFactory : IBookFactory
{
public IBook CreateBook() => new EBook();
}
// 구체적인 팩토리 클래스: 종이책 팩토리
public class PaperBookFactory : IBookFactory
{
public IBook CreateBook() => new PaperBook();
}
// 구체적인 팩토리 클래스: 오디오북 팩토리
public class AudioBookFactory : IBookFactory
{
public IBook CreateBook() => new AudioBook();
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
IBookFactory factory = new EBookFactory();
IBook ebook = factory.CreateBook();
ebook.Read();
// Output: Reading an E-Book.
factory = new PaperBookFactory();
IBook paperBook = factory.CreateBook();
paperBook.Read();
// Output: Reading a Paper Book.
factory = new AudioBookFactory();
IBook audioBook = factory.CreateBook();
audioBook.Read();
// Output: Listening to an Audio Book.
}
}
패턴 구성 요소
Product
팩토리에서 생성할 제품들의 공통 인터페이스를 정의합니다.
예제에서는 IBook
이 제품 인터페이스 역할을 합니다.
Concrete Product
팩토리에서 생성하는 구현 클래스입니다.
예제에서는 EBook
, PaperBook
, AudioBook
이 구체적인 제품 역할을 합니다.
Abstract Factory
객체를 생성하는 팩토리 인터페이스입니다.
예제에서는 IBookFactory
가 그 역할을 합니다.
Concrete Factory
각각의 제품군에 대한 구체적인 객체 생성 로직을 구현합니다.
예제에서는 EBookFactory
, PaperBookFactory
, AudioBookFactory
가 구체적인 팩토리 역할을 합니다.
개선 사항
유연한 객체 생성
클라이언트는 구체적인 클래스에 의존하지 않고, 다양한 형식의 객체를 생성할 수 있으므로, 객체 생성의 유연성이 증가합니다.
확장성 증가
새로운 도서 종류가 추가되더라도, 새로운 팩토리와 제품 클래스를 추가하면 되므로 기존 코드에 영향을 주지 않고 확장할 수 있습니다.
적용 시 유의 사항
객체 수 증가
객체 생성의 책임을 분리함에 따라 팩토리 클래스와 제품 클래스가 증가할 수 있습니다. 따라서 클래스가 너무 많아지지 않도록 주의해야 합니다.
제품군 관리의 일관성
Abstract Factory 패턴은 제품군을 일관성 있게 관리하는 데 유리하지만, 제품군에 속하지 않는 예외적인 객체 생성의 경우 패턴 적용이 복잡해질 수 있습니다.
객체지향 원칙과의 관계
Abstract Factory와 단일 책임 원칙
팩토리는 객체 생성이라는 하나의 책임만을 가지므로, 단일 책임 원칙을 잘 준수합니다. 클라이언트 코드에서 객체 생성 로직이 분리되기 때문에, 객체 생성과 관련된 변경이 발생하더라도 클라이언트 코드에 영향을 미치지 않습니다.
Abstract Factory와 개방_폐쇄 원칙
새로운 제품군이 추가되더라도 기존 코드의 수정 없이 팩토리 클래스를 확장할 수 있습니다. 예를 들어, 새로운 책 형식이 추가될 때 새로운 팩토리 클래스를 정의하면 되므로, 시스템은 확장에는 열려 있고 수정에는 닫혀 있습니다.
Abstract Factory와 의존 역전 원칙
클라이언트는 구체적인 클래스가 아닌 추상 팩토리 인터페이스에 의존하므로, 의존 역전 원칙을 따릅니다. 클라이언트는 객체의 생성 방식을 몰라도 팩토리에서 제공하는 인터페이스를 통해 객체를 사용할 수 있습니다.
맺음말
Abstract Factory 패턴은 객체 생성의 유연성과 확장성을 높이는 디자인 패턴으로, 관련된 객체들을 일관된 방식으로 생성할 수 있습니다. 특히, 여러 제품군이 함께 사용될 때, 객체 생성을 분리하여 시스템의 유지보수성을 향상시키고, 클라이언트 코드의 복잡성을 줄여줍니다.
심화 학습
Abstract Factory와 Factory Method 차이
팩토리 메서드 패턴과 추상 팩토리 패턴은 모두 객체 생성에 중점을 두고 결합도를 낮추는 디자인 패턴입니다. 두 패턴의 공통점과 차이점은 다음과 같습니다:
공통점
- 객체 생성 캡슐화: 두 패턴 모두 객체 생성 로직을 캡슐화하여 클라이언트가 직접 객체를 생성하지 않고 팩토리 메서드를 통해 객체를 생성합니다.
- 구상 클래스 의존성 제거: 클라이언트는 구체적인 제품 클래스에 의존하지 않고, 추상 클래스나 인터페이스에 의존합니다.
- 확장성: 제품의 종류를 변경하거나 확장할 때 기존 클라이언트 코드를 수정하지 않고 확장이 가능합니다.
차이점
항목 | 팩토리 메서드 패턴 | 추상 팩토리 패턴 |
---|---|---|
목적 | 구체적인 한 종류의 제품을 생성하는 책임을 하위 클래스에 위임. | 서로 관련된 여러 종류의 제품을 일관성 있게 생성. |
생성되는 제품 수 | 하나의 제품을 생성. | 여러 제품군을 생성. |
결합도를 낮추는 대상 | 구체적인 제품(ConcreteProduct)과 클라이언트 간의 결합도. | 구체적인 팩토리(ConcreteFactory)와 클라이언트 간의 결합도. |
코딩 대상 | 클라이언트는 구체적인 제품 대신 팩토리 메서드를 호출. | 클라이언트는 팩토리 인터페이스를 통해 여러 제품을 일관되게 생성. |
확장성 | 새로운 제품을 추가하려면 새로운 서브클래스를 만들어야 함. | 여러 제품군을 추가하기 위해서는 새로운 팩토리와 제품군을 정의. |
코드 복잡도 | 상대적으로 간단함. | 서로 연관된 제품군이 많아질수록 복잡성이 증가. |
적용 범위 | 단일 제품군에만 적합. | 여러 관련 제품군을 생성할 때 적합. |
제품 생성 방식 | 한 종류의 제품을 생성하는 메서드를 제공. | 여러 종류의 관련된 제품을 생성하는 팩토리를 제공. |
사용 예시 | 전자책 보기 모드(라이트 모드, 다크 모드)를 선택. | 전자책 보기 모드와 글자 크기(라이트 모드 + 작은 글씨 등)를 함께 설정. |
상속과 구성을 통한 확장성
팩토리 메서드 패턴은 상속을 통해 구체적인 제품 생성을 하위 클래스에서 처리합니다. 즉, 제품의 생성 책임을 하위 클래스에 위임하며, 제품을 확장할 때 새로운 서브클래스를 추가합니다. 추상 팩토리는 제품군의 생성을 구체적인 팩토리 클래스에 위임합니다. 새로운 제품군을 추가할 때는 새로운 팩토리와 관련 제품들을 정의합니다.
결합도 제어
팩토리 메서드 패턴은 주로 제품과 클라이언트 사이의 결합도를 줄입니다. 클라이언트가 제품의 구체적인 클래스를 알 필요가 없습니다. 추상 팩토리 패턴은 여러 관련된 제품군을 생성하는 팩토리와 클라이언트 간의 결합도를 줄입니다. 클라이언트는 팩토리 인터페이스만 의존하며, 각 제품군의 일관성을 보장받습니다.
상속 기반과 의존성 주입 기반
팩토리 메서드 패턴은 상속을 사용하여 구체적인 제품의 생성을 서브클래스에서 처리합니다. 추상 팩토리 패턴은 의존성 주입Defendancy Injection, DI을 사용하여 팩토리 객체를 외부에서 주입받고, 이를 통해 여러 제품군을 생성할 수 있습니다.
예제를 통한 비교
도서 관리 시스템에서 전자책(E-book) 보기 모드와 글자 크기 설정을 관리하는 경우를 예로 들어, 팩토리 메서드 패턴과 추상 팩토리 패턴을 비교합니다.
- 팩토리 메서드 패턴 예제 : 보기 모드(라이트 모드 또는 다크 모드)만을 동적으로 선택하는 상황입니다.
// Product 인터페이스: 전자책 보기 모드
public interface IViewMode
{
void ApplyViewMode();
}
// ConcreteProduct: 라이트 모드
public class LightMode : IViewMode
{
public void ApplyViewMode() => Console.WriteLine("Light mode applied.");
}
// ConcreteProduct: 다크 모드
public class DarkMode : IViewMode
{
public void ApplyViewMode() => Console.WriteLine("Dark mode applied.");
}
// Creator 클래스: 팩토리 메서드를 정의하는 추상 클래스
public abstract class ViewModeFactory
{
public abstract IViewMode CreateViewMode();
}
// ConcreteCreator: 라이트 모드 팩토리
public class LightModeFactory : ViewModeFactory
{
public override IViewMode CreateViewMode() => LightMode();
}
// ConcreteCreator: 다크 모드 팩토리
public class DarkModeFactory : ViewModeFactory
{
public override IViewMode CreateViewMode() => new DarkMode();
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
// 보기 모드를 동적으로 선택하여 팩토리 메서드를 사용해 생성
ViewModeFactory factory = new LightModeFactory();
IViewMode viewMode = factory.CreateViewMode();
viewMode.ApplyViewMode(); // Output: Light mode applied.
factory = new DarkModeFactory();
viewMode = factory.CreateViewMode();
viewMode.ApplyViewMode(); // Output: Dark mode applied.
}
}
팩토리 메서드 패턴은 구체적인 보기 모드를 선택하여 라이트 모드 또는 다크 모드를 동적으로 생성합니다. 각 보기 모드를 적용하는 방식은 클라이언트 코드가 구체적인 모드를 알지 못한 채 팩토리를 통해 생성된 객체를 사용할 수 있습니다.
- 추상 팩토리 패턴 예제 : 보기 모드와 글자 크기를 함께 관리하여, 여러 관련된 설정을 일관성 있게 생성하는 상황입니다.
// Product 인터페이스: 보기 모드
public interface IViewMode
{
void ApplyViewMode();
}
// Product 인터페이스: 글자 크기
public interface IFontSize
{
void ApplyFontSize();
}
// ConcreteProduct: 라이트 모드
public class LightMode : IViewMode
{
public void ApplyViewMode() => Console.WriteLine("Light mode applied.");
}
// ConcreteProduct: 다크 모드
public class DarkMode : IViewMode
{
public void ApplyViewMode() => Console.WriteLine("Dark mode applied.");
}
// ConcreteProduct: 작은 글자 크기
public class SmallFontSize : IFontSize
{
public void ApplyFontSize() => Console.WriteLine("Small font size applied.");
}
// ConcreteProduct: 큰 글자 크기
public class LargeFontSize : IFontSize
{
public void ApplyFontSize() => Console.WriteLine("Large font size applied.");
}
// 추상 팩토리 인터페이스
public interface IViewerSettingsFactory
{
IViewMode CreateViewMode();
IFontSize CreateFontSize();
}
// ConcreteFactory: 라이트 모드와 작은 글자 크기 팩토리
public class LightModeFactory : IViewerSettingsFactory
{
public IViewMode CreateViewMode() => new LightMode();
public IFontSize CreateFontSize() => new SmallFontSize();
}
// ConcreteFactory: 다크 모드와 큰 글자 크기 팩토리
public class DarkModeFactory : IViewerSettingsFactory
{
public IViewMode CreateViewMode() => new DarkMode();
public IFontSize CreateFontSize() => new LargeFontSize();
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
// 추상 팩토리를 사용하여 보기 모드와 글자 크기 설정을 함께 관리
IViewerSettingsFactory factory = new LightModeFactory();
IViewMode viewMode = factory.CreateViewMode();
IFontSize fontSize = factory.CreateFontSize();
viewMode.ApplyViewMode(); // Output: Light mode applied.
fontSize.ApplyFontSize(); // Output: Small font size applied.
factory = new DarkModeFactory();
viewMode = factory.CreateViewMode();
fontSize = factory.CreateFontSize();
viewMode.ApplyViewMode(); // Output: Dark mode applied.
fontSize.ApplyFontSize(); // Output: Large font size applied.
}
}
Abstract Factory와 의존성 주입
Abstract Factory 패턴은 의존성 주입Dependency Injection, DI과 결합하여 시스템의 유연성을 극대화할 수 있습니다. DI 컨테이너는 객체의 생성을 관리하며, 이를 통해 클라이언트 코드에서 구체적인 팩토리 클래스에 의존하지 않도록 만들 수 있습니다.
의존성 주입을 통한 팩토리 관리
의존성 주입Dependency Injection, DI은 추상 팩토리 패턴과 매우 자연스럽게 조합될 수 있습니다. 추상 팩토리를 클라이언트 코드에 주입함으로써 클라이언트가 구체적인 팩토리 구현에 의존하지 않고 객체를 생성할 수 있게 됩니다. 이를 통해 클라이언트 코드가 더 유연해지고 테스트 가능성이 높아집니다.
// Product 인터페이스: 보기 모드
public interface IViewMode
{
void ApplyViewMode();
}
// Product 인터페이스: 글자 크기
public interface IFontSize
{
void ApplyFontSize();
}
// ConcreteProduct: 라이트 모드
public class LightMode : IViewMode
{
public void ApplyViewMode() => Console.WriteLine("Light mode applied.");
}
// ConcreteProduct: 작은 글자 크기
public class SmallFontSize : IFontSize
{
public void ApplyFontSize() => Console.WriteLine("Small font size applied.");
}
// 추상 팩토리 인터페이스
public interface IViewerSettingsFactory
{
IViewMode CreateViewMode();
IFontSize CreateFontSize();
}
// ConcreteFactory: 라이트 모드와 작은 글자 크기 팩토리
public class LightModeFactory : IViewerSettingsFactory
{
public IViewMode CreateViewMode() => new LightMode();
public IFontSize CreateFontSize() => new SmallFontSize();
}
// 도서 관리 시스템에서 보기 설정을 위한 클래스
public class ViewerSettings
{
private readonly IViewerSettingsFactory _factory;
// 의존성 주입을 통해 팩토리 주입
public ViewerSettings(IViewerSettingsFactory factory)
{
_factory = factory;
}
public void ApplySettings()
{
var viewMode = _factory.CreateViewMode();
var fontSize = _factory.CreateFontSize();
viewMode.ApplyViewMode();
fontSize.ApplyFontSize();
}
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
// DI 컨테이너를 통해 팩토리를 주입할 수 있음
IViewerSettingsFactory factory = new LightModeFactory();
var viewerSettings = new ViewerSettings(factory);
viewerSettings.ApplySettings();
// Output: Light mode applied.
// Small font size applied.
}
}
의존성 주입을 통해 클라이언트 코드(ViewerSettings)가 구체적인 팩토리(LightModeFactory)에 의존하지 않고 추상 팩토리 인터페이스(IViewerSettingsFactory)에 의존하게 됩니다. 이를 통해 클라이언트 코드의 유연성과 재사용성을 높일 수 있습니다.
Abstract Factory와 Factory Method 조합
추상 팩토리 패턴은 하나 이상의 팩토리 메서드Factory Method를 포함할 수 있습니다. 팩토리 메서드 패턴은 객체 생성 책임을 서브클래스로 위임하는 패턴이며, 추상 팩토리 패턴을 사용하면 여러 팩토리 메서드를 일관되게 사용할 수 있습니다.
// 팩토리 메서드를 사용하는 팩토리 인터페이스
public abstract class ViewerSettingsFactory
{
public abstract IViewMode CreateViewMode();
public abstract IFontSize CreateFontSize();
}
// ConcreteFactory: 라이트 모드 팩토리
public class LightModeFactory : ViewerSettingsFactory
{
public override IViewMode CreateViewMode() => new LightMode();
public override IFontSize CreateFontSize() =>new SmallFontSize();
}
// ConcreteFactory: 다크 모드 팩토리
public class DarkModeFactory : ViewerSettingsFactory
{
public override IViewMode CreateViewMode() => new DarkMode();
public override IFontSize CreateFontSize() => new LargeFontSize();
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
ViewerSettingsFactory factory = new DarkModeFactory();
var viewMode = factory.CreateViewMode();
var fontSize = factory.CreateFontSize();
viewMode.ApplyViewMode();
// Output: Dark mode applied.
fontSize.ApplyFontSize();
// Output: Large font size applied.
}
}
팩토리 메서드 패턴을 이용하여 각 팩토리 클래스에서 구체적인 객체를 생성하는 책임을 하위 클래스에 위임했습니다. 이를 통해 다양한 설정을 유연하게 처리할 수 있습니다.
Abstract Factory와 Singleton
추상 팩토리 패턴에서 팩토리를 싱글톤Singleton 패턴으로 구현하면, 팩토리 인스턴스가 애플리케이션 전역에서 하나만 생성되어 사용됩니다. 이는 객체 생성이 빈번하지 않고, 특정 팩토리를 여러 곳에서 공유해야 할 때 유용합니다.
// 팩토리를 싱글톤으로 구현
public class SingletonLightModeFactory : IViewerSettingsFactory
{
private static SingletonLightModeFactory _instance = new SingletonLightModeFactory();
private SingletonLightModeFactory() {}
public static SingletonLightModeFactory Instance => _instance;
public IViewMode CreateViewMode() => new LightMode();
public IFontSize CreateFontSize() => new SmallFontSize();
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
IViewerSettingsFactory factory = SingletonLightModeFactory.Instance;
var viewMode = factory.CreateViewMode();
var fontSize = factory.CreateFontSize();
viewMode.ApplyViewMode(); // Output: Light mode applied.
fontSize.ApplyFontSize(); // Output: Small font size applied.
}
}
싱글톤 패턴을 적용하여 팩토리 인스턴스가 애플리케이션 내에서 하나만 존재하도록 하였습니다. 이는 여러 클라이언트가 동일한 팩토리 객체를 공유할 때 유용합니다.
Abstract Factory와 Prototype
프로토타입Prototype 패턴은 기존 객체를 복제하여 새로운 객체를 생성하는 패턴입니다. 추상 팩토리 패턴에서 객체 생성 시 매번 새로운 객체를 생성하는 대신, 프로토타입 패턴을 사용하면 초기 설정이 동일한 객체를 빠르게 복제하여 사용할 수 있습니다.
// Product 인터페이스에 복제 메서드를 추가
public interface IViewMode : ICloneable
{
void ApplyViewMode();
}
// ConcreteProduct: 라이트 모드
public class LightMode : IViewMode
{
public void ApplyViewMode() => Console.WriteLine("Light mode applied.");
public object Clone() => this.MemberwiseClone();
}
// 추상 팩토리 인터페이스
public interface IViewerSettingsFactory
{
IViewMode CreateViewMode();
IFontSize CreateFontSize();
}
// ConcreteFactory: 프로토타입을 활용한 라이트 모드 팩토리
public class PrototypeLightModeFactory : IViewerSettingsFactory
{
private readonly IViewMode _prototypeViewMode;
private readonly IFontSize _prototypeFontSize;
public PrototypeLightModeFactory(IViewMode viewMode, IFontSize fontSize)
{
_prototypeViewMode = viewMode;
_prototypeFontSize = fontSize;
}
public IViewMode CreateViewMode()
=> (IViewMode)_prototypeViewMode.Clone();
public IFontSize CreateFontSize()
=> (IFontSize)_prototypeFontSize.Clone();
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
var lightModePrototype = new LightMode();
var smallFontSizePrototype = new SmallFontSize();
IViewerSettingsFactory factory = new PrototypeLightModeFactory(lightModePrototype, smallFontSizePrototype);
var viewMode = factory.CreateViewMode();
var fontSize = factory.CreateFontSize();
viewMode.ApplyViewMode(); // Output: Light mode applied.
fontSize.ApplyFontSize(); // Output: Small font size applied.
}
}
프로토타입 패턴을 통해 팩토리에서 객체를 복제하여 생성하므로, 매번 새로운 객체를 생성하지 않고, 초기 설정이 동일한 객체를 빠르게 복제할 수 있습니다. 이는 성능을 향상시키고 객체 생성 비용을 줄이는 데 유리합니다.
Abstract Factory와 Strategy
도서 관리 시스템에서 사용자에게 제공되는 보기 모드와 글자 크기 설정이 있으며, 각 설정에 따라 다양한 전략Stragegy<//sup>을 적용해야 한다면, 각각 다른 전략을 사용해 UI를 렌더링할 수 있습니다.
// Strategy 인터페이스
public interface IViewRenderingStrategy
{
void Render();
}
// Concrete Strategies: 다크 모드와 라이트 모드에 대한 구현
public class DarkModeRenderingStrategy : IViewRenderingStrategy
{
public void Render()
{
Console.WriteLine("Rendering in Dark Mode.");
}
}
public class LightModeRenderingStrategy : IViewRenderingStrategy
{
public void Render()
{
Console.WriteLine("Rendering in Light Mode.");
}
}
// Product 인터페이스: 보기 모드
public interface IViewMode
{
IViewRenderingStrategy GetRenderingStrategy();
}
// Concrete Products
public class DarkMode : IViewMode
{
public IViewRenderingStrategy GetRenderingStrategy()
{
return new DarkModeRenderingStrategy();
}
}
public class LightMode : IViewMode
{
public IViewRenderingStrategy GetRenderingStrategy()
{
return new LightModeRenderingStrategy();
}
}
// 추상 팩토리 인터페이스
public interface IViewerSettingsFactory
{
IViewMode CreateViewMode();
}
// Concrete Factory: 다크 모드 팩토리
public class DarkModeFactory : IViewerSettingsFactory
{
public IViewMode CreateViewMode()
{
return new DarkMode();
}
}
// Concrete Factory: 라이트 모드 팩토리
public class LightModeFactory : IViewerSettingsFactory
{
public IViewMode CreateViewMode()
{
return new LightMode();
}
}
// 클라이언트 코드
public class ViewerSettings
{
private readonly IViewerSettingsFactory _factory;
public ViewerSettings(IViewerSettingsFactory factory)
{
_factory = factory;
}
public void ApplySettings()
{
IViewMode viewMode = _factory.CreateViewMode();
IViewRenderingStrategy renderingStrategy = viewMode.GetRenderingStrategy();
renderingStrategy.Render();
}
}
// 실행 코드
public class Program
{
public static void Main(string[] args)
{
IViewerSettingsFactory factory = new DarkModeFactory();
ViewerSettings settings = new ViewerSettings(factory);
settings.ApplySettings(); // Output: Rendering in Dark Mode.
factory = new LightModeFactory();
settings = new ViewerSettings(factory);
settings.ApplySettings(); // Output: Rendering in Light Mode.
}
}
Abstract Factory와 Builder
도서 관리 시스템에서 종이책과 전자책을 관리한다고 가정합시다. 각각의 책은 제목, 저자, 대여 가능 여부 등 다양한 속성을 가지고 있으며, 각 유형에 따라 생성되는 방식이 다릅니다. 여기서 추상 팩토리는 제품군(책의 종류)을 관리하고, 빌더 패턴은 그 책의 세부 속성들을 단계적으로 설정하는 역할을 합니다.
// Abstract Factory
public interface IBookFactory
{
IBookBuilder CreateBookBuilder();
}
// Concrete Factories
public class PaperBookFactory : IBookFactory
{
public IBookBuilder CreateBookBuilder() => new PaperBookBuilder();
}
public class EBookFactory : IBookFactory
{
public IBookBuilder CreateBookBuilder() => new EBookBuilder();
}
// Builder Interface
public interface IBookBuilder
{
IBookBuilder SetTitle(string title);
IBookBuilder SetAuthor(string author);
IBookBuilder SetAvailable(bool isAvailable);
Book Build();
}
// Concrete Builders
public class PaperBookBuilder : IBookBuilder
{
private Book _book = new Book();
public IBookBuilder SetTitle(string title)
{
_book.Title = title;
return this;
}
public IBookBuilder SetAuthor(string author)
{
_book.Author = author;
return this;
}
public IBookBuilder SetAvailable(bool isAvailable)
{
_book.IsAvailable = isAvailable;
return this;
}
public Book Build() => _book;
}
public class EBookBuilder : IBookBuilder
{
private Book _book = new Book();
public IBookBuilder SetTitle(string title)
{
_book.Title = title;
return this;
}
public IBookBuilder SetAuthor(string author)
{
_book.Author = author;
return this;
}
public IBookBuilder SetAvailable(bool isAvailable)
{
_book.IsAvailable = isAvailable;
return this;
}
public Book Build() => _book;
}
// Product
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public bool IsAvailable { get; set; }
public override string ToString()
=> $"{Title} by {Author} - Available: {IsAvailable}";
}
// Client Code
public class Program
{
public static void Main(string[] args)
{
IBookFactory paperBookFactory = new PaperBookFactory();
IBookBuilder paperBookBuilder = paperBookFactory.CreateBookBuilder();
Book paperBook = paperBookBuilder
.SetTitle("The Great Gatsby")
.SetAuthor("F. Scott Fitzgerald")
.SetAvailable(true)
.Build();
Console.WriteLine(paperBook);
IBookFactory eBookFactory = new EBookFactory();
IBookBuilder eBookBuilder = eBookFactory.CreateBookBuilder();
Book eBook = eBookBuilder
.SetTitle("Digital Fortress")
.SetAuthor("Dan Brown")
.SetAvailable(false)
.Build();
Console.WriteLine(eBook);
}
}
빌더Builder 패턴은 복잡한 객체의 생성 과정을 단계적으로 처리하는 데 유용하고, 추상 팩토리 패턴은 여러 관련 객체들을 생성하는 데 중점을 둡니다. 두 패턴을 조합하면, 복잡한 객체를 생성할 때 일관된 제품군을 관리하면서 각 단계별로 유연하게 객체를 구성할 수 있게 됩니다.