Pengiriman ganda hanyalah salah satu alasan antara lain untuk menggunakan pola ini .
Tetapi perhatikan bahwa ini adalah cara tunggal untuk menerapkan pengiriman ganda atau lebih dalam bahasa yang menggunakan paradigma pengiriman tunggal.
Berikut adalah alasan untuk menggunakan polanya:
1) Kami ingin mendefinisikan operasi baru tanpa mengubah model setiap kali karena model tidak sering berubah karena operasi sering berubah.
2) Kami tidak ingin memadukan model dan perilaku karena kami ingin memiliki model yang dapat digunakan kembali dalam banyak aplikasi atau kami ingin memiliki model yang dapat diperluas yang memungkinkan kelas klien untuk mendefinisikan perilaku mereka dengan kelas mereka sendiri.
3) Kami memiliki operasi umum yang bergantung pada jenis konkret model tetapi kami tidak ingin menerapkan logika di setiap subkelas karena akan meledak logika umum di beberapa kelas dan di banyak tempat .
4) Kami menggunakan desain model domain dan kelas model hierarki yang sama melakukan terlalu banyak hal berbeda yang dapat dikumpulkan di tempat lain .
5) Kami membutuhkan pengiriman ganda .
Kami memiliki variabel yang dideklarasikan dengan tipe antarmuka dan kami ingin dapat memprosesnya berdasarkan tipe runtime mereka ... tentu saja tanpa menggunakan if (myObj instanceof Foo) {}
atau trik apa pun.
Idenya adalah misalnya untuk meneruskan variabel-variabel ini ke metode yang mendeklarasikan tipe konkret antarmuka sebagai parameter untuk menerapkan pemrosesan tertentu. Cara melakukan ini tidak dimungkinkan di luar kotak dengan bahasa bergantung pada pengiriman tunggal karena yang dipilih dipanggil saat runtime hanya bergantung pada jenis runtime penerima.
Perhatikan bahwa di Jawa, metode (tanda tangan) untuk memanggil dipilih pada waktu kompilasi dan itu tergantung pada tipe parameter yang dideklarasikan, bukan tipe runtime mereka.
Poin terakhir yang menjadi alasan untuk menggunakan pengunjung juga merupakan konsekuensi karena ketika Anda menerapkan pengunjung (tentu saja untuk bahasa yang tidak mendukung pengiriman ganda), Anda perlu memperkenalkan implementasi pengiriman ganda.
Perhatikan bahwa traversal elemen (iterasi) untuk menerapkan pengunjung pada masing-masing bukan alasan untuk menggunakan pola.
Anda menggunakan pola karena Anda membagi model dan pemrosesan.
Dan dengan menggunakan polanya, Anda mendapat manfaat selain dari kemampuan iterator.
Kemampuan ini sangat kuat dan melampaui iterasi pada tipe umum dengan metode spesifik sebagaimana accept()
metode generik.
Ini adalah kasus penggunaan khusus. Jadi saya akan meletakkannya di satu sisi.
Contoh di Jawa
Saya akan mengilustrasikan nilai tambah dari pola dengan contoh catur di mana kami ingin mendefinisikan pemrosesan saat pemain meminta bagian bergerak.
Tanpa penggunaan pola pengunjung, kita bisa mendefinisikan perilaku pemindahan potongan langsung dalam subkelas potongan.
Misalnya kita dapat memiliki Piece
antarmuka seperti:
public interface Piece{
boolean checkMoveValidity(Coordinates coord);
void performMove(Coordinates coord);
Piece computeIfKingCheck();
}
Setiap subkelas Piece akan mengimplementasikannya seperti:
public class Pawn implements Piece{
@Override
public boolean checkMoveValidity(Coordinates coord) {
...
}
@Override
public void performMove(Coordinates coord) {
...
}
@Override
public Piece computeIfKingCheck() {
...
}
}
Dan hal yang sama untuk semua subclass Sepotong.
Berikut adalah diagram diagram yang menggambarkan desain ini:
Pendekatan ini menghadirkan tiga kelemahan penting:
- perilaku seperti performMove()
atau computeIfKingCheck()
akan sangat mungkin menggunakan logika umum.
Misalnya apa pun yang konkret Piece
, performMove()
akhirnya akan mengatur potongan saat ini ke lokasi tertentu dan berpotensi mengambil potongan lawan.
Memisahkan perilaku terkait dalam beberapa kelas alih-alih mengumpulkan mereka dengan cara tertentu merupakan pola tanggung jawab tunggal. Membuat pemeliharaan mereka lebih sulit.
- Memproses sebagaimana checkMoveValidity()
seharusnya tidak menjadi sesuatu yang Piece
dapat dilihat atau diubah oleh subclass.
Ini adalah pemeriksaan yang melampaui tindakan manusia atau komputer. Pemeriksaan ini dilakukan pada setiap tindakan yang diminta oleh pemain untuk memastikan bahwa kepindahan potongan yang diminta valid.
Jadi kami bahkan tidak ingin memberikan itu di Piece
antarmuka.
- Dalam permainan catur yang menantang bagi pengembang bot, umumnya aplikasi menyediakan API standar ( Piece
antarmuka, subkelas, Dewan, perilaku umum, dll ...) dan memungkinkan pengembang memperkaya strategi bot mereka.
Untuk dapat melakukan itu, kami harus mengusulkan model di mana data dan perilaku tidak digabungkan secara erat dalam Piece
implementasi.
Jadi mari kita pergi menggunakan pola pengunjung!
Kami memiliki dua jenis struktur:
- kelas model yang menerima untuk dikunjungi (bagian)
- pengunjung yang mengunjungi mereka (operasi yang bergerak)
Berikut adalah diagram kelas yang menggambarkan polanya:
Di bagian atas kami memiliki pengunjung dan di bagian bawah kami memiliki kelas model.
Berikut adalah PieceMovingVisitor
antarmuka (perilaku yang ditentukan untuk setiap jenis 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);
}
Sepotong didefinisikan sekarang:
public interface Piece {
void accept(PieceMovingVisitor pieceVisitor);
Coordinates getCoordinates();
void setCoordinates(Coordinates coordinates);
}
Metode utamanya adalah:
void accept(PieceMovingVisitor pieceVisitor);
Ini memberikan pengiriman pertama: doa berdasarkan Piece
penerima.
Pada waktu kompilasi, metode terikat ke accept()
metode antarmuka Piece dan pada saat runtime, metode terikat akan dipanggil pada Piece
kelas runtime .
Dan itu adalah accept()
implementasi metode yang akan melakukan pengiriman kedua.
Memang, setiap Piece
subclass yang ingin dikunjungi oleh PieceMovingVisitor
objek memanggil PieceMovingVisitor.visit()
metode dengan melewati sebagai argumen itu sendiri.
Dengan cara ini, kompiler batas segera setelah waktu kompilasi, tipe parameter yang dideklarasikan dengan tipe beton.
Ada pengiriman kedua.
Berikut adalah Bishop
subclass yang menggambarkan bahwa:
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;
}
}
Dan inilah contoh penggunaannya:
// 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));
}
Kerugian pengunjung
Pola Pengunjung adalah pola yang sangat kuat tetapi juga memiliki beberapa batasan penting yang harus Anda pertimbangkan sebelum menggunakannya.
1) Risiko untuk mengurangi / menghancurkan enkapsulasi
Dalam beberapa jenis operasi, pola pengunjung dapat mengurangi atau menghancurkan enkapsulasi objek domain.
Sebagai contoh, karena MovePerformingVisitor
kelas perlu mengatur koordinat potongan aktual, Piece
antarmuka harus menyediakan cara untuk melakukan itu:
void setCoordinates(Coordinates coordinates);
Tanggung jawab Piece
perubahan koordinat sekarang terbuka untuk kelas selain Piece
subkelas.
Memindahkan pemrosesan yang dilakukan oleh pengunjung di Piece
subkelas juga bukan pilihan.
Itu memang akan membuat masalah lain karena Piece.accept()
menerima implementasi pengunjung. Ia tidak tahu apa yang dilakukan pengunjung dan karenanya tidak tahu tentang apakah dan bagaimana mengubah keadaan Sepotong.
Cara untuk mengidentifikasi pengunjung adalah dengan melakukan pemrosesan pos Piece.accept()
sesuai dengan implementasi pengunjung. Ini akan menjadi ide yang sangat buruk karena akan membuat sambungan tinggi antara implementasi Pengunjung dan subkelas Sepotong dan selain itu mungkin akan perlu menggunakan trik getClass()
, instanceof
atau penanda apa pun yang mengidentifikasi implementasi Pengunjung.
2) Persyaratan untuk mengubah model
Berlawanan dengan beberapa pola desain perilaku lainnya seperti Decorator
misalnya, pola pengunjung mengganggu.
Kita memang perlu memodifikasi kelas penerima awal untuk menyediakan accept()
metode untuk menerima untuk dikunjungi.
Kami tidak memiliki masalah untuk Piece
dan subkelasnya karena ini adalah kelas kami .
Di kelas bawaan atau pihak ketiga, banyak hal tidak mudah.
Kita perlu membungkus atau mewarisi (jika kita bisa) mereka untuk menambahkan accept()
metode.
3) Indirections
Pola ini menciptakan banyak tipuan.
Pengiriman ganda berarti dua doa alih-alih satu:
call the visited (piece) -> that calls the visitor (pieceMovingVisitor)
Dan kita dapat memiliki tipuan tambahan saat pengunjung mengubah status objek yang dikunjungi.
Itu mungkin terlihat seperti sebuah siklus:
call the visited (piece) -> that calls the visitor (pieceMovingVisitor) -> that calls the visited (piece)