방문자 디자인 패턴은 언제 사용해야합니까? [닫은]


315

블로그에서 방문자 패턴에 대한 참조가 계속 표시되지만 인정해야합니다. 패턴에 대한 wikipedia 기사를 읽고 그 역학을 이해하지만 그것을 사용할 때 여전히 혼란 스럽습니다.

최근에 정말 사람으로 가지고 데코레이터 패턴을 그리고 지금에 대한 용도를보고있다 절대적으로 모든 곳에서 정말뿐만 아니라 직관적이 보이는 편리한 패턴을 이해할 수 있도록하고 싶습니다.


7
마지막으로 로비에서 2 시간 동안 기다리면서 제 블랙 베리에서 Jermey Miller 가이 기사 를 읽은 후 그것을 얻었습니다 . 길지만 이중 디스패치, 방문자 및 컴포지트에 대한 훌륭한 설명과 함께 할 수있는 작업에 대해 설명합니다.
George Mauer


3
방문자 패턴? 어느 것? 요점은이 디자인 패턴에 대해 많은 오해와 순수한 혼란이 있다는 것입니다. 나는이 혼란에 약간의 질서를 부여하는 글과 기사를 작성했다 : rgomes-info.blogspot.co.uk/2013/01/…
Richard Gomes

통합 데이터 형식에 함수 개체를 사용하려면 방문자 패턴이 필요합니다. 함수 객체와 공용체 데이터 유형이 무엇인지 궁금 할 수 있습니다. ccs.neu.edu/home/matthias/htdc.html
Wei Qiu

답변:


315

방문자 패턴에 익숙하지 않습니다. 내가 제대로했는지 보자. 동물의 계층 구조가 있다고 가정하십시오.

class Animal {  };
class Dog: public Animal {  };
class Cat: public Animal {  };

인터페이스가 잘 구성된 복잡한 계층 구조를 제공하십시오.

이제 계층 구조에 새로운 작업을 추가하려고합니다. 즉, 각 동물이 소리를 내기를 원합니다. 계층 구조가이 간단한 한, 당신은 직선 다형성으로 그것을 할 수 있습니다 :

class Animal
{ public: virtual void makeSound() = 0; };

class Dog : public Animal
{ public: void makeSound(); };

void Dog::makeSound()
{ std::cout << "woof!\n"; }

class Cat : public Animal
{ public: void makeSound(); };

void Cat::makeSound()
{ std::cout << "meow!\n"; }

그러나 이런 방식으로 작업을 추가 할 때마다 계층 구조의 모든 단일 클래스에 대한 인터페이스를 수정해야합니다. 이제 원래 인터페이스에 만족하고 가능한 최소한의 수정을 원한다고 가정하십시오.

방문자 패턴을 사용하면 각각의 새 작업을 적절한 클래스로 이동할 수 있으며 계층의 인터페이스를 한 번만 확장해야합니다. 해보자 먼저 계층 구조의 모든 클래스에 대한 메소드가있는 추상 작업 ( GoF 의 "방문자"클래스 )을 정의합니다 .

class Operation
{
public:
    virtual void hereIsADog(Dog *d) = 0;
    virtual void hereIsACat(Cat *c) = 0;
};

그런 다음 새 작업을 수락하기 위해 계층을 수정합니다.

class Animal
{ public: virtual void letsDo(Operation *v) = 0; };

class Dog : public Animal
{ public: void letsDo(Operation *v); };

void Dog::letsDo(Operation *v)
{ v->hereIsADog(this); }

class Cat : public Animal
{ public: void letsDo(Operation *v); };

void Cat::letsDo(Operation *v)
{ v->hereIsACat(this); }

마지막으로 Cat이나 Dog를 수정하지 않고 실제 작업을 구현합니다 .

class Sound : public Operation
{
public:
    void hereIsADog(Dog *d);
    void hereIsACat(Cat *c);
};

void Sound::hereIsADog(Dog *d)
{ std::cout << "woof!\n"; }

void Sound::hereIsACat(Cat *c)
{ std::cout << "meow!\n"; }

이제 더 이상 계층 구조를 수정하지 않고 작업을 추가 할 수 있습니다. 작동 방식은 다음과 같습니다.

int main()
{
    Cat c;
    Sound theSound;
    c.letsDo(&theSound);
}

19
S.Lott, 나무를 걷는 것은 실제로 방문자 패턴이 아닙니다. (이것은 "계층 적 방문자 패턴"이며, 이는 완전히 혼란 스럽습니다.) 상속 또는 인터페이스 구현을 사용하지 않고 GoF 방문자 패턴을 표시 할 방법이 없습니다.
munificent

14
@Knownasilya-사실이 아닙니다. & 연산자는 인터페이스에 필요한 Sound-Object의 주소를 제공합니다. letsDo(Operation *v) 포인터가 필요합니다.
AquilaRapax

3
명확성을 위해 방문자 디자인 패턴의이 예가 맞습니까?
godzilla

4
많은 생각을 한 후에, 왜 Dog와 Cat을 메소드에 이미 전달했지만 왜 당신이 hereIsADog와 hereIsACat이라는 두 가지 메소드를 호출했는지 궁금합니다. 간단한 performTask (Object * obj)를 선호하며이 클래스를 Operation 클래스에서 캐스팅합니다. (그리고 언어, 오버라이드 (override) 캐스팅에 대한 필요를 지원하지 않음)
Abdalrahman 임칙서

6
마지막에 "main"예제에서 : theSound.hereIsACat(c)작업을 수행했을 것입니다. 패턴에 의해 발생 된 모든 오버 헤드를 어떻게 정당화합니까? 이중 파견 이 정당화됩니다.
franssu

131

당신의 혼란의 이유는 아마도 방문객이 치명적인 잘못된 이름이기 때문일 것입니다. 많은 (유명한 1 !) 프로그래머들이이 문제를 우연히 발견했습니다. 실제로는 기본적으로 지원하지 않는 언어로 이중 디스패치 를 구현 합니다 (대부분은 지원하지 않음).


1) 내가 가장 좋아하는 예는 Scott Eyers입니다.“Effective C ++”의 저명한 저자는 이것을 그의 가장 중요한 C ++ 아하 라고 부릅니다 ! 지금까지의 순간 .


3
+1 "패턴이 없습니다"– 완벽한 답변. 가장 많이 제기 된 답변은 많은 c ++ 프로그래머들이 타입 열거 형과 스위치 케이스 (c 방식)를 사용하여 "특별한"다형성에 비해 가상 함수의 한계를 아직 깨닫지 못하고 있음을 증명합니다. 가상을 사용하는 것이 더 깔끔하고 보이지 않을 수 있지만 여전히 단일 디스패치로 제한됩니다. 내 개인적인 견해로는 이것이 C ++의 가장 큰 결함입니다.
user3125280

@ user3125280 지금은 방문자 패턴에 대한 4/5 기사와 디자인 패턴 장을 읽었으며 그 중 어느 것도 사례 stmt에 대해이 모호한 패턴을 사용하거나 다른 방식으로 사용할 수있는 이점을 설명하지 않습니다. 적어도 그것을 기르는 것에 대해 Thx!
spinkus

4
그건 - 나는 그들이 그것을 설명 할 확신 @sam 같은 이점 이 있음을 항상 얻을 서브 클래스 / 런타임 다형성에서 이상 switch: switch(클라이언트 측 (코드 중복)에서 의사 결정 하드 코드와 정적 유형 검사를 제공하지 않습니다 사례의 완전성 및 명확성 확인). 방문자 패턴은 유형 검사기로 확인되며 일반적으로 클라이언트 코드가 더 단순 해집니다.
Konrad Rudolph

@KonradRudolph 감사합니다. 그러나 패턴이나 위키피디아 기사에서는 명시 적으로 다루지 않았습니다. 나는 당신에 동의하지 않지만, 당신은 case stmt를 사용하는 것의 이점이 너무 일반적이라는 것과는 달리 이상한 점을 주장 할 수 있습니다 : 1. 컬렉션의 객체에 accept () 메소드가 필요하지 않습니다. 2. ~ 방문자는 알 수없는 유형의 객체를 처리 할 수 ​​있습니다. 따라서 stmt 케이스는 변경 가능한 유형의 콜렉션으로 오브젝트 구조에서 작동하는 데 더 적합합니다. 패턴은 방문자 패턴이 이러한 시나리오에 적합하지 않다는 것을 인정합니다 (p333).
spinkus

1
@ SamPinkus konrad의 자리-이것이 virtual현대 프로그래밍 언어에서 기능이 매우 유용한 이유 입니다. 확장 가능한 프로그램의 기본 구성 요소입니다. 내 의견으로는 c 언어 (선택한 언어에 따라 스위치 또는 패턴 일치 등)는 다음과 같습니다. 확장 할 필요가없는 코드에서 훨씬 더 깔끔하며, 9 번과 같은 복잡한 소프트웨어에서이 스타일을 보게되어 기뻤습니다. 확장 성을 제공하려는 언어는 재귀 적 단일 디스패치보다 더 나은 디스패치 패턴을 수용해야합니다. 방문객).
user3125280

84

여기의 모든 사람은 정확하지만 "언제"를 해결하지 못한다고 생각합니다. 먼저, 디자인 패턴에서 :

방문자는 조작하는 요소의 클래스를 변경하지 않고 새 조작을 정의 할 수 있습니다.

이제 간단한 클래스 계층 구조를 생각해 봅시다. 클래스 1, 2, 3 및 4와 메소드 A, B, C 및 D가 있습니다. 스프레드 시트에서와 같이 배치하십시오. 클래스는 선이고 메소드는 열입니다.

이제 Object Oriented 디자인은 새로운 메소드보다 새로운 클래스를 확장 할 가능성이 높으므로 더 많은 라인을 추가하는 것이 더 쉽다고 가정합니다. 새 클래스를 추가하고 해당 클래스의 다른 것을 지정하고 나머지는 상속합니다.

그러나 때로는 클래스가 비교적 정적이지만 열을 추가하여 더 많은 메소드를 자주 추가해야합니다. OO 디자인의 표준 방법은 모든 클래스에 이러한 메서드를 추가하는 것이므로 비용이 많이들 수 있습니다. 방문자 패턴은 이것을 쉽게 만듭니다.

그건 그렇고, 이것은 스칼라의 패턴 일치가 해결하려는 문제입니다.


왜 utlity 클래스보다 방문자 패턴을 사용합니까? 다음과 같이 유틸리티 클래스를 호출 할 수 있습니다 : AnalyticsManger.visit (someObjectToVisit) vs AnalyticsVisitor.visit (someOjbectToVisit). 차이점이 뭐야 ? 그들은 둘 다 우려의 분리가 옳은가? 당신이 도울 수 있기를 바랍니다.
j2emanue

@ j2emanue 방문자 패턴은 런타임시 올바른 방문자 과부하를 사용하기 때문에. 올바른 오버로드를 호출하려면 코드에서 유형 캐스팅이 필요합니다.
액세스 거부

그것으로 효율성이 있습니까? 내가 그것을 자사의 좋은 아이디어 주조 피하는 것 같아요
j2emanue

@ j2emanue의 아이디어는 성능상의 이유가 아니라 개방 / 폐쇄 원칙에 맞는 코드를 작성하는 것입니다. Bob 아저씨의 공개 폐쇄보기 butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
액세스 거부

22

방문자 디자인 패턴은 디렉토리 트리, XML 구조 또는 문서 개요와 같은 "순환"구조 정말 잘 작동합니다.

방문자 오브젝트는 재귀 구조의 각 노드 (각 디렉토리, 각 XML 태그)를 방문합니다. 방문자 개체는 구조를 반복하지 않습니다. 대신 방문자 메소드가 구조의 각 노드에 적용됩니다.

일반적인 재귀 노드 구조는 다음과 같습니다. 디렉토리 또는 XML 태그 일 수 있습니다. [자바 사람이라면 자녀 목록을 작성하고 유지하기위한 많은 추가 방법을 상상해보십시오.]

class TreeNode( object ):
    def __init__( self, name, *children ):
        self.name= name
        self.children= children
    def visit( self, someVisitor ):
        someVisitor.arrivedAt( self )
        someVisitor.down()
        for c in self.children:
            c.visit( someVisitor )
        someVisitor.up()

visit메소드는 방문자 오브젝트를 구조의 각 노드에 적용합니다. 이 경우 하향식 방문자입니다. visit상향식 또는 다른 순서를 수행 하도록 메소드 구조를 변경할 수 있습니다 .

방문자를위한 수퍼 클래스입니다. 이 visit방법으로 사용됩니다 . 구조의 각 노드에 "도착"합니다. 때문에 visit방법을 호출 up하고 down, 방문자는 깊이 추적 할 수 있습니다.

class Visitor( object ):
    def __init__( self ):
        self.depth= 0
    def down( self ):
        self.depth += 1
    def up( self ):
        self.depth -= 1
    def arrivedAt( self, aTreeNode ):
        print self.depth, aTreeNode.name

서브 클래스는 각 레벨에서 노드 수를 계산하고 노드 목록을 누적하여 멋진 경로 계층 섹션 번호를 생성 할 수 있습니다.

여기 응용 프로그램이 있습니다. 트리 구조를 만듭니다 someTree. Visitor,을 만듭니다 dumpNodes.

그런 다음를 dumpNodes트리에 적용합니다 . dumpNode객체는 트리의 각 노드를 "방문"것입니다.

someTree= TreeNode( "Top", TreeNode("c1"), TreeNode("c2"), TreeNode("c3") )
dumpNodes= Visitor()
someTree.visit( dumpNodes )

TreeNode visit알고리즘은 모든 TreeNode가 방문자의 arrivedAt메소드에 대한 인수로 사용되도록합니다 .


8
다른 사람들이 말했듯이 이것은 "계층 적 방문자 패턴"입니다.
PPC 코더

1
@ PPC- 코더 '계층 적 방문자 패턴'과 방문자 패턴의 차이점은 무엇입니까?
Tim Lovell-Smith

3
계층 적 방문자 패턴은 기존 방문자 패턴보다 융통성이 있습니다. 예를 들어, 계층 적 패턴을 사용하면 순회 깊이를 추적하고 순회 할 분기를 결정하거나 모두 순회를 중지 할 수 있습니다. 클래식 방문자에게는이 개념이 없으며 모든 노드를 방문합니다.
PPC 코더

18

방문자 패턴은 클라이언트가 특정 클래스 계층의 모든 클래스에 추가 메소드를 추가 할 수있게하는 방법입니다.

상당히 안정적인 클래스 계층 구조가 있지만 해당 계층 구조로 수행해야 할 사항에 대한 요구 사항이 변경 될 때 유용합니다.

전형적인 예는 컴파일러 등입니다. AST (Abstract Syntax Tree)는 프로그래밍 언어의 구조를 정확하게 정의 할 수 있지만 AST에서 수행하려는 작업은 프로젝트가 진행됨에 따라 변경됩니다 (코드 생성기, 프리티 프린터, 디버거, 복잡도 메트릭 분석).

방문자 패턴이 없으면 개발자가 새 기능을 추가 할 때마다 기본 클래스의 모든 기능에 해당 메소드를 추가해야합니다. 이것은 기본 클래스가 별도의 라이브러리에 나타나거나 별도의 팀에서 생성 할 때 특히 어렵습니다.

(방문자 패턴이 데이터 조작을 데이터에서 멀리 이동시키기 때문에 방문자 패턴이 우수 OO 사례와 충돌한다고 주장하는 것을 들었습니다. 방문자 패턴은 정상적인 OO 사례가 실패하는 상황에서 정확하게 유용합니다.)


나는 또한 다음에 대한 귀하의 의견을 원합니다 : 왜 utlity 클래스보다 방문자 패턴을 사용합니까? 다음과 같이 유틸리티 클래스를 호출 할 수 있습니다 : AnalyticsManger.visit (someObjectToVisit) vs AnalyticsVisitor.visit (someOjbectToVisit). 차이점이 뭐야 ? 그들은 둘 다 우려의 분리가 옳은가? 당신이 도울 수 있기를 바랍니다.
j2emanue

@ j2emanue : 질문을 이해하지 못합니다. 나는 당신이 그것을 육체로 만들고 누군가가 대답 할 수있는 완전한 질문으로 게시하도록 제안합니다.
Oddthinking

1
나는 여기에 새로운 질문을 게시했다 : stackoverflow.com/questions/52068876/…
j2emanue

14

방문자 패턴을 사용하는 데는 세 가지 이유가 있습니다.

  1. 데이터 구조가 변경 될 때 약간 다른 코드의 확산을 줄입니다.

  2. 계산을 구현하는 코드를 변경하지 않고 여러 데이터 구조에 동일한 계산을 적용합니다.

  3. 레거시 코드를 변경하지 않고 레거시 라이브러리에 정보를 추가하십시오.

내가 쓴 기사를 살펴보십시오 .


1
방문자에게 가장 큰 단일 용도로 기사에 댓글을 달았습니다. 생각?
George Mauer

13

Konrad Rudolph가 이미 지적했듯이 이중 파견이 필요한 경우에 적합합니다.

다음은 이중 파견이 필요한 상황과 방문자가 어떻게 도움을 주는지를 보여주는 예입니다.

예 :

iPhone, Android, Windows Mobile의 세 가지 유형의 모바일 장치가 있다고 가정 해 보겠습니다.

이 세 장치에는 모두 Bluetooth 라디오가 설치되어 있습니다.

파란 이빨 라디오가 두 개의 개별 OEM (Intel & Broadcom)에서 나올 수 있다고 가정합니다.

예제를 논의와 관련시키기 위해 Intel 라디오에 의해 노출되는 API가 Broadcom 라디오에 의해 노출 된 API와 다르다고 가정합니다.

이것이 나의 수업 모습입니다 –

여기에 이미지 설명을 입력하십시오 여기에 이미지 설명을 입력하십시오

이제 모바일 장치에서 Bluetooth 켜기 작업을 소개하겠습니다.

함수 서명은 다음과 같아야합니다.

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

따라서 올바른 장치 유형에 따라올바른 유형의 Bluetooth 라디오에 따라 적절한 단계 또는 알고리즘호출 하여 수 있습니다 .

원칙적으로 3 x 2 행렬이되어 관련 객체의 올바른 유형에 따라 올바른 작업을 벡터화하려고합니다.

두 인수의 유형에 따라 다형성 동작.

여기에 이미지 설명을 입력하십시오

이제이 패턴에 방문자 패턴을 적용 할 수 있습니다. Wikipedia 페이지에서 영감을 얻습니다. “실제로 방문자는 클래스 자체를 수정하지 않고도 클래스 패밀리에 새로운 가상 기능을 추가 할 수 있습니다. 대신, 가상 함수의 모든 적절한 전문화를 구현하는 방문자 클래스를 작성합니다. 방문자는 인스턴스 참조를 입력으로 가져와 이중 디스패치를 ​​통해 목표를 구현합니다.”

3x2 매트릭스로 인해 이중 디스패치가 필요합니다.

설정은 다음과 같습니다. 여기에 이미지 설명을 입력하십시오

다른 질문에 대답하기 위해 예제를 작성했습니다. 코드 및 설명은 여기 에 언급되어 있습니다 .


9

다음 링크에서 더 쉽다는 것을 알았습니다.

에서 http://www.remondo.net/visitor-pattern-example-csharp/ 나는 예를 발견 쇼 방문자 패턴의 장점은 무엇 쇼 있다는 모의 예. 여기에 다른 컨테이너 클래스가 있습니다 Pill.

namespace DesignPatterns
{
    public class BlisterPack
    {
        // Pairs so x2
        public int TabletPairs { get; set; }
    }

    public class Bottle
    {
        // Unsigned
        public uint Items { get; set; }
    }

    public class Jar
    {
        // Signed
        public int Pieces { get; set; }
    }
}

위에서 볼 수 있듯이, BilsterPackPills '쌍이 포함되어 있으므로 쌍 수에 2를 곱해야 Bottle합니다.unit 다른 데이터 타입과 필요가 캐스팅 될 것입니다.

따라서 주요 방법에서는 다음 코드를 사용하여 알약 수를 계산할 수 있습니다.

foreach (var item in packageList)
{
    if (item.GetType() == typeof (BlisterPack))
    {
        pillCount += ((BlisterPack) item).TabletPairs * 2;
    }
    else if (item.GetType() == typeof (Bottle))
    {
        pillCount += (int) ((Bottle) item).Items;
    }
    else if (item.GetType() == typeof (Jar))
    {
        pillCount += ((Jar) item).Pieces;
    }
}

위의 코드가 위배됩니다 Single Responsibility Principle 됩니다. 즉, 새로운 유형의 컨테이너를 추가하는 경우 기본 메소드 코드를 변경해야합니다. 또한 스위치를 더 길게 만드는 것은 나쁜 습관입니다.

따라서 다음 코드를 도입하여

public class PillCountVisitor : IVisitor
{
    public int Count { get; private set; }

    #region IVisitor Members

    public void Visit(BlisterPack blisterPack)
    {
        Count += blisterPack.TabletPairs * 2;
    }

    public void Visit(Bottle bottle)
    {
        Count += (int)bottle.Items;
    }

    public void Visit(Jar jar)
    {
        Count += jar.Pieces;
    }

    #endregion
}

의 수를 세는 책임을 Pill호출 된 클래스 로 옮겼습니다 PillCountVisitor(그리고 우리는 switch case 문을 제거했습니다). 즉, 새로운 유형의 환약 용기를 추가해야 할 때마다 PillCountVisitor등급 만 변경해야합니다 . 또한 IVisitor다른 시나리오에서 사용하기위한 인터페이스가 일반적입니다.

알약 컨테이너 클래스에 Accept 메소드를 추가하여 :

public class BlisterPack : IAcceptor
{
    public int TabletPairs { get; set; }

    #region IAcceptor Members

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }

    #endregion
}

우리는 방문자가 알약 컨테이너 클래스를 방문하도록 허용합니다.

결국 우리는 다음 코드를 사용하여 알약 수를 계산합니다.

var visitor = new PillCountVisitor();

foreach (IAcceptor item in packageList)
{
    item.Accept(visitor);
}

즉, 모든 알약 용기는 PillCountVisitor방문자가 알약 개수를 볼 수 있도록합니다 . 그는 약을 세는 법을 알고 있습니다.

상기 visitor.Count알약의 값을가집니다.

http://butunclebob.com/ArticleS.UncleBob.IuseVisitor 에는 단일 책임 원칙을 따르기 위해 다형성 (답변)을 사용할 수없는 실제 시나리오가 있습니다 . 실제로 :

public class HourlyEmployee extends Employee {
  public String reportQtdHoursAndPay() {
    //generate the line for this hourly employee
  }
}

reportQtdHoursAndPay방법은보고 및 표현을위한 것이며 단일 책임 원칙을 위반합니다. 따라서 방문자 패턴을 사용하여 문제점을 극복하는 것이 좋습니다.


2
안녕하세요. 가장 밝게 생각하는 부분을 추가하기 위해 답을 편집 해 주시겠습니까? 따라서 목표는 지식 데이터베이스가되고 링크가 다운되므로 링크 전용 답변을 사용하지 않는 것이 좋습니다.
George Mauer

8

이중 디스패치는 이 패턴을 사용하는 이유 중 하나 일뿐 입니다 .
그러나 단일 디스패치 패러다임을 사용하는 언어로 두 번 이상의 디스패치를 ​​구현하는 유일한 방법입니다.

패턴을 사용해야하는 이유는 다음과 같습니다.

1) 모델이 자주 변경되지 않기 때문에 매번 모델을 변경하지 않고 새로운 작업을 정의하려고합니다 .

2) 우리는 여러 응용 프로그램에서 재사용 가능한 모델 하거나 클라이언트 클래스가 자신의 클래스로 동작을 정의 할 수 있는 확장 가능한 모델원하기 때문에 모델과 동작을 결합하지 않습니다 .

3) 우리는 모델의 구체적인 유형에 의존하는 공통 연산을 가지고 있지만 , 각 서브 클래스에서 로직을 구현하고 싶지 않기 때문에 여러 클래스에서 여러 장소에서 공통 로직을 분해 할 수 있습니다 .

4) 우리는 도메인 모델 설계를 사용하고 있으며 동일한 계층 구조의 모델 클래스는 다른 곳에 모을 수있는 너무 많은 고유 한 것들을 수행합니다 .

5) 우리는 이중 파견이 필요합니다 .
인터페이스 유형으로 선언 된 변수가 있으며 런타임 유형에 따라 변수를 처리 할 수 ​​있기를 원합니다. 물론 물론 if (myObj instanceof Foo) {}어떤 트릭 도 사용하지 않습니다.
예를 들어 특정 처리를 적용하기 위해 구체적인 유형의 인터페이스를 매개 변수로 선언하는 메서드에 이러한 변수를 전달하는 것이 좋습니다. 런타임에 선택한 호출은 수신자의 런타임 유형에만 의존하기 때문에 언어가 단일 디스패치에 의존하는 경우 이러한 방법을 즉시 사용할 수 없습니다.
Java에서 호출 할 메소드 (서명)는 컴파일시 선택되며 런타임 유형이 아니라 선언 된 매개 변수 유형에 따라 다릅니다.

방문자를 사용하는 마지막 이유는 방문자를 구현할 때 (물론 다중 디스패치를 ​​지원하지 않는 언어의 경우) 반드시 이중 디스패치 구현을 도입해야하기 때문에 결과입니다.

각 방문자에게 방문자를 적용하기위한 요소 순회 (반복)가 패턴을 사용하는 이유는 아닙니다.
모델과 처리를 분할하기 때문에 패턴을 사용합니다.
또한 패턴을 사용하면 반복자 기능을 활용할 수 있습니다.
이 기능은 매우 강력하며 일반적인 방법과 마찬가지로 특정 방법을 사용하여 공통 유형에서 반복을 뛰어 넘습니다 accept().
특별한 사용 사례입니다. 그래서 한쪽에 넣겠습니다.


자바 예제

플레이어가 조각 이동을 요청할 때 처리를 정의하려는 체스 예제로 패턴의 부가 가치를 설명하겠습니다.

방문자 패턴을 사용하지 않으면 조각 서브 클래스에서 직접 조각 이동 동작을 정의 할 수 있습니다.
예를 들어 다음 Piece과 같은 인터페이스를 가질 수 있습니다 .

public interface Piece{

    boolean checkMoveValidity(Coordinates coord);

    void performMove(Coordinates coord);

    Piece computeIfKingCheck();

}

각 Piece 서브 클래스는 다음과 같이 구현합니다.

public class Pawn implements Piece{

    @Override
    public boolean checkMoveValidity(Coordinates coord) {
        ...
    }

    @Override
    public void performMove(Coordinates coord) {
        ...
    }

    @Override
    public Piece computeIfKingCheck() {
        ...
    }

}

그리고 모든 Piece 서브 클래스에 대해 같은 것입니다.
이 디자인을 보여주는 다이어그램 클래스는 다음과 같습니다.

[모델 클래스 다이어그램

이 접근법은 세 가지 중요한 단점을 제시합니다.

- 같은 행동 performMove()또는 computeIfKingCheck()매우 아마 일반적인 논리를 사용합니다.
콘크리트는 어떤 예를 들어 Piece, performMove()마지막으로 잠재적으로 특정 위치에 현재의 조각을 설정합니다 상대의 조각을합니다.
관련 행동을 모으는 대신 여러 클래스로 나누면 어떤 방식 으로든 단일 책임 패턴이 무효화됩니다. 유지 관리가 더 어려워집니다.

– 서브 클래스가 보거나 변경할 수 checkMoveValidity()없는 처리 Piece.
사람이나 컴퓨터의 행동을 넘어서는 것은 점검입니다. 이 확인은 요청 된 조각 이동이 유효한지 확인하기 위해 플레이어가 요청한 각 작업에서 수행됩니다.
따라서 우리는 Piece인터페이스 에서 그것을 제공하고 싶지 않습니다 .

– 봇 개발자에게 어려운 체스 게임에서 일반적으로 응용 프로그램은 표준 API ( Piece인터페이스, 서브 클래스, 보드, 공통 동작 등)를 제공하고 개발자가 봇 전략을 강화할 수 있습니다.
이를 위해서는 Piece구현시 데이터와 동작이 밀접하게 결합되지 않은 모델을 제안해야합니다 .

방문자 패턴을 사용해 봅시다!

우리는 두 종류의 구조를 가지고 있습니다 :

– 방문을 수락하는 모델 클래스 (조각)

– 방문한 방문자 (이동 작업)

다음은 패턴을 보여주는 클래스 다이어그램입니다.

여기에 이미지 설명을 입력하십시오

상단에는 방문자가 있고 하단에는 모델 클래스가 있습니다.

PieceMovingVisitor인터페이스 는 다음과 같습니다 (각 종류에 대해 지정된 동작 Piece).

public interface PieceMovingVisitor {

    void visitPawn(Pawn pawn);

    void visitKing(King king);

    void visitQueen(Queen queen);

    void visitKnight(Knight knight);

    void visitRook(Rook rook);

    void visitBishop(Bishop bishop);

}

조각은 이제 정의됩니다 :

public interface Piece {

    void accept(PieceMovingVisitor pieceVisitor);

    Coordinates getCoordinates();

    void setCoordinates(Coordinates coordinates);

}

주요 방법은 다음과 같습니다.

void accept(PieceMovingVisitor pieceVisitor);

첫 번째 디스패치를 ​​제공합니다 : Piece수신자에 기반한 호출 .
컴파일시, 메소드는 accept()Piece 인터페이스 의 메소드에 바인드 되고 런타임시 바운딩 된 메소드가 런타임 Piece클래스 에서 호출됩니다 .
그리고 accept()두 번째 디스패치를 ​​수행하는 것은 메소드 구현입니다.

실제로 객체가 Piece방문하려는 각 하위 클래스는 인수 자체로 전달 PieceMovingVisitor하여 PieceMovingVisitor.visit()메서드를 호출합니다 .
이런 식으로, 컴파일러는 컴파일 시간과 함께 선언 된 매개 변수의 유형 인 구체적 유형으로 바인드됩니다.
두 번째 파견이 있습니다.
다음은이 Bishop를 설명하는 서브 클래스입니다.

public class Bishop implements Piece {

    private Coordinates coord;

    public Bishop(Coordinates coord) {
        super(coord);
    }

    @Override
    public void accept(PieceMovingVisitor pieceVisitor) {
        pieceVisitor.visitBishop(this);
    }

    @Override
    public Coordinates getCoordinates() {
        return coordinates;
    }

   @Override
    public void setCoordinates(Coordinates coordinates) {
        this.coordinates = coordinates;
   }

}

그리고 여기에 사용 예가 있습니다.

// 1. Player requests a move for a specific piece
Piece piece = selectPiece();
Coordinates coord = selectCoordinates();

// 2. We check with MoveCheckingVisitor that the request is valid
final MoveCheckingVisitor moveCheckingVisitor = new MoveCheckingVisitor(coord);
piece.accept(moveCheckingVisitor);

// 3. If the move is valid, MovePerformingVisitor performs the move
if (moveCheckingVisitor.isValid()) {
    piece.accept(new MovePerformingVisitor(coord));
}

방문자 단점

방문자 패턴은 매우 강력한 패턴이지만 사용하기 전에 고려해야 할 몇 가지 중요한 제한 사항이 있습니다.

1) 캡슐화를 줄이거 나 끊을 위험

일부 종류의 작업에서 방문자 패턴은 도메인 개체의 캡슐화를 줄이거 나 끊을 수 있습니다.

예를 들어, MovePerformingVisitor 클래스가 실제 조각의 좌표를 설정해야하므로 Piece인터페이스는 다음과 같은 방법을 제공해야합니다.

void setCoordinates(Coordinates coordinates);

Piece좌표 변경 의 책임 은 이제 Piece서브 클래스 이외의 다른 클래스에 공개됩니다 . 서브 클래스
에서 방문자가 수행 한 처리를 이동 Piece하는 것도 옵션이 아닙니다. 방문자 구현을 수락
하면 실제로 다른 문제가 발생합니다 Piece.accept(). 방문자가 수행하는 작업을 알지 못하므로 조각 상태를 변경하는지 여부와 방법을 모릅니다.
방문자를 식별하는 방법 Piece.accept()은 방문자 구현 에 따라 사후 처리를 수행하는 것입니다. 그것은 방문자 구현 및 조각 서브 클래스 사이에 아마 같은 트릭을 사용하는 것이 필요 게다가 높은 커플 링 만드는 것처럼 그것은 매우 좋은 생각 일 것 getClass(), instanceof또는 방문자 구현을 식별 어떤 마커를.

2) 모델 변경 요구 사항

Decorator예를 들어 다른 행동 디자인 패턴과 달리 방문자 패턴은 방해가됩니다.
우리는 accept()방문을 받아들이 는 방법을 제공하기 위해 초기 수신자 클래스를 수정해야합니다 .
우리를 위해 어떤 문제를 가지고 있지 않은 Piece이들로 그 서브 클래스는 우리의 클래스 .
기본 제공 또는 타사 클래스에서는 상황이 그리 쉽지 않습니다. 메소드
를 추가하려면 랩핑하거나 상속 (가능한 경우)해야합니다 accept().

3) 간접 지정

이 패턴은 다중 간접 지정을 만듭니다.
이중 디스패치는 단일 호출 대신 두 개의 호출을 의미합니다.

call the visited (piece) -> that calls the visitor (pieceMovingVisitor)

방문자가 방문한 객체 상태를 변경함에 따라 추가 간접 지정을 가질 수 있습니다.
사이클처럼 보일 수 있습니다.

call the visited (piece) -> that calls the visitor (pieceMovingVisitor) -> that calls the visited (piece)

6

Cay Horstmann은 자신의 OO 디자인 및 패턴 북에서 방문자 를 적용 할 수있는 훌륭한 예를 가지고 있습니다 . 그는 문제를 요약합니다.

복합 객체는 종종 개별 요소로 구성된 복잡한 구조를 가지고 있습니다. 일부 요소에는 하위 요소가 다시있을 수 있습니다. ... 요소에 대한 작업은 하위 요소를 방문하고 해당 요소에 작업을 적용하고 결과를 결합합니다. ... 그러나 그러한 디자인에 새로운 작업을 추가하는 것은 쉽지 않습니다.

쉽지 않은 이유는 구조 클래스 자체 내에 작업이 추가 되었기 때문입니다. 예를 들어, 파일 시스템이 있다고 가정하십시오.

FileSystem 클래스 다이어그램

다음은이 구조로 구현하려는 일부 작업 (기능)입니다.

  • 노드 요소의 이름을 표시합니다 (파일 목록).
  • 계산 된 노드 요소의 크기를 표시합니다 (디렉토리의 크기에는 모든 하위 요소의 크기가 포함됨)
  • 기타

FileSystem의 각 클래스에 함수를 추가하여 오퍼레이션을 구현할 수 있습니다 (과거 사람들은 과거에이를 수행하는 방법이 매우 분명했기 때문에이를 수행했습니다). 문제는 새로운 기능 (위의 "etc."행)을 추가 할 때마다 구조 클래스에 더 많은 메소드를 추가해야한다는 것입니다. 어느 시점에서 소프트웨어에 몇 가지 작업을 추가 한 후에는 해당 클래스의 메소드가 클래스의 기능적 응집력 측면에서 더 이상 의미가 없습니다. 예를 들어, FileNode메소드가있는calculateFileColorForFunctionABC() 파일 시스템에서 최신 시각화 기능을 구현하기 이 있습니다.

방문자 패턴 (많은 디자인 패턴과 같은)은 모든 곳에서 많은 변경을 요구하지 않고 우수한 디자인 원칙 (높은 응집력, 낮은 커플 링)을 존중하지 않고 코드를 변경할 수있는 더 좋은 방법이 있다는 것을 알고있는 개발자 의 고통과 고통 에서 비롯 되었습니다. ). 당신이 그 고통을 느낄 때까지 많은 패턴의 유용성을 이해하기 어렵다는 것이 나의 의견입니다. 고통을 설명하는 것 (우리가 추가하는 "등"기능으로 위에서 시도한 것처럼)은 설명에서 공간을 차지하고주의를 산만하게합니다. 이러한 이유로 패턴을 이해하기가 어렵습니다.

방문자는 데이터 구조 (예 :)의 기능을 FileSystemNodes데이터 구조 자체에서 분리 할 수 있습니다. 이 패턴을 사용하면 설계가 응집력을 존중할 수 있습니다. 데이터 구조 클래스가 더 단순하고 (메소드 수가 적음) 기능도 Visitor구현 으로 캡슐화됩니다 . 이는 이중 디스 패칭 (패턴의 복잡한 부분)을 통해 수행됩니다 accept(). 구조 클래스의 visitX()메소드 및 방문자 (기능) 클래스의 메소드 사용 :

방문자가 적용된 FileSystem 클래스 다이어그램

이 구조를 사용하면 구조 클래스를 변경하지 않고 콘크리트 방문자로서 구조에서 작동하는 새로운 기능을 추가 할 수 있습니다.

방문자가 적용된 FileSystem 클래스 다이어그램

예를 들어, PrintNameVisitor디렉토리 목록 기능 PrintSizeVisitor을 구현하는 a 와 크기가있는 버전을 구현하는 a 입니다. 언젠가 XML로 데이터를 생성하는 'ExportXMLVisitor'또는 JSON 등에서 데이터를 생성하는 다른 방문자가 있다고 상상할 수 있습니다. DOT와 같은 그래픽 언어를 사용하여 디렉토리 트리를 표시하여 방문자를 시각화 할 수도 있습니다. 다른 프로그램과 함께.

마지막으로 더블 디스패치 기능이있는 방문자의 복잡성은 이해, 코딩 및 디버깅이 더 어렵다는 것을 의미합니다. 요컨대, 그것은 괴짜 요소가 높으며 다시 KISS 원칙을 따릅니다. 연구원들이 수행 한 설문 조사에서 방문자는 논란의 여지가있는 패턴 인 것으로 나타났습니다 (유용성에 대한 합의는 없었습니다). 일부 실험에서는 코드를 유지 관리하기가 쉽지 않은 것으로 나타났습니다.


내가 생각하는 디렉토리 구조는 좋은 복합 패턴이지만 마지막 단락에 동의합니다.
zar

5

제 생각에 새로운 작업을 추가하는 작업량 Visitor Pattern은 각 요소 구조를 사용 하거나 직접 수정 하여 거의 동일 합니다. 또한 새 요소 클래스를 추가하려는 경우 CowOperation 인터페이스가 영향을 받고 기존의 모든 요소 클래스로 전파되므로 모든 요소 클래스를 다시 컴파일해야합니다. 그래서 요점이 뭐야?


4
내가 방문자를 사용했을 때마다 거의 항상 개체 계층을 통과하는 작업을합니다. 중첩 트리 메뉴를 고려하십시오. 모든 노드를 축소하려고합니다. 방문자를 구현하지 않으면 그래프 순회 코드를 작성해야합니다. 또는 방문자와 함께 : rootElement.visit (node) -> node.collapse(). 방문자와 함께 각 노드는 모든 하위 노드에 대한 그래프 순회를 구현하므로 완료됩니다.
George Mauer

@GeorgeMauer, 이중 디스패치의 개념은 저에게 동기를 부여했습니다. 순회 논리를 배포한다는 아이디어는 여전히 일시 중지를 제공합니다. 더 효율적입니까? 더 유지 관리가 가능합니까? "레벨 N으로 접기"가 요구 사항으로 추가되면 어떻게됩니까?
nik.shornikov

@ nik.shornikov 효율성은 실제로 걱정할 필요가 없습니다. 거의 모든 언어에서 몇 가지 함수 호출은 무시할 수있는 오버 헤드입니다. 그 이상은 미시 최적화입니다. 더 유지 관리가 가능합니까? 글쎄요. 나는 대부분 그렇지 않다고 생각합니다. 「폴드 레벨 N」에 관해서. levelsRemaining매개 변수로 카운터에 쉽게 전달하십시오 . 다음 단계의 어린이에게 전화하기 전에 그것을 줄이십시오. 손님의 내부 if(levelsRemaining == 0) return.
George Mauer

1
@GeorgeMauer는 효율성이 사소한 문제라는 점에 전적으로 동의했습니다. 그러나 승인 서명의 재정의와 같은 유지 관리 가능성은 결정이 내려야 할 것이라고 정확히 생각합니다.
nik.shornikov

5

Aspect Object 프로그래밍과 동일한 지하 구현으로 방문자 패턴 ..

예를 들어, 조작하는 요소의 클래스를 변경하지 않고 새 조작을 정의하는 경우


Aspect Object Programming을 언급 해
주셔서 감사합니다

5

방문자 패턴에 대한 빠른 설명. 수정이 필요한 클래스는 모두 'accept'메소드를 구현해야합니다. 클라이언트는이 accept 메소드를 호출하여 해당 클래스 계열에 대해 새로운 조치를 수행하여 기능을 확장합니다. 고객은이 하나의 accept 메소드를 사용하여 각 특정 조치마다 다른 방문자 클래스를 전달하여 광범위한 새 조치를 수행 할 수 있습니다. 방문자 클래스에는 패밀리 내의 모든 클래스에 대해 동일한 특정 조치를 수행하는 방법을 정의하는 여러 대체 된 방문 방법이 포함됩니다. 이러한 방문 메소드는 작업 할 인스턴스를 전달받습니다.

사용을 고려할 때

  1. 클래스 패밀리가 있으면 많은 새로운 조치를 추가해야한다는 것을 알고 있지만, 어떤 이유로 나중에 클래스 패밀리를 변경하거나 다시 컴파일 할 수 없습니다.
  2. 새로운 액션을 추가하고 새로운 액션을 여러 클래스에 분산시키지 않고 방문자 클래스 내에 완전히 정의하려는 경우.
  3. 당신의 상사가 말할 때 당신은 지금 무언가를해야하는 다양한 종류의 클래스를 만들어야한다고합니다 .

4

나는 아저씨 밥 기사를 만나고 의견을 읽을 때 까지이 패턴을 이해하지 못했습니다 . 다음 코드를 고려하십시오.

public class Employee
{
}

public class SalariedEmployee : Employee
{
}

public class HourlyEmployee : Employee
{
}

public class QtdHoursAndPayReport
{
    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        foreach (Employee e in employees)
        {
            if (e is HourlyEmployee he)
                PrintReportLine(he);
            if (e is SalariedEmployee se)
                PrintReportLine(se);
        }
    }

    public void PrintReportLine(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hours");
    }
    public void PrintReportLine(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    }
}

class Program
{
    static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }
}

단일 책임을 확인하기 때문에보기에 좋지만 공개 / 종료 원칙을 위반 합니다. 새로운 직원 유형을 가질 때마다 유형 확인 기능이있는 경우 추가해야합니다. 그리고 당신이하지 않으면 컴파일 타임에 그것을 알 수 없습니다.

방문자 패턴을 사용하면 개방 / 폐쇄 원칙을 위반하지 않고 단일 책임을 위반하지 않기 때문에 코드를 더 깨끗하게 만들 수 있습니다. 방문을 잊어 버린 경우 컴파일되지 않습니다.

public abstract class Employee
{
    public abstract void Accept(EmployeeVisitor v);
}

public class SalariedEmployee : Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public class HourlyEmployee:Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public interface EmployeeVisitor
{
    void Visit(HourlyEmployee he);
    void Visit(SalariedEmployee se);
}

public class QtdHoursAndPayReport : EmployeeVisitor
{
    public void Visit(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hourly");
        // generate the line of the report.
    }
    public void Visit(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    } // do nothing

    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        QtdHoursAndPayReport v = new QtdHoursAndPayReport();
        foreach (var emp in employees)
        {
            emp.Accept(v);
        }
    }
}

class Program
{

    public static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }       
}  
}

마술은 v.Visit(this)동일하게 보이지만 방문자의 다른 과부하를 호출하기 때문에 실제로 다르다는 것입니다.


그렇습니다. 평평한 목록뿐만 아니라 나무 구조로 작업 할 때 특히 유용합니다 (평평한 목록은 나무의 특별한 경우입니다). 아시다시피, 목록에서 그다지 복잡하지는 않지만 노드 간 탐색이 더욱 복잡 해짐에 따라 방문자는 구세주가 될 수 있습니다.
George Mauer

3

@Federico A. Ramponi의 탁월한 답변을 바탕으로합니다.

이 계층 구조가 있다고 상상해보십시오.

public interface IAnimal
{
    void DoSound();
}

public class Dog : IAnimal
{
    public void DoSound()
    {
        Console.WriteLine("Woof");
    }
}

public class Cat : IAnimal
{
    public void DoSound(IOperation o)
    {
        Console.WriteLine("Meaw");
    }
}

여기에 "Walk"메소드를 추가해야하는 경우 어떻게됩니까? 그것은 전체 디자인에 고통을 줄 것입니다.

동시에 "Walk"방법을 추가하면 새로운 질문이 생깁니다. "식사"또는 "수면"은 어떻습니까? 추가하려는 모든 새로운 작업이나 작업에 대해 Animal 계층 구조에 새로운 방법을 추가해야합니까? 그것은 추악하고 가장 중요하며, Animal 인터페이스를 닫을 수 없습니다. 따라서 방문자 패턴을 사용하면 계층을 수정하지 않고도 계층에 새로운 메소드를 추가 할 수 있습니다!

따라서이 C # 예제를 확인하고 실행하십시오.

using System;
using System.Collections.Generic;

namespace VisitorPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var animals = new List<IAnimal>
            {
                new Cat(), new Cat(), new Dog(), new Cat(), 
                new Dog(), new Dog(), new Cat(), new Dog()
            };

            foreach (var animal in animals)
            {
                animal.DoOperation(new Walk());
                animal.DoOperation(new Sound());
            }

            Console.ReadLine();
        }
    }

    public interface IOperation
    {
        void PerformOperation(Dog dog);
        void PerformOperation(Cat cat);
    }

    public class Walk : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Dog walking");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Cat Walking");
        }
    }

    public class Sound : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Woof");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Meaw");
        }
    }

    public interface IAnimal
    {
        void DoOperation(IOperation o);
    }

    public class Dog : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }

    public class Cat : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }
}

걷고, 식사를하는 것은 적절한 예부터 둘 수있는 좋은 방법입니다 일반적이지 수 있습니다 Dog뿐만 아니라 같은 Cat. 기본 클래스에서 만들었으므로 상속되거나 적합한 예를 선택할 수 있습니다.
Abhinav Gauniyal

소리는 다르지만 좋은 샘플이지만 방문자 패턴과 관련이 있는지 확실하지 않습니다.
DAG

3

방문객

방문자는 클래스 자체를 수정하지 않고도 클래스 제품군에 새로운 가상 기능을 추가 할 수 있습니다. 대신 가상 함수의 모든 적절한 전문화를 구현하는 방문자 클래스를 작성합니다.

방문자 구조 :

여기에 이미지 설명을 입력하십시오

다음과 같은 경우 방문자 패턴을 사용하십시오.

  1. 구조로 그룹화 된 다른 유형의 객체에 대해서도 유사한 작업을 수행해야합니다.
  2. 많은 별개의 관련없는 작업을 실행해야합니다. 객체와 연산을 분리
  3. 객체 구조를 변경하지 않고 새로운 작업을 추가해야합니다
  4. 강의를 변경하거나 유도하도록 강요하지 않고 관련 작업을 단일 클래스로 모으기
  5. 소스가 없거나 소스를 변경할 수없는 클래스 라이브러리에 함수 추가

방문자 패턴은 Object의 기존 코드를 변경하지 않고 새로운 작업을 추가 할 수있는 유연성을 제공 하지만 이 유연성에는 단점이 있습니다.

새로운 Visitable 객체가 추가 된 경우 Visitor & ConcreteVisitor 클래스의 코드를 변경해야합니다 . 이 문제를 해결하는 해결 방법이 있습니다. 리플렉션 사용. 성능에 영향을줍니다.

코드 스 니펫 :

import java.util.HashMap;

interface Visitable{
    void accept(Visitor visitor);
}

interface Visitor{
    void logGameStatistics(Chess chess);
    void logGameStatistics(Checkers checkers);
    void logGameStatistics(Ludo ludo);    
}
class GameVisitor implements Visitor{
    public void logGameStatistics(Chess chess){
        System.out.println("Logging Chess statistics: Game Completion duration, number of moves etc..");    
    }
    public void logGameStatistics(Checkers checkers){
        System.out.println("Logging Checkers statistics: Game Completion duration, remaining coins of loser");    
    }
    public void logGameStatistics(Ludo ludo){
        System.out.println("Logging Ludo statistics: Game Completion duration, remaining coins of loser");    
    }
}

abstract class Game{
    // Add game related attributes and methods here
    public Game(){

    }
    public void getNextMove(){};
    public void makeNextMove(){}
    public abstract String getName();
}
class Chess extends Game implements Visitable{
    public String getName(){
        return Chess.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Checkers extends Game implements Visitable{
    public String getName(){
        return Checkers.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Ludo extends Game implements Visitable{
    public String getName(){
        return Ludo.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}

public class VisitorPattern{
    public static void main(String args[]){
        Visitor visitor = new GameVisitor();
        Visitable games[] = { new Chess(),new Checkers(), new Ludo()};
        for (Visitable v : games){
            v.accept(visitor);
        }
    }
}

설명:

  1. Visitable( Element)는 인터페이스이며이 인터페이스 메소드는 클래스 세트에 추가해야합니다.
  2. Visitor인터페이스는 Visitable요소에 대한 조작을 수행하는 메소드를 포함합니다 .
  3. GameVisitorVisitor인터페이스 ( ConcreteVisitor) 를 구현하는 클래스 입니다.
  4. Visitable요소 Visitor는 관련 Visitor인터페이스 메소드를 승인 하고 호출합니다 .
  5. 당신은 치료할 수 GameElement및 콘크리트 게임을 좋아하는 Chess,Checkers and LudoConcreteElements.

위의 예에서 Chess, Checkers and Ludo세 가지 다른 게임 (및 Visitable클래스)이 있습니다. 어느 날, 각 게임의 통계를 기록하는 시나리오에 직면했습니다. 따라서 통계 기능을 구현하기 위해 개별 클래스를 수정하지 않고도 클래스의 책임을 중앙 집중식으로 관리 할 수 ​​있으므로 GameVisitor각 게임의 구조를 수정하지 않고도 트릭을 수행 할 수 있습니다.

산출:

Logging Chess statistics: Game Completion duration, number of moves etc..
Logging Checkers statistics: Game Completion duration, remaining coins of loser
Logging Ludo statistics: Game Completion duration, remaining coins of loser

인용하다

oodesign 기사

소스 메이킹 기사

상세 사항은

데코레이터

패턴을 사용하면 동일한 클래스의 다른 객체의 동작에 영향을주지 않고 정적 또는 동적으로 개별 객체에 동작을 추가 할 수 있습니다

관련 게시물:

IO 데코레이터 패턴

데코레이터 패턴을 사용하는시기


2

나는 http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html 의 설명과 예제를 정말로 좋아합니다 .

기본 클래스 계층 구조가 고정되어 있다고 가정합니다. 다른 공급 업체에서 제공 한 것일 수 있으며 해당 계층을 변경할 수 없습니다. 그러나 의도는 해당 계층 구조에 새로운 다형성 메소드를 추가하려는 것이므로 일반적으로 기본 클래스 인터페이스에 무언가를 추가해야합니다. 따라서 딜레마는 기본 클래스에 메소드를 추가해야하지만 기본 클래스를 건드릴 수 없다는 것입니다. 이 문제를 어떻게 해결합니까?

이런 종류의 문제를 해결하는 디자인 패턴을 "방문자"(디자인 패턴 책의 마지막 패턴)라고하며 마지막 섹션에 표시된 이중 디스패치 체계를 기반으로합니다.

방문자 패턴을 사용하면 방문자 유형의 별도 클래스 계층을 작성하여 기본 유형에서 수행되는 조작을 가상화하여 기본 유형의 인터페이스를 확장 할 수 있습니다. 기본 유형의 객체는 단순히 방문자를 "수락"한 다음 방문자의 동적 바인딩 멤버 함수를 호출합니다.


기술적으로는 방문자 패턴이지만 실제로는 예제에서 기본적인 이중 디스패치입니다. 나는 유용성이 특별히 이것에서만 보이지는 않는다고 주장 할 것이다.
George Mauer

1

방법과시기를 이해했지만 그 이유를 이해하지 못했습니다. C ++과 같은 언어의 배경 지식을 가진 사람에게 도움 이되는 경우이 내용을 주의 깊게 읽으십시오 .

게으른 경우, 우리는 방문자 패턴을 사용합니다. "가상 함수는 C ++로 동적으로 전달되지만 함수 오버로딩은 정적으로 수행됩니다 . "

또는 실제로 ApolloSpacecraft 객체에 바인딩 된 SpaceShip 참조를 전달할 때 CollideWith (ApolloSpacecraft &)가 호출되도록 다른 방법을 사용하십시오.

class SpaceShip {};
class ApolloSpacecraft : public SpaceShip {};
class ExplodingAsteroid : public Asteroid {
public:
  virtual void CollideWith(SpaceShip&) {
    cout << "ExplodingAsteroid hit a SpaceShip" << endl;
  }
  virtual void CollideWith(ApolloSpacecraft&) {
    cout << "ExplodingAsteroid hit an ApolloSpacecraft" << endl;
  }
}

2
방문자 패턴에서 동적 디스패치를 ​​사용하면 완전히 당황합니다. 패턴의 제안 된 사용은 컴파일 타임에 수행 될 수있는 분기를 설명합니다. 이 경우 함수 템플릿을 사용하는 것이 더 나을 것 같습니다.
Praxeolitic

0

@Federico A. Ramponi 에 대한 멋진 설명에 감사드립니다 . 나는 이것을 Java 버전으로 만들었습니다 . 도움이 되길 바랍니다.

또한 @Konrad Rudolph가 지적했듯이 실제로 는 런타임 방법을 결정하기 위해 두 개의 구체적인 인스턴스를 함께 사용 하는 이중 디스패치 입니다.

따라서 실제로 조작 인터페이스가 올바르게 정의 되어 있는 한 조작 실행기에 대한 공통 인터페이스 를 작성할 필요가 없습니다 .

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showTheHobby(food);
        Katherine katherine = new Katherine();
        katherine.presentHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void embed(Katherine katherine);
}


class Hearen {
    String name = "Hearen";
    void showTheHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine {
    String name = "Katherine";
    void presentHobby(Hobby hobby) {
        hobby.embed(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void embed(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

예상대로, 공통 인터페이스는 실제로이 패턴에서 필수적인 부분은 아니지만 더 명확 해집니다.

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showHobby(food);
        Katherine katherine = new Katherine();
        katherine.showHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void insert(Katherine katherine);
}

abstract class Person {
    String name;
    protected Person(String n) {
        this.name = n;
    }
    abstract void showHobby(Hobby hobby);
}

class Hearen extends  Person {
    public Hearen() {
        super("Hearen");
    }
    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine extends Person {
    public Katherine() {
        super("Katherine");
    }

    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void insert(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

0

당신의 질문은 알아야 할 때입니다.

먼저 방문자 패턴으로 코드를 작성하지 않습니다. 나는 표준 코드를 작성하고 필요가 발생할 때까지 기다렸다가 리팩터링합니다. 한 번에 하나씩 여러 결제 시스템을 설치했다고 가정 해 보겠습니다. 체크 아웃시 많은 if 조건 (또는 instanceOf)을 가질 수 있습니다. 예를 들면 다음과 같습니다.

//psuedo code
    if(payPal) 
    do paypal checkout 
    if(stripe)
    do strip stuff checkout
    if(payoneer)
    do payoneer checkout

이제 10 가지 지불 방법이 있다고 상상해보십시오. 따라서 방문자가 발생하는 일종의 패턴을 볼 때 방문자가 모든 것을 분리하고 나면 다음과 같은 것을 호출하게됩니다.

new PaymentCheckoutVistor(paymentType).visit()

유스 케이스를 보여주는 im의 예제 수에서 구현하는 방법을 볼 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.