소프트웨어 개발에서 디자인 패턴은 문제를 해결하기 위한 재사용 가능한 솔루션을 제공합니다. 이 글에서는 디자인 패턴의 기본 개념을 살펴보고, 특히 C#에서 구조 패턴인 어댑터, 데코레이터 및 프록시 패턴을 구체적인 예제와 함께 설명하겠습니다.
디자인 패턴 개요
디자인 패턴은 개발자들이 반복적으로 직면하는 일반적인 문제에 대한 표준화된 접근 방식을 제공합니다. 디자인 패턴은 다음과 같은 주요 범주로 나뉩니다:
- 생성 패턴: 객체 생성과 관련된 문제를 다룹니다.
- 구조 패턴: 객체 간의 관계를 정의하고 조합하는 방법을 제공합니다.
- 행위 패턴: 객체 간의 상호작용과 역할을 다룹니다.
구조 패턴
구조 패턴은 클래스와 객체의 구성이라는 측면에서 관계를 정의하고 조합하여 더 큰 구조를 형성하는 방법을 제공합니다. 이 섹션에서는 세 가지 중요한 구조 패턴인 어댑터, 데코레이터 및 프록시 패턴을 다룹니다.
1. 어댑터 패턴
어댑터 패턴은 기존 인터페이스와 호환되지 않는 인터페이스를 가진 클래스를 연결하기 위해 사용됩니다. 즉, 어댑터가 기존 클래스의 인터페이스를 포장하여 클라이언트 코드가 해당 클래스를 사용할 수 있도록 합니다.
예제
다음은 C#에서 어댑터 패턴을 구현한 간단한 예제입니다:
using System;
interface ITarget
{
void Request();
}
class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("특정 요청");
}
}
class Adapter : ITarget
{
private Adaptee _adaptee;
public Adapter(Adaptee adaptee)
{
_adaptee = adaptee;
}
public void Request()
{
_adaptee.SpecificRequest();
}
}
class Client
{
public void Main()
{
Adaptee adaptee = new Adaptee();
ITarget target = new Adapter(adaptee);
target.Request();
}
}
class Program
{
static void Main(string[] args)
{
Client client = new Client();
client.Main();
}
}
이 예제에서 Adaptee
클래스는 기존의 복잡한 인터페이스를 가집니다. 그러나 클라이언트는 ITarget
인터페이스를 통해 Request
메소드를 호출하여 Adaptee
의 기능을 사용할 수 있습니다.
2. 데코레이터 패턴
데코레이터 패턴은 객체의 기능을 동적으로 추가하거나 변경하는 데 사용됩니다. 이를 통해 기존 클래스를 변경하지 않고도 새로운 기능을 쉽게 추가할 수 있습니다.
예제
아래는 C#에서 데코레이터 패턴을 구현한 예제입니다:
using System;
interface IComponent
{
string Operation();
}
class ConcreteComponent : IComponent
{
public string Operation()
{
return "기본 구성 요소";
}
}
class Decorator : IComponent
{
protected IComponent _component;
public Decorator(IComponent component)
{
_component = component;
}
public virtual string Operation()
{
return _component.Operation();
}
}
class ConcreteDecoratorA : Decorator
{
public ConcreteDecoratorA(IComponent component) : base(component) { }
public override string Operation()
{
return $"데코레이터 A 추가 기능 ({base.Operation()})";
}
}
class ConcreteDecoratorB : Decorator
{
public ConcreteDecoratorB(IComponent component) : base(component) { }
public override string Operation()
{
return $"데코레이터 B 추가 기능 ({base.Operation()})";
}
}
class Client
{
public void Main()
{
IComponent component = new ConcreteComponent();
Console.WriteLine(component.Operation());
component = new ConcreteDecoratorA(component);
Console.WriteLine(component.Operation());
component = new ConcreteDecoratorB(component);
Console.WriteLine(component.Operation());
}
}
class Program
{
static void Main(string[] args)
{
Client client = new Client();
client.Main();
}
}
위 예제에서 ConcreteComponent
는 기본 구성 요소입니다. Decorator
클래스를 통해 ConcreteDecoratorA
와 ConcreteDecoratorB
가 서로 다른 기능을 추가하여 원래 기능을 확장합니다.
3. 프록시 패턴
프록시 패턴은 다른 객체에 대한 접근을 제어하기 위해 대리 객체를 제공합니다. 프록시는 실제 객체에 대한 참조를 포함하고 요청을 전달하여 추가적인 기능(예: 지연 로딩, 캐싱, 권한 확인 등)을 구현합니다.
예제
아래는 C#에서 프록시 패턴을 구현한 예제입니다:
using System;
interface ISubject
{
void Request();
}
class RealSubject : ISubject
{
public void Request()
{
Console.WriteLine("실제 요청!");
}
}
class Proxy : ISubject
{
private RealSubject _realSubject;
public void Request()
{
if (_realSubject == null)
{
_realSubject = new RealSubject();
}
Console.WriteLine("프록시: 실제 객체에 요청 전달 중...");
_realSubject.Request();
}
}
class Client
{
public void Main()
{
ISubject subject = new Proxy();
subject.Request();
}
}
class Program
{
static void Main(string[] args)
{
Client client = new Client();
client.Main();
}
}
위의 프록시 예제에서 Proxy
클래스는 실제 객체인 RealSubject
에 대한 요청을 관리합니다. 클라이언트는 프록시를 통해 실제 객체에 요청을 전달하여 추가적인 제어를 수행합니다.
결론
이번 글에서는 C#에서 구조 패턴의 주요 개념인 어댑터, 데코레이터 및 프록시 패턴에 대해서 알아보았습니다. 각 패턴의 실용적인 예제를 살펴보면서 디자인 패턴이 어떻게 객체 지향 프로그래밍에 도움을 줄 수 있는지 이해하게 되었습니다. 이러한 패턴들은 소프트웨어 구조를 효과적으로 설계하고 유지 보수하기 위한 강력한 도구가 될 것입니다.