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