Facade
Facade 패턴이란?
퍼사드 패턴Facade Pattern은 복잡한 서브시스템의 집합에 대해 간단한 인터페이스를 제공하여, 클라이언트가 더 쉽게 시스템과 상호작용할 수 있도록 돕는 구조적 디자인 패턴입니다. 이 패턴은 복잡한 시스템을 사용하는 클라이언트가 서브시스템의 세부 사항에 대한 지식 없이도 기능을 쉽게 사용할 수 있도록 인터페이스를 단순화합니다.
Facade 패턴의 필요성
퍼사드 패턴은 다음과 같은 상황에서 유용합니다:
서브시스템의 복잡성을 숨겨야 할 때
클라이언트가 복잡한 서브시스템과 상호작용해야 할 경우, 퍼사드 패턴을 사용해 간단한 인터페이스를 제공함으로써 클라이언트의 부담을 줄일 수 있습니다.
시스템의 모듈화를 개선할 때
퍼사드 패턴을 사용하면 서브시스템을 모듈화하고, 각 모듈이 독립적으로 작동할 수 있도록 해줍니다. 이는 시스템의 유지보수성과 확장성을 높이는 데 도움이 됩니다.
Facade 패턴의 구성 요소
퍼사드 패턴은 다음과 같은 구성 요소로 이루어집니다:
퍼사드Facade
서브시스템의 복잡한 인터페이스를 단순화하여 클라이언트에게 제공하는 고수준의 인터페이스를 정의합니다.
서브시스템Subsystem
퍼사드가 캡슐화하는 복잡한 서브시스템입니다. 서브시스템은 여러 개의 클래스나 모듈로 구성되며, 클라이언트는 서브시스템과 직접 상호작용하지 않습니다.
구조
Client
Facade
를 통해 서브시스템의 복잡한 동작을 간단히 요청합니다.- 클라이언트는
SubsystemA
,SubsystemB
,SubsystemC
와 직접 상호작용하지 않습니다.
Facade
- 복잡한 서브시스템을 감싸고, 클라이언트가 단순화된 인터페이스를 통해 요청을 처리할 수 있도록 합니다.
- 내부적으로 여러 서브시스템을 호출하여 작업을 조정합니다.
SubsystemA, SubsystemB, SubsystemC
- 각각의 서브시스템은 독립적으로 작업을 처리합니다.
- 서브시스템은
Facade
를 통해 호출되며, 직접적으로 클라이언트와 상호작용하지 않습니다.
Facade 패턴 적용
잘못된 서브시스템 접근 방식
퍼사드 패턴을 적용하지 않으면, 클라이언트가 복잡한 서브시스템의 세부 사항에 직접 접근해야 하므로 코드가 복잡해질 수 있습니다. 도서관 관리 시스템에서 여러 서브시스템을 사용하는 예시를 생각해보겠습니다.
// 서브시스템 클래스 1: 카탈로그 관리
public class Catalog
{
public void AddBook(string book)
=> Console.WriteLine($"Book '{book}' added to catalog.");
}
// 서브시스템 클래스 2: 대여 관리
public class Borrow
{
public void IssueBorrow(string book, string member)
=> Console.WriteLine($"Book '{book}' borrowed by member '{member}'.");
}
// 서브시스템 클래스 3: 알림 관리
public class Notification
{
public void SendNotification(string message)
=> Console.WriteLine($"Notification sent: {message}");
}
// 퍼사드 클래스
public class LibraryFacade
{
private Catalog _catalog;
private Borrow _borrow;
private Notification _notification;
public LibraryFacade()
{
_catalog = new Catalog();
_borrow = new Borrow();
_notification = new Notification();
}
public void AddBookAndBorrow(string book, string member)
{
_catalog.AddBook(book);
_borrow.IssueBorrow(book, member);
_notification.SendNotification($"Book '{book}' has been borrowed by '{member}'.");
}
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
LibraryFacade library = new LibraryFacade();
library.AddBookAndBorrow("Design Patterns", "John Doe");
}
}
서브시스템의 복잡성 노출
클라이언트는 모든 서브시스템에 대해 개별적으로 접근해야 하며, 이는 복잡하고 유지보수가 어려운 코드를 초래할 수 있습니다.
유연성 부족
새로운 서브시스템이 추가되거나 기존 서브시스템이 변경될 경우, 클라이언트 코드를 수정해야 하므로 시스템의 유연성이 떨어집니다.
Facade 패턴 적용 예시
퍼사드 패턴을 적용하면, 복잡한 서브시스템의 세부 사항을 숨기고, 단순한 인터페이스를 제공할 수 있습니다.
// 서브시스템 클래스 1: 카탈로그 관리
public class Catalog
{
public void AddBook(string book)
=> Console.WriteLine($"Book '{book}' added to catalog.");
}
// 서브시스템 클래스 2: 대여 관리
public class Borrow
{
public void IssueBorrow(string book, string member)
=> Console.WriteLine($"Book '{book}' borrowed by member '{member}'.");
}
// 서브시스템 클래스 3: 알림 관리
public class Notification
{
public void SendNotification(string message)
=> Console.WriteLine($"Notification sent: {message}");
}
// 퍼사드 클래스
public class LibraryFacade
{
private Catalog _catalog;
private Borrow _borrow;
private Notification _notification;
public LibraryFacade()
{
_catalog = new Catalog();
_borrow = new Borrow();
_notification = new Notification();
}
public void AddBookAndBorrow(string book, string member)
{
_catalog.AddBook(book);
_borrow.IssueBorrow(book, member);
_notification.SendNotification($"Book '{book}' has been borrowed by '{member}'.");
}
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
LibraryFacade library = new LibraryFacade();
library.AddBookAndBorrow("Design Patterns", "John Doe");
}
}
LibraryFacade 클래스
퍼사드 클래스는 서브시스템의 복잡성을 숨기고, 클라이언트가 단순한 인터페이스를 통해 서브시스템을 사용할 수 있도록 합니다. 이 클래스는 AddBookAndBorrow
메서드를 통해 여러 서브시스템을 한 번에 처리할 수 있습니다.
클라이언트 코드
클라이언트는 퍼사드 인터페이스만 사용하여 복잡한 서브시스템과 상호작용할 수 있습니다. 클라이언트 코드가 훨씬 간결해졌으며, 서브시스템의 세부 사항을 알 필요가 없습니다.
Facade 패턴 장단점
장점
서브시스템의 복잡성을 숨김
클라이언트는 서브시스템의 복잡한 세부 사항을 알 필요 없이, 단순한 인터페이스를 통해 필요한 작업을 수행할 수 있습니다.
코드의 유지보수성 향상
퍼사드 패턴을 사용하면, 서브시스템이 변경되더라도 퍼사드 인터페이스만 수정하면 되므로, 코드의 유지보수성이 향상됩니다.
모듈화
서브시스템을 모듈화하고, 클라이언트와 서브시스템 간의 결합도를 낮춤으로써, 시스템의 유연성과 확장성을 높일 수 있습니다.
단점
퍼사드의 단일 책임 과부하
퍼사드 클래스에 너무 많은 책임이 집중되면, 퍼사드 클래스 자체가 복잡해질 수 있습니다.
성능 저하 가능성
퍼사드를 통해 서브시스템을 호출하는 경우, 추가적인 레이어가 생기므로 성능이 저하될 가능성이 있습니다.
단점 해결 방안
퍼사드 클래스의 책임 분리
퍼사드 클래스의 책임이 과도해질 경우, 서브 퍼사드 클래스를 도입하여 책임을 분산시킵니다.
퍼사드와 직접 서브시스템 접근 병행
퍼사드를 통해 기본적인 작업을 처리하면서, 클라이언트가 필요할 때 서브시스템에 직접 접근할 수 있도록 허용하여 유연성을 유지합니다.
객체지향 원칙과의 관계
Facade와 캡슐화
퍼사드 패턴은 복잡한 서브시스템의 세부 구현을 캡슐화하여, 클라이언트가 간단한 인터페이스를 통해 서브시스템과 상호작용할 수 있도록 합니다.
Facade와 단일 책임 원칙
퍼사드 패턴은 서브시스템과 클라이언트 간의 상호작용을 간소화하는 책임을 가짐으로써, 단일 책임 원칙을 잘 준수합니다.
Facade와 개방_폐쇄 원칙
퍼사드 패턴을 사용하면 서브시스템을 확장할 때 퍼사드 인터페이스를 수정하지 않고도 새로운 기능을 추가할 수 있으므로, 개방-폐쇄 원칙을 잘 준수합니다.
맺음말
퍼사드 패턴은 복잡한 서브시스템을 단순화하여 클라이언트가 쉽게 접근할 수 있도록 돕는 유용한 패턴입니다. 이 패턴을 사용하면 시스템의 유지보수성과 확장성이 크게 향상되며, 서브시스템의 복잡성을 클라이언트로부터 숨길 수 있습니다. 다만, 퍼사드 클래스에 너무 많은 책임이 집중되지 않도록 설계에 주의해야 합니다.
심화학습
여러 Facade 간의 협력
복잡한 시스템에서는 여러 퍼사드가 서로 협력하여 작업을 수행할 수 있습니다. 예를 들어, 도서 관리 시스템에서 대출 관리 퍼사드와 회원 관리 퍼사드가 협력하여 회원의 대출 기록을 관리할 수 있습니다.
// 서브시스템 클래스 1: 카탈로그 관리
public class Catalog
{
public void Add(string book) => Console.WriteLine($"Added: {book}");
}
// 서브시스템 클래스 2: 대여 관리
public class Borrow
{
public void Issue(string book, string member) => Console.WriteLine($"Borrowed: {book} by {member}");
}
// 서브시스템 클래스 3: 회원 관리
public class MemberManagement
{
public bool IsMember(string member) => member == "John";
}
// 서브시스템 클래스 4: 알림 관리
public class Notification
{
public void Send(string msg) => Console.WriteLine($"Notification: {msg}");
}
// 첫 번째 퍼사드: 회원 서비스 관리
public class MemberServiceFacade
{
private MemberManagement _member;
private Notification _notification;
public MemberServiceFacade(MemberManagement member, Notification notification)
{
_member = member;
_notification = notification;
}
// 회원 검증 및 알림을 처리하고, 대여 가능 여부를 반환
public bool VerifyAndNotify(string member)
{
if (_member.IsMember(member))
{
_notification.Send($"Verified: {member}");
return true;
}
else
{
_notification.Send($"Member not found: {member}");
return false;
}
}
}
// 두 번째 퍼사드: 도서관 관리
public class LibraryFacade
{
private Catalog _catalog;
private Borrow _borrow;
private MemberServiceFacade _memberService; // 회원 서비스 퍼사드를 의존성으로 추가
public LibraryFacade(Catalog catalog, Borrow borrow, MemberServiceFacade memberService)
{
_catalog = catalog;
_borrow = borrow;
_memberService = memberService;
}
// 회원 검증을 요청하고, 대여 작업을 처리
public void AddBookAndBorrow(string book, string member)
{
if (_memberService.VerifyAndNotify(member))
{
_catalog.Add(book);
_borrow.Issue(book, member);
}
}
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
var catalog = new Catalog();
var borrow = new Borrow();
var memberManagement = new MemberManagement();
var notification = new Notification();
var memberService = new MemberServiceFacade(memberManagement, notification);
var library = new LibraryFacade(catalog, borrow, memberService);
// 회원 검증 후, 책을 추가하고 대여하는 과정에서 퍼사드 간 협력
library.AddBookAndBorrow("Design Patterns", "John");
}
}
MemberServiceFacade
: 회원의 검증을 담당하고 알림을 보냅니다.LibraryFacade
: 책 추가 및 대여를 처리하는데, 먼저MemberServiceFacade
를 사용해 회원을 검증하고, 검증이 통과되면 대여 작업을 진행합니다.- 두 퍼사드가 협력하여 하나의 작업을 처리하는 구조입니다.
LibraryFacade
는MemberServiceFacade
를 의존성으로 받아서 회원 검증 및 알림을 처리한 후에 책을 대여합니다.
Facade와 Singleton
퍼사드 패턴을 싱글턴 패턴과 결합하여 시스템 전체에서 퍼사드의 인스턴스를 공유할 수 있습니다. 이를 통해 전역 접근점을 제공하고, 퍼사드의 상태를 일관되게 유지할 수 있습니다.
public class LibraryFacade
{
private static LibraryFacade _instance;
private Catalog _catalog;
private Borrow _borrow;
private Notification _notification;
private LibraryFacade()
{
_catalog = new Catalog();
_borrow = new Borrow();
_notification = new Notification();
}
public static LibraryFacade Instance
{
get
{
if (_instance == null)
{
_instance = new LibraryFacade();
}
return _instance;
}
}
public void AddBookAndBorrow(string book, string member)
{
_catalog.AddBook(book);
_borrow.IssueBorrow(book, member);
_notification.SendNotification($"Book '{book}' has been borrowed to '{member}'.");
}
}
Facade와 Strategy
퍼사드 패턴을 전략 패턴Strategy Pattern과 결합하여, 퍼사드의 동작을 동적으로 변경할 수 있습니다. 예를 들어, 도서 관리 시스템에서 대출 정책을 전략 패턴으로 구현하여, 퍼사드를 통해 다양한 대출 정책을 적용할 수 있습니다.
public interface IBorrowStrategy
{
void IssueBorrow(string book, string member);
}
public class RegularBorrowStrategy : IBorrowStrategy
{
public void IssueBorrow(string book, string member)
{
Console.WriteLine($"Regular Borrow issued for '{book}' to '{member}'.");
}
}
public class PremiumBorrowStrategy : IBorrowStrategy
{
public void IssueBorrow(string book, string member)
{
Console.WriteLine($"Premium Borrow issued for '{book}' to '{member}'.");
}
}
public class LibraryFacade
{
private IBorrowStrategy _borrowStrategy;
public LibraryFacade(IBorrowStrategy borrowStrategy)
{
_borrowStrategy = borrowStrategy;
}
public void AddBookAndBorrow(string book, string member)
{
// Other subsystems...
_borrowStrategy.IssueBorrow(book, member);
}
}
// 클라이언트 코드
public class Program
{
public static void Main(string[] args)
{
IBorrowStrategy regularStrategy = new RegularBorrowStrategy();
LibraryFacade library = new LibraryFacade(regularStrategy);
library.AddBookAndBorrow("Design Patterns", "John Doe");
IBorrowStrategy premiumStrategy = new PremiumBorrowStrategy();
library = new LibraryFacade(premiumStrategy);
library.AddBookAndBorrow("Clean Code", "Jane Doe");
}
}