Ziyaretçi Tasarım Desenini ne zaman kullanmalıyım? [kapalı]


315

Bloglarda ziyaretçi kalıbına referanslar görmeye devam ediyorum ama itiraf etmeliyim, sadece anlamıyorum. Desen için wikipedia makalesini okudum ve mekaniğini anlıyorum ama ne zaman kullanacağım konusunda hala kafam karıştı.

Sadece son zamanlarda gerçekten birisi olarak var dekoratör desen ve şimdi bunun için kullandığı görmektir kesinlikle her yerde ben gerçekten de bunu sezgisel olarak görünüşte kullanışlı desen anlamak mümkün istiyorum.


7
Sonunda iki saat boyunca bir lobide beklerken sıkışmış Jermey Miller tarafından böğürtlen üzerinde bu makaleyi okuduktan sonra anladım . Uzun ama çift sevkıyat, ziyaretçi ve kompozit hakkında harika bir açıklama ve bunlarla neler yapabileceğinizi açıklıyor.
George Mauer


3
Ziyaretçi Deseni? Hangisi? Mesele şu: Bu tasarım modelinin etrafında çok fazla yanlış anlama ve saf karışıklık var. Yazdım
Richard Gomes

Birleşim veri türlerinde işlev nesnelerine sahip olmak istediğinizde ziyaretçi düzenine ihtiyacınız olacaktır. Fonksiyon nesnelerinin ve birleşim veri türlerinin ne olduğunu merak edebilirsiniz, o zaman ccs.neu.edu/home/matthias/htdc.html
Wei Qiu

Burada ve burada örnekler .
jaco0646

Yanıtlar:


315

Ziyaretçi desenine pek aşina değilim. Bakalım doğru anladım. Bir hayvan hiyerarşisine sahip olduğunuzu varsayalım

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

(İyi kurulmuş bir arayüze sahip karmaşık bir hiyerarşi olduğunu varsayalım.)

Şimdi hiyerarşiye yeni bir işlem eklemek istiyoruz, yani her hayvanın sesini çıkarmasını istiyoruz. Hiyerarşi bu kadar basit olduğunda, bunu düz polimorfizmle yapabilirsiniz:

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

Ancak bu şekilde ilerlerken, her işlem eklemek istediğinizde, arabirimi hiyerarşinin her bir sınıfına değiştirmeniz gerekir. Şimdi, bunun yerine orijinal arayüzden memnun olduğunuzu ve arayüzde mümkün olan en az değişikliği yapmak istediğinizi varsayalım.

Ziyaretçi kalıbı, her yeni işlemi uygun bir sınıfta taşımanıza olanak tanır ve hiyerarşinin arayüzünü yalnızca bir kez genişletmeniz gerekir. Haydi Yapalım şunu. İlk olarak, hiyerarşideki her sınıf için bir metoda sahip olan soyut bir işlemi ( GoF'deki "Ziyaretçi" sınıfı ) tanımlarız :

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

Ardından, yeni işlemleri kabul etmek için hiyerarşiyi değiştiriyoruz:

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

Son olarak, gerçek operasyonu, ne Cat ne de Köpek'i değiştirmeden uyguluyoruz :

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

Artık hiyerarşiyi değiştirmeden işlemler eklemenin bir yolu var. Nasıl çalışır:

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

19
S.Lott, bir ağacı yürümek aslında ziyaretçi kalıbı değil. (Bu kafa karıştırıcı olarak tamamen farklı olan "hiyerarşik ziyaretçi modeli" dir.) GoF Ziyaretçi modelini kalıtım veya arayüz uygulamasını kullanmadan göstermenin bir yolu yoktur.
Münih

14
@Knownasilya - Bu doğru değil. & -Operator, arayüz tarafından ihtiyaç duyulan Ses Nesnesinin adresini verir. letsDo(Operation *v) bir işaretçi gerekiyor.
AquilaRapax

3
sadece açıklık adına, bu ziyaretçi tasarım modeli örneği doğru mu?
godzilla

4
Bir çok düşünmeden sonra, neden burada iki yöntem çağırdığınızı merak ediyorum.Adog ve buradaIsACat, Köpek ve Kediyi yöntemlere zaten geçirmiş olmanıza rağmen. Basit bir performTask (Object * obj) tercih ediyorum ve bu nesneyi Operation sınıfında yayınladınız. (ve geçersiz
kılmayı

6
Sonundaki "ana" örneğinizde: theSound.hereIsACat(c)işi yapardınız, desenin getirdiği tüm ek yük için nasıl haklı çıkarsınız? çifte sevk , gerekçe.
franssu

131

Karışıklığınızın nedeni muhtemelen Ziyaretçinin ölümcül bir yanlış adlandırma olmasıdır. Birçok (önde gelen 1 !) Programcı bu sorun üzerinde tökezledi. Aslında yaptığı, onu doğal olarak desteklemeyen dillerde çift ​​dağıtım uygulamaktır (çoğu desteklemez).


1) En sevdiğim örnek, “En Etkili C ++” ın ünlü yazarı Scott Meyers, buna en önemli C ++ aha! anlar hiç .


3
+1 "desen yok" - mükemmel cevap. en çok oylanan cevap, birçok c ++ programcısının bir tür enum ve anahtar durumu (c yolu) kullanarak sanal işlevlerin "adhoc" polimorfizmi üzerindeki sınırlamalarını henüz gerçekleştirmediğini kanıtlamaktadır. Sanal kullanmak daha net ve görünmez olabilir, ancak yine de tek bir gönderim ile sınırlıdır. Benim düşünceme göre, bu c ++ 'ın en büyük kusuru.
user3125280

@ user3125280 Ziyaretçi deseninde 4/5 makale ve Tasarım Desenleri bölümünü okudum ve hiçbiri bu belirsiz kalıbı bir vaka stmt üzerinde veya diğerini üzerinde kullanabileceğinizi açıklamıyor. En azından getirmek için teşekkürler!
spinkus

4
bu - Ben bunu açıklıyorsun eminim @sam aynı avantajı o zaman olsun sınıflara / çalışma zamanı polimorfizmi gelen aşırı switch: switch(istemci tarafında (kod çoğaltılması) de karar alma sert kodları ve statik tür denetlemesi sunmuyor vakaların eksiksiz ve farklı olup olmadığını kontrol edin). Ziyaretçi kalıbı tür denetleyicisi tarafından doğrulanır ve genellikle istemci kodunu basitleştirir.
Konrad Rudolph

@KonradRudolph bunun için teşekkürler. Yine de, örneğin Desenler veya wikipedia makalesinde açıkça ele alınmamıştır. Sana katılmıyorum, ama bir vaka stmt'i kullanmanın faydaları olduğunu iddia edebilirsin, bu yüzden garip onun genel olarak zıt değil: 1. koleksiyonunuzun nesneleri üzerinde bir accept () yöntemine ihtiyacınız yoktur. 2. ~ ziyaretçisi bilinmeyen tipte nesneleri işleyebilir. Bu nedenle, vaka stmt, değiştirilebilir tipte koleksiyonlarla nesne yapıları üzerinde çalışmak için daha uygun görünmektedir. Desenler Ziyaretçi deseninin böyle bir senaryoya uygun olmadığını kabul eder (p333).
spinkus

1
@SamPinkus konrad'ın hedefi - bu yüzden virtualmodern programlama dillerinde özellikler çok kullanışlı - genişletilebilir programların temel yapı taşıdır - bence c yolu (tercih ettiğiniz dile bağlı olarak iç içe geçiş veya desen eşleşmesi vb.) Genişletilebilir olması gerekmeyen kodda çok daha temiz ve bu stili prover 9 gibi karmaşık yazılımlarda görmek hoş bir sürpriz oldu. Daha da önemlisi, genişletilebilirlik sağlamak isteyen herhangi bir dil muhtemelen yinelenen tek gönderimden daha iyi gönderim modellerini barındırmalıdır (yani ziyaretçi).
user3125280

84

Buradaki herkes doğrudur, ancak bence "ne zaman" konusunu ele almayı başaramaz. İlk olarak, Tasarım Desenlerinden:

Ziyaretçi, üzerinde çalıştığı öğelerin sınıflarını değiştirmeden yeni bir işlem tanımlamanızı sağlar.

Şimdi basit bir sınıf hiyerarşisini düşünelim. Sınıf 1, 2, 3 ve 4 ve A, B, C ve D yöntemleri var. Bunları bir e-tabloda olduğu gibi düzenleyin: sınıflar çizgiler ve yöntemler sütunlar.

Şimdi, Nesneye Dayalı tasarım, yeni sınıfları büyütme olasılığının yeni yöntemlerden daha fazla olduğunu varsayar, bu nedenle daha fazla satır eklemek daha kolaydır. Sadece yeni bir sınıf eklersiniz, o sınıfta neyin farklı olduğunu belirtir ve gerisini miras alırsınız.

Bununla birlikte, bazen, sınıflar nispeten statiktir, ancak sık sık daha fazla yöntem eklemeniz gerekir - sütun ekleme. Bir OO tasarımının standart yolu, bu tür yöntemleri tüm sınıflara eklemek olabilir, bu da maliyetli olabilir. Ziyaretçi kalıbı bunu kolaylaştırır.

Bu arada, Scala'nın deseninin eşleşmesi gereken problem budur.


Neden ziyaretçi desenini sadece bir utlity sınıfı üzerinde kullanayım? hizmet sınıfımı şöyle çağırabilirim: AnalyticsManger.visit (someObjectToVisit) vs AnalyticsVisitor.visit (someOjbectToVisit). Fark ne ? İkisi de endişe ayrılığı yapıyorlar değil mi? Umarım yardım edebilirsin.
j2emanue

@ j2emanue Çünkü Ziyaretçi kalıbı çalışma zamanında doğru Ziyaretçinin aşırı yüklenmesini kullanıyor. Doğru aşırı yüklemeyi çağırmak için kodunuzun döküm yapması gerekir.
Erişim Reddedildi

bununla bir verimlilik kazancı var mı? sanırım iyi bir fikir döküm önler
j2emanue

@ j2emanue, performans nedenlerine değil, açık / kapalı prensibine uygun kod yazmaktır. Bob amca kapalı açık bakın butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
Erişim Reddedildi

22

Ziyaretçi tasarım deseni dizin ağaçları, XML yapıları veya belge hatları gibi "özyinelemeli" yapılar için gerçekten iyi çalışıyor.

Bir Ziyaretçi nesnesi, özyinelemeli yapıdaki her düğümü ziyaret eder: her dizin, her XML etiketi, her neyse. Visitor nesnesi yapı içinde döngü yapmaz. Bunun yerine, yapının her bir düğümüne Ziyaretçi yöntemleri uygulanır.

İşte tipik bir özyinelemeli düğüm yapısı. Bir dizin veya XML etiketi olabilir. [Java kullanıcısıysanız, çocuk listesini oluşturmak ve korumak için birçok ek yöntem düşünün.]

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

visitYöntem yapıdaki her bir düğüme Ziyaretçi nesnesi uygular. Bu durumda, yukarıdan aşağıya bir ziyaretçi. visitAşağıdan yukarıya veya başka bir sipariş vermek için yöntemin yapısını değiştirebilirsiniz .

İşte ziyaretçiler için bir üst sınıf. visitYöntem tarafından kullanılır . Yapıdaki her düğüme "ulaşır". Yana visityöntemini çağırır upve down, ziyaretçi derinliği takip edebilirsiniz.

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

Bir alt sınıf, her düzeyde sayma düğümleri gibi şeyler yapabilir ve güzel bir yol hiyerarşik bölüm numaraları oluşturarak bir düğüm listesi biriktirebilir.

İşte bir uygulama. Bir ağaç yapısı oluşturur someTree. Bir Visitor, oluşturur dumpNodes.

Sonra dumpNodesağaca uygulanır . dumpNodeNesne ağacının her düğümü "ziyaret" olacak.

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

TreeNode visitalgoritması, her TreeNode öğesinin Ziyaretçinin arrivedAtyöntemine bağımsız değişken olarak kullanılmasını sağlar .


8
Diğerlerinin de belirttiği gibi, bu "hiyerarşik ziyaretçi modeli" dir.
PPC-Kodlayıcı

1
@ PPC-Coder 'Hiyerarşik ziyaretçi kalıbı' ile ziyaretçi kalıbı arasındaki fark nedir?
Tim Lovell-Smith

3
Hiyerarşik ziyaretçi modeli, klasik ziyaretçi modelinden daha esnektir. Örneğin, hiyerarşik desenle geçişin derinliğini izleyebilir ve hangi dalın birlikte geçiş yapacağına veya geçişi durduracağına karar verebilirsiniz. Klasik ziyaretçi bu konsepte sahip değildir ve tüm düğümleri ziyaret edecektir.
PPC-Kodlayıcı

18

Buna bakmanın bir yolu, ziyaretçi kalıbının, müşterilerinizin belirli bir sınıf hiyerarşisindeki tüm sınıflarınıza ek yöntemler eklemesine izin vermenin bir yoludur.

Oldukça kararlı bir sınıf hiyerarşisine sahip olduğunuzda yararlıdır, ancak bu hiyerarşide yapılması gerekenlerle ilgili değişen gereksinimleriniz vardır.

Klasik örnek derleyiciler ve benzerleri içindir. Soyut Bir Sözdizimi Ağacı (AST) programlama dilinin yapısını doğru bir şekilde tanımlayabilir, ancak projeniz ilerledikçe AST üzerinde yapmak isteyebileceğiniz işlemler değişecektir: kod üreticileri, güzel yazıcılar, hata ayıklayıcılar, karmaşıklık metrikleri analizi.

Ziyaretçi Deseni olmadan, bir geliştirici her yeni özellik eklemek istediğinde, bu yöntemi temel sınıftaki her özelliğe eklemeleri gerekir. Temel sınıflar ayrı bir kütüphanede göründüğünde veya ayrı bir ekip tarafından üretildiğinde bu özellikle zordur.

(Ziyaretçi kalıbının iyi OO uygulamalarıyla çeliştiğini duydum, çünkü verilerin işlemlerini verilerden uzaklaştırıyor. Ziyaretçi kalıbı, normal OO uygulamalarının başarısız olması durumunda faydalıdır.)


ben de aşağıdaki hakkında görüşlerinizi istiyorum: Neden sadece bir utlity sınıfı üzerinde ziyaretçi desen kullanmak istiyorum. hizmet sınıfımı şöyle çağırabilirim: AnalyticsManger.visit (someObjectToVisit) vs AnalyticsVisitor.visit (someOjbectToVisit). Fark ne ? İkisi de endişe ayrılığı yapıyorlar değil mi? Umarım yardım edebilirsin.
j2emanue

@ j2emanue: Soruyu anlamıyorum. Bunu etmenizi ve herkesin cevaplaması için tam bir soru olarak göndermenizi öneririm.
Tuhaf Düşünme

1
Buraya yeni bir soru gönderdim: stackoverflow.com/questions/52068876/…
j2emanue

14

Ziyaretçi Şablonunu kullanmak için en az üç çok iyi neden vardır:

  1. Veri yapıları değiştiğinde yalnızca biraz farklı olan kod çoğalmasını azaltın.

  2. Hesaplamayı uygulayan kodu değiştirmeden aynı hesaplamayı birkaç veri yapısına uygulayın.

  3. Eski kodu değiştirmeden eski kitaplıklara bilgi ekleyin.

Lütfen bunun hakkında yazdığım bir makaleye göz atın .


1
Makalenizi, ziyaretçi için gördüğüm en büyük kullanımla yorumladım. Düşünceler?
George Mauer

13

Konrad Rudolph'un daha önce belirttiği gibi, çift ​​sevkıyata ihtiyaç duyduğumuz durumlar için uygundur

Burada, çifte gönderime ihtiyacımız olan ve ziyaretçinin bize bu konuda nasıl yardımcı olduğu gösterilecek bir örnek var.

Misal :

Diyelim ki 3 tür mobil cihazım var - iPhone, Android, Windows Mobile.

Bu üç cihazın hepsinde de bir Bluetooth radyo vardır.

Mavi diş telsizinin 2 ayrı OEM'den (Intel ve Broadcom) olabileceğini varsayalım.

Sadece tartışmamızla ilgili örneği yapmak için, Intel radyosunun maruz bıraktığı API'ların Broadcom radyosunun maruz kaldıklarından farklı olduğunu varsayalım.

Sınıflarım şöyle görünüyor -

resim açıklamasını buraya girin resim açıklamasını buraya girin

Şimdi, bir işlem tanıtmak istiyorum - Mobil cihazda Bluetooth'u Açma.

İşlev imzası böyle bir şeyi sevmelidir -

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

Yani bağlı cihazın sağ tip ve Bluetooth radyosunun sağ türüne bağlı olarak , bu tarafından açılabilir uygun adımları veya algoritması çağıran .

Prensip olarak, 3 x 2 matris olur, nerede-dahil ilgili nesnelerin doğru türüne bağlı olarak doğru işlemi vektör etmeye çalışıyorum.

Her iki argümanın türüne bağlı olarak polimorfik bir davranış.

resim açıklamasını buraya girin

Şimdi, Ziyaretçi kalıbı bu soruna uygulanabilir. İlham Wikipedia sayfasından geliyor - “Özünde ziyaretçi, sınıfları değiştirmeden sınıf ailesine yeni sanal işlevler eklemesine izin veriyor; bunun yerine, sanal işlevin tüm uygun uzmanlıklarını uygulayan bir ziyaretçi sınıfı oluşturulur. Ziyaretçi örnek referansını girdi olarak alır ve hedefi çift gönderim yoluyla uygular. ”

3x2 matrisi nedeniyle burada çift gönderim bir zorunluluktur

Kurulum şöyle görünecek - resim açıklamasını buraya girin

Başka bir soruyu cevaplamak için örnek yazdım, kod ve açıklaması burada belirtildi .


9

Aşağıdaki bağlantılarda daha kolay buldum:

Gelen http://www.remondo.net/visitor-pattern-example-csharp/ Bir örnek bulduğunu gösterir ziyaretçi desen yararı nedir gösterileri dair bir sahte bir örnek. Burada aşağıdakiler için farklı kap sınıflarınız var 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; }
    }
}

Yukarıda gördüğünüz gibi, sen BilsterPackPills 'çiftleri içerir, bu yüzden çift sayısını 2 ile çarpmanız gerekir. Ayrıca , farklı veri tipi ve kullanılması gereken Bottlekullanımı fark edebilirsiniz unit.

Yani ana yöntemde hap sayısını aşağıdaki kodu kullanarak hesaplayabilirsiniz:

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

Yukarıdaki kodun ihlal edildiğine dikkat edin Single Responsibility Principle. Bu, yeni bir konteyner türü eklerseniz ana yöntem kodunu değiştirmeniz gerektiği anlamına gelir. Ayrıca geçişi daha uzun yapmak kötü bir uygulamadır.

Yani aşağıdaki kodu girerek:

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
}

Sayıyı Pills sayma sorumluluğunu denilen sınıfa taşıdınız PillCountVisitor(Ve anahtar durum bildirimini kaldırdık). Bu, yeni tip hap kabı eklemeniz gerektiğinde sadece PillCountVisitorsınıfı değiştirmeniz gerektiği anlamına gelir . Ayrıca uyarı IVisitorarayüzü başka senaryolarda kullanmak için geneldir.

Hap kabı sınıfına Accept yöntemi ekleyerek:

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

    #region IAcceptor Members

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

    #endregion
}

ziyaretçinin hap kabı derslerini ziyaret etmesine izin veriyoruz.

Sonunda hap sayısını aşağıdaki kodu kullanarak hesaplıyoruz:

var visitor = new PillCountVisitor();

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

Bunun anlamı: Her hap kabı, PillCountVisitorziyaretçinin haplarının sayılmasını görmesini sağlar. Haplarını nasıl sayacağını biliyor.

At visitor.Counthapları değeri vardır.

Gelen http://butunclebob.com/ArticleS.UncleBob.IuseVisitor Eğer kullanamazsınız hangi gerçek senaryo bkz polimorfizm tek sorumluluk prensibi takip etmek (cevap). Aslında:

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

reportQtdHoursAndPayyöntem raporlama ve temsiline ve bu Tek Sorumluluk Prensibi ihlal etmektedir. Bu nedenle, sorunun üstesinden gelmek için ziyaretçi kalıbını kullanmak daha iyidir.


2
Merhaba Sayed, en aydınlatıcı bulduğunuz parçaları eklemek için cevabınızı düzenleyebilir misiniz? SO genellikle bir bilgi veritabanı olmak ve bağlantılar aşağı gitmek olduğundan sadece bağlantı cevapları cesaret kırıcı.
George Mauer

8

Ç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 Piecegibi 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ı:

[model sınıfı diyagramı

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 Piecearayüzde sağlamak bile istemiyoruz .

- Bot geliştiricileri için zorlu satranç oyunlarında genellikle uygulama standart bir API ( Piecearayü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ı:

resim açıklamasını buraya girin

Üst kısımda ziyaretçiler, alt kısımda model derslerimiz var.

İşte PieceMovingVisitorarayü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: Piecealı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ı Piecesı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, Piecebir PieceMovingVisitornesne 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 Bishopbunu 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, Piecearayüz bunu yapmak için bir yol sağlamalıdır:

void setCoordinates(Coordinates coordinates);

PieceKoordinat değişikliklerinin sorumluluğu artık Piecealt sınıflardan başka sınıflara da açıktır .
Ziyaretçi tarafından yapılan işlemlerin Piecealt 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 instanceofveya 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 Pieceve 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)

6

Cay Horstmann, OO Tasarım ve desen kitabında Ziyaretçiyi nereye uygulayacağına dair harika bir örneğe sahiptir . Sorunu özetler:

Bileşik nesneler genellikle tek tek unsurlardan oluşan karmaşık bir yapıya sahiptir. Bazı öğelerin yine alt öğeleri olabilir. ... Bir eleman üzerindeki bir işlem, alt elemanlarını ziyaret eder, bunları onlara uygular ve sonuçları birleştirir. ... Ancak böyle bir tasarıma yeni işlemler eklemek kolay değil.

Kolay olmamalarının nedeni, yapı sınıflarının içine işlemlerin eklenmiş olmasıdır. Örneğin, bir Dosya Sisteminiz olduğunu düşünün:

FileSystem sınıf diyagramı

Bu yapı ile uygulamak isteyebileceğimiz bazı işlemler (işlevler):

  • Düğüm öğelerinin adlarını görüntüleme (dosya listesi)
  • Hesaplanan düğüm öğelerinin boyutunu görüntüler (bir dizinin boyutunun tüm alt öğelerinin boyutunu içerir)
  • vb.

İşlemleri uygulamak için FileSystem'daki her sınıfa işlevler ekleyebilirsiniz (ve insanlar bunu nasıl yapacağını çok açık olduğu için geçmişte yapmışlardır). Sorun, yeni bir işlev eklediğinizde (yukarıdaki "vb." Satırı), yapı sınıflarına gittikçe daha fazla yöntem eklemeniz gerekebilmesidir. Bir noktada, yazılımınıza eklediğiniz birkaç işlemden sonra, bu sınıflardaki yöntemler artık sınıfların işlevsel bütünlüğü açısından bir anlam ifade etmiyor. Örneğin , dosya sistemine en son görselleştirme işlevini uygulamak için FileNodebir yöntemi calculateFileColorForFunctionABC()olan bir yönteminiz var .

Ziyaretçi Deseni (birçok tasarım deseni gibi), kodlarının her yerde çok fazla değişiklik gerektirmeden ve aynı zamanda iyi tasarım ilkelerine (yüksek uyum, düşük kuplaj) saygı duymadan değişmesine izin vermenin daha iyi bir yolu olduğunu bilen geliştiricilerin acılarından ve acılarından doğmuştur. ). O acıyı hissedene kadar birçok desenin yararlılığını anlamak zor. Acıyı açıklamak (eklediğimiz "vb." İşlevselliklerle yukarıda yapmaya çalıştığımız gibi) açıklamada yer kaplar ve dikkat dağıtıcıdır. Bu nedenle kalıpları anlamak zordur.

Ziyaretçi, veri yapısı (örn. FileSystemNodes) Üzerindeki işlevsellikleri veri yapılarının kendisinden ayırmamıza izin verir . Desen, tasarımın kohezyona saygı duymasına izin verir - veri yapısı sınıfları daha basittir (daha az yöntemi vardır) ve ayrıca işlevler Visitoruygulamalara yerleştirilir. Bu, çift ​​gönderme (desenin karmaşık kısmıdır) ile yapılır: accept()Yapı sınıflarındaki visitX()yöntemleri ve Ziyaretçi (işlevsellik) sınıflarındaki yöntemleri kullanarak :

Ziyaretçi uygulanmışken FileSystem sınıf diyagramı

Bu yapı, somut ziyaretçiler olarak yapı üzerinde çalışan yeni işlevler eklememize izin verir (yapı sınıflarını değiştirmeden).

Ziyaretçi uygulanmışken FileSystem sınıf diyagramı

Örneğin PrintNameVisitor, dizin listeleme işlevini PrintSizeVisitoruygulayan a ve sürümü boyutla uygulayan a . Biz XML veri veya JSON bunu üreten başka ziyaretçinin vb Hatta görüntüler bir kullanarak dizin ağacı bir ziyaretçinin olabilir üreten bir 'ExportXMLVisitor` sahip bir gün düşünebiliriz böyle DOT gibi grafiksel dil , görsel edilecek başka bir program ile.

Son bir not olarak: Ziyaretçinin çift gönderimi ile karmaşıklığı, anlaşılması, kodlanması ve hata ayıklaması daha zor olduğu anlamına gelir. Kısacası, yüksek bir geek faktörüne sahiptir ve KISS ilkesine yöneliktir. Araştırmacılar tarafından yapılan bir ankette, Ziyaretçinin tartışmalı bir desen olduğu gösterildi (yararlılığı konusunda bir fikir birliği yoktu). Hatta bazı deneyler kodun bakımını kolaylaştırmadığını bile gösterdi.


Sanırım dizin yapısı iyi bir bileşik kalıp ama son paragrafınızla aynı fikirde.
zar

5

Kanımca, yeni bir işlem eklemek için yapılan iş miktarı Visitor Pattern, her bir eleman yapısının kullanımı veya doğrudan modifikasyonu ile aşağı yukarı aynıdır . Ayrıca, yeni eleman sınıfı ekleyecek olsaydım Cow, Operasyon arayüzü etkilenecek ve bu, mevcut tüm eleman sınıfına yayılacak ve bu nedenle tüm eleman sınıflarının yeniden derlenmesini gerektirecektir. Peki amaç ne?


4
Ziyaretçiyi neredeyse her kullandığımda, bir nesne hiyerarşisinde geçiş yapmakla çalışırken. İç içe bir ağaç menüsü düşünün. Tüm düğümleri daraltmak istiyorsunuz. Ziyaretçiyi uygulamıyorsanız, grafik geçiş kodu yazmanız gerekir. Veya ziyaretçi ile: rootElement.visit (node) -> node.collapse(). Ziyaretçi ile, her düğüm tüm çocukları için grafik geçişini uygular, böylece işiniz bitti.
George Mauer

@GeorgeMauer, çift sevkıyat kavramı benim için motivasyonu temizledi: ya tipe bağlı mantık tipiyle ya da acı dünyasıyla. Geçiş mantığını dağıtma fikri hala bana duraklama veriyor. Daha verimli mi? Daha sürdürülebilir mi? Gerekirse "N seviyesine katla" eklenirse ne olur?
nik.shornikov

@ nik.shornikov verimliliği gerçekten burada bir endişe olmamalı. Hemen hemen her dilde, birkaç işlev çağrısı ihmal edilebilir ek yüktür. Bunun ötesinde her şey mikro optimizasyon. Daha sürdürülebilir mi? Buna bağlı. Bence çoğu zaman, bazen değil. "Kat N seviyesine" gelince. levelsRemainingParametre olarak sayaçta kolay geçiş . Çocukları bir sonraki seviyeye çağırmadan önce azaltın. Ziyaretçinizin içi if(levelsRemaining == 0) return.
George Mauer

1
@ GeorgeMauer, verimliliğin küçük bir endişe olduğu konusunda tamamen anlaştı. Ancak, sürdürülebilirlik, örneğin kabul imzasının geçersiz kılınması, tam olarak kararın kayda değer olduğunu düşünüyorum.
nik.shornikov

5

Aspect Object programlamasına aynı yeraltı uygulaması ile Ziyaretçi Kalıbı ..

Örneğin, üzerinde çalıştığı öğelerin sınıflarını değiştirmeden yeni bir işlem tanımlarsanız


Nesne Programlama bahsetmek için yukarı
milesma

5

Ziyaretçi deseninin kısa açıklaması. Değişiklik gerektiren sınıfların tümü 'kabul et' yöntemini uygulamalıdır. İstemciler, bu sınıflandırma ailesi üzerinde bazı yeni eylemler gerçekleştirmek ve böylece işlevlerini genişletmek için bu kabul etme yöntemini kullanırlar. Müşteriler bu kabul yöntemini, her bir işlem için farklı bir ziyaretçi sınıfından geçerek çok çeşitli yeni işlemler gerçekleştirmek için kullanabilirler. Bir ziyaretçi sınıfı, ailenin her sınıfı için aynı özel eylemin nasıl gerçekleştirileceğini tanımlayan birden fazla geçersiz kılınmış ziyaret yöntemi içerir. Bu ziyaret yöntemleri üzerinde çalışılacak bir örnek geçirilir.

Ne zaman kullanmayı düşünebilirsiniz?

  1. Bir sınıf aileniz olduğunda, hepsine birçok yeni eylem eklemeniz gerektiğini bilirsiniz, ancak bir nedenden dolayı gelecekte sınıf ailesini değiştiremezsiniz veya derleyemezsiniz.
  2. Yeni bir eylem eklemek ve bu yeni eylemin birden çok sınıfa yayılmak yerine bir ziyaretçi sınıfında tamamen tanımlanmasını istediğinizde.
  3. Patronunuz şu anda bir şeyler yapmak zorunda olan bir dizi sınıf üretmeniz gerektiğini söylediğinde ... ... ama kimse aslında bir şeyin ne olduğunu tam olarak bilmiyor.

4

Bob amca makalesiyle karşılaşana ve yorumları okuyana kadar bu modeli anlamadım . Aşağıdaki kodu göz önünde bulundurun:

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

Tek Sorumluluğu onayladığı için iyi görünse de Açık / Kapalı prensibini ihlal eder . Her yeni Çalışan türüne sahip olduğunuzda, tip kontrolü ile birlikte eklemeniz gerekecektir. Ve eğer yapmayacaksan bunu derleme zamanında asla bilemezsin.

Ziyaretçi kalıbı ile açık / kapalı prensibini ihlal etmediği ve Tek sorumluluğu ihlal etmediği için kodunuzu daha temiz hale getirebilirsiniz. Ve ziyaret uygulamayı unutursanız, derleme olmaz:

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

Sihir, v.Visit(this)aynı görünüyor olsa da, farklı ziyaretçi aşırı yükleri çağırdığı için aslında farklıdır.


Evet, özellikle düz listeler değil, düz listeler bir ağaç için özel bir durum olacaktır). Belirttiğiniz gibi, sadece listelerde çok dağınık değil, ancak düğümler arasındaki gezinme daha karmaşık hale geldiğinden ziyaretçi kurtarıcı olabilir
George Mauer

3

@Federico A. Ramponi'nin mükemmel cevabına dayanmaktadır.

Sadece bu hiyerarşiye sahip olduğunuzu hayal edin:

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

Buraya bir "Yürüme" yöntemi eklemeniz gerekirse ne olur? Bu, tüm tasarım için acı verici olacaktır.

Aynı zamanda, "Yürüme" yöntemini eklemek yeni sorular oluşturur. "Yemek" veya "Uyku" ne olacak? Eklemek istediğimiz her yeni eylem veya işlem için Hayvan hiyerarşisine gerçekten yeni bir yöntem eklemeli miyiz? Bu çirkin ve en önemlisi, Hayvan arayüzünü asla kapatamayacağız. Ziyaretçi kalıbıyla, hiyerarşiyi değiştirmeden hiyerarşiye yeni bir yöntem ekleyebiliriz!

Yani, sadece bu C # örneğini kontrol edin ve çalıştırın:

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

yürümek, yemek uygun örnekler beri onlar hem konum ortak değiliz Dogsıra sıra Cat. Onları temel sınıfta yapabilirsiniz, böylece miras alınabilirler veya uygun bir örnek seçebilirler.
Abhinav Gauniyal

sesler farklı tho, iyi örnek, ama ziyaretçi desen ile ilgili bir şey olup olmadığından emin değil
DAG

3

Ziyaretçi

Ziyaretçi, sınıfları değiştirmeden sınıf ailesine yeni sanal işlevler eklemesine izin verir; bunun yerine, sanal işlevin tüm uygun uzmanlıklarını uygulayan bir ziyaretçi sınıfı oluşturulur

Ziyaretçi yapısı:

resim açıklamasını buraya girin

Aşağıdaki durumlarda Ziyaretçi kalıbını kullanın:

  1. Bir yapıda gruplandırılmış farklı tipteki nesneler üzerinde benzer işlemler yapılmalıdır .
  2. Birçok farklı ve ilgisiz işlem yürütmeniz gerekir. Operasyonu nesnelerden ayırır Yapısı
  3. Nesne yapısında değişiklik olmadan yeni işlemler eklenmelidir
  4. İlgili işlemleri sınıfları değiştirmeye veya türetmeye zorlamak yerine tek bir sınıfta toplayın
  5. Kaynağına sahip olmadığınız veya kaynağı değiştiremediğiniz sınıf kitaplıklarına işlev ekleme

Ziyaretçi kalıbı, Object'teki mevcut kodu değiştirmeden yeni işlem ekleme esnekliği sağlasa da , bu esnekliğin bir dezavantajı vardır.

Yeni bir Visitable nesnesi eklendiyse, Visitor & ConcreteVisitor sınıflarında kod değişiklikleri gerektirir . Bu sorunu çözmek için bir geçici çözüm vardır: Performansı etkileyecek yansıma kullanın.

Kod snippet'i:

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

Açıklama:

  1. Visitable( Element) bir arabirimdir ve bu arabirim yönteminin bir sınıf grubuna eklenmesi gerekir.
  2. Visitor, Visitableöğeler üzerinde işlem gerçekleştirme yöntemlerini içeren bir arabirimdir .
  3. GameVisitorVisitorinterface ( ConcreteVisitor) öğesini uygulayan bir sınıftır .
  4. Her Visitableöğe Visitorilgili bir Visitorarabirim yöntemini kabul eder ve çağırır .
  5. Sen davranabilirsiniz Gameolarak Elementve somut oyunlar gibi Chess,Checkers and Ludoolarak ConcreteElements.

Yukarıdaki örnekte Chess, Checkers and Ludoüç farklı oyun (ve Visitablesınıf) bulunmaktadır. İyi bir günde, her oyunun istatistiklerini kaydetmek için bir senaryo ile karşılaştım. Dolayısıyla, istatistik işlevselliğini uygulamak için bireysel sınıfı değiştirmeden, bu sorumluluğu GameVisitorsınıfta merkezileştirebilirsiniz , bu da her oyunun yapısını değiştirmeden sizin için hile yapar.

çıktı:

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

Bakın

oodesign makalesi

kaynak yapımı makalesi

daha fazla ayrıntı için

Dekoratör

desen, aynı sınıftaki diğer nesnelerin davranışlarını etkilemeden, davranışın statik veya dinamik olarak tek bir nesneye eklenmesine izin verir

İlgili Mesajlar:

ES için Dekoratör Kalıbı

Dekoratör Kalıbı Ne Zaman Kullanılmalı?


2

Http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html adresindeki açıklama ve örneği gerçekten beğendim .

Varsayım, düzeltilmiş bir birincil sınıf hiyerarşisine sahip olmanızdır; belki de başka bir satıcıdan ve bu hiyerarşide değişiklik yapamazsınız. Ancak, amacınız bu hiyerarşiye yeni polimorfik yöntemler eklemek istemenizdir, bu da normalde temel sınıf arayüzüne bir şeyler eklemeniz gerektiği anlamına gelir. İkilem, temel sınıfa yöntemler eklemeniz gerektiğidir, ancak temel sınıfa dokunamazsınız. Bunun üstesinden nasıl gelirsiniz?

Bu tür bir sorunu çözen tasarım desenine “ziyaretçi” (Tasarım Desenleri kitabında sonuncusu) denir ve son bölümde gösterilen çift gönderim şemasına dayanır.

Ziyaretçi kalıbı, birincil tür üzerinde gerçekleştirilen işlemleri sanallaştırmak için Ziyaretçi türünde ayrı bir sınıf hiyerarşisi oluşturarak birincil türün arayüzünü genişletmenize olanak tanır. Birincil türdeki nesneler ziyaretçiyi basitçe kabul eder, ardından ziyaretçinin dinamik bağlı üye işlevini çağırır.


Teknik olarak Ziyaretçi kalıbı olsa da, bu aslında örneklerinden sadece temel çift dağıtımdır. Yararlılığının sadece bununla özellikle görülmediğini iddia ediyorum.
George Mauer

1

Nasıl ve ne zaman anladım, nedenini hiç anlamadım. C ++ gibi bir dilde geçmişi olan herkese yardımcı olması durumunda, bunu çok dikkatli okumak istersiniz .

Tembel için ziyaretçi desenini kullanıyoruz çünkü "sanal fonksiyonlar C ++ 'da dinamik olarak gönderilirken fonksiyon aşırı yüklenmesi statik olarak yapılır" .

Veya başka bir deyişle, aslında bir ApolloSpacecraft nesnesine bağlı bir SpaceShip referansını ilettiğinizde CollideWith'in (ApolloSpacecraft &) çağrıldığından emin olun.

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
Ziyaretçi deseninde dinamik gönderilerin kullanılması beni tamamen şaşırtıyor. Desenin önerilen kullanımları derleme zamanında yapılabilecek dallanmayı tanımlar. Bu durumlar bir fonksiyon şablonuyla daha iyi görünecektir.
Praxeolitic

0

@Federico A. Ramponi'nin muhteşem açıklaması için teşekkürler , bunu java sürümünde yaptım . Umarım faydalı olabilir.

Ayrıca @Konrad Rudolph'un işaret ettiği gibi , aslında çalışma zamanı yöntemlerini belirlemek için iki somut örneği birlikte kullanan bir çift ​​gönderimdir .

Bu nedenle , işletim arabirimini doğru tanımladığımız sürece işlem yürütücüsü için ortak bir arabirim oluşturmaya gerek yoktur .

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

Beklediğiniz gibi, ortak bir arayüz bize daha fazla netlik getirecektir, ancak aslında bu modelin temel parçası değildir .

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

sorunuz ne zaman bilinecek:

Ben ziyaretçi desenli ilk kod yok. i kod standart ve yeniden oluşması ihtiyacını bekleyin ve sonra refactor. diyelim ki bir kerede bir tane yüklediğiniz birden çok ödeme sisteminiz var. Ödeme sırasında birçok if koşulunuz (veya exampleOf) olabilir, örneğin:

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

Şimdi 10 ödeme yöntemim olduğunu hayal edin, çirkinleşiyor. Böylece, ziyaretçinin bu tür desenini gördüğünüzde, tüm bunları ayırmak için elverişli bir şekilde gelir ve daha sonra böyle bir şey çağırırsınız:

new PaymentCheckoutVistor(paymentType).visit()

Sadece örnek olarak göstererek burada nasıl uygulanacağını görebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.