큰 객체 계층 구조로 방문자 패턴 사용


12

문맥

객체 계층 구조 (표현식 트리)에서 "의사"방문자 패턴 (이중 디스패치를 ​​사용하지 않는 의사)을 사용했습니다.

 public interface MyInterface
 {
      void Accept(SomeClass operationClass);
 }

 public class MyImpl : MyInterface 
 {
      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }
 }

그러나 MyInterface의 구현 수가 상당수 (~ 50 이상)이고 추가 작업을 추가 할 필요가 없기 때문에이 디자인은 의문의 여지가 없었으며 매우 편안했습니다.

각 구현은 고유하며 (다른 표현식 또는 연산자 임) 일부는 복합입니다 (예 : 다른 연산자 / 리프 노드를 포함하는 연산자 노드).

순회는 현재 트리의 루트 노드에서 Accept 작업을 호출하여 수행됩니다. 트리 노드는 차례로 각 하위 노드에서 Accept를 호출합니다.

그러나 예쁜 인쇄와 같은 새로운 작업추가 해야 할 시점이 왔습니다 .

 public class MyImpl : MyInterface 
 {
      // Property does not come from MyInterface
      public string SomeProperty { get; set; }

      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }

      public void Accept(SomePrettyPrinter printer)
      {
           printer.PrettyPrint(this.SomeProperty);
      }
 }    

기본적으로 두 가지 옵션이 있습니다.

  • 유지 관리 비용 (옵션이 아닌 IMHO)을 희생하여 각 파생 클래스에 새로운 작업 방법을 추가하여 동일한 디자인을 유지합니다.
  • 확장 성을 희생시키면서 "참된"방문자 패턴을 사용하십시오 (추가 구현이 필요할 것으로 예상되므로 옵션이 아닙니다 ...). ?

질문

방문자 패턴을 사용하여 다시 명령 하시겠습니까? 이 문제를 해결하는 데 도움이되는 다른 패턴이 있습니까?


1
아마도 데코레이터 체인이 더 적합할까요?
MattDavey

몇 가지 질문 : 이러한 구현은 어떻게 다릅니 까? 계층 구조는 무엇입니까? 항상 같은 구조입니까? 항상 같은 순서로 구조를 횡단해야합니까?
jk.

@MattDavey : 구현과 작업 당 하나의 데코레이터를 사용하도록 명령 하시겠습니까?
T. Fabre

2
@ T.Fabre 말하기 어렵다. 이 중 50 개 이상의 구현은 MyInterface.. 그 모든 클래스는 고유 한 구현을해야합니까 DoSomething그리고 DoSomethingElse? 당신의 방문자 클래스는 실제로 계층 구조를 통과 어디 표시되지 않습니다 - 그것은 더 많은처럼 보이는 facade순간 ..
MattDavey

또한 C #의 버전은 무엇입니까? 람다 있습니까? 또는 linq? 당신의 처분에
jk.

답변:


13

3 가지 프로그래밍 언어로 6 개의 대규모 프로젝트에서 10 년 이상 표현 트리를 표현하기 위해 방문자 패턴을 사용하고 있으며 결과에 매우 만족합니다. 패턴을 훨씬 쉽게 적용 할 수있는 몇 가지 사항을 발견했습니다.

방문자의 인터페이스에서 과부하를 사용하지 마십시오

메소드 이름에 유형을 입력하십시오.

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
}

오히려

IExpressionVisitor {
    void Visit(IPrimitiveExpression expr);
    void Visit(ICompositeExpression expr);
}

방문자 인터페이스에 "catch unknown"메소드를 추가하십시오.

코드를 수정할 수없는 사용자가 가능할 수 있습니다.

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
    void VisitExpression(IExpression expr);
};

이것은 그들 자신의 구현을 구축 할 것입니다 IExpression그리고 IVisitor그것을 자신의 포괄의 구현에 런타임 타입 정보를 이용하여 자신의 표현 "이해" VisitExpression방법은.

IVisitor인터페이스 의 기본 수행 작업 구현

이렇게하면 식 유형의 하위 집합을 처리해야하는 사용자가 방문자를 더 빠르게 구축하고에 더 많은 메서드를 추가하여 코드에 면역성을 부여 할 수 IVisitor있습니다. 예를 들어, 표현식에서 모든 변수 이름을 수집하는 방문자를 작성하는 것은 쉬운 작업이되고 IVisitor나중에 새 표현식 유형을 추가하더라도 코드가 중단되지 않습니다 .


2
왜 당신이 말하는지 명확히 할 수 있습니까 Do not use overloads in the interface of the visitor?
Steven Evers

1
과부하를 사용하여 재지 정하지 않는 이유를 설명 할 수 있습니까? 오버로드 사용 여부에 관계없이 실제로 (oodesign.com의) 어딘가를 읽었습니다. 그 디자인을 선호하는 특별한 이유가 있습니까?
T. Fabre

2
@ T.Fabre 속도면에서는 중요하지 않지만 가독성면에서는 중요합니다. 내가 이것을 구현 한 3 개 언어 중 2 개 언어 ( Java 및 C #)의 메소드 분석 은 잠재적 인 과부하 중에서 선택하기 위해 런타임 단계를 필요로하므로 과부하가 많은 코드를 읽기가 조금 더 어려워진다. 수정하려는 방법을 선택하는 것이 쉽지 않기 때문에 코드 리팩토링도 쉬워집니다.
dasblinkenlight

@SnOrfus 위의 T.Fabre에 대한 답변을 참조하십시오.
dasblinkenlight

@dasblinkenlight C #은 이제 런타임에 어떤 오버로드 된 메소드를 사용해야하는지 (컴파일 타임이 아닌) 결정할 수 있도록 동적을 제공합니다. 과부하를 사용하지 않는 이유가 여전히 있습니까?
Tintenfiisch 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.