[객체지향] 3.디자인 패턴 개요 및 구현 예제, 구조 패턴 어댑터, 데코레이터, 프록시

소프트웨어 개발에서 디자인 패턴은 문제를 해결하기 위한 재사용 가능한 솔루션을 제공합니다. 이 글에서는 디자인 패턴의 기본 개념을 살펴보고, 특히 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 클래스를 통해 ConcreteDecoratorAConcreteDecoratorB가 서로 다른 기능을 추가하여 원래 기능을 확장합니다.

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#에서 구조 패턴의 주요 개념인 어댑터, 데코레이터 및 프록시 패턴에 대해서 알아보았습니다. 각 패턴의 실용적인 예제를 살펴보면서 디자인 패턴이 어떻게 객체 지향 프로그래밍에 도움을 줄 수 있는지 이해하게 되었습니다. 이러한 패턴들은 소프트웨어 구조를 효과적으로 설계하고 유지 보수하기 위한 강력한 도구가 될 것입니다.