Builder
Builder 패턴이란?
빌더Builder 패턴은 복잡한 객체의 생성 과정을 단계별로 분리하여, 객체를 유연하고 체계적으로 생성할 수 있게 해주는 디자인 패턴입니다. 이 패턴을 사용하면 동일한 생성 절차로 서로 다른 표현을 가진 객체를 생성할 수 있으며, 객체 생성 로직을 분리하여 가독성과 유지보수성을 높일 수 있습니다.
Builder 패턴 구조
- Client → Director
- 클라이언트는
Director
에게 제품 생성을 요청합니다(Construct()
).
- 클라이언트는
- Director → Builder
Director
는 생성 과정을 관리하며,Builder
의 단계별 메서드(BuildPartA
,BuildPartB
,BuildPartC
)를 호출합니다.
- ConcreteBuilder Builder
Builder
인터페이스를 구현한ConcreteBuilder
가 구체적인Product
생성 로직을 정의합니다.
- ConcreteBuilder Product
ConcreteBuilder
는 각 파트를 조립하고 최종적으로Product
를 생성합니다.
- Builder → Client
- 클라이언트는 생성된
Product
를Builder
를 통해 가져옵니다.
- 클라이언트는 생성된
Builder 패턴 적용
Builder 패턴의 필요성
도서 관리 시스템에서 다양한 속성을 가진 도서 객체를 생성해야 하는 상황을 가정해보겠습니다. 예를 들어, 도서는 제목, 저자, 페이지 수, 출판사 등 다양한 속성을 가질 수 있습니다. 그러나 일부 속성은 선택적으로 추가될 수도 있습니다. 이때, 객체 생성자가 너무 많은 매개변수를 받아야 하는 경우가 발생할 수 있으며, 이는 코드의 복잡성을 증가시키고 가독성을 떨어뜨릴 수 있습니다. Builder 패턴은 이러한 문제를 해결하는 데 적합합니다.
잘못된 처리
일반적으로 많은 매개변수를 가진 객체를 생성할 때, 아래와 같이 긴 생성자(constructor)를 사용할 수 있습니다.
public class Book
{
public string Title { get; }
public string Author { get; }
public int Pages { get; }
public string Publisher { get; }
public Book(string title, string author, int pages, string publisher)
{
Title = title;
Author = author;
Pages = pages;
Publisher = publisher;
}
}
// 클라이언트 코드
var book = new Book("Design Patterns", "Erich Gamma", 395, "Addison-Wesley");
- 가독성 저하: 생성자의 매개변수가 많을 경우, 어떤 값이 어떤 속성에 해당하는지 명확하지 않으며 가독성이 떨어질 수 있습니다.
- 확장성 문제: 새로운 속성이 추가되면 생성자를 계속해서 수정해야 하므로, 유지보수가 어렵습니다.
Builder 패턴 적용 예시
Builder 패턴을 적용하면 객체 생성 과정을 단계별로 분리하고, 선택적인 속성도 유연하게 설정할 수 있습니다.
// Product 클래스: 도서
public class Book
{
public string Title { get; }
public string Author { get; }
public int Pages { get; }
public string Publisher { get; }
private Book(BookBuilder builder)
{
Title = builder.Title;
Author = builder.Author;
Pages = builder.Pages;
Publisher = builder.Publisher;
}
public void DisplayInfo()
{
Console.WriteLine($"Title: {Title}, Author: {Author}, Pages: {Pages}, Publisher: {Publisher}");
}
// Builder 클래스
public class BookBuilder
{
public string Title { get; private set; }
public string Author { get; private set; }
public int Pages { get; private set; }
public string Publisher { get; private set; }
public BookBuilder SetTitle(string title)
{
Title = title;
return this;
}
public BookBuilder SetAuthor(string author)
{
Author = author;
return this;
}
public BookBuilder SetPages(int pages)
{
Pages = pages;
return this;
}
public BookBuilder SetPublisher(string publisher)
{
Publisher = publisher;
return this;
}
public Book Build() => new Book(this);
}
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
var book = new Book.BookBuilder()
.SetTitle("Design Patterns")
.SetAuthor("Erich Gamma")
.SetPages(395)
.SetPublisher("Addison-Wesley")
.Build();
book.DisplayInfo();
// Output: Title: Design Patterns, Author: Erich Gamma, Pages: 395, Publisher: Addison-Wesley
}
}
Builder 패턴 구성 요소
Product
Builder 패턴에서 생성될 객체를 나타내며, 생성될 객체의 구조를 정의합니다.
예제에서는 Book
클래스가 해당합니다.
Builder
Product 객체의 복잡한 생성을 담당하는 클래스입니다. Product의 각 속성을 단계별로 설정할 수 있도록 메서드를 제공합니다.
예제에서는 BookBuilder
클래스가 해당합니다.
Director (옵션)
생성 절차를 정의하여 Builder가 Product를 어떻게 생성할지 결정합니다. 이 예제에서는 Director가 없으며, 클라이언트가 직접 생성 절차를 조정합니다. 자세한 내용은 Builder 패턴 심화 예제를 참조하시기 바랍니다.
개선 사항
가독성 및 유지보수성 향상
Builder 패턴을 사용하면 객체를 생성하는 각 단계가 명확하게 구분되므로, 코드의 가독성과 유지보수성이 향상됩니다. 또한 선택적인 속성도 유연하게 설정할 수 있어, 불필요한 매개변수를 줄일 수 있습니다.
유연한 객체 생성
객체 생성 절차를 단계별로 분리함으로써, 객체의 각 속성을 유연하게 설정할 수 있습니다. 이를 통해 복잡한 객체를 간단하고 명확하게 생성할 수 있습니다.
적용 시 유의 사항
불필요한 클래스 증가
Builder 패턴을 사용하면 Builder 클래스를 추가로 작성해야 하므로, 작은 프로젝트에서는 클래스의 수가 불필요하게 증가할 수 있습니다. 객체가 간단한 경우에는 Builder 패턴을 사용하는 것이 오히려 복잡성을 증가시킬 수 있습니다.
객체 생성의 단계가 많을 경우
Builder 패턴은 복잡한 객체를 생성하는 데 유리하지만, 객체의 생성 단계가 지나치게 많을 경우 오히려 코드를 복잡하게 만들 수 있습니다. 따라서 객체 생성 절차를 적절히 조정할 필요가 있습니다.
객체지향 원칙과의 관계
Builder와 캡슐화
Builder 패턴은 객체의 복잡한 생성 과정을 캡슐화하여 클라이언트 코드로부터 숨깁니다. 객체의 생성 과정은 Builder 클래스 내부에 정의되어 있으며, 클라이언트는 객체의 생성 과정에 대한 세부 사항을 알 필요 없이 Builder
를 사용해 객체를 단계별로 생성할 수 있습니다. 이는 클라이언트가 직접 복잡한 생성 로직을 다루지 않도록 하여, 객체 생성의 복잡성을 줄이고 코드의 가독성과 유지보수성을 높이는 데 기여합니다.
Builder와 다형성
Builder 패턴 자체는 다형성을 기본적으로 활용하지 않지만, 다형성을 적용할 수 있는 유연한 구조를 가지고 있습니다. 예를 들어, 여러 종류의 Builder
클래스를 정의하고, 이를 통해 서로 다른 종류의 객체를 생성할 수 있습니다. 이 경우, 공통 인터페이스를 사용하여 다양한 Builder
클래스를 다형적으로 처리할 수 있습니다. 다형성을 활용하면 같은 방식으로 여러 유형의 객체를 생성할 수 있으므로 확장성이 높아집니다.
예를 들어, 도서 관리 시스템에서 BookBuilder
외에도 EBookBuilder
, AudioBookBuilder
같은 구체적인 빌더를 만들어 다형성을 활용할 수 있습니다. 이러한 빌더들이 공통 Builder
인터페이스를 구현한다면, 클라이언트는 어떤 구체적인 빌더가 사용되는지 신경 쓸 필요 없이 동일한 방식으로 다양한 도서 객체를 생성할 수 있습니다.
Builder와 단일 책임 원칙
Builder 패턴은 단일 책임 원칙Single Responsibility Principle, SRP을 매우 잘 준수하는 패턴입니다. Product
클래스(예: Book
클래스)는 객체의 속성이나 비즈니스 로직에만 집중하고, 객체의 생성과 관련된 로직은 모두 Builder
클래스에 위임됩니다. 즉, 객체의 생성을 담당하는 로직과 객체의 비즈니스 로직이 명확하게 분리됩니다.
Book
클래스는 도서 객체의 속성을 보유하고, 생성된 후에 어떤 역할을 해야 할지에 대해 책임을 집니다. 반면, BookBuilder
클래스는 Book
객체를 단계별로 생성하는 책임만을 가집니다. 이렇게 책임을 분리함으로써, 각 클래스는 자신이 맡은 역할에만 집중할 수 있습니다.
Builder와 개방_폐쇄 원칙
Builder 패턴은 개방_폐쇄 원칙Open/Closed Principle, OCP을 잘 준수합니다. 새로운 속성이나 옵션이 추가될 때 기존 코드를 수정하지 않고, 기존의 Builder 클래스를 확장하는 방식으로 기능을 추가할 수 있습니다. 예를 들어, 도서 객체에 새로운 속성(예: 출판 연도, ISBN)을 추가해야 할 경우, 기존 BookBuilder
클래스를 수정하지 않고 새로운 메서드만 추가하면 됩니다.
public BookBuilder SetPublicationYear(int year)
{
PublicationYear = year;
return this;
}
이 방식으로, 기존의 코드에 영향을 주지 않고도 객체 생성에 새로운 옵션을 추가할 수 있으며, 확장성을 높일 수 있습니다.
Builder와 의존 역전 원칙
Builder 패턴은 의존 역전 원칙Dependency Inversion Principle, DIP을 간접적으로 지원할 수 있습니다. 클라이언트는 구체적인 Product
클래스에 의존하지 않고, Builder
클래스를 통해 객체를 생성합니다. 이로 인해 클라이언트는 구체적인 객체의 생성 방식을 직접 다루지 않고, 생성 로직은 Builder
내부에 숨겨지므로 클라이언트와 객체 생성 과정 사이의 의존성을 줄일 수 있습니다.
추가적으로, Director
클래스를 도입하면 객체 생성의 책임이 Director
로 집중되고, 클라이언트는 객체 생성의 세부 사항에 신경 쓰지 않아도 됩니다. Director
는 다양한 Builder
를 주입받아 사용할 수 있으므로, 객체 생성 과정에서 유연성을 높일 수 있습니다.
결론
Builder 패턴은 복잡한 객체를 유연하게 생성하는 데 적합한 패턴입니다. 객체 생성 절차를 단계별로 분리함으로써 코드의 가독성과 유지보수성을 높일 수 있으며, 선택적인 속성도 유연하게 설정할 수 있습니다. 객체 생성의 복잡성이 커질 때, Builder 패턴은 훌륭한 해결책이 될 수 있습니다.
심화 학습
Director Class
디렉터 클래스Director Class는 빌더 객체를 이용해 객체 생성 과정을 관리하는 핵심 역할을 합니다. 이를 통해 객체의 생성 과정을 여러 단계로 나누고, 빌더를 통해 생성 과정이 일관되게 진행되도록 할 수 있습니다.
- 객체 생성 과정 관리: 디렉터는 빌더의 메서드들을 호출하여 객체 생성 과정을 순차적으로 관리합니다.
- 빌더 단계 분리: 복잡한 객체의 생성 과정을 여러 단계로 분리하여 빌더에게 전달합니다.
- 객체 생성 과정의 재사용성: 동일한 빌더를 사용해 다양한 설정을 적용하여 서로 다른 객체를 만들 수 있습니다.
- 캡슐화: 객체 생성 과정을 클라이언트로부터 숨겨서, 복잡한 생성 과정을 단순하게 유지할 수 있습니다. 도서 관리 시스템에서 디렉터의 역할을 강조하는 적합한 예제로, 도서 대여 과정을 들 수 있습니다. 도서 대여 시, 사용자 정보와 책 정보가 모두 필요하고, 대여 시점에서 대여 기간, 연체 여부, 대여자 정보 등이 단계별로 설정됩니다. 이러한 과정에서 디렉터가 각 단계를 조율하여 대여 객체를 단계별로 완성하는 역할을 할 수 있습니다.
// Product 클래스: BorrowRecord
public class BorrowRecord
{
public string BookTitle { get; set; }
public string BorrowerName { get; set; }
public DateTime BorrowDate { get; set; }
public DateTime? ReturnDate { get; set; }
public bool IsOverdue { get; set; }
public override string ToString()
{
return $"Borrow Details:\nBook: {BookTitle}\nBorrower: {BorrowerName}\nBorrow Date: {BorrowDate}\nReturn Date: {ReturnDate}\nOverdue: {IsOverdue}";
}
}
// IBorrowBuilder 인터페이스
public interface IBorrowBuilder
{
IBorrowBuilder SetBookTitle(string title);
IBorrowBuilder SetBorrowerName(string name);
IBorrowBuilder SetBorrowDate(DateTime borrowDate);
IBorrowBuilder SetReturnDate(DateTime returnDate);
IBorrowBuilder SetOverdueStatus(bool isOverdue);
BorrowRecord Build();
}
// Concrete Builder: BorrowBuilder
public class BorrowBuilder : IBorrowBuilder
{
private BorrowRecord _borrowRecord = new BorrowRecord();
public IBorrowBuilder SetBookTitle(string title)
{
_borrowRecord.BookTitle = title;
return this;
}
public IBorrowBuilder SetBorrowerName(string name)
{
_borrowRecord.BorrowerName = name;
return this;
}
public IBorrowBuilder SetBorrowDate(DateTime borrowDate)
{
_borrowRecord.BorrowDate = borrowDate;
return this;
}
public IBorrowBuilder SetReturnDate(DateTime returnDate)
{
_borrowRecord.ReturnDate = returnDate;
return this;
}
public IBorrowBuilder SetOverdueStatus(bool isOverdue)
{
_borrowRecord.IsOverdue = isOverdue;
return this;
}
public BorrowRecord Build() => _borrowRecord;
}
// Director 클래스: BorrowDirector
public class BorrowDirector
{
// 기본 대여: 책 제목과 대여자 정보만 설정
public BorrowRecord BuildBasicBorrow(IBorrowBuilder builder, string bookTitle, string borrowerName)
{
return builder
.SetBookTitle(bookTitle)
.SetBorrowerName(borrowerName)
.SetBorrowDate(DateTime.Now) // 대여일 기본값 설정
.SetOverdueStatus(false) // 연체 여부 초기화
.Build();
}
// 반환 대여 기록: 반환 날짜와 연체 여부 설정
public BorrowRecord BuildReturnedBorrow(IBorrowBuilder builder, string bookTitle, string borrowerName, DateTime returnDate, bool isOverdue)
{
return builder
.SetBookTitle(bookTitle)
.SetBorrowerName(borrowerName)
.SetBorrowDate(DateTime.Now.AddDays(-14)) // 기본 2주 대여
.SetReturnDate(returnDate)
.SetOverdueStatus(isOverdue)
.Build();
}
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
BorrowDirector director = new BorrowDirector();
IBorrowBuilder builder = new BorrowBuilder();
// 기본 대여 기록 생성
BorrowRecord basicBorrow = director.BuildBasicBorrow(builder, "1984", "John Doe");
Console.WriteLine(basicBorrow);
// 반환 대여 기록 생성
BorrowRecord returnedBorrow = director.BuildReturnedBorrow(builder, "Brave New World", "Jane Smith", DateTime.Now, false);
Console.WriteLine(returnedBorrow);
}
}
조건부 객체 생성
디렉터를 통해 객체 생성 과정에 조건부 로직을 추가할 수 있습니다. 예를 들어, 대출 상태에 따라 객체를 다르게 생성하거나 특정 조건에 따라 다른 설정을 할 수 있습니다.
public class BorrowDirector
{
public void BuildBorrow(BorrowBuilder builder, bool isReturning)
{
builder.SetTitle();
builder.SetBorrowerName();
if (isReturning)
{
builder.SetReturnDate();
}
else
{
builder.SetDueDate();
}
}
}
Builder와 Strategy
빌더 패턴과 전략 패턴Strategy을 조합하면, 디렉터가 전략을 선택해 빌더와 함께 다양한 객체 생성 로직을 수행할 수 있습니다. 예를 들어, 도서 대출 정책을 전략 패턴으로 구현하고, 빌더 패턴으로 대출 객체를 생성하는 방법이 있습니다.
// 전략 인터페이스: 대출 기간 정책
public interface IBorrowPolicy
{
DateTime CalculateDueDate(DateTime borrowDate);
}
// Concrete 전략: 14일 대출 정책
public class TwoWeeksPolicy : IBorrowPolicy
{
public DateTime CalculateDueDate(DateTime borrowDate) => borrowDate.AddDays(14);
}
// Concrete 전략: 21일 대출 정책
public class ThreeWeeksPolicy : IBorrowPolicy
{
public DateTime CalculateDueDate(DateTime borrowDate) => borrowDate.AddDays(21);
}
// Builder 인터페이스
public interface IBorrowBuilder
{
void SetTitle(string title);
void SetBorrowerName(string name);
void SetDueDate(DateTime dueDate);
}
// Concrete Builder: EBook 대출 빌더
public class EBookBorrowBuilder : IBorrowBuilder
{
private string _title;
private string _borrowerName;
private DateTime? _dueDate;
private string _supportedDevices;
public void SetTitle(string title) => _title = title;
public void SetBorrowerName(string name) => _borrowerName = name;
public void SetDueDate(DateTime dueDate) => _dueDate = dueDate;
public void SetSupportedDevices(string devices) => _supportedDevices = devices;
public void Display()
{
Console.WriteLine($"EBook - Title: {_title}, Borrower: {_borrowerName}, Due Date: {_dueDate}, Supported Devices: {_supportedDevices}");
}
}
// Concrete Builder: PaperBook 대출 빌더
public class PaperBookBorrowBuilder : IBorrowBuilder
{
private string _title;
private string _borrowerName;
private DateTime? _dueDate;
public void SetTitle(string title) => _title = title;
public void SetBorrowerName(string name) => _borrowerName = name;
public void SetDueDate(DateTime dueDate) => _dueDate = dueDate;
public void Display()
{
Console.WriteLine($"PaperBook - Title: {_title}, Borrower: {_borrowerName}, Due Date: {_dueDate}");
}
}
// Director 클래스
public class BorrowDirector
{
private readonly IBorrowPolicy _policy;
public BorrowDirector(IBorrowPolicy policy)
{
_policy = policy;
}
public void BuildBorrow(IBorrowBuilder builder, string title, string borrowerName, DateTime borrowDate, string devices = null)
{
builder.SetTitle(title);
builder.SetBorrowerName(borrowerName);
DateTime dueDate = _policy.CalculateDueDate(borrowDate);
builder.SetDueDate(dueDate);
// EBookBuilder의 경우에만 지원 기기 설정
if (builder is EBookBorrowBuilder ebookBuilder && devices != null)
{
ebookBuilder.SetSupportedDevices(devices);
}
}
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
// 대출 정책을 전략 패턴으로 설정 (2주 대출 정책)
IBorrowPolicy twoWeeksPolicy = new TwoWeeksPolicy();
BorrowDirector director = new BorrowDirector(twoWeeksPolicy);
// EBook 대출 빌더 사용
EBookBorrowBuilder ebookBuilder = new EBookBorrowBuilder();
director.BuildBorrow(ebookBuilder, "The Great Gatsby", "John Doe", DateTime.Now, "Kindle, iPad");
ebookBuilder.Display();
// Output: EBook - Title: The Great Gatsby, Borrower: John Doe, Due Date: [2주 후], Supported Devices: Kindle, iPad
// 대출 정책을 3주 대출로 변경
IBorrowPolicy threeWeeksPolicy = new ThreeWeeksPolicy();
director = new BorrowDirector(threeWeeksPolicy);
// PaperBook 대출 빌더 사용
PaperBookBorrowBuilder paperBookBuilder = new PaperBookBorrowBuilder();
director.BuildBorrow(paperBookBuilder, "Moby Dick", "Jane Doe", DateTime.Now);
paperBookBuilder.Display();
// Output: PaperBook - Title: Moby Dick, Borrower: Jane Doe, Due Date: [3주 후]
}
}
Builder와 Memento
빌더 패턴과 메멘토 패턴Memento을 조합하면 빌더의 각 단계를 캡처하고 필요할 때 상태를 복원할 수 있습니다. 다음은 빌더가 도서 대출 객체를 단계별로 생성하고, 중간에 상태를 저장했다가 복원하는 예제입니다.
// Memento 클래스
public class BorrowMemento
{
public string Title { get; private set; }
public string BorrowerName { get; private set; }
public DateTime? DueDate { get; private set; }
public BorrowMemento(string title, string borrowerName, DateTime? dueDate)
{
Title = title;
BorrowerName = borrowerName;
DueDate = dueDate;
}
}
// BorrowBuilder 인터페이스
public interface IBorrowBuilder
{
void SetTitle(string title);
void SetBorrowerName(string name);
void SetDueDate(DateTime date);
BorrowMemento Save();
void Restore(BorrowMemento memento);
}
//Concrete Builder 클래스
public class BookBorrowBuilder : IBorrowBuilder
{
private string _title;
private string _borrowerName;
private DateTime? _dueDate;
public void SetTitle(string title) => _title = title;
public void SetBorrowerName(string name) => _borrowerName = name;
public void SetDueDate(DateTime date) => _dueDate = date;
// 현재 상태를 저장하는 메서드
public BorrowMemento Save() => new BorrowMemento(_title, _borrowerName, _dueDate);
// 저장된 상태를 복원하는 메서드
public void Restore(BorrowMemento memento)
{
_title = memento.Title;
_borrowerName = memento.BorrowerName;
_dueDate = memento.DueDate;
}
public void Display()
{
Console.WriteLine($"Title: {_title}, Borrower: {_borrowerName}, Due Date: {_dueDate}");
}
}
// BorrowDirector 클래스
public class BorrowDirector
{
private readonly IBorrowBuilder _builder;
private BorrowMemento _savedState;
public BorrowDirector(IBorrowBuilder builder)
{
_builder = builder;
}
public void BuildBasicBorrow(string title, string borrowerName)
{
_builder.SetTitle(title);
_builder.SetBorrowerName(borrowerName);
_savedState = _builder.Save(); // 상태 저장
}
public void BuildDueDateBorrow(DateTime dueDate)
{
_builder.Restore(_savedState); // 이전 상태 복원
_builder.SetDueDate(dueDate);
}
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
IBorrowBuilder builder = new BookBorrowBuilder();
BorrowDirector director = new BorrowDirector(builder);
// 기본 대출 상태 설정
director.BuildBasicBorrow("The Great Gatsby", "John Doe");
builder.Display(); // Output: Title: The Great Gatsby, Borrower: John Doe, Due Date: null
// 대출 날짜 추가
director.BuildDueDateBorrow(DateTime.Now.AddDays(14));
builder.Display(); // Output: Title: The Great Gatsby, Borrower: John Doe, Due Date: [14일 후 날짜]
}
}
단계별 상태 저장을 통한 취소 기능 구현
빌더가 객체 생성 도중에 변경 사항이 발생했을 때, 이전 상태로 되돌릴 수 있도록 취소Undo 기능을 추가할 수 있습니다. 다음은 도서 대출 빌더에서 취소 기능을 구현한 예시입니다.
public class BorrowBuilderWithUndo : IBorrowBuilder
{
private string _title;
private string _borrowerName;
private DateTime? _dueDate;
private Stack<BorrowMemento> _history = new Stack<BorrowMemento>();
public void SetTitle(string title)
{
_history.Push(Save());
_title = title;
}
public void SetBorrowerName(string name)
{
_history.Push(Save());
_borrowerName = name;
}
public void SetDueDate(DateTime date)
{
_history.Push(Save());
_dueDate = date;
}
public BorrowMemento Save() => new BorrowMemento(_title, _borrowerName, _dueDate);
public void Restore(BorrowMemento memento)
{
_title = memento.Title;
_borrowerName = memento.BorrowerName;
_dueDate = memento.DueDate;
}
public void Undo()
{
if (_history.Count > 0)
{
Restore(_history.Pop());
}
}
public void Display()
{
Console.WriteLine($"Title: {_title}, Borrower: {_borrowerName}, Due Date: {_dueDate}");
}
}
이 예시에서는 SetTitle()
, SetBorrowerName()
, SetDueDate()
와 같은 메서드가 호출될 때마다 상태를 저장하고, Undo()
메서드를 통해 이전 상태로 복원할 수 있게 합니다.
대출 상태의 조건부 복원
대출 상태를 여러 조건에 맞게 저장하고, 특정 조건에 따라 상태를 복원하는 방식입니다. 예를 들어, 사용자가 반납 기한이 초과된 도서만 복원하도록 조건을 추가할 수 있습니다.
public class BorrowBuilderWithCondition : IBorrowBuilder
{
private string _title;
private string _borrowerName;
private DateTime? _dueDate;
private List<BorrowMemento> _history = new List<BorrowMemento>();
public void SetTitle(string title)
{
_history.Add(Save());
_title = title;
}
public void SetBorrowerName(string name)
{
_history.Add(Save());
_borrowerName = name;
}
public void SetDueDate(DateTime date)
{
_history.Add(Save());
_dueDate = date;
}
public BorrowMemento Save() => new BorrowMemento(_title, _borrowerName, _dueDate);
public void RestoreLateBorrow()
{
foreach (var memento in _history)
{
if (memento.DueDate.HasValue && memento.DueDate.Value < DateTime.Now)
{
Restore(memento);
break;
}
}
}
public void Restore(BorrowMemento memento)
{
_title = memento.Title;
_borrowerName = memento.BorrowerName;
_dueDate = memento.DueDate;
}
public void Display()
{
Console.WriteLine($"Title: {_title}, Borrower: {_borrowerName}, Due Date: {_dueDate}");
}
}
RestoreLateBorrow()
메서드는 상태 기록을 확인하고, 기한이 초과된 대출 상태만 복원합니다.