point of accept () method ใน Visitor pattern คืออะไร?


88

มีการพูดคุยมากมายเกี่ยวกับการแยกอัลกอริทึมออกจากคลาส แต่มีสิ่งหนึ่งที่ไม่สามารถอธิบายได้

พวกเขาใช้ผู้เยี่ยมชมเช่นนี้

abstract class Expr {
  public <T> T accept(Visitor<T> visitor) {visitor.visit(this);}
}

class ExprVisitor extends Visitor{
  public Integer visit(Num num) {
    return num.value;
  }

  public Integer visit(Sum sum) {
    return sum.getLeft().accept(this) + sum.getRight().accept(this);
  }

  public Integer visit(Prod prod) {
    return prod.getLeft().accept(this) * prod.getRight().accept(this);
  }

แทนที่จะเรียกการเยี่ยมชม (องค์ประกอบ) โดยตรงผู้เยี่ยมชมจะขอให้องค์ประกอบเรียกวิธีการเยี่ยมชม มันขัดแย้งกับความคิดที่ประกาศในชั้นเรียนไม่รู้เกี่ยวกับผู้เยี่ยมชม

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

PS2 การเดาของฉัน: เนื่องจากgetLeft()ส่งคืนค่าพื้นฐานการExpressionโทรvisit(getLeft())จะส่งผลในvisit(Expression)ขณะที่การgetLeft()โทรvisit(this)จะส่งผลให้มีการร้องขอการเยี่ยมชมอื่นที่เหมาะสมกว่า ดังนั้น,accept()ทำการแปลงประเภท (aka casting)

การจับคู่รูปแบบของ PS3 Scala = รูปแบบผู้เยี่ยมชมบนเตียรอยด์แสดงให้เห็นว่ารูปแบบผู้เยี่ยมชมนั้นง่ายเพียงใดโดยไม่มีวิธีการยอมรับ Wikipedia เพิ่มข้อความนี้ : โดยการเชื่อมโยงกระดาษที่ระบุว่า " accept()วิธีการนั้นไม่จำเป็นเมื่อมีการสะท้อนกลับแนะนำคำว่า 'Walkabout' สำหรับเทคนิคนี้"



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

คำตอบ:


155

รูปแบบผู้เยี่ยมชมvisit/acceptโครงสร้างเป็นสิ่งชั่วร้ายที่จำเป็นเนื่องจากความหมายของภาษาซี (C #, Java, ฯลฯ ) เป้าหมายของรูปแบบผู้เยี่ยมชมคือการใช้การจัดส่งสองครั้งเพื่อกำหนดเส้นทางการโทรของคุณตามที่คุณคาดหวังจากการอ่านรหัส

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

Node root = GetTreeRoot();
new MyVisitor().visit(root);

นี่คือปัญหา หากMyVisitorคลาสของเราถูกกำหนดไว้ดังต่อไปนี้:

class MyVisitor implements IVisitor {
  void visit(CarNode node);
  void visit(TrainNode node);
  void visit(PlaneNode node);
  void visit(Node node);
}

หากรันไทม์โดยไม่คำนึงถึงประเภทที่แท้จริงการrootโทรของเราจะเข้าสู่การโอเวอร์โหลดvisit(Node node)มีการเรียกร้องของเราจะเข้าไปเกินพิกัดNodeนี้จะเป็นจริงสำหรับตัวแปรทั้งหมดประกาศประเภท ทำไมถึงเป็นแบบนี้? เนื่องจาก Java และภาษาอื่น ๆ ที่คล้าย C จะพิจารณาเฉพาะประเภทคงที่หรือประเภทที่ตัวแปรถูกประกาศเป็นของพารามิเตอร์เมื่อตัดสินใจว่าจะเรียกโอเวอร์โหลดใด Java ไม่ได้ใช้ขั้นตอนพิเศษในการถามสำหรับการเรียกทุกเมธอดในรันไทม์ว่า "โอเคประเภทไดนามิกrootคืออะไรโอ้ฉันเห็นมันเป็นTrainNodeลองดูว่ามีวิธีใดบ้างMyVisitorที่ยอมรับพารามิเตอร์ประเภทTrainNode... ". ในเวลาคอมไพล์กำหนดว่าจะเรียกใช้เมธอดใด (ถ้า Java ตรวจสอบชนิดไดนามิกของอาร์กิวเมนต์จริงๆประสิทธิภาพจะแย่มาก)

Java ให้เครื่องมือหนึ่งแก่เราในการพิจารณาประเภทรันไทม์ (เช่นไดนามิก) ของอ็อบเจ็กต์เมื่อมีการเรียกเมธอด - virtual methodวิธีการจัดส่งเสมือนเมื่อเราเรียกวิธีการเสมือนจริงการเรียกจะไปที่ตารางในหน่วยความจำที่ประกอบด้วยตัวชี้ฟังก์ชัน แต่ละประเภทมีตาราง ถ้าเมธอดเฉพาะถูกแทนที่โดยคลาสรายการตารางฟังก์ชันของคลาสนั้นจะมีแอดเดรสของฟังก์ชันที่ถูกแทนที่ หากคลาสไม่ได้แทนที่เมธอดคลาสนั้นจะมีตัวชี้ไปที่การใช้งานคลาสพื้นฐาน สิ่งนี้ยังคงมีค่าใช้จ่ายด้านประสิทธิภาพ (โดยทั่วไปการเรียกแต่ละวิธีจะเป็นการยกเลิกการอ้างอิงสองพอยน์เตอร์: อันหนึ่งชี้ไปที่ตารางฟังก์ชันของประเภทและอีกฟังก์ชันหนึ่งเอง) แต่ก็ยังเร็วกว่าการตรวจสอบประเภทพารามิเตอร์

เป้าหมายของรูปแบบผู้เยี่ยมชมคือการบรรลุการจัดส่งแบบสองครั้ง - ไม่เพียง แต่พิจารณาประเภทของเป้าหมายการโทร ( MyVisitorผ่านวิธีการเสมือน) แต่ยังรวมถึงประเภทของพารามิเตอร์ด้วย ( Nodeเรากำลังดูประเภทใด) รูปแบบผู้เยี่ยมชมช่วยให้เราสามารถทำได้โดยใช้visit / acceptcombination

โดยเปลี่ยนบรรทัดของเราเป็นสิ่งนี้:

root.accept(new MyVisitor());

เราจะได้รับสิ่งที่ต้องการ: ผ่านวิธีการจัดส่งเสมือนเราป้อนการเรียกยอมรับ () ที่ถูกต้องตามที่คลาสย่อยดำเนินการ - ในตัวอย่างของTrainElementเราเราจะเข้าสู่TrainElementการใช้งานaccept():

class TrainNode extends Node implements IVisitable {
  void accept(IVisitor v) {
    v.visit(this);
  }
}

อะไรคอมไพเลอร์รู้ที่จุดนี้อยู่ภายในขอบเขตของTrainNode's accept? มันรู้ว่าประเภทคงที่ของthisคือTrainNode . นี่เป็นข้อมูลเพิ่มเติมที่สำคัญที่คอมไพเลอร์ไม่ทราบในขอบเขตของผู้โทรของเรา: ที่นั่นทั้งหมดที่รู้rootก็คือมันเป็นไฟล์Node. ตอนนี้คอมไพเลอร์รู้แล้วว่าthis( root) ไม่ใช่แค่ a Nodeแต่จริงๆแล้วมันคือไฟล์TrainNode. ดังนั้นบรรทัดเดียวที่พบภายในaccept(): v.visit(this)หมายถึงอย่างอื่นทั้งหมด ตอนนี้คอมไพเลอร์จะมองหาการโอเวอร์โหลดvisit()ที่ใช้เวลา a TrainNode. หากไม่พบมันจะรวบรวมการโทรไปยังโอเวอร์โหลดที่ใช้เวลา aNode :หากไม่มีอยู่คุณจะได้รับข้อผิดพลาดในการคอมไพล์ (เว้นแต่คุณจะมีโอเวอร์โหลดobject) การดำเนินการจะเข้าสู่สิ่งที่เราตั้งใจไว้ตลอดมา: MyVisitorการนำไปใช้visit(TrainNode e). ไม่จำเป็นต้องมีการร่ายและที่สำคัญที่สุดคือไม่จำเป็นต้องมีการสะท้อนกลับ ดังนั้นค่าใช้จ่ายของกลไกนี้จึงค่อนข้างต่ำ: ประกอบด้วยการอ้างอิงตัวชี้เท่านั้นและไม่มีอะไรอื่น

ตอบคำถามของคุณถูกต้อง - เราสามารถใช้นักแสดงและปรับพฤติกรรมที่ถูกต้องได้ อย่างไรก็ตามบ่อยครั้งเราไม่รู้ด้วยซ้ำว่า Node คืออะไร ใช้กรณีของลำดับชั้นต่อไปนี้:

abstract class Node { ... }
abstract class BinaryNode extends Node { Node left, right; }
abstract class AdditionNode extends BinaryNode { }
abstract class MultiplicationNode extends BinaryNode { }
abstract class LiteralNode { int value; }

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

class Interpreter implements IVisitor<int> {
  int visit(AdditionNode n) {
    int left = n.left.accept(this);
    int right = n.right.accept(this); 
    return left + right;
  }
  int visit(MultiplicationNode n) {
    int left = n.left.accept(this);
    int right = n.right.accept(this);
    return left * right;
  }
  int visit(LiteralNode n) {
    return n.value;
  }
}

หล่อจะไม่ได้รับเราไปไกลมากเนื่องจากเราไม่ทราบชนิดของleftหรือrightในvisit()วิธีการ โปรแกรมแยกวิเคราะห์ของเรามักจะส่งคืนวัตถุประเภทNodeที่ชี้ไปที่รากของลำดับชั้นเช่นกันดังนั้นเราจึงไม่สามารถส่งสิ่งนั้นได้อย่างปลอดภัย ดังนั้นล่ามธรรมดาของเราจึงมีลักษณะดังนี้:

Node program = parse(args[0]);
int result = program.accept(new Interpreter());
System.out.println("Output: " + result);

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

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


5
ไอเท็ม Java ที่ใช้ได้ผล # 41มีคำเตือนนี้: " หลีกเลี่ยงสถานการณ์ที่พารามิเตอร์ชุดเดียวกันสามารถส่งผ่านไปยังการโอเวอร์โหลดที่แตกต่างกันได้โดยการเพิ่มแคสต์ " accept()เมธอดนี้จำเป็นเมื่อมีการละเมิดคำเตือนนี้ในผู้เยี่ยมชม
jaco0646

" โดยปกติเมื่อใช้รูปแบบผู้เยี่ยมชมลำดับชั้นของวัตถุจะเกี่ยวข้องโดยที่โหนดทั้งหมดได้มาจากประเภทโหนดฐาน " ซึ่งไม่จำเป็นอย่างยิ่งใน C ++ ดู Boost.Variant, Eggs Variant
Jean-Michaël

สำหรับฉันแล้วดูเหมือนว่าใน java เราไม่ต้องการวิธีการยอมรับจริงๆเพราะใน java เรามักจะเรียก method ที่เฉพาะเจาะจงที่สุดเสมอ
Gilad Baruchian

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

@GiladBaruchian คอมไพเลอร์สร้างการเรียกไปยังเมธอดประเภทที่เฉพาะเจาะจงที่สุดซึ่งคอมไพเลอร์สามารถกำหนดได้
mmw

15

แน่นอนว่าจะโง่ถ้าเป็นเพียงอย่างเดียววิธีการที่จะดำเนินการยอมรับ

แต่มันไม่ใช่

ตัวอย่างเช่นผู้เยี่ยมชมมีประโยชน์มากเมื่อต้องจัดการกับลำดับชั้นซึ่งในกรณีนี้การใช้งานโหนดที่ไม่ใช่เทอร์มินัลอาจเป็นเช่นนี้

interface IAcceptVisitor<T> {
  void Accept(IVisit<T> visitor);
}
class HierarchyNode : IAcceptVisitor<HierarchyNode> {
  public void Accept(IVisit<T> visitor) {
    visitor.visit(this);
    foreach(var n in this.children)
      n.Accept(visitor);
  }

  private IEnumerable<HierarchyNode> children;
  ....
}

เห็นมั้ย? สิ่งที่คุณอธิบายเป็นโง่โซลูชั่นสำหรับลำดับชั้น traversing

นี่คือนานและในเชิงลึกบทความที่ทำให้ฉันเข้าใจของผู้เข้าชม

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


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

1
การบอกว่าการยอมรับเป็นสิ่งที่ดีสำหรับการแวะผ่านประจำนั้นสมเหตุสมผลและคุ้มค่าสำหรับคนทั่วไป แต่ฉันเอาตัวอย่างของฉันมาจากใครบางคน "ฉันไม่เข้าใจรูปแบบผู้เยี่ยมชมจนกว่าฉันจะอ่านandymaleh.blogspot.com/2008/04/… " ทั้งตัวอย่างนี้หรือ Wikipedia หรือคำตอบอื่น ๆ ไม่ได้กล่าวถึงข้อได้เปรียบในการนำทาง อย่างไรก็ตามพวกเขาทั้งหมดต้องการการยอมรับโง่ ๆ () นี้ นั่นคือเหตุผลที่ถามคำถามของฉัน: ทำไม?
วาล

1
@Val - หมายความว่าไง? ฉันไม่แน่ใจว่าคุณกำลังถามอะไร ฉันไม่สามารถพูดถึงบทความอื่น ๆ ได้เนื่องจากคนเหล่านั้นมีมุมมองที่แตกต่างกันในเรื่องนี้ แต่ฉันสงสัยว่าเราไม่เห็นด้วย โดยทั่วไปในการคำนวณปัญหาต่างๆสามารถแมปกับเครือข่ายได้ดังนั้นการใช้งานอาจไม่เกี่ยวข้องกับกราฟในพื้นผิว แต่จริงๆแล้วเป็นปัญหาที่คล้ายกันมาก
George Mauer

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

1
@Val กระดาษที่พวกเขาเชื่อมโยงไปยัง "แก่นแท้ของรูปแบบผู้เยี่ยมชม" บันทึกการแยกการนำทางและการดำเนินการแบบเดียวกันในบทคัดย่อตามที่ฉันให้ไว้ พวกเขาพูดง่ายๆว่าการใช้งาน GOF (ซึ่งเป็นสิ่งที่คุณกำลังถามถึง) มีข้อ จำกัด และความน่ารำคาญบางอย่างที่สามารถลบออกได้โดยใช้การสะท้อน - ดังนั้นจึงแนะนำรูปแบบ Walkabout สิ่งนี้มีประโยชน์อย่างแน่นอนและสามารถทำสิ่งเดียวกันกับที่ผู้เยี่ยมชมทำได้ แต่เป็นรหัสที่ค่อนข้างซับซ้อนและ (ในการอ่านคร่าวๆ) จะสูญเสียประโยชน์บางประการของความปลอดภัยประเภท เป็นเครื่องมือสำหรับกล่องเครื่องมือ แต่มีน้ำหนักมากกว่าผู้เยี่ยมชม
George Mauer

0

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

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

รูปแบบผู้เยี่ยมชมเสนอ (อย่างน้อย) สามแนวทางในการหลีกเลี่ยงปัญหานั้น:

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

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


2
1. การเยี่ยมชมหมายความว่าคุณสามารถเข้าถึงวิธีการเยี่ยมชมสาธารณะเท่านั้นดังนั้นจึงจำเป็นต้องทำให้การล็อกภายในสามารถเข้าถึงได้สำหรับสาธารณะเพื่อให้เป็นประโยชน์กับผู้เยี่ยมชม 2 / ไม่มีตัวอย่างใดที่ฉันเคยเห็นมาก่อนบ่งบอกว่าผู้เยี่ยมชมควรถูกใช้เพื่อเปลี่ยนสถานะของการเยี่ยมชม 3. "ด้วย VisitorPattern แบบเดิมเราสามารถระบุได้ว่าเรากำลังเข้าสู่โหนดเมื่อใดเท่านั้นเราไม่รู้ว่าเราได้ออกจากโหนดก่อนหน้านี้ก่อนที่เราจะเข้าสู่โหนดปัจจุบัน" คุณจะปลดล็อกด้วยการเยี่ยมชมเพียงอย่างเดียวแทน visitEnter และ visitLeave ได้อย่างไร สุดท้ายฉันถามเกี่ยวกับแอปพลิเคชันของ accpet () แทนที่จะเป็นผู้เยี่ยมชม
วาล

บางทีฉันอาจไม่ได้เร่งความเร็วด้วยคำศัพท์เฉพาะสำหรับรูปแบบ แต่ "รูปแบบผู้เยี่ยมชม" ดูเหมือนจะคล้ายกับวิธีการที่ฉันใช้โดยที่ X ส่งผ่าน Y เป็นผู้รับมอบสิทธิ์ซึ่ง Y สามารถส่งผ่านข้อมูลที่จำเป็นต้องใช้ได้ในฐานะ ตราบเท่าที่ผู้รับมอบสิทธิ์ทำงานอยู่ บางทีรูปแบบนั้นอาจมีชื่ออื่น?
supercat

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

0

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

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


-1

ดีตัวอย่างเช่นในรหัสที่มาสะสม:

interface CompilingVisitor {
   build(SourceFile source);
}

ลูกค้าสามารถใช้JavaBuilder, RubyBuilder,XMLValidatorฯลฯ และการดำเนินการในการเก็บรวบรวมและเยี่ยมชมไฟล์ทั้งหมดที่แหล่งที่มาในโครงการที่ไม่จำเป็นต้องมีการเปลี่ยนแปลง

นี่จะเป็นรูปแบบที่ไม่ดีหากคุณมีคลาสแยกกันสำหรับไฟล์ซอร์สแต่ละประเภท:

interface CompilingVisitor {
   build(JavaSourceFile source);
   build(RubySourceFile source);
   build(XMLSourceFile source);
}

ขึ้นอยู่กับบริบทและส่วนใดของระบบที่คุณต้องการให้ขยายได้


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