Kapan saya harus menggunakan Pola Desain Pengunjung? [Tutup]


315

Saya terus melihat referensi ke pola pengunjung di blog tetapi saya harus mengakui, saya hanya tidak mengerti. Saya membaca artikel wikipedia untuk polanya dan saya memahami mekanismenya, tetapi saya masih bingung kapan saya akan menggunakannya.

Sebagai seseorang yang baru-baru ini benar - benar mendapatkan pola dekorator dan sekarang melihat kegunaan untuk itu di mana-mana saya ingin dapat benar-benar memahami secara intuitif pola yang tampaknya berguna ini juga.


7
Akhirnya mendapatkannya setelah membaca artikel ini oleh Jermey Miller di blackberry saya sambil menunggu di lobi selama dua jam. Ini panjang tetapi memberikan penjelasan yang bagus tentang pengiriman ganda, pengunjung, dan komposit, dan apa yang dapat Anda lakukan dengan ini.
George Mauer


3
Pola Pengunjung? Yang mana? Intinya adalah: ada banyak kesalahpahaman dan kebingungan murni di sekitar pola desain ini. Saya sudah menulis dan artikel yang mudah-mudahan bisa menertibkan kekacauan ini: rgomes-info.blogspot.co.uk/2013/01/…
Richard Gomes

Saat Anda ingin memiliki objek fungsi pada tipe data gabungan, Anda perlu pola pengunjung. Anda mungkin bertanya-tanya apa fungsi objek dan tipe data gabungan, maka ada baiknya membaca ccs.neu.edu/home/matthias/htdc.html
Wei Qiu

Contoh di sini dan di sini .
jaco0646

Jawaban:


315

Saya tidak terlalu mengenal pola Pengunjung. Mari kita lihat apakah saya benar. Misalkan Anda memiliki hierarki hewan

class Animal {  };
class Dog: public Animal {  };
class Cat: public Animal {  };

(Misalkan itu adalah hierarki yang kompleks dengan antarmuka yang mapan.)

Sekarang kami ingin menambahkan operasi baru ke hierarki, yaitu kami ingin setiap hewan membuat suaranya. Sejauh hierarkinya sesederhana ini, Anda dapat melakukannya dengan polimorfisme langsung:

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

Tetapi melanjutkan dengan cara ini, setiap kali Anda ingin menambahkan operasi Anda harus memodifikasi antarmuka untuk setiap kelas hirarki. Sekarang, anggaplah Anda puas dengan antarmuka asli, dan bahwa Anda ingin membuat modifikasi sesedikit mungkin untuk itu.

Pola Pengunjung memungkinkan Anda untuk memindahkan setiap operasi baru di kelas yang sesuai, dan Anda perlu memperluas antarmuka hierarki hanya sekali. Ayo lakukan. Pertama, kami mendefinisikan operasi abstrak (kelas "Pengunjung" di GoF ) yang memiliki metode untuk setiap kelas dalam hierarki:

class Operation
{
public:
    virtual void hereIsADog(Dog *d) = 0;
    virtual void hereIsACat(Cat *c) = 0;
};

Kemudian, kami memodifikasi hierarki untuk menerima operasi baru:

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

Akhirnya, kami menerapkan operasi yang sebenarnya, tanpa memodifikasi baik Kucing maupun Anjing :

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

Sekarang Anda memiliki cara untuk menambahkan operasi tanpa memodifikasi hierarki lagi. Inilah cara kerjanya:

int main()
{
    Cat c;
    Sound theSound;
    c.letsDo(&theSound);
}

19
S.Lott, berjalan di pohon sebenarnya bukan pola pengunjung. (Ini adalah "pola pengunjung hierarkis", yang sangat membingungkan.) Tidak ada cara untuk menunjukkan pola Pengunjung GoF tanpa menggunakan pewarisan atau implementasi antarmuka.
murah hati

14
@Knownasilya - Itu tidak benar. & -Operator memberikan alamat Objek-Suara, yang diperlukan oleh antarmuka. letsDo(Operation *v) membutuhkan pointer.
AquilaRapax

3
hanya demi kejelasan, apakah contoh pola desain pengunjung ini benar?
godzilla

4
Setelah banyak berpikir, saya bertanya-tanya mengapa Anda memanggil dua metode di sini isadog dan di sini isat meskipun Anda sudah melewati anjing dan kucing untuk metode. Saya lebih suka performTask sederhana (Object * obj) dan Anda melemparkan objek ini di kelas Operation. (dan dalam bahasa mendukung penggantian, tidak perlu casting)
Abdalrahman Shatou

6
Dalam contoh "utama" Anda di akhir: theSound.hereIsACat(c)akan melakukan pekerjaan itu, bagaimana Anda membenarkan untuk semua biaya overhead yang diperkenalkan oleh pola? pengiriman ganda adalah pembenaran.
franssu

131

Alasan untuk kebingungan Anda mungkin adalah bahwa Pengunjung adalah keliru fatal. Banyak (menonjol 1 !) Programmer telah tersandung masalah ini. Apa yang sebenarnya dilakukannya adalah menerapkan pengiriman ganda dalam bahasa yang tidak mendukungnya secara asli (kebanyakan dari mereka tidak).


1) Contoh favorit saya adalah Scott Meyers, penulis terkenal “Effective C ++”, yang menyebut ini salah satu C ++ aha paling penting! saat-saat yang pernah ada .


3
+1 "tidak ada pola" - jawaban yang sempurna. jawaban yang paling banyak dibuktikan membuktikan banyak programmer c ++ belum menyadari keterbatasan fungsi virtual atas polimorfisme "adhoc" menggunakan tipe enum dan switch case (cara c). Mungkin lebih rapi dan tidak terlihat untuk menggunakan virtual, tetapi masih terbatas pada pengiriman tunggal. Menurut pendapat pribadi saya, ini adalah kelemahan terbesar c ++.
user3125280

@ user3125280 Saya sudah membaca 4/5 artikel dan bab Pola Desain pada pola Pengunjung sekarang, dan tidak ada yang menjelaskan keuntungan menggunakan pola tidak jelas ini di atas stmt kasus, atau ketika Anda mungkin menggunakan satu di atas yang lain. Terima kasih untuk setidaknya membawanya!
spinkus

4
@ Sam Aku cukup yakin mereka menjelaskan - itu keuntungan yang sama bahwa Anda selalu mendapatkan dari subclassing / runtime polimorfisme lebih switch: switchhard-kode pengambilan keputusan di sisi client (duplikasi kode) dan tidak menawarkan memeriksa jenis statis ( periksa kelengkapan dan perbedaan kasus, dll.). Pola pengunjung diverifikasi oleh pemeriksa tipe, dan biasanya membuat kode klien lebih sederhana.
Konrad Rudolph

@KonradRudolph terima kasih untuk itu. Memperhatikan, itu tidak dibahas secara eksplisit dalam Pola atau artikel wikipedia misalnya. Saya tidak setuju dengan Anda, tetapi Anda bisa berargumen bahwa ada manfaat menggunakan stmt case juga jadi ini aneh biasanya tidak kontras: 1. Anda tidak perlu metode accept () pada objek koleksi Anda. 2. Pengunjung dapat menangani objek dengan tipe yang tidak dikenal. Jadi case stmt tampaknya lebih cocok untuk beroperasi pada struktur objek dengan koleksi jenis yang dapat diubah. Pola tidak mengakui bahwa pola Pengunjung tidak cocok untuk skenario seperti itu (hal. 333).
spinkus

1
@SamPinkus tempat Konrad aktif - itulah sebabnya virtualfitur seperti sangat berguna dalam bahasa pemrograman modern - mereka adalah blok bangunan dasar program yang dapat dikembangkan - menurut saya cara c (sakelar bersarang atau pencocokan pola, dll. Tergantung pada bahasa pilihan Anda) adalah jauh lebih bersih dalam kode yang tidak perlu diperpanjang dan saya sangat terkejut melihat gaya ini dalam perangkat lunak yang rumit seperti prover 9. Lebih penting lagi bahasa apa pun yang ingin memberikan ekstensibilitas mungkin harus mengakomodasi pola pengiriman yang lebih baik daripada pengiriman tunggal rekursif (yaitu pengunjung).
user3125280

84

Semua orang di sini benar, tetapi saya pikir gagal untuk menjawab "kapan". Pertama, dari Pola Desain:

Pengunjung memungkinkan Anda menentukan operasi baru tanpa mengubah kelas elemen tempat operasinya.

Sekarang, mari kita pikirkan hirarki kelas yang sederhana. Saya memiliki kelas 1, 2, 3 dan 4 dan metode A, B, C dan D. Lay out seperti dalam spreadsheet: kelas adalah garis dan metode adalah kolom.

Sekarang, desain Berorientasi Objek menganggap Anda lebih mungkin untuk mengembangkan kelas baru daripada metode baru, sehingga menambahkan lebih banyak garis, sehingga untuk berbicara, lebih mudah. Anda cukup menambahkan kelas baru, menentukan apa yang berbeda di kelas itu, dan mewarisi sisanya.

Namun, kadang-kadang, kelasnya relatif statis, tetapi Anda perlu menambahkan lebih banyak metode - menambah kolom. Cara standar dalam desain OO adalah menambahkan metode seperti itu ke semua kelas, yang bisa mahal. Pola Pengunjung memudahkan ini.

Ngomong-ngomong, ini adalah masalah yang ingin diselesaikan oleh pola Scala.


Mengapa saya menggunakan pola pengunjung di atas kelas utlity saja. saya dapat memanggil kelas utilitas saya seperti ini: AnalyticsManger.visit (someObjectToVisit) vs AnalyticsVisitor.visit (someOjbectToVisit). Apa bedanya ? mereka berdua melakukan pemisahan hak bukan? semoga kamu bisa membantu
j2emanue

@ j2emanue Karena pola Pengunjung menggunakan kelebihan pengunjung yang benar saat runtime. Sementara kode Anda perlu mengetik casting untuk memanggil kelebihan yang benar.
Akses Ditolak

apakah ada keuntungan efisiensi dengan itu? Saya kira itu menghindari casting itu ide yang bagus
j2emanue

@ j2emanue idenya adalah untuk menulis kode yang sesuai dengan prinsip terbuka / tertutup, bukan alasan kinerja. Lihat buka tutup di paman Bob butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
Access Denied

22

The Visitor pola desain bekerja dengan sangat baik untuk "recursive" struktur seperti pohon direktori, struktur XML, atau garis besar dokumen.

Objek Pengunjung mengunjungi setiap node dalam struktur rekursif: setiap direktori, setiap tag XML, apa pun. Objek Pengunjung tidak melewati struktur. Sebaliknya, metode Pengunjung diterapkan ke setiap simpul struktur.

Berikut adalah struktur simpul rekursif khas. Bisa berupa direktori atau tag XML. [Jika Anda orang Jawa, bayangkan banyak metode tambahan untuk membangun dan memelihara daftar anak-anak.]

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()

The visitMetode berlaku objek pengunjung ke setiap node dalam struktur. Dalam hal ini, ini adalah pengunjung top-down. Anda dapat mengubah struktur visitmetode untuk melakukan bottom-up atau pemesanan lainnya.

Ini adalah superclass untuk pengunjung. Ini digunakan oleh visitmetode. Itu "tiba di" setiap node dalam struktur. Karena visitmetode ini memanggil updan down, pengunjung dapat melacak kedalamannya.

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

Subclass dapat melakukan hal-hal seperti menghitung node di setiap level dan mengumpulkan daftar node, menghasilkan jalur yang bagus, nomor bagian hierarkis.

Ini sebuah aplikasi. Itu membangun struktur pohon someTree,. Ini menciptakan Visitor, dumpNodes.

Kemudian itu berlaku dumpNodesuntuk pohon itu. The dumpNodeobjek akan "mengunjungi" setiap node di pohon.

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

visitAlgoritma TreeNode akan memastikan bahwa setiap TreeNode digunakan sebagai argumen untuk metode Pengunjung arrivedAt.


8
Seperti yang orang lain nyatakan ini adalah "pola pengunjung hirarkis".
PPC-Coder

1
@ PPC-Coder Apa perbedaan antara 'pola pengunjung hierarkis' dan pola pengunjung?
Tim Lovell-Smith

3
Pola pengunjung hierarkis lebih fleksibel daripada pola pengunjung klasik. Misalnya, dengan pola hierarkis Anda dapat melacak kedalaman traversal dan memutuskan cabang mana yang akan dilintasi atau berhenti melintasi semuanya. Pengunjung klasik tidak memiliki konsep ini dan akan mengunjungi semua node.
PPC-Coder

18

Salah satu cara untuk melihatnya adalah bahwa pola pengunjung adalah cara untuk membiarkan klien Anda menambahkan metode tambahan ke semua kelas Anda dalam hierarki kelas tertentu.

Ini berguna ketika Anda memiliki hierarki kelas yang cukup stabil, tetapi Anda telah mengubah persyaratan tentang apa yang perlu dilakukan dengan hierarki itu.

Contoh klasik adalah untuk kompiler dan sejenisnya. Abstract Syntax Tree (AST) dapat secara akurat mendefinisikan struktur bahasa pemrograman, tetapi operasi yang mungkin ingin Anda lakukan pada AST akan berubah seiring kemajuan proyek Anda: generator kode, printer cantik, debugger, analisis metrik kompleksitas.

Tanpa Pola Pengunjung, setiap kali pengembang ingin menambahkan fitur baru, mereka perlu menambahkan metode itu ke setiap fitur di kelas dasar. Ini sangat sulit ketika kelas dasar muncul di perpustakaan yang terpisah, atau diproduksi oleh tim yang terpisah.

(Saya telah mendengarnya berpendapat bahwa pola Pengunjung bertentangan dengan praktik OO yang baik, karena memindahkan operasi data menjauh dari data. Pola Pengunjung berguna dalam situasi yang tepat ketika praktik OO normal gagal.)


Saya juga ingin pendapat Anda tentang hal-hal berikut: Mengapa saya menggunakan pola pengunjung hanya pada kelas utlity. saya dapat memanggil kelas utilitas saya seperti ini: AnalyticsManger.visit (someObjectToVisit) vs AnalyticsVisitor.visit (someOjbectToVisit). Apa bedanya ? mereka berdua melakukan pemisahan hak bukan? semoga kamu bisa membantu
j2emanue

@ j2emanue: Saya tidak mengerti pertanyaannya. Saya sarankan Anda menyempurnakannya dan mempostingnya sebagai pertanyaan lengkap bagi siapa pun untuk menjawab.
Oddthinking

1
saya memposting pertanyaan baru di sini: stackoverflow.com/questions/52068876/…
j2emanue

14

Setidaknya ada tiga alasan yang sangat baik untuk menggunakan Pola Pengunjung:

  1. Mengurangi proliferasi kode yang hanya sedikit berbeda ketika struktur data berubah.

  2. Menerapkan perhitungan yang sama untuk beberapa struktur data, tanpa mengubah kode yang mengimplementasikan perhitungan.

  3. Tambahkan informasi ke pustaka lawas tanpa mengubah kode lawas.

Silakan lihat artikel yang saya tulis tentang ini .


1
Saya mengomentari artikel Anda dengan penggunaan tunggal terbesar yang pernah saya lihat untuk pengunjung. Pikiran?
George Mauer

13

Seperti yang ditunjukkan oleh Konrad Rudolph, sangat cocok untuk kasus-kasus di mana kita perlu pengiriman ganda

Berikut adalah contoh untuk menunjukkan situasi di mana kami perlu pengiriman ganda & bagaimana pengunjung membantu kami melakukannya.

Contoh:

Katakanlah saya memiliki 3 jenis perangkat seluler - iPhone, Android, Windows Mobile.

Ketiga perangkat ini memiliki radio Bluetooth yang terpasang di dalamnya.

Mari kita asumsikan bahwa radio gigi biru dapat berasal dari 2 OEM terpisah - Intel & Broadcom.

Hanya untuk membuat contoh yang relevan untuk diskusi kita, mari kita asumsikan juga bahwa API yang diekspos oleh radio Intel berbeda dari yang diekspos oleh radio Broadcom.

Beginilah tampilan kelas saya -

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Sekarang, saya ingin memperkenalkan operasi - Mengaktifkan Bluetooth di perangkat seluler.

Tanda tangannya fungsinya harus seperti ini -

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

Jadi tergantung pada jenis perangkat yang Tepat dan Tergantung pada jenis radio Bluetooth yang tepat , itu dapat diaktifkan dengan memanggil langkah-langkah atau algoritma yang sesuai .

Pada prinsipnya, itu menjadi matriks 3 x 2, di mana-dalam saya mencoba untuk vektor operasi yang tepat tergantung pada jenis objek yang tepat yang terlibat.

Perilaku polimorfik tergantung pada jenis kedua argumen.

masukkan deskripsi gambar di sini

Sekarang, pola Pengunjung dapat diterapkan untuk masalah ini. Inspirasi berasal dari halaman Wikipedia yang menyatakan - “Pada dasarnya, pengunjung memungkinkan seseorang untuk menambahkan fungsi virtual baru ke keluarga kelas tanpa memodifikasi kelas sendiri; alih-alih, seseorang membuat kelas pengunjung yang mengimplementasikan semua spesialisasi yang sesuai dari fungsi virtual. Pengunjung mengambil referensi contoh sebagai input, dan mengimplementasikan tujuan melalui pengiriman ganda. "

Pengiriman ganda adalah suatu keharusan di sini karena matriks 3x2

Beginilah tampilan pengaturannya - masukkan deskripsi gambar di sini

Saya menulis contoh untuk menjawab pertanyaan lain, kode & penjelasannya disebutkan di sini .


9

Saya merasa lebih mudah dalam tautan berikut:

Dalam http://www.remondo.net/visitor-pattern-example-csharp/ saya menemukan contoh yang menunjukkan contoh tiruan yang menunjukkan apa manfaat dari pola pengunjung. Di sini Anda memiliki kelas wadah yang berbeda untuk 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; }
    }
}

Seperti yang Anda lihat di atas, Anda BilsterPackmengandung pasangan Pil sehingga Anda perlu mengalikan jumlah pasangan dengan 2. Juga, Anda mungkin memperhatikan bahwa Bottlepenggunaan unityang merupakan tipe data berbeda dan perlu dilemparkan.

Jadi dalam metode utama Anda dapat menghitung jumlah pil menggunakan kode berikut:

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

Perhatikan bahwa kode di atas melanggar Single Responsibility Principle. Itu berarti Anda harus mengubah kode metode utama jika Anda menambahkan jenis wadah baru. Juga beralih lebih lama adalah praktik yang buruk.

Jadi dengan memperkenalkan kode berikut:

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
}

Anda memindahkan tanggung jawab menghitung jumlah Pills ke kelas yang disebut PillCountVisitor(Dan kami menghapus pernyataan kasus sakelar). Itu artinya setiap kali Anda perlu menambahkan wadah pil jenis baru, Anda hanya perlu mengubah PillCountVisitorkelas. Juga perhatikan IVisitorantarmuka umum untuk digunakan dalam skenario lain.

Dengan menambahkan metode Terima ke kelas wadah pil:

public class BlisterPack : IAcceptor
{
    public int TabletPairs { get; set; }

    #region IAcceptor Members

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }

    #endregion
}

kami mengizinkan pengunjung untuk mengunjungi kelas wadah pil.

Pada akhirnya kami menghitung jumlah pil menggunakan kode berikut:

var visitor = new PillCountVisitor();

foreach (IAcceptor item in packageList)
{
    item.Accept(visitor);
}

Itu artinya: Setiap wadah pil memungkinkan PillCountVisitorpengunjung untuk melihat jumlah pil mereka. Dia tahu bagaimana cara menghitung pil Anda.

Pada visitor.Countmemiliki nilai pil.

Dalam http://butunclebob.com/ArticleS.UncleBob.IuseVisitor Anda melihat skenario nyata di mana Anda tidak dapat menggunakan polimorfisme (jawabannya) untuk mengikuti Prinsip Tanggung Jawab Tunggal. Bahkan di:

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

yang reportQtdHoursAndPaymetode adalah untuk pelaporan dan representasi dan ini melanggar Tanggung Jawab Single Prinsip. Jadi lebih baik menggunakan pola pengunjung untuk mengatasi masalah tersebut.


2
Hai Sayed, bisakah Anda mengedit jawaban Anda untuk menambahkan bagian-bagian yang menurut Anda paling mencerahkan. SO umumnya tidak menyarankan jawaban tautan saja karena tujuannya adalah untuk menjadi basis data pengetahuan dan tautan turun.
George Mauer

8

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 Pieceantarmuka 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:

[diagram kelas model

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 Piecedapat 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 Pieceantarmuka.

- Dalam permainan catur yang menantang bagi pengembang bot, umumnya aplikasi menyediakan API standar ( Pieceantarmuka, 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 Pieceimplementasi.

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:

masukkan deskripsi gambar di sini

Di bagian atas kami memiliki pengunjung dan di bagian bawah kami memiliki kelas model.

Berikut adalah PieceMovingVisitorantarmuka (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 Piecepenerima.
Pada waktu kompilasi, metode terikat ke accept()metode antarmuka Piece dan pada saat runtime, metode terikat akan dipanggil pada Piecekelas runtime .
Dan itu adalah accept()implementasi metode yang akan melakukan pengiriman kedua.

Memang, setiap Piecesubclass yang ingin dikunjungi oleh PieceMovingVisitorobjek 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 Bishopsubclass 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, Pieceantarmuka harus menyediakan cara untuk melakukan itu:

void setCoordinates(Coordinates coordinates);

Tanggung jawab Pieceperubahan koordinat sekarang terbuka untuk kelas selain Piecesubkelas.
Memindahkan pemrosesan yang dilakukan oleh pengunjung di Piecesubkelas 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(), instanceofatau penanda apa pun yang mengidentifikasi implementasi Pengunjung.

2) Persyaratan untuk mengubah model

Berlawanan dengan beberapa pola desain perilaku lainnya seperti Decoratormisalnya, pola pengunjung mengganggu.
Kita memang perlu memodifikasi kelas penerima awal untuk menyediakan accept()metode untuk menerima untuk dikunjungi.
Kami tidak memiliki masalah untuk Piecedan 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)

6

Cay Horstmann memiliki contoh yang bagus tentang tempat menerapkan Pengunjung dalam buku OO Desain dan pola-nya . Dia merangkum masalah:

Benda majemuk sering memiliki struktur yang kompleks, terdiri atas unsur-unsur individual. Beberapa elemen mungkin lagi memiliki elemen anak. ... Operasi pada elemen mengunjungi elemen anaknya, menerapkan operasi padanya, dan menggabungkan hasilnya. ... Namun, tidak mudah untuk menambahkan operasi baru ke desain seperti itu.

Alasannya tidak mudah adalah karena operasi ditambahkan dalam kelas struktur itu sendiri. Misalnya, bayangkan Anda memiliki Sistem File:

Diagram kelas FileSystem

Berikut adalah beberapa operasi (fungsi) yang mungkin ingin kami terapkan dengan struktur ini:

  • Menampilkan nama elemen simpul (daftar file)
  • Tampilkan ukuran elemen simpul yang dihitung (di mana ukuran direktori mencakup ukuran semua elemen turunannya)
  • dll.

Anda bisa menambahkan fungsi ke setiap kelas di FileSystem untuk mengimplementasikan operasi (dan orang-orang telah melakukan ini di masa lalu karena sangat jelas bagaimana melakukannya). Masalahnya adalah bahwa setiap kali Anda menambahkan fungsionalitas baru (baris "dll." Di atas), Anda mungkin perlu menambahkan lebih banyak metode ke kelas struktur. Pada titik tertentu, setelah beberapa operasi yang Anda tambahkan ke perangkat lunak Anda, metode di kelas-kelas itu tidak masuk akal lagi dalam hal kohesi fungsional kelas '. Misalnya, Anda memiliki FileNodeyang memiliki metode calculateFileColorForFunctionABC()untuk menerapkan fungsi visualisasi terbaru pada sistem file.

Pola Pengunjung (seperti banyak pola desain) lahir dari rasa sakit dan penderitaan pengembang yang tahu ada cara yang lebih baik untuk memungkinkan kode mereka untuk berubah tanpa memerlukan banyak perubahan di mana-mana dan juga menghormati prinsip-prinsip desain yang baik (kohesi tinggi, kopling rendah ). Menurut saya sulit memahami manfaat dari banyak pola sampai Anda merasakan sakit itu. Menjelaskan rasa sakit (seperti yang kami coba lakukan di atas dengan fungsionalitas "dll." Yang ditambahkan) membutuhkan ruang dalam penjelasan dan merupakan pengalih perhatian. Memahami pola sulit karena alasan ini.

Pengunjung memungkinkan kita untuk memisahkan fungsi pada struktur data (misalnya, FileSystemNodes) dari struktur data itu sendiri. Pola ini memungkinkan desain untuk menghormati kohesi - kelas struktur data lebih sederhana (mereka memiliki metode yang lebih sedikit) dan juga fungsionalitas dienkapsulasi ke dalam Visitorimplementasi. Ini dilakukan melalui pengiriman ganda (yang merupakan bagian rumit dari pola): menggunakan accept()metode di kelas struktur dan visitX()metode di kelas Pengunjung (fungsi):

Diagram kelas FileSystem dengan Pengunjung diterapkan

Struktur ini memungkinkan kami untuk menambahkan fungsionalitas baru yang bekerja pada struktur sebagai Pengunjung nyata (tanpa mengubah kelas struktur).

Diagram kelas FileSystem dengan Pengunjung diterapkan

Misalnya, PrintNameVisitoryang mengimplementasikan fungsi daftar direktori, dan PrintSizeVisitoryang mengimplementasikan versi dengan ukuran. Kita dapat membayangkan suatu hari memiliki 'ExportXMLVisitor` yang menghasilkan data dalam XML, atau pengunjung lain yang menghasilkannya di JSON, dll. Kita bahkan dapat memiliki pengunjung yang menampilkan pohon direktori saya menggunakan bahasa grafis seperti DOT , untuk divisualisasikan dengan program lain.

Sebagai catatan terakhir: Kompleksitas Pengunjung dengan pengiriman ganda berarti lebih sulit untuk dipahami, untuk kode dan debug. Singkatnya, ia memiliki faktor geek yang tinggi dan bertentangan dengan prinsip KISS. Dalam survei yang dilakukan oleh para peneliti, Pengunjung terbukti menjadi pola yang kontroversial (tidak ada konsensus tentang kegunaannya). Beberapa percobaan bahkan menunjukkan itu tidak membuat kode lebih mudah dipelihara.


Struktur direktori yang saya pikir merupakan pola komposit yang baik tetapi setuju dengan paragraf terakhir Anda.
zar

5

Menurut pendapat saya, jumlah pekerjaan untuk menambah operasi baru kurang lebih sama dengan menggunakan Visitor Patternatau modifikasi langsung dari masing-masing struktur elemen. Juga, jika saya menambahkan kelas elemen baru, katakanlah Cow, antarmuka Operasi akan terpengaruh dan ini menyebar ke semua kelas elemen yang ada, karena itu membutuhkan kompilasi ulang semua kelas elemen. Jadi apa gunanya?


4
Hampir setiap kali saya menggunakan Pengunjung adalah ketika Anda bekerja dengan melintasi hierarki objek. Pertimbangkan menu pohon bersarang. Anda ingin menutup semua node. Jika Anda tidak menerapkan pengunjung, Anda harus menulis kode traversal grafik. Atau dengan pengunjung: rootElement.visit (node) -> node.collapse(). Dengan pengunjung, setiap node mengimplementasikan grafik traversal untuk semua anak-anaknya sehingga Anda selesai.
George Mauer

@ GeorgeMauer, konsep pengiriman ganda membersihkan motivasi bagi saya: logika tipe-tergantung adalah dengan jenis atau, dunia kesakitan. Gagasan mendistribusikan logika traversal masih memberi saya jeda. Apakah ini lebih efisien? Apakah lebih mudah dirawat? Bagaimana jika "lipat ke level N" ditambahkan sebagai persyaratan?
nik.shornikov

@ efisiensi nik.shornikov seharusnya tidak menjadi perhatian di sini. Di hampir semua bahasa, beberapa panggilan fungsi dapat diabaikan. Apa pun di luar itu adalah optimasi mikro. Apakah lebih mudah dirawat? Yah, itu tergantung. Saya pikir sebagian besar kali, kadang tidak. Adapun "lipat ke level N". Pass mudah dalam levelsRemainingpenghitung sebagai parameter. Kurangi sebelum memanggil anak-anak tingkat selanjutnya. Di dalam pengunjung Anda if(levelsRemaining == 0) return.
George Mauer

1
@ GeorgeMauer, setuju sepenuhnya bahwa efisiensi menjadi perhatian kecil. Tetapi perawatan, misalnya penggantian tanda terima, adalah persis apa yang saya pikir keputusan harus didasari.
nik.shornikov

5

Pola Pengunjung sebagai implementasi bawah tanah yang sama untuk pemrograman Objek Aspect ..

Misalnya jika Anda mendefinisikan operasi baru tanpa mengubah kelas elemen di mana ia beroperasi


untuk menyebutkan Pemrograman Objek Aspek
milesma

5

Deskripsi singkat tentang pola pengunjung. Kelas-kelas yang membutuhkan modifikasi semua harus menerapkan metode 'accept'. Klien menyebut metode penerimaan ini untuk melakukan beberapa tindakan baru pada keluarga kelas itu sehingga memperluas fungsionalitasnya. Klien dapat menggunakan metode yang diterima ini untuk melakukan berbagai tindakan baru dengan meneruskan kelas pengunjung yang berbeda untuk setiap tindakan tertentu. Kelas pengunjung berisi beberapa metode kunjungan yang diganti-ganti yang mendefinisikan cara mencapai tindakan spesifik yang sama untuk setiap kelas dalam keluarga. Metode kunjungan ini dilewatkan sebagai contoh untuk bekerja.

Ketika Anda mungkin mempertimbangkan menggunakannya

  1. Ketika Anda memiliki keluarga kelas, Anda tahu Anda harus menambahkan banyak tindakan baru semuanya, tetapi untuk beberapa alasan Anda tidak dapat mengubah atau mengkompilasi ulang keluarga kelas di masa depan.
  2. Ketika Anda ingin menambahkan tindakan baru dan memiliki tindakan baru yang sepenuhnya ditentukan dalam satu kelas pengunjung daripada menyebar di beberapa kelas.
  3. Ketika bos Anda mengatakan Anda harus menghasilkan berbagai kelas yang harus melakukan sesuatu sekarang ! ... tetapi tidak ada yang benar-benar tahu persis apa sesuatu itu belum.

4

Saya tidak mengerti pola ini sampai saya menemukan artikel paman bob dan membaca komentar. Pertimbangkan kode berikut:

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

Walaupun terlihat bagus karena mengonfirmasi ke Single Responsibility, itu melanggar prinsip Terbuka / Tertutup . Setiap kali Anda memiliki tipe Karyawan baru Anda harus menambahkan jika dengan tipe check. Dan jika Anda tidak mau, Anda tidak akan pernah tahu itu pada waktu kompilasi.

Dengan pola pengunjung, Anda dapat membuat kode Anda lebih bersih karena tidak melanggar prinsip terbuka / tertutup dan tidak melanggar tanggung jawab tunggal. Dan jika Anda lupa menerapkan kunjungan, itu tidak akan dikompilasi:

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

Keajaibannya adalah walaupun v.Visit(this)terlihat sama, kenyataannya berbeda karena memanggil pengunjung yang berlebihan.


Yup, saya secara khusus merasa berguna ketika bekerja dengan struktur pohon, bukan hanya daftar datar (daftar datar akan menjadi kasus khusus pohon). Seperti yang Anda perhatikan, ini bukan berantakan hanya pada daftar, tetapi pengunjung dapat menjadi penyelamat karena navigasi antar node menjadi lebih kompleks
George Mauer

3

Berdasarkan jawaban yang sangat baik dari @Federico A. Ramponi.

Bayangkan saja Anda memiliki hierarki ini:

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

Apa yang terjadi jika Anda perlu menambahkan metode "Jalan" di sini? Itu akan menyakitkan bagi seluruh desain.

Pada saat yang sama, menambahkan metode "Jalan" menghasilkan pertanyaan baru. Bagaimana dengan "Makan" atau "Tidur"? Haruskah kita benar-benar menambahkan metode baru ke hierarki Hewan untuk setiap tindakan atau operasi baru yang ingin kita tambahkan? Itu jelek dan yang paling penting, kita tidak akan pernah bisa menutup antarmuka Animal. Jadi, dengan pola pengunjung, kita dapat menambahkan metode baru ke hierarki tanpa memodifikasi hierarki!

Jadi, cukup periksa dan jalankan contoh C # ini:

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

berjalan, makan bukan contoh yang cocok karena mereka umum sedang untuk kedua Dogserta Cat. Anda bisa membuatnya di kelas dasar sehingga mereka mewarisi atau memilih contoh yang cocok.
Abhinav Gauniyal

suara berbeda, sampel bagus, tetapi tidak yakin apakah itu ada hubungannya dengan pola pengunjung
DAG

3

Pengunjung

Pengunjung memungkinkan seseorang untuk menambahkan fungsi virtual baru ke keluarga kelas tanpa memodifikasi kelas itu sendiri; alih-alih, seseorang membuat kelas pengunjung yang mengimplementasikan semua spesialisasi yang sesuai dari fungsi virtual

Struktur pengunjung:

masukkan deskripsi gambar di sini

Gunakan pola Pengunjung jika:

  1. Operasi serupa harus dilakukan pada objek dari berbagai jenis yang dikelompokkan dalam suatu struktur
  2. Anda perlu menjalankan banyak operasi yang berbeda dan tidak terkait. Ini memisahkan Operasi dari objek Struktur
  3. Operasi baru harus ditambahkan tanpa perubahan dalam struktur objek
  4. Kumpulkan operasi terkait menjadi satu kelas daripada memaksa Anda untuk mengubah atau menurunkan kelas
  5. Tambahkan fungsi ke pustaka kelas yang Anda tidak memiliki sumbernya atau tidak dapat mengubah sumbernya

Meskipun pola Pengunjung memberikan fleksibilitas untuk menambahkan operasi baru tanpa mengubah kode yang ada di Objek, fleksibilitas ini telah datang dengan kelemahan.

Jika objek Visitable baru telah ditambahkan, itu memerlukan perubahan kode di kelas Pengunjung & BetonVisitor . Ada solusi untuk mengatasi masalah ini: Gunakan refleksi, yang akan berdampak pada kinerja.

Cuplikan kode:

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

Penjelasan:

  1. Visitable( Element) adalah antarmuka dan metode antarmuka ini harus ditambahkan ke satu set kelas.
  2. Visitoradalah antarmuka, yang berisi metode untuk melakukan operasi pada Visitableelemen.
  3. GameVisitoradalah kelas, yang mengimplementasikan Visitorantarmuka ( ConcreteVisitor).
  4. Setiap Visitableelemen menerima Visitordan menjalankan metode Visitorantarmuka yang relevan .
  5. Anda dapat memperlakukan Gamesebagai Elementdan permainan beton seperti Chess,Checkers and Ludosebagai ConcreteElements.

Dalam contoh di atas, Chess, Checkers and Ludoada tiga permainan yang berbeda (dan Visitablekelas). Pada suatu hari yang cerah, saya menemukan skenario untuk mencatat statistik setiap pertandingan. Jadi tanpa memodifikasi kelas individu untuk mengimplementasikan fungsionalitas statistik, Anda dapat memusatkan tanggung jawab itu di GameVisitorkelas, yang melakukan trik untuk Anda tanpa mengubah struktur setiap permainan.

keluaran:

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

Mengacu pada

artikel oodesign

sumber pembuatan artikel

untuk lebih jelasnya

Penghias

pola memungkinkan perilaku ditambahkan ke objek individu, baik secara statis atau dinamis, tanpa mempengaruhi perilaku objek lain dari kelas yang sama

Pos terkait:

Pola dekorator untuk IO

Kapan Menggunakan Pola Penghias?


2

Saya sangat suka deskripsi dan contoh dari http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html .

Asumsinya adalah bahwa Anda memiliki hierarki kelas primer yang diperbaiki; mungkin itu dari vendor lain dan Anda tidak dapat membuat perubahan pada hierarki itu. Namun, maksud Anda adalah Anda ingin menambahkan metode polimorfik baru ke hierarki itu, yang berarti biasanya Anda harus menambahkan sesuatu ke antarmuka kelas dasar. Jadi dilema adalah bahwa Anda perlu menambahkan metode ke kelas dasar, tetapi Anda tidak dapat menyentuh kelas dasar. Bagaimana Anda menyiasati ini?

Pola desain yang memecahkan masalah semacam ini disebut "pengunjung" (yang terakhir dalam buku Pola Desain), dan itu dibangun di atas skema pengiriman ganda yang ditunjukkan pada bagian terakhir.

Pola pengunjung memungkinkan Anda untuk memperluas antarmuka tipe primer dengan membuat hierarki kelas terpisah dari tipe Pengunjung untuk memvirtualisasikan operasi yang dilakukan pada tipe primer. Objek dari tipe utama hanya "menerima" pengunjung, kemudian memanggil fungsi anggota pengunjung yang terikat secara dinamis.


Sementara secara teknis pola Pengunjung ini benar-benar hanya pengiriman ganda dasar dari contoh mereka. Saya berpendapat kegunaannya tidak terlalu terlihat dari ini saja.
George Mauer

1

Sementara saya mengerti bagaimana dan kapan, saya tidak pernah mengerti mengapa. Jika itu membantu siapa saja dengan latar belakang dalam bahasa seperti C ++, Anda ingin membaca ini dengan sangat hati-hati.

Untuk yang malas, kami menggunakan pola pengunjung karena "sementara fungsi virtual dikirim secara dinamis di C ++, fungsi kelebihan beban dilakukan secara statis" .

Atau, dengan kata lain, untuk memastikan bahwa CollideWith (ApolloSpacecraft &) dipanggil saat Anda melewati referensi SpaceShip yang sebenarnya terikat pada objek 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
Penggunaan pengiriman dinamis dalam pola pengunjung benar-benar membingungkan saya. Penggunaan pola yang disarankan menggambarkan percabangan yang bisa dilakukan pada waktu kompilasi. Kasus-kasus ini tampaknya akan lebih baik dengan templat fungsi.
Praxeolitic

0

Terima kasih atas penjelasan mengagumkan dari @Federico A. Ramponi , saya baru saja membuat ini dalam versi java . Semoga ini bisa membantu.

Juga seperti yang ditunjukkan @Konrad Rudolph , sebenarnya ini adalah pengiriman ganda menggunakan dua contoh konkret bersama untuk menentukan metode run-time.

Jadi sebenarnya tidak perlu membuat antarmuka umum untuk pelaksana operasi selama kita memiliki antarmuka operasi yang ditetapkan dengan benar.

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

Seperti yang Anda harapkan, antarmuka umum akan membawa kita lebih jelas meskipun sebenarnya bukan bagian penting dalam pola ini.

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

pertanyaan Anda adalah kapan harus tahu:

saya tidak pertama kode dengan pola pengunjung. saya kode standar dan menunggu kebutuhan terjadi & kemudian refactor. jadi katakanlah Anda memiliki beberapa sistem pembayaran yang Anda instal satu per satu. Pada waktu checkout Anda dapat memiliki banyak kondisi jika (atau instanceOf), misalnya:

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

sekarang bayangkan saya punya 10 metode pembayaran, jadi agak jelek. Jadi ketika Anda melihat pola semacam itu yang terjadi pengunjung datang dengan mudah untuk memisahkan semua itu dan Anda akhirnya memanggil sesuatu seperti ini setelahnya:

new PaymentCheckoutVistor(paymentType).visit()

Anda dapat melihat bagaimana menerapkannya dari sejumlah contoh di sini, saya hanya menunjukkan Anda usecase.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.