블로그에서 방문자 패턴에 대한 참조가 계속 표시되지만 인정해야합니다. 패턴에 대한 wikipedia 기사를 읽고 그 역학을 이해하지만 그것을 사용할 때 여전히 혼란 스럽습니다.
최근에 정말 사람으로 가지고 데코레이터 패턴을 그리고 지금에 대한 용도를보고있다 절대적으로 모든 곳에서 정말뿐만 아니라 직관적이 보이는 편리한 패턴을 이해할 수 있도록하고 싶습니다.
답변
방문자 패턴에 익숙하지 않습니다. 내가 제대로했는지 보자. 동물의 계층 구조가 있다고 가정하십시오.
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);
}
답변
당신의 혼란의 이유는 아마도 방문객이 치명적인 잘못된 이름이기 때문일 것입니다. 많은 (유명한 1 !) 프로그래머들이이 문제를 우연히 발견했습니다. 실제로는 기본적으로 지원하지 않는 언어로 이중 디스패치 를 구현 합니다 (대부분은 지원하지 않음).
1) 내가 가장 좋아하는 예는 Scott Eyers입니다.“Effective C ++”의 저명한 저자는 이것을 그의 가장 중요한 C ++ 아하 라고 부릅니다 ! 지금까지의 순간 .
답변
여기의 모든 사람은 정확하지만 “언제”를 해결하지 못한다고 생각합니다. 먼저, 디자인 패턴에서 :
방문자는 조작하는 요소의 클래스를 변경하지 않고 새 조작을 정의 할 수 있습니다.
이제 간단한 클래스 계층 구조를 생각해 봅시다. 클래스 1, 2, 3 및 4와 메소드 A, B, C 및 D가 있습니다. 스프레드 시트에서와 같이 배치하십시오. 클래스는 선이고 메소드는 열입니다.
이제 Object Oriented 디자인은 새로운 메소드보다 새로운 클래스를 확장 할 가능성이 높으므로 더 많은 라인을 추가하는 것이 더 쉽다고 가정합니다. 새 클래스를 추가하고 해당 클래스의 다른 것을 지정하고 나머지는 상속합니다.
그러나 때로는 클래스가 비교적 정적이지만 열을 추가하여 더 많은 메소드를 자주 추가해야합니다. OO 디자인의 표준 방법은 모든 클래스에 이러한 메서드를 추가하는 것이므로 비용이 많이들 수 있습니다. 방문자 패턴은 이것을 쉽게 만듭니다.
그건 그렇고, 이것은 스칼라의 패턴 일치가 해결하려는 문제입니다.
답변
방문자 디자인 패턴은 디렉토리 트리, 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
메소드에 대한 인수로 사용되도록합니다 .
답변
방문자 패턴은 클라이언트가 특정 클래스 계층의 모든 클래스에 추가 메소드를 추가 할 수있게하는 방법입니다.
상당히 안정적인 클래스 계층 구조가 있지만 해당 계층 구조로 수행해야 할 사항에 대한 요구 사항이 변경 될 때 유용합니다.
전형적인 예는 컴파일러 등입니다. AST (Abstract Syntax Tree)는 프로그래밍 언어의 구조를 정확하게 정의 할 수 있지만 AST에서 수행하려는 작업은 프로젝트가 진행됨에 따라 변경됩니다 (코드 생성기, 프리티 프린터, 디버거, 복잡도 메트릭 분석).
방문자 패턴이 없으면 개발자가 새 기능을 추가 할 때마다 기본 클래스의 모든 기능에 해당 메소드를 추가해야합니다. 이것은 기본 클래스가 별도의 라이브러리에 나타나거나 별도의 팀에서 생성 할 때 특히 어렵습니다.
(방문자 패턴이 데이터 조작을 데이터에서 멀리 이동시키기 때문에 방문자 패턴이 우수 OO 사례와 충돌한다고 주장하는 것을 들었습니다. 방문자 패턴은 정상적인 OO 사례가 실패하는 상황에서 정확하게 유용합니다.)
답변
방문자 패턴을 사용하는 데는 세 가지 이유가 있습니다.
-
데이터 구조가 변경 될 때 약간 다른 코드의 확산을 줄입니다.
-
계산을 구현하는 코드를 변경하지 않고 여러 데이터 구조에 동일한 계산을 적용합니다.
-
레거시 코드를 변경하지 않고 레거시 라이브러리에 정보를 추가하십시오.
내가 쓴 기사를 살펴보십시오 .
답변
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 페이지에서 영감을 얻습니다. “실제로 방문자는 클래스 자체를 수정하지 않고도 클래스 패밀리에 새로운 가상 기능을 추가 할 수 있습니다. 대신, 가상 함수의 모든 적절한 전문화를 구현하는 방문자 클래스를 작성합니다. 방문자는 인스턴스 참조를 입력으로 가져와 이중 디스패치를 통해 목표를 구현합니다.”
3×2 매트릭스로 인해 이중 디스패치가 필요합니다.
![](http://daplus.net/wp-content/uploads/2023/04/coupang_part-e1630022808943-2.png)