푸터 콘텐츠로 바로가기
Iron Academy Logo
C# 배우기
C# 배우기

다른 카테고리

이중 디스패치 C#: 디펜던시를 주입하는 것이 합리적인 경우 – Derek Comartin의 도메인 중심 설계를 통해 설명

Derek Comartin
8분 44초

C# 프로그래밍 언어 분야에서 이중 디스패치는 종종 오해되거나 제대로 활용되지 않습니다. 이는 관련된 두 객체 간의 다형적 동작을 가능하게 하는 강력한 기술이며, 특히 파생 클래스 간의 동작을 처리할 때 유용합니다.

데릭 코마틴은 자신의 영상 " DDD에서의 이중 디스패치: 의존성 주입이 의미 있는 경우 "에서 이중 디스패치가 도메인 주도 설계(DDD)에 완벽하게 들어맞는 방식을 분석합니다. 그의 예시들을 심층적으로 살펴보면서, 이 방법이 유지보수 부담을 줄이고, 기존 코드를 단순화하며, 다른 언어에서 흔히 볼 수 있는 비지터 패턴과 같은 패턴을 모방하는 데 어떻게 도움이 되는지 보여드리겠습니다.

C#에서 단일 디스패치의 한계를 경험했거나, 객체 인스턴스와 주입된 전략 또는 규칙에 따라 동작을 결정하려고 시도한 적이 있다면, 이 글이 C#에서 이중 디스패치를 ​​효과적으로 사용하는 방법을 명확히 이해하는 데 도움이 될 것입니다.

도메인 모델에서 동작을 주입하는 것이 타당한 이유

데릭은 DDD에서 흔히 볼 수 있는 원칙, 즉 도메인 모델에는 의존성이 전혀 없어야 한다는 점을 지적하며 이야기를 시작합니다. 하지만 그것이 항상 실용적이거나 유용한 것은 아닙니다. 동작을 모델링할 때 규칙, 정책 ​​또는 전략과 같은 논리를 주입해야 할 수도 있습니다. 이러한 점에서 이중 디스패치가 유용합니다. 정책과 같은 도메인 개념을 도메인 객체에 전달하고 메서드 기반 평가를 외부에서 처리할 수 있지만, 최종 제어권은 여전히 ​​도메인 객체에 있습니다.

이 패턴은 연결 대상이 인프라가 아닌 도메인 동작이라는 점을 고려할 때 타당합니다.

잘못된 예시: 도메인에 하드코딩된 로직

이 문제를 설명하기 위해 데릭은 지연 여부를 판단하는 로직이 하드코딩된 Shipment 클래스부터 시작합니다.

public bool IsLate(DateTime expectedDelivery)
{
    return _systemClock.Now() > expectedDelivery;
}
public bool IsLate(DateTime expectedDelivery)
{
    return _systemClock.Now() > expectedDelivery;
}

기존 코드는 간단하지만 유연성이 떨어집니다. 이는 컴파일 시점의 결정에 의존하며 동작을 클래스와 밀접하게 연결합니다. 규칙을 변경하려면 도메인 모델을 변경해야 하며, 테스트는 시간에 따라 달라지기 때문에 더 어렵습니다.

리팩토링: 정책 및 이중 디스패치 사용

데릭은 IDeliveryTimingPolicy 인터페이스를 소개합니다.

public interface IDeliveryTimingPolicy
{
    bool IsLate(Shipment shipment);
}
public interface IDeliveryTimingPolicy
{
    bool IsLate(Shipment shipment);
}

그는 두 가지 구현체를 만듭니다.

  1. 표준 배송 타이밍 정책

  2. 버퍼링된 전달 타이밍 정책

이 클래스들은 Shipment 객체를 입력받아 자체 규칙에 따라 부울 값을 반환합니다. 다음은 BufferedDeliveryTimingPolicy의 코드 일부입니다.

public bool IsLate(Shipment shipment)
{
    return _clock.Now() > shipment.DeliveryDate.AddMinutes(30);
}
public bool IsLate(Shipment shipment)
{
    return _clock.Now() > shipment.DeliveryDate.AddMinutes(30);
}

이제 Shipment 클래스에서는 이중 배송을 사용합니다.

public bool IsLate(IDeliveryTimingPolicy policy)
{
    return policy.IsLate(this);
}
public bool IsLate(IDeliveryTimingPolicy policy)
{
    return policy.IsLate(this);
}

이것은 전형적인 이중 배송입니다. 첫 번째 배송은 배송에 대해 IsLate()를 호출하고 정책을 전달합니다. 두 번째 전달은 배송 정보를 매개변수로 사용하여 정책에 대해 IsLate() 함수를 호출합니다. 두 개의 객체가 관련되어 있으며, 각각이 동작을 결정하는 데 참여합니다. 이는 단일 디스패치로는 달성할 수 없는 것입니다.

테스트 및 유연성 측면에서의 이점

이러한 접근 방식은 테스트 용이성과 결정론적 정확성이 매우 높은 코드를 생성합니다. 데릭은 표준 정책과 버퍼링된 정책을 사용한 예시를 보여주는데, 여기서 테스트 데이터는 제어되고 각 객체의 런타임 유형에 따라 동작이 결정됩니다.

var policy = new BufferedDeliveryTimingPolicy(TimeSpan.FromMinutes(30));
var shipment = new Shipment { DeliveryDate = DateTime.UtcNow.AddMinutes(-15) };
Assert.False(shipment.IsLate(policy)); // Because of 30-minute buffer
var policy = new BufferedDeliveryTimingPolicy(TimeSpan.FromMinutes(30));
var shipment = new Shipment { DeliveryDate = DateTime.UtcNow.AddMinutes(-15) };
Assert.False(shipment.IsLate(policy)); // Because of 30-minute buffer

이는 도메인 모델을 변경하지 않고도 런타임 동작을 변경할 수 있음을 보여줍니다. 정책의 동적 특성에 기반한 다형적 동작을 얻으면서 도메인 무결성을 유지할 수 있습니다.

C#에서 더블 디스패치 패턴과 비지터 패턴 비교

데릭의 예시는 객체의 구조를 변경하지 않고 객체에 대해 수행할 일련의 작업을 정의하는 방문자 패턴과 유사합니다. 일반적으로 방문자 패턴에서는 다음과 같은 내용을 볼 수 있습니다.

public abstract class Shape
{
    public abstract void Accept(IVisitor visitor);
}

public class Circle : Shape
{
    public override void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}
public abstract class Shape
{
    public abstract void Accept(IVisitor visitor);
}

public class Circle : Shape
{
    public override void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

여기서 Accept() 및 Visit() 메서드는 Derek이 Shipment 및 IDeliveryTimingPolicy를 사용하는 방식과 마찬가지로 객체와 방문자라는 두 가지 유형에 걸쳐 조정됩니다. 이 패턴은 C#과 같은 단일 디스패치 언어에서도 여러 디스패치 동작을 구현하는 데 도움이 됩니다.

정책 모음을 사용하여 규칙 구성하기

이어지는 또 다른 예시에서 데릭은 컬렉션을 통해 여러 규칙을 평가하는 방법을 보여줍니다. 그는 다음과 같은 규칙들을 도입합니다:

  • HasValidDestinationRule

  • 모든 패키지 포장 규칙

  • 이미 배송되지 않은 상품 규칙

각각은 다음과 같은 IShipmentReadinessRule을 구현합니다.

public bool IsSatisfiedBy(Shipment shipment)
public bool IsSatisfiedBy(Shipment shipment)

그런 다음 Shipment 클래스는 다음과 같이 평가합니다.

public bool CanShip(IEnumerable<IShipmentReadinessRule> rules)
{
    return rules.All(rule => rule.IsSatisfiedBy(this));
}
public bool CanShip(IEnumerable<IShipmentReadinessRule> rules)
{
    return rules.All(rule => rule.IsSatisfiedBy(this));
}

이 void accept 패턴을 사용하면 여러 도메인 규칙을 동적으로 평가할 수 있습니다. 규칙을 구성 파일에 저장하는 경우(특히 멀티테넌트 앱의 경우), 런타임에 새 목록을 생성하여 배포에 전달할 수 있습니다.

다중 테넌트 애플리케이션의 동적 규칙

데릭은 멀티테넌트 애플리케이션에서 규칙 세트가 고객별로 달라질 수 있다고 지적합니다. 저장소에서 정책 목록을 가져와 동적으로 주입할 수 있습니다.

var rules = LoadRulesForCustomer(customerId);
var canShip = shipment.CanShip(rules);
var rules = LoadRulesForCustomer(customerId);
var canShip = shipment.CanShip(rules);

이는 동적 디스패치와 런타임 의사 결정 기능을 애플리케이션에 어떻게 통합할 수 있는지 보여줍니다. 원하는 동작은 모델을 변경하는 것이 아니라 구성을 변경함으로써 얻을 수 있습니다.

오해 바로잡기: 모든 의존성이 나쁜 것은 아닙니다

마지막 부분에서 데릭은 도메인에 무엇이든 주입하는 것이 나쁘다는 오해를 바로잡습니다. 그는 무엇 을 주입하느냐가 중요하다고 강조합니다. 도메인 동작(예: 명세 또는 정책)을 주입하는 것은 인프라를 주입하는 것과는 다릅니다.

도메인 모델은 여전히 ​​출발점입니다. 결정권은 해당 객체에 있지만, 동일한 도메인 컨텍스트 내에 있는 다른 객체에게 결정을 위임할 수도 있습니다.

마무리: DDD에서 C#의 더블 디스패치가 강력한 이유

데릭은 마지막으로 비판적 사고를 촉구하며, 명확성과 유지 관리성을 제공하는 '공개 방문', '공개 가상 공간 수용' 또는 이와 유사한 패턴을 두려워하지 말라고 당부합니다. 비즈니스 로직을 통제된 방식으로 주입하면 유연성과 정확성을 얻을 수 있습니다.

따라서 새로운 클래스를 개발하든 기존 코드베이스를 리팩토링하든, C#의 더블 디스패치는 도메인에 집중하면서 관심사를 깔끔하게 분리할 수 있는 방법을 제공합니다.

정책, 명세 또는 방문자 패턴을 사용하고 있거나 심지어 자신도 모르게 이를 활용하고 있다면, 이미 이중 디스패치를 ​​구현하는 데 가까워진 것입니다. 이를 이해하면 런타임 시 코드 동작 방식을 더 잘 제어할 수 있어 테스트 용이성과 적응성을 향상시킬 수 있습니다.

결론

요약하자면, C#에서 이중 디스패치는 도메인 로직을 주입하고, 캡슐화를 유지하며, 유연한 동작을 지원하는 우아하고 실용적인 해결책이 될 수 있습니다. 방문자, 다중 디스패치, 동적 키워드 사용(신중하게)과 같은 패턴과 함께 사용하면 표현력이 풍부하고 견고한 도메인 모델을 작성할 수 있습니다.

그러므로 다음에 shipment.IsLate(policy)를 호출할 때는 C#을 진정한 다형적 설계에 더 가깝게 만드는 기본적인 패턴을 활용하고 있다는 것을 알아두세요.

예시 팁: PurchaseOrder 클래스를 만들고 정책에 따라 품목 추가 가능 여부를 판단하려면, PurchaseOrder에 정책을 전달하고 해당 정책이 주문을 방문하도록 해보세요. 이것이 바로 이중 배송의 실제 모습입니다.

그의 유튜브 채널 에서 전체 영상을 시청하고 주제에 대한 더 자세한 정보를 얻으세요.

Hero Worlddot related to 이중 디스패치 C#: 디펜던시를 주입하는 것이 합리적인 경우 – Derek Comar...
Hero Affiliate related to 이중 디스패치 C#: 디펜던시를 주입하는 것이 합리적인 경우 – Derek Coma...

사랑하는 것을 공유하여 더 많은 수익을 얻으세요

당신은 .NET, C#, Java, Python, 또는 Node.js를 다루는 개발자를 위한 콘텐츠를 만드나요? 당신의 전문성을 추가 수입으로 전환하세요!

아이언 서포트 팀

저희는 주 5일, 24시간 온라인으로 운영합니다.
채팅
이메일
전화해