Çifte sevkiyat, diğerlerinin yanı sıra bu modeli kullanmanın sadece bir nedenidir .
Ancak, tek bir gönderim paradigması kullanan dillerde çift veya daha fazla gönderi uygulamanın tek yolu olduğunu unutmayın.
Deseni kullanmanın nedenleri şunlardır:
1) Her seferinde modeli değiştirmeden yeni operasyonlar tanımlamak istiyoruz çünkü operasyonlar sık sık değiştiği için model sık değişmiyor.
2) Modeli ve davranışı birleştirmek istemiyoruz çünkü birden fazla uygulamada yeniden kullanılabilir bir modelimiz veya istemci sınıflarının kendi sınıflarıyla davranışlarını tanımlamasına olanak tanıyan genişletilebilir bir modelimiz olsun istiyoruz .
3) Modelin somut tipine bağlı ortak operasyonlarımız var, ancak her alt sınıfta mantığı birden fazla sınıfta ve benzeri yerlerde ortak mantığı patlatacağı için uygulamak istemiyoruz .
4) Aynı hiyerarşinin bir etki alanı modeli tasarımı ve model sınıfları kullanıyoruz, başka bir yerde toplanabilecek çok farklı şeyler gerçekleştiriyoruz .
5) bir çift sevk gerekir .
Arabirim türleriyle bildirilen değişkenlerimiz var ve bunları çalışma zamanı türlerine göre işleyebilmek istiyoruz… tabii ki kullanmadan if (myObj instanceof Foo) {}
veya hile yapmadan .
Fikir, örneğin, bu değişkenleri, belirli bir işlemi uygulamak için somut bir arabirim türü olarak parametre olarak bildiren yöntemlere aktarmaktır. Bu tür bir işlem, dillerle birlikte kutudan çıkarılması mümkün değildir, çünkü çalışma zamanında çağrılan seçilen yalnızca alıcının çalışma zamanı türüne bağlıdır.
Java'da çağrılacak yöntemin (imza) derleme zamanında seçildiğini ve çalışma zamanı türlerine değil, bildirilen parametrelerin türüne bağlı olduğunu unutmayın.
Ziyaretçiyi kullanmanın bir nedeni olan son nokta da bir sonuçtur, çünkü ziyaretçiyi uygularken (elbette birden fazla gönderimi desteklemeyen diller için), mutlaka bir çift gönderim uygulaması tanıtmanız gerekir.
Ziyaretçiyi her birine uygulamak için öğelerin geçişinin (yineleme) deseni kullanmak için bir neden olmadığını unutmayın.
Modeli ve işlemi bölündüğünüz için deseni kullanırsınız.
Ve kalıbı kullanarak, bir yineleyici yeteneğinden ek olarak faydalanırsınız.
Bu yetenek çok güçlüdür ve genel bir yöntem gibi belirli bir yöntemle ortak tipte yinelemenin ötesine geçer accept()
.
Özel bir kullanım durumudur. Bu yüzden bir tarafa koyacağım.
Java örneği
Modelin katma değerini, oyuncu bir parça hareket etmesini istediği için işlemeyi tanımlamak istediğimiz bir satranç örneğiyle göstereceğim.
Ziyaretçi kalıbı kullanımı olmadan, parça taşıma davranışlarını doğrudan parça alt sınıflarında tanımlayabiliriz.
Örneğin, aşağıdaki Piece
gibi bir arayüze sahip olabiliriz :
public interface Piece{
boolean checkMoveValidity(Coordinates coord);
void performMove(Coordinates coord);
Piece computeIfKingCheck();
}
Her Piece alt sınıfı aşağıdaki gibi uygular:
public class Pawn implements Piece{
@Override
public boolean checkMoveValidity(Coordinates coord) {
...
}
@Override
public void performMove(Coordinates coord) {
...
}
@Override
public Piece computeIfKingCheck() {
...
}
}
Ve tüm Piece alt sınıfları için aynı şey.
İşte bu tasarımı gösteren bir diyagram sınıfı:
Bu yaklaşım üç önemli dezavantaj sunmaktadır:
- büyük olasılıkla ortak mantık kullanacak performMove()
veya computeIfKingCheck()
kullanacak davranışlar .
Beton ne olursa olsun Örneğin Piece
, performMove()
son olarak potansiyel belirli bir konuma cari parça ve kurulacaktır rakip parçasını alır.
İlgili davranışları toplamak yerine birden fazla sınıfa ayırmak, tek sorumluluk modelini bir şekilde yener. Sürdürülebilirliklerini zorlaştırmak.
- Alt sınıfların görebileceği veya değiştirebileceği bir checkMoveValidity()
şey olmamalıdır Piece
.
İnsan veya bilgisayar eylemlerinin ötesine geçen bir kontroldür. Bu kontrol, oyuncu tarafından istenen parça hareketinin geçerli olduğundan emin olmak için talep edilen her eylemde gerçekleştirilir.
Bu yüzden bunu Piece
arayüzde sağlamak bile istemiyoruz .
- Bot geliştiricileri için zorlu satranç oyunlarında genellikle uygulama standart bir API ( Piece
arayüzler, alt sınıflar, Yönetim Kurulu, ortak davranışlar, vb.) Sağlar ve geliştiricilerin bot stratejilerini zenginleştirmesine izin verir.
Bunu yapabilmek için uygulamalarda veri ve davranışların sıkı bir şekilde eşleşmediği bir model önermeliyiz Piece
.
Şimdi ziyaretçi desenini kullanalım!
İki çeşit yapımız var:
- ziyaret edilmeyi kabul eden model sınıfları (parçalar)
- ziyaret eden ziyaretçiler (taşınma işlemleri)
Deseni gösteren bir sınıf diyagramı:
Üst kısımda ziyaretçiler, alt kısımda model derslerimiz var.
İşte PieceMovingVisitor
arayüz (her tür için belirtilen davranış 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);
}
Parça şimdi tanımlanmıştır:
public interface Piece {
void accept(PieceMovingVisitor pieceVisitor);
Coordinates getCoordinates();
void setCoordinates(Coordinates coordinates);
}
Anahtar yöntemi:
void accept(PieceMovingVisitor pieceVisitor);
İlk gönderimi sağlar: Piece
alıcıya dayalı bir çağırma .
Derleme zamanında, yöntem accept()
Parça arabiriminin yöntemine bağlıdır ve çalışma zamanında, sınırlı yöntem çalışma zamanı Piece
sınıfında çağrılır .
Ve accept()
ikinci bir dağıtım gerçekleştirecek olan yöntem uygulamasıdır.
Gerçekten de, Piece
bir PieceMovingVisitor
nesne tarafından ziyaret etmek isteyen her alt sınıf PieceMovingVisitor.visit()
yöntemi bağımsız değişken olarak geçirerek yöntemi çağırır .
Bu şekilde, derleyici derleme zamanı, beyan edilen parametrenin türünü somut tiple sınırlar bağlamaz.
İkinci sevkiyat var.
İşte Bishop
bunu gösteren alt sınıf:
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;
}
}
Ve burada bir kullanım örneği:
// 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));
}
Ziyaretçi sakıncaları
Ziyaretçi kalıbı çok güçlü bir kalıptır, ancak kullanmadan önce dikkate almanız gereken bazı önemli sınırlamalar da vardır.
1) Kapsüllemeyi azaltma / kırma riski
Bazı işlem türlerinde ziyaretçi şablonu, etki alanı nesnelerinin kapsüllenmesini azaltabilir veya kırabilir.
Örneğin, MovePerformingVisitor
sınıfın gerçek parçanın koordinatlarını ayarlaması gerektiğinden, Piece
arayüz bunu yapmak için bir yol sağlamalıdır:
void setCoordinates(Coordinates coordinates);
Piece
Koordinat değişikliklerinin sorumluluğu artık Piece
alt sınıflardan başka sınıflara da açıktır .
Ziyaretçi tarafından yapılan işlemlerin Piece
alt sınıflarda taşınması da bir seçenek değildir. Herhangi bir ziyaretçi uygulamasını kabul
ettiği için gerçekten başka bir sorun yaratacaktır Piece.accept()
. Ziyaretçinin ne yaptığını bilmiyor ve bu yüzden Parça durumunu değiştirip değiştirmeyeceği ve nasıl değiştirileceği hakkında hiçbir fikir yok.
Ziyaretçiyi tanımlamanın bir yolu Piece.accept()
, ziyaretçi uygulamasına göre bir son işlem gerçekleştirmektir. Ziyaretçi uygulamaları ve Parça alt sınıfları arasında yüksek bir bağlantı oluşturacağı için çok kötü bir fikir olacaktır ve bunun yanı sıra getClass()
, hileyi instanceof
veya Ziyaretçi uygulamasını tanımlayan herhangi bir markörü kullanması gerekebilir .
2) Modeli değiştirme gereksinimi
Decorator
Örneğin, bazı diğer davranışsal tasarım modellerinin aksine , ziyaretçi modeli müdahaleci. Ziyaret edilmeyi kabul edecek
bir accept()
yöntem sağlamak için ilk alıcı sınıfını değiştirmemiz gerekiyor . Sınıflarımız
için herhangi bir sorunumuz Piece
ve alt sınıflarımız yoktu .
Yerleşik veya üçüncü taraf sınıflarında işler o kadar kolay değildir. Yöntemi
eklemek için bunları sarmamız veya devralmamız gerekir .
accept()
3) İndirimler
Desen, birden fazla dolaylı oluşturma oluşturur.
Çift gönderme, tek bir yerine iki çağrı anlamına gelir:
call the visited (piece) -> that calls the visitor (pieceMovingVisitor)
Ziyaretçi ziyaret edilen nesne durumunu değiştirdikçe ilave indirimler de alabiliriz.
Bir döngü gibi görünebilir:
call the visited (piece) -> that calls the visitor (pieceMovingVisitor) -> that calls the visited (piece)