ฉันควรใช้รูปแบบการออกแบบของผู้เข้าชมเมื่อใด [ปิด]


315

ฉันเห็นการอ้างอิงถึงรูปแบบผู้เข้าชมในบล็อก แต่ฉันต้องยอมรับว่าฉันไม่เข้าใจ ฉันอ่านบทความวิกิพีเดียสำหรับรูปแบบและฉันเข้าใจกลไกของมัน แต่ฉันก็ยังสับสนว่าเมื่อไรฉันจะใช้มัน

ในฐานะที่เป็นคนที่เพิ่งได้รับรูปแบบมัณฑนากรจริงๆและตอนนี้ได้เห็นการใช้งานมันทุกที่ฉันต้องการที่จะเข้าใจรูปแบบที่มีประโยชน์นี้ดูเหมือนจะใช้งานง่าย


7
ในที่สุดได้รับหลังจากอ่านบทความนี้โดย Jermey Miller บน blackberry ของฉันในขณะที่ติดรออยู่ในล็อบบี้เป็นเวลาสองชั่วโมง มันยาว แต่ให้คำอธิบายที่ยอดเยี่ยมเกี่ยวกับการแจกจ่ายสองครั้งผู้เยี่ยมชมและการรวมและสิ่งที่คุณสามารถทำได้กับสิ่งเหล่านี้
George Mauer

1
นี่เป็นบทความที่ดี: codeproject.com/Articles/186185/Visitor-Design-Pattern
Seyed Morteza Mousavi

3
รูปแบบของผู้เข้าชม อันไหน? ประเด็นคือ: มีความเข้าใจผิดและความสับสนมากมายเกี่ยวกับรูปแบบการออกแบบนี้ ฉันได้เขียนและบทความที่หวังว่าจะทำให้ความวุ่นวายนี้: rgomes-info.blogspot.co.uk/2013/01/…
Richard Gomes

เมื่อคุณต้องการให้วัตถุฟังก์ชั่นกับชนิดข้อมูลรวมคุณจะต้องรูปแบบของผู้เข้าชม คุณอาจสงสัยว่าฟังก์ชั่นวัตถุและชนิดข้อมูลรวมเป็นอย่างไรแล้วมันก็คุ้มค่าที่จะอ่านccs.neu.edu/home/matthias/htdc.html
Wei Qiu

ตัวอย่างที่นี่และที่นี่
jaco0646

คำตอบ:


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 Visitor โดยไม่ต้องใช้การสืบทอดหรือการใช้อินเทอร์เฟซ
มหานคร

14
@Knownasilya - ไม่จริง & -Operator ให้ที่อยู่ของวัตถุเสียงซึ่งจำเป็นสำหรับอินเทอร์เฟซ letsDo(Operation *v) ต้องการตัวชี้
AquilaRapax

3
เพื่อความชัดเจนเท่านั้นตัวอย่างของรูปแบบการออกแบบผู้เข้าชมนี้ถูกต้องหรือไม่
godzilla

4
หลังจากคิดมากฉันสงสัยว่าทำไมคุณถึงเรียกสองวิธีในที่นี้คือ ADOG และที่นี่ ISACAT แม้ว่าคุณจะผ่าน Dog และ Cat ไปที่วิธีการแล้ว ฉันต้องการ performTask อย่างง่าย (Object * obj) และคุณใช้งานวัตถุนี้ในคลาส Operation (และในภาษาที่รองรับการเอาชนะไม่จำเป็นต้องคัดเลือก)
Abdalrahman Shatou

6
ในตัวอย่าง "หลัก" ของคุณในตอนท้าย: theSound.hereIsACat(c)จะทำงานเสร็จแล้วคุณจะแสดงให้เห็นถึงค่าใช้จ่ายทั้งหมดที่เกิดจากรูปแบบได้อย่างไร คู่เยี่ยงอย่างคือเหตุผล
franssu

131

สาเหตุของความสับสนของคุณอาจเป็นไปได้ว่าผู้เยี่ยมชมเป็นคนเรียกชื่อผิดร้ายแรง โปรแกรมเมอร์(มีชื่อเสียง1 !) หลายคนสะดุดปัญหานี้ สิ่งที่จริงแล้วคือการใช้การแจกจ่ายสองครั้งในภาษาที่ไม่สนับสนุนโดยกำเนิด (ส่วนใหญ่ไม่ได้)


1)ตัวอย่างที่ฉันชอบคือ Scott Meyers ผู้แต่งรางวัล“ Effective C ++” ที่เรียกคนนี้ว่าC ++ aha ที่สำคัญที่สุดของเขา! ช่วงเวลาที่เคย


3
+1 "ไม่มีรูปแบบ" - คำตอบที่สมบูรณ์แบบ คำตอบ upvoted ที่สุดพิสูจน์โปรแกรมเมอร์ c ++ หลายคนที่ยังตระหนักถึงข้อ จำกัด ของฟังก์ชั่นเสมือนมากกว่า "adhoc" polymorphism โดยใช้ชนิด enum และ switch case (the c way) มันอาจจะดูดีและใช้งานไม่ได้ แต่ก็ยัง จำกัด อยู่เพียงการส่งแบบครั้งเดียว ในความเห็นส่วนตัวของฉันนี่เป็นข้อบกพร่องที่ใหญ่ที่สุดของ c ++
user3125280

@ user3125280 ฉันได้อ่านบทความ 4/5 บทและรูปแบบการออกแบบในรูปแบบของผู้เข้าชมตอนนี้และไม่มีใครอธิบายถึงข้อดีของการใช้รูปแบบที่ไม่ชัดเจนนี้ในกรณี stmt หรือเมื่อคุณอาจใช้อีกบทความหนึ่ง ขอบคุณอย่างน้อยก็นำมันขึ้นมา!
spinkus

4
@ Sam ฉันค่อนข้างแน่ใจว่าพวกเขาอธิบายมัน - มันเป็นข้อได้เปรียบเดียวกับที่คุณได้รับเสมอจาก subclassing / polymorphism แบบ over over switch: switchรหัสที่ยากต่อการตัดสินใจที่ฝั่งไคลเอ็นต์ (การทำสำเนารหัส) และไม่มีการตรวจสอบชนิดคงที่ ( ตรวจสอบความสมบูรณ์และความแตกต่างของคดี ฯลฯ ) รูปแบบผู้เยี่ยมชมได้รับการตรวจสอบโดยตัวตรวจสอบประเภทและมักจะทำให้รหัสลูกค้าง่ายขึ้น
Konrad Rudolph

@ KonradRudolph ขอบคุณสำหรับสิ่งนั้น อย่างไรก็ตามมันไม่ได้ระบุไว้อย่างชัดเจนในรูปแบบหรือบทความวิกิพีเดีย ฉันไม่เห็นด้วยกับคุณ แต่คุณสามารถโต้แย้งได้ว่ามีประโยชน์ในการใช้เคส stmt ด้วยดังนั้นมันแปลก ๆ ที่มันไม่ได้เปรียบเทียบกันโดยทั่วไป: 1. คุณไม่จำเป็นต้องมีวิธีการ accept () บนวัตถุของคอลเลกชันของคุณ 2. ผู้เยี่ยมชม ~ สามารถจัดการกับวัตถุประเภทที่ไม่รู้จัก ดังนั้น stmt ของเคสน่าจะเหมาะสมกว่าสำหรับการทำงานกับโครงสร้างของวัตถุที่มีการรวบรวมประเภทที่เกี่ยวข้อง รูปแบบไม่ยอมรับว่ารูปแบบผู้เข้าชมไม่เหมาะกับสถานการณ์ดังกล่าว (p333)
spinkus

1
@SamPinkus konrad จุด - นั่นคือเหตุผลที่virtualชอบคุณสมบัติมีประโยชน์มากในภาษาการเขียนโปรแกรมที่ทันสมัย ​​- พวกเขาเป็นหน่วยการสร้างพื้นฐานของโปรแกรมที่ขยายได้ - ในความคิดของฉันวิธี c (ซ้อนสลับหรือรูปแบบการจับคู่ ฯลฯ ขึ้นอยู่กับภาษาที่คุณเลือก) ทำความสะอาดในโค้ดที่ไม่จำเป็นต้องขยายและฉันรู้สึกประหลาดใจที่เห็นรูปแบบนี้ในซอฟต์แวร์ที่ซับซ้อนเช่น prover 9 ที่สำคัญกว่าภาษาใด ๆ ที่ต้องการให้ความสามารถในการขยายควรน่าจะรองรับรูปแบบการกระจายที่ดีกว่า ผู้เข้าชม)
user3125280

84

ทุกคนที่นี่ถูกต้อง แต่ฉันคิดว่าไม่สามารถพูดถึง "เมื่อ" ก่อนจากรูปแบบการออกแบบ:

ผู้เยี่ยมชมช่วยให้คุณกำหนดการทำงานใหม่โดยไม่ต้องเปลี่ยนคลาสขององค์ประกอบที่ใช้งาน

ทีนี้ลองนึกถึงลำดับชั้นแบบง่าย ๆ ฉันมีชั้นเรียน 1, 2, 3 และ 4 และวิธีการ A, B, C และ D วางพวกเขาออกมาเหมือนในกระดาษคำนวณ: ชั้นเรียนเป็นเส้นและวิธีการที่มีคอลัมน์

ตอนนี้การออกแบบเชิงวัตถุถือว่าคุณมีแนวโน้มที่จะเพิ่มคลาสใหม่มากกว่าวิธีการใหม่ดังนั้นการเพิ่มบรรทัดเพิ่มเติมเพื่อพูดจะง่ายขึ้น คุณเพิ่งเพิ่มคลาสใหม่ระบุสิ่งที่แตกต่างในคลาสนั้นและสืบทอดส่วนที่เหลือ

แม้ว่าบางครั้งคลาสจะค่อนข้างคงที่ แต่คุณต้องเพิ่มวิธีการบ่อยขึ้น - เพิ่มคอลัมน์ วิธีมาตรฐานในการออกแบบ OO คือการเพิ่มวิธีการดังกล่าวให้กับทุกชั้นเรียนซึ่งอาจมีค่าใช้จ่ายสูง รูปแบบของผู้เข้าชมทำให้เป็นเรื่องง่าย

โดยวิธีการนี้เป็นปัญหาที่รูปแบบของ Scala ตรงกับความตั้งใจที่จะแก้ปัญหา


เหตุใดฉันจึงใช้รูปแบบผู้เข้าชมมากกว่าคลาสที่เต็มอิ่ม ฉันสามารถเรียกชั้นยูทิลิตี้ของฉันเช่นนี้: AnalyticsManger.visit (someObjectToVisit) กับ AnalyticsVisitor.visit (someOjbectToVisit) ความแตกต่างคืออะไร? พวกเขาทั้งสองแยกจากกันด้วยความกังวลใช่มั้ย หวังว่าคุณสามารถช่วย
j2emanue

@ j2emanue เนื่องจากรูปแบบผู้เยี่ยมชมใช้โอเวอร์โหลดของผู้เข้าชมที่ถูกต้องในขณะทำงาน ในขณะที่รหัสของคุณต้องการการคัดเลือกนักแสดงเพื่อเรียกโอเวอร์โหลดที่ถูกต้อง
เข้าถึงถูกปฏิเสธ

มีประสิทธิภาพเพิ่มขึ้นด้วยที่? ฉันคิดว่ามันหลีกเลี่ยงการคัดเลือกไอเดียที่ดี
j2emanue

@ j2emanue ความคิดคือการเขียนโค้ดที่สอดคล้องกับหลักการเปิด / ปิดไม่ใช่เหตุผลด้านประสิทธิภาพ ดูเปิดปิดที่ลุง Bob butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
Access Denied

22

ผู้เข้าชมรูปแบบการออกแบบทำงานได้ดีจริงๆสำหรับ "recursive" โครงสร้างเช่นต้นไม้ไดเรกทอรีโครงสร้าง XML หรือโครงร่างเอกสาร

วัตถุผู้เยี่ยมชมเยี่ยมชมแต่ละโหนดในโครงสร้างแบบเรียกซ้ำ: แต่ละไดเรกทอรี, แต่ละแท็ก XML, อะไรก็ตาม วัตถุผู้เยี่ยมชมไม่วนซ้ำผ่านโครงสร้าง แทนที่จะใช้วิธีผู้เยี่ยมชมกับแต่ละโหนดของโครงสร้าง

นี่คือโครงสร้างโหนดแบบเรียกซ้ำโดยทั่วไป อาจเป็นไดเรกทอรีหรือแท็ก XML [หากคน Java ของคุณจินตนาการถึงวิธีการมากมายในการสร้างและดูแลรายชื่อลูก ๆ ]

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. มันจะสร้าง, VisitordumpNodes

จากนั้นมันจะใช้dumpNodesกับต้นไม้ dumpNodeวัตถุจะ "เยี่ยมชม" แต่ละโหนดในต้นไม้

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

visitอัลกอริทึมTreeNode จะรับรองว่า TreeNode ทั้งหมดจะถูกใช้เป็นอาร์กิวเมนต์สำหรับarrivedAtวิธีการของผู้เข้าชม


8
ตามที่คนอื่นระบุไว้นี่เป็น "รูปแบบผู้เข้าชมตามลำดับชั้น"
PPC-Coder

1
@ PPC-Coder อะไรคือความแตกต่างระหว่าง 'รูปแบบผู้เข้าชมตามลำดับชั้น' และรูปแบบของผู้เข้าชม
ทิมโลเวลล์ - สมิ ธ

3
รูปแบบผู้เข้าชมตามลำดับชั้นมีความยืดหยุ่นมากกว่ารูปแบบผู้เข้าชมแบบดั้งเดิม ตัวอย่างเช่นด้วยรูปแบบลำดับชั้นคุณสามารถติดตามความลึกของการสำรวจเส้นทางและตัดสินใจว่าสาขาใดที่จะทำการสำรวจหรือหยุดการสำรวจทั้งหมด ผู้เยี่ยมชมแบบคลาสสิกไม่มีแนวคิดนี้และจะไปที่โหนดทั้งหมด
PPC-Coder

18

วิธีหนึ่งที่จะดูได้ก็คือรูปแบบของผู้เข้าชมเป็นวิธีที่ทำให้ลูกค้าของคุณเพิ่มวิธีการเพิ่มเติมให้กับคลาสทั้งหมดของคุณในลำดับชั้นของชั้นเรียนที่เฉพาะเจาะจง

มันมีประโยชน์เมื่อคุณมีลำดับชั้นของคลาสที่ค่อนข้างคงที่ แต่คุณเปลี่ยนข้อกำหนดของสิ่งที่ต้องทำกับลำดับชั้นนั้น

ตัวอย่างคลาสสิกสำหรับคอมไพเลอร์และไม่ชอบ ทรีบทคัดย่อไวยากรณ์ (AST) สามารถกำหนดโครงสร้างของภาษาการเขียนโปรแกรมได้อย่างถูกต้อง แต่การดำเนินการที่คุณอาจต้องการทำใน AST จะเปลี่ยนตามความก้าวหน้าของโครงการของคุณ: ผู้สร้างรหัสเครื่องพิมพ์สวยเครื่องพิมพ์ดีบั๊กการวิเคราะห์ตัวชี้วัดที่ซับซ้อน

หากไม่มีรูปแบบผู้เข้าชมทุกครั้งที่นักพัฒนาต้องการเพิ่มคุณลักษณะใหม่พวกเขาจะต้องเพิ่มวิธีการนั้นให้กับทุกคุณสมบัติในคลาสพื้นฐาน นี่เป็นเรื่องยากโดยเฉพาะอย่างยิ่งเมื่อคลาสพื้นฐานปรากฏในไลบรารีที่แยกต่างหากหรือถูกผลิตโดยทีมอื่น

(ฉันได้ยินมาว่ามันเป็นที่ถกเถียงกันอยู่ว่ารูปแบบของผู้เข้าชมขัดแย้งกับแนวทางปฏิบัติของ OO ที่ดีเพราะมันย้ายการดำเนินการของข้อมูลออกไปจากข้อมูลรูปแบบของผู้เข้าชมนั้นมีประโยชน์ในสถานการณ์ที่การปฏิบัติ OO ปกติล้มเหลว)


ฉันต้องการความคิดเห็นของคุณเกี่ยวกับสิ่งต่อไปนี้: ทำไมฉันถึงใช้รูปแบบผู้เยี่ยมชมในชั้นเรียนที่เต็มไปด้วยผู้คน ฉันสามารถเรียกชั้นยูทิลิตี้ของฉันเช่นนี้: AnalyticsManger.visit (someObjectToVisit) กับ AnalyticsVisitor.visit (someOjbectToVisit) ความแตกต่างคืออะไร? พวกเขาทั้งสองแยกจากกันด้วยความกังวลใช่มั้ย หวังว่าคุณสามารถช่วย
j2emanue

@ j2emanue: ฉันไม่เข้าใจคำถาม ฉันแนะนำให้คุณเติมเต็มและโพสต์เป็นคำถามเต็มสำหรับทุกคนที่จะตอบ
Oddthinking

1
ฉันโพสต์คำถามใหม่ที่นี่: stackoverflow.com/questions/52068876/…
j2emanue

14

มีเหตุผลที่ดีอย่างน้อยสามประการในการใช้รูปแบบผู้เข้าชม:

  1. ลดการแพร่กระจายของรหัสซึ่งแตกต่างกันเพียงเล็กน้อยเมื่อโครงสร้างข้อมูลเปลี่ยนแปลง

  2. ใช้การคำนวณแบบเดียวกันกับโครงสร้างข้อมูลหลาย ๆ แบบโดยไม่ต้องเปลี่ยนรหัสซึ่งใช้การคำนวณ

  3. เพิ่มข้อมูลลงในไลบรารีระบบเดิมโดยไม่ต้องเปลี่ยนรหัสระบบเดิม

กรุณาดูได้ที่บทความที่ผมเคยเขียนเกี่ยวกับเรื่องนี้


1
ฉันแสดงความคิดเห็นในบทความของคุณด้วยการใช้งานที่ยิ่งใหญ่ที่สุดที่ฉันเคยเห็นสำหรับผู้เยี่ยมชม คิด?
George Mauer

13

ตามที่คอนราดรูดอล์ฟชี้ไปแล้วมันเหมาะสำหรับกรณีที่เราต้องการการจัดส่งสองครั้ง

นี่คือตัวอย่างเพื่อแสดงสถานการณ์ที่เราต้องการการจัดส่งซ้ำสองครั้งและวิธีที่ผู้เยี่ยมชมช่วยเราในการทำเช่นนั้น

ตัวอย่าง:

ให้บอกว่าฉันมีอุปกรณ์มือถือ 3 ประเภท - iPhone, Android, Windows Mobile

อุปกรณ์ทั้งสามนี้มีวิทยุ Bluetooth ติดตั้งอยู่

สมมติว่าวิทยุฟันสีน้ำเงินนั้นมาจาก OEM แยกกัน 2 รุ่นคือ Intel & Broadcom

เพียงเพื่อให้ตัวอย่างที่เกี่ยวข้องกับการสนทนาของเราให้สมมติว่า APIs ที่เปิดเผยโดยวิทยุของ Intel นั้นแตกต่างจากวิทยุ Broadcom

นี่คือลักษณะที่คลาสของฉัน -

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

ตอนนี้ฉันอยากจะแนะนำการดำเนินการ - การสลับกับบลูทู ธ บนอุปกรณ์มือถือ

ฟังก์ชันของลายเซ็นควรเป็นแบบนี้ -

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

ดังนั้นขึ้นอยู่กับชนิดของอุปกรณ์ที่เหมาะสมและขึ้นอยู่กับชนิดของวิทยุ Bluetooth ที่ถูกต้องมันสามารถเปิดได้โดยการเรียกขั้นตอนหรืออัลกอริทึมที่เหมาะสม

โดยหลักแล้วมันจะกลายเป็นเมทริกซ์ 3 x 2 ซึ่งในนั้นฉันพยายามที่จะเวกเตอร์การดำเนินการที่ถูกต้องขึ้นอยู่กับประเภทของวัตถุที่เกี่ยวข้อง

พฤติกรรม polymorphic ขึ้นอยู่กับชนิดของอาร์กิวเมนต์ทั้งสอง

ป้อนคำอธิบายรูปภาพที่นี่

ตอนนี้รูปแบบผู้เยี่ยมชมสามารถนำไปใช้กับปัญหานี้ได้ แรงบันดาลใจมาจากหน้า 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; }
    }
}

ดังที่คุณเห็นข้างต้นคุณBilsterPackมีคู่ยา 'ดังนั้นคุณต้องคูณจำนวนคู่ด้วย 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
}

คุณย้ายความรับผิดชอบในการนับจำนวนPills ไปยังคลาสที่เรียกว่าPillCountVisitor(และเราลบคำสั่ง switch case) นั่นหมายความว่าเมื่อใดก็ตามที่คุณต้องการเพิ่มประเภทของภาชนะบรรจุยาใหม่คุณควรเปลี่ยนPillCountVisitorชั้นเรียนเท่านั้น IVisitorอินเทอร์เฟซการแจ้งเตือนเป็นเรื่องทั่วไปสำหรับใช้ในสถานการณ์อื่น

โดยการเพิ่มวิธีการยอมรับลงในคลาสคอนเทนเนอร์ของยา:

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คุณเห็นสถานการณ์จริงที่คุณไม่สามารถใช้polymorphism (คำตอบ) เพื่อปฏิบัติตามหลักการความรับผิดชอบเดี่ยว ในความเป็นจริงใน:

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

reportQtdHoursAndPayวิธีการสำหรับการรายงานและการเป็นตัวแทนและละเมิด Single รับผิดชอบหลักการ ดังนั้นจะเป็นการดีกว่าถ้าใช้รูปแบบผู้เยี่ยมชมเพื่อเอาชนะปัญหา


2
สวัสดี Sayed คุณช่วยแก้ไขคำตอบของคุณเพื่อเพิ่มชิ้นส่วนที่คุณพบว่าให้ความสว่างมากที่สุด ดังนั้นโดยทั่วไปจึงไม่สนับสนุนคำตอบเฉพาะลิงก์เนื่องจากเป้าหมายคือการเป็นฐานข้อมูลองค์ความรู้และลิงก์ลงไป
George Mauer

8

การจัดส่งซ้ำซ้อนเป็นเพียงหนึ่งในเหตุผลอื่นที่ใช้รูปแบบนี้
แต่โปรดทราบว่ามันเป็นวิธีเดียวที่จะใช้การจัดส่งสองครั้งขึ้นไปในภาษาที่ใช้กระบวนทัศน์การจัดส่งเดียว

นี่คือเหตุผลที่จะใช้รูปแบบ:

1) เราต้องการกำหนดการดำเนินการใหม่โดยไม่เปลี่ยนโมเดลในแต่ละครั้งเนื่องจากโมเดลไม่เปลี่ยนแปลงบ่อยครั้งการดำเนินการเลเยอร์จะเปลี่ยนบ่อยครั้ง

2) เราไม่ต้องการเชื่อมโยงโมเดลและพฤติกรรมเข้าด้วยกันเพราะเราต้องการมีโมเดลที่สามารถใช้ซ้ำได้ในหลายแอพพลิเคชั่นหรือเราต้องการให้มีโมเดลที่ขยายได้ซึ่งอนุญาตให้คลาสไคลเอนต์กำหนดพฤติกรรมของพวกเขาด้วยคลาสของตนเอง

3) มีการดำเนินงานร่วมกันว่าขึ้นอยู่กับชนิดคอนกรีตของรูปแบบ แต่เราไม่ต้องการที่จะใช้ตรรกะในแต่ละประเภทรองเป็นที่จะเกิดการระเบิดตรรกะทั่วไปในหลายชั้นเรียนและอื่น ๆ ในหลายสถานที่

4) เราจะใช้การออกแบบและรูปแบบโดเมนรูปแบบการเรียนของลำดับชั้นเดียวกันดำเนินการในสิ่งที่แตกต่างกันมากเกินไปที่อาจจะมีการรวมตัวกันที่อื่น

5) เราจำเป็นต้องมีการจัดส่งคู่
เรามีตัวแปรที่ประกาศด้วยประเภทอินเตอร์เฟสและเราต้องการให้สามารถประมวลผลตามประเภทรันไทม์ของพวกเขา ... แน่นอนโดยไม่ต้องใช้if (myObj instanceof Foo) {}หรือเคล็ดลับใด ๆ
แนวคิดนี้เป็นตัวอย่างในการส่งตัวแปรเหล่านี้ไปยังวิธีการที่ประกาศประเภทที่เป็นรูปธรรมของอินเทอร์เฟซเป็นพารามิเตอร์เพื่อใช้การประมวลผลเฉพาะ วิธีการทำเช่นนี้ไม่สามารถทำได้นอกกรอบด้วยภาษาที่อาศัยการส่งแบบเดี่ยวเนื่องจากการเลือกที่เรียกใช้ ณ รันไทม์ขึ้นอยู่กับประเภทรันไทม์ของผู้รับเท่านั้น
โปรดทราบว่าใน Java จะใช้วิธีเลือก (ลายเซ็น) ในการรวบรวม ณ เวลารวบรวมและขึ้นอยู่กับชนิดของพารามิเตอร์ที่ประกาศไม่ใช่ประเภทรันไทม์

จุดสุดท้ายที่เป็นเหตุผลในการใช้ผู้เข้าชมเป็นผลมาจากเมื่อคุณใช้ผู้เข้าชม (แน่นอนสำหรับภาษาที่ไม่สนับสนุนการส่งหลายครั้ง) คุณจำเป็นต้องแนะนำการใช้การส่งแบบคู่

โปรดทราบว่าการสำรวจองค์ประกอบ (การวนซ้ำ) เพื่อใช้ผู้เข้าชมในแต่ละรายการนั้นไม่ใช่เหตุผลที่จะใช้รูปแบบ
คุณใช้รูปแบบเนื่องจากคุณแบ่งรูปแบบและการประมวลผล
และด้วยการใช้รูปแบบคุณจะได้รับประโยชน์เพิ่มเติมจากความสามารถในการวนซ้ำ
ความสามารถนี้มีประสิทธิภาพมากและอยู่นอกเหนือการทำซ้ำในประเภททั่วไปด้วยวิธีการเฉพาะเช่นเดียวaccept()กับวิธีทั่วไป
มันเป็นกรณีการใช้งานพิเศษ ดังนั้นฉันจะใส่มันไว้ด้านหนึ่ง


ตัวอย่างใน Java

ฉันจะแสดงให้เห็นถึงมูลค่าเพิ่มของรูปแบบด้วยตัวอย่างหมากรุกที่เราต้องการกำหนดการประมวลผลตามที่ผู้เล่นร้องขอให้ชิ้นส่วนเคลื่อนไหว

หากไม่ใช้รูปแบบผู้เยี่ยมชมเราสามารถกำหนดพฤติกรรมการเคลื่อนที่ของชิ้นส่วนโดยตรงในคลาสย่อย
เราอาจมี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() {
        ...
    }

}

และสิ่งเดียวกันสำหรับคลาสย่อยทั้งหมด
นี่คือคลาสไดอะแกรมที่แสดงการออกแบบนี้:

[แผนภาพคลาสโมเดล

วิธีการนี้นำเสนอข้อบกพร่องที่สำคัญสามประการ:

- พฤติกรรมเช่น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คลาสรันไทม์
และมันก็เป็น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) ความเสี่ยงในการลด / ทำลาย encapsulation

ในการทำงานบางประเภทรูปแบบผู้เยี่ยมชมอาจลดหรือทำลายการห่อหุ้มของวัตถุโดเมน

ตัวอย่างเช่นในขณะที่MovePerformingVisitor คลาสจำเป็นต้องตั้งค่าพิกัดของชิ้นส่วนที่เกิดขึ้นจริงPieceอินเทอร์เฟซที่มีให้วิธีการทำ:

void setCoordinates(Coordinates coordinates);

ตอนนี้ความรับผิดชอบของPieceการเปลี่ยนแปลงพิกัดเปิดให้คลาสอื่นนอกเหนือจากPieceคลาสย่อย
การย้ายการประมวลผลที่ดำเนินการโดยผู้เยี่ยมชมในPieceคลาสย่อยนั้นไม่ใช่ตัวเลือก
มันจะสร้างปัญหาอีกอย่างแน่นอนเมื่อPiece.accept()ยอมรับการใช้งานของผู้เยี่ยมชม ไม่ทราบว่าผู้เข้าชมดำเนินการอย่างไรจึงไม่ทราบว่าจะเปลี่ยนสถานะชิ้นส่วนเป็นอย่างไรและอย่างไร
วิธีในการระบุตัวตนของผู้เข้าชมคือการดำเนินการโพสต์Piece.accept()ตามการดำเนินการของผู้เข้าชม มันจะเป็นความคิดที่ดีมากที่สุดเท่าที่จะสร้างการมีเพศสัมพันธ์สูงระหว่างการใช้งานของผู้เข้าชมและ subclasses ชิ้นและนอกจากนี้ก็อาจจะต้องใช้เคล็ดลับเป็นgetClass(), instanceofหรือเครื่องหมายใด ๆ ระบุการดำเนินการของผู้เข้าชม

2) ข้อกำหนดในการเปลี่ยนรูปแบบ

ตรงกันข้ามกับรูปแบบการออกแบบพฤติกรรมอื่น ๆDecoratorตัวอย่างเช่นรูปแบบผู้เยี่ยมชมนั้นล่วงล้ำ
แน่นอนเราจำเป็นต้องแก้ไขคลาสตัวรับเริ่มต้นเพื่อให้accept()วิธีการที่จะยอมรับการเยี่ยมชม
เราไม่ได้มีปัญหาใด ๆPieceและ subclasses ของเหล่านี้เป็นชั้นเรียนของเรา
ในชั้นเรียนในตัวหรือบุคคลที่สามสิ่งต่าง ๆ ไม่ใช่เรื่องง่าย
เราจำเป็นต้องห่อหรือสืบทอด (ถ้าทำได้) เพื่อเพิ่ม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 Design และรูปแบบหนังสือของเขา เขาสรุปปัญหา:

วัตถุผสมมักจะมีโครงสร้างที่ซับซ้อนประกอบด้วยองค์ประกอบของแต่ละบุคคล องค์ประกอบบางอย่างอาจมีองค์ประกอบของเด็กอีกครั้ง ... การดำเนินการกับองค์ประกอบเข้าชมองค์ประกอบย่อยของมันใช้การดำเนินการกับองค์ประกอบเหล่านั้นและรวมผลลัพธ์ ... อย่างไรก็ตามมันไม่ง่ายเลยที่จะเพิ่มการทำงานใหม่ให้กับการออกแบบ

เหตุผลที่ไม่ง่ายคือการเพิ่มการดำเนินการภายในคลาสโครงสร้างเอง ตัวอย่างเช่นสมมติว่าคุณมีระบบไฟล์:

แผนภาพคลาส FileSystem

นี่คือการดำเนินการ (ฟังก์ชัน) ที่เราอาจต้องการนำไปใช้กับโครงสร้างนี้:

  • แสดงชื่อขององค์ประกอบโหนด (รายการไฟล์)
  • แสดงขนาดขององค์ประกอบโหนดที่คำนวณได้ (ซึ่งขนาดของไดเรกทอรีรวมถึงขนาดขององค์ประกอบลูกทั้งหมด)
  • เป็นต้น

คุณสามารถเพิ่มฟังก์ชั่นให้กับแต่ละคลาสใน FileSystem เพื่อใช้การดำเนินการ (และผู้คนเคยทำสิ่งนี้มาแล้วในอดีตเพราะมันชัดเจนมากว่าจะทำอย่างไร) ปัญหาคือเมื่อใดก็ตามที่คุณเพิ่มฟังก์ชันการทำงานใหม่ (บรรทัด "ฯลฯ " ด้านบน) คุณอาจต้องเพิ่มวิธีการมากขึ้นในคลาสโครงสร้าง เมื่อถึงจุดหนึ่งหลังจากการดำเนินการบางอย่างที่คุณได้เพิ่มลงในซอฟต์แวร์ของคุณวิธีการในชั้นเรียนเหล่านั้นไม่เหมาะสมอีกต่อไปในแง่ของการทำงานร่วมกันของชั้นเรียน ตัวอย่างเช่นคุณมีFileNodeวิธีการที่calculateFileColorForFunctionABC()จะใช้ฟังก์ชั่นการสร้างภาพข้อมูลล่าสุดในระบบไฟล์

รูปแบบของผู้เข้าชม (เหมือนรูปแบบการออกแบบหลายอย่าง) เกิดจากความเจ็บปวดและความทุกข์ทรมานของนักพัฒนาที่รู้ว่ามีวิธีที่ดีกว่าในการอนุญาตให้โค้ดของพวกเขาเปลี่ยนแปลงโดยไม่ต้องมีการเปลี่ยนแปลงมากมายในทุกที่และเคารพหลักการออกแบบที่ดี ) ฉันคิดว่ามันยากที่จะเข้าใจถึงประโยชน์ของรูปแบบมากมายจนกว่าคุณจะรู้สึกเจ็บปวดนั้น การอธิบายความเจ็บปวด (เช่นที่เราพยายามทำข้างบนด้วยฟังก์ชัน "ฯลฯ " ที่เพิ่มเข้ามา) ใช้พื้นที่ในการอธิบายและเป็นการเบี่ยงเบนความสนใจ รูปแบบการทำความเข้าใจนั้นยากสำหรับเหตุผลนี้

ผู้เยี่ยมชมช่วยให้เราสามารถแยกฟังก์ชันในโครงสร้างข้อมูล (เช่นFileSystemNodes) จากโครงสร้างข้อมูลเอง รูปแบบที่ช่วยให้การออกแบบเคารพการทำงานร่วมกัน - คลาสโครงสร้างข้อมูลนั้นง่ายกว่า (มีวิธีการที่น้อยกว่า) และฟังก์ชันการทำงานนั้นถูกห่อหุ้มอยู่ในVisitorการประยุกต์ใช้งาน สิ่งนี้ทำได้ผ่านการกดซ้ำสองครั้ง (ซึ่งเป็นส่วนที่ซับซ้อนของรูปแบบ): การใช้accept()เมธอดในคลาสโครงสร้างและvisitX()เมธอดในคลาสVisitor (the function):

FileSystem คลาสไดอะแกรมที่มีผู้เยี่ยมชมใช้

โครงสร้างนี้ช่วยให้เราสามารถเพิ่มฟังก์ชั่นใหม่ที่ทำงานกับโครงสร้างในฐานะผู้เยี่ยมชมที่เป็นรูปธรรม (โดยไม่ต้องเปลี่ยนคลาสโครงสร้าง)

FileSystem คลาสไดอะแกรมที่มีผู้เยี่ยมชมใช้

ตัวอย่างเช่น a PrintNameVisitorที่ใช้ฟังก์ชันรายการไดเรกทอรีและ a PrintSizeVisitorที่ใช้รุ่นที่มีขนาด เราอาจจินตนาการว่าวันหนึ่งมี 'ExportXMLVisitor` ที่สร้างข้อมูลใน XML หรือผู้เข้าชมคนอื่นที่สร้างมันใน JSON เป็นต้นเราอาจมีผู้เยี่ยมชมที่แสดงแผนผังไดเรกทอรีของฉันโดยใช้ภาษากราฟิกเช่น DOTเพื่อให้มองเห็นได้ ด้วยโปรแกรมอื่น

ในฐานะที่เป็นหมายเหตุสุดท้าย: ความซับซ้อนของผู้เข้าชมที่มีการส่งซ้ำสองครั้งหมายความว่าเป็นการยากที่จะเข้าใจรหัสและการแก้ปัญหา ในระยะสั้นมันมีปัจจัยที่เกินบรรยายและเป็นไปตามหลักการ KISS ในการสำรวจที่ทำโดยนักวิจัยผู้เยี่ยมชมได้แสดงให้เห็นว่าเป็นรูปแบบที่ขัดแย้ง (ไม่มีมติเกี่ยวกับประโยชน์ของมัน) การทดลองบางอย่างแสดงให้เห็นว่ามันไม่ได้ทำให้การบำรุงรักษารหัสง่ายขึ้น


โครงสร้างไดเรกทอรีที่ฉันคิดว่าเป็นรูปแบบคอมโพสิตที่ดี แต่เห็นด้วยกับย่อหน้าสุดท้ายของคุณ
zar

5

ในความคิดของฉันปริมาณงานที่จะเพิ่มการดำเนินการใหม่จะมากหรือน้อยเหมือนกันโดยใช้Visitor Patternหรือปรับเปลี่ยนโดยตรงของแต่ละองค์ประกอบโครงสร้าง นอกจากนี้หากฉันต้องเพิ่มคลาสองค์ประกอบใหม่กล่าวว่าCowส่วนต่อประสานการดำเนินงานจะได้รับผลกระทบและสิ่งนี้จะแพร่กระจายไปยังคลาสขององค์ประกอบที่มีอยู่ทั้งหมดดังนั้นต้องมีการคอมไพล์ใหม่ของคลาสองค์ประกอบทั้งหมด ดังนั้นประเด็นคืออะไร?


4
เกือบทุกครั้งที่ฉันใช้ผู้เยี่ยมชมคือเมื่อคุณทำงานกับการข้ามลำดับชั้นวัตถุ พิจารณาเมนูแบบต้นไม้ซ้อนกัน คุณต้องการยุบโหนดทั้งหมด หากคุณไม่ได้ติดตั้งผู้เยี่ยมชมคุณจะต้องเขียนรหัสผ่านกราฟ rootElement.visit (node) -> node.collapse()หรือมีผู้เข้าชม: เมื่อมีผู้เยี่ยมชมแต่ละโหนดจะใช้กราฟสำรวจเส้นทางสำหรับลูก ๆ ของมันเพื่อให้คุณทำเสร็จ
George Mauer

@GeorgeMauer แนวคิดของการจัดส่งสองครั้งล้างแรงจูงใจสำหรับฉัน: ตรรกะที่ขึ้นอยู่กับประเภทนั้นขึ้นอยู่กับประเภทหรือโลกแห่งความเจ็บปวด ความคิดในการกระจายตรรกะการสำรวจเส้นทางยังทำให้ฉันหยุดชั่วคราว มันมีประสิทธิภาพมากกว่านี้ไหม? มันบำรุงรักษาได้มากกว่านี้ไหม? จะเกิดอะไรขึ้นหากเพิ่ม "โฟลเดอร์ถึงระดับ N" ตามข้อกำหนด
nik.shornikov

@ nik.shornikov ประสิทธิภาพไม่ควรเป็นปัญหาที่นี่ ในเกือบทุกภาษาการเรียกใช้ฟังก์ชั่นบางอย่างนั้นมีค่าใช้จ่ายเล็กน้อย สิ่งใดนอกเหนือจากนั้นคือการเพิ่มประสิทธิภาพแบบไมโคร มันบำรุงรักษาได้มากกว่านี้ไหม? มันขึ้นอยู่กับ ฉันคิดว่าเป็นส่วนใหญ่บางครั้งก็ไม่เป็นเช่นนั้น สำหรับ "fold ถึงระดับ N" ส่งผ่านง่ายในตัวlevelsRemainingนับเป็นพารามิเตอร์ ลดระดับลงก่อนที่จะเรียกเด็กระดับถัดไป if(levelsRemaining == 0) returnภายในของผู้เข้าชม
George Mauer

1
@GeorgeMauer ตกลงกันโดยสิ้นเชิงเกี่ยวกับประสิทธิภาพการเป็นความกังวลเล็กน้อย แต่การบำรุงรักษาเช่นการแทนที่ของการยอมรับลายเซ็นเป็นสิ่งที่ฉันคิดว่าการตัดสินใจควรต้มลงไป
nik.shornikov

5

รูปแบบของผู้เข้าชมเช่นเดียวกับการใช้งานใต้ดินกับการเขียนโปรแกรม Aspect Object

ตัวอย่างเช่นหากคุณกำหนดการดำเนินการใหม่โดยไม่เปลี่ยนคลาสขององค์ประกอบที่ใช้งาน


สำหรับการกล่าวถึงการเขียนโปรแกรม Aspect Object
milesma

5

คำอธิบายอย่างรวดเร็วของรูปแบบผู้เข้าชม คลาสที่ต้องมีการแก้ไขต้องใช้วิธีการ '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)ดูเหมือนกัน แต่ในความเป็นจริงมันแตกต่างกันเพราะมันเรียกว่าเกินพิกัดที่แตกต่างกันของผู้เข้าชม


ใช่ฉันพบว่ามีประโยชน์โดยเฉพาะเมื่อทำงานกับโครงสร้างต้นไม้ไม่ใช่แค่รายการแบบเรียบ (รายการแบบเรียบจะเป็นกรณีพิเศษของต้นไม้) ในขณะที่คุณทราบมันไม่ยุ่งมากในรายการ แต่ผู้เข้าชมสามารถเป็นผู้ช่วยให้รอดเนื่องจากการนำทางระหว่างโหนดจะซับซ้อนมากขึ้น
จอร์จมูเออร์

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");
    }
}

จะเกิดอะไรขึ้นหากคุณต้องการเพิ่มวิธี "เดิน" ที่นี่ นั่นจะเจ็บปวดกับการออกแบบทั้งหมด

ในเวลาเดียวกันการเพิ่มวิธี "เดิน" สร้างคำถามใหม่ แล้ว "กิน" หรือ "นอน" เราต้องเพิ่มวิธีการใหม่ในลำดับชั้นสัตว์สำหรับการกระทำหรือการดำเนินการใหม่ทุกครั้งที่เราต้องการเพิ่มหรือไม่ นั่นเป็นสิ่งที่น่าเกลียดและสำคัญที่สุดเราจะไม่สามารถปิดส่วนต่อประสานสัตว์ได้ ดังนั้นด้วยรูปแบบผู้เยี่ยมชมเราสามารถเพิ่มวิธีการใหม่ในลำดับชั้นโดยไม่ต้องแก้ไขลำดับชั้น!

ดังนั้นเพียงแค่ตรวจสอบและเรียกใช้ตัวอย่าง 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. เพิ่มฟังก์ชันให้กับไลบรารีคลาสที่คุณไม่มีแหล่งที่มาหรือไม่สามารถเปลี่ยนแหล่งที่มา

แม้ว่ารูปแบบผู้เข้าชมจะให้ความยืดหยุ่นในการเพิ่มการดำเนินการใหม่โดยไม่ต้องเปลี่ยนรหัสที่มีอยู่ในวัตถุความยืดหยุ่นนี้มาพร้อมกับข้อเสียเปรียบ

หากวัตถุ visitable ใหม่ได้รับการเพิ่มก็ต้องมีการเปลี่ยนแปลงรหัสในการเข้าชมและ 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. GameVisitorเป็นคลาสซึ่งใช้Visitorอินเตอร์เฟส ( ConcreteVisitor)
  4. แต่ละVisitableองค์ประกอบยอมรับVisitorและเรียกใช้วิธีการVisitorเชื่อมต่อที่เกี่ยวข้อง
  5. คุณสามารถรักษาGameฐานะElementและเกมคอนกรีตชอบเป็น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

sourcemakingบทความ

สำหรับรายละเอียดเพิ่มเติม

มัณฑนากร

รูปแบบอนุญาตให้มีการเพิ่มพฤติกรรมให้กับวัตถุแต่ละชิ้นไม่ว่าจะเป็นแบบคงที่หรือแบบไดนามิกโดยไม่ส่งผลกระทบต่อพฤติกรรมของวัตถุอื่นจากคลาสเดียวกัน

กระทู้ที่เกี่ยวข้อง:

รูปแบบมัณฑนากรสำหรับ IO

เมื่อใดที่จะใช้รูปแบบมัณฑนากร?


2

ผมชอบคำอธิบายและตัวอย่างจากhttp://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html

สมมติฐานคือคุณมีลำดับชั้นของคลาสหลักที่ได้รับการแก้ไข อาจมาจากผู้ขายรายอื่นและคุณไม่สามารถเปลี่ยนแปลงลำดับชั้นได้ อย่างไรก็ตามเจตนาของคุณคือคุณต้องการเพิ่มวิธีการ polymorphic ใหม่ในลำดับชั้นนั้นซึ่งหมายความว่าโดยปกติคุณจะต้องเพิ่มบางอย่างในอินเทอร์เฟซคลาสพื้นฐาน ดังนั้นภาวะที่กลืนไม่เข้าคายไม่ออกคือการที่คุณต้องเพิ่มวิธีการในชั้นฐาน แต่คุณไม่สามารถสัมผัสชั้นฐาน คุณได้รับรอบนี้ได้อย่างไร

รูปแบบการออกแบบที่แก้ปัญหาแบบนี้เรียกว่า "ผู้เยี่ยมชม" (คนสุดท้ายในหนังสือรูปแบบการออกแบบ) และมันสร้างตามแผนการส่งคู่ที่แสดงในส่วนสุดท้าย

รูปแบบผู้เยี่ยมชมช่วยให้คุณสามารถขยายอินเทอร์เฟซของชนิดหลักโดยการสร้างลำดับชั้นคลาสแยกต่างหากของชนิดผู้เยี่ยมชมเสมือนการดำเนินการที่ทำกับชนิดหลัก วัตถุประเภทหลักเพียง "ยอมรับ" ผู้เข้าชมจากนั้นเรียกฟังก์ชันสมาชิกที่ผูกไว้แบบไดนามิกของผู้เข้าชม


ในขณะที่ในทางเทคนิครูปแบบของผู้เข้าชมนี่เป็นเพียงการส่งสองครั้งพื้นฐานจากตัวอย่างของพวกเขา ฉันจะยืนยันว่ามีประโยชน์ที่ไม่สามารถมองเห็นได้จากสิ่งนี้เพียงอย่างเดียว
George Mauer

1

ในขณะที่ฉันเข้าใจวิธีและเวลาที่ฉันไม่เคยเข้าใจว่าทำไม ในกรณีที่ช่วยทุกคนที่มีพื้นหลังเป็นภาษาเช่น C ++ คุณต้องการอ่านอย่างระมัดระวัง

สำหรับคนขี้เกียจเราจะใช้รูปแบบของผู้เข้าชมเพราะ"ในขณะที่ฟังก์ชั่นเสมือนถูกส่งแบบไดนามิกใน C ++, ฟังก์ชั่นมากไปจะทำแบบคงที่"

หรือหาวิธีอื่นเพื่อให้แน่ใจว่ามีการเรียก CollideWith (ApolloSpacecraft &) เมื่อคุณผ่านการอ้างอิง SpaceShip ที่จริง ๆ แล้วผูกกับวัตถุ 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 หวังว่ามันจะเป็นประโยชน์

เช่นเดียวกับที่@Kradrad 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

คำถามของคุณคือเมื่อรู้ว่า:

ฉันไม่ได้รหัสแรกกับรูปแบบของผู้เข้าชม ฉันรหัสมาตรฐานและรอความต้องการที่จะเกิดขึ้นแล้ว refactor ดังนั้นสมมติว่าคุณมีระบบการชำระเงินหลายระบบที่คุณติดตั้งครั้งละหนึ่งระบบ ณ เวลาเช็คเอาต์คุณสามารถมีเงื่อนไขได้หลายอย่างเช่น (หรือ instanceOf)

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

ตอนนี้คิดว่าฉันมี 10 วิธีการชำระเงินมันน่าเกลียด ดังนั้นเมื่อคุณเห็นรูปแบบดังกล่าวที่เกิดขึ้นกับผู้เข้าชมจะแยกออกจากกันอย่างหมดจดและคุณก็จะเรียกสิ่งนี้ในภายหลัง:

new PaymentCheckoutVistor(paymentType).visit()

คุณสามารถดูวิธีการใช้จากจำนวนตัวอย่างที่นี่ฉันแค่แสดง usecase ให้คุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.