Ziyaretçi Desenini Anlama


16

GUI denetimlerini temsil eden bir sınıflar hiyerarşisi var. Bunun gibi bir şey:

Control->ContainerControl->Form

Çeşitli şeyler yapan nesnelerle çalışan bir dizi algoritma uygulamak zorundayım ve Ziyaretçi deseninin en temiz çözüm olacağını düşünüyorum. Örneğin, bir nesne hiyerarşisinin Xml temsilini oluşturan bir algoritmayı ele alalım. 'Klasik' yaklaşımını kullanarak şunu yaparım:

public abstract class Control
{
    public virtual XmlElement ToXML(XmlDocument document)
    {
        XmlElement xml = document.CreateElement(this.GetType().Name);
        // Create element, fill it with attributes declared with control
        return xml;
    }
}

public abstract class ContainerControl : Control
{
    public override XmlElement ToXML(XmlDocument document)
    {
        XmlElement xml = base.ToXML(document);
        // Use forech to fill XmlElement with child XmlElements
        return xml;
    }
}

public class Form : ContainerControl
{
    public override XmlElement ToXML(XmlDocument document)
    {
        XmlElement xml = base.ToXML(document);
        // Fill remaining elements declared in Form class
        return xml;
    }
}

Ancak bunu ziyaretçi kalıbıyla nasıl yapacağımdan emin değilim. Temel uygulama budur:

public class ToXmlVisitor : IVisitor
{
    public void Visit(Form form)
    {
    }
}

Soyut sınıflar bile uygulamaya yardımcı olduğundan, ToXmlVisitor'da bunu nasıl doğru bir şekilde yapacağımdan emin değilim?

Ziyaretçi desen dikkate almamın nedeni, bazı algoritmaların sınıfların uygulandığı projede mevcut olmayan referanslara ihtiyaç duyması ve bu nedenle büyük sınıflardan kaçınmak için bir dizi farklı algoritma olmasıdır.


sorun nedir?
gnat

Temelde bir ziyaretçi kalıbı kullanarak ToXml () yöntemini yeniden yazma.
Nezreli


Bağlantı için teşekkürler. Dinamik dağıtım, geleneksel ziyaretçi modelini basitleştirir, ancak çok fazla değişmez.
Nezreli

@Nezreli Evet öyle. Ziyaret ettiğiniz Windows Forms denetimleri gibi Ziyaretçi desenini desteklemeyen sınıflarla çalışır.
Kris Vandermotten

Yanıtlar:


17

Ziyaretçi kalıbı, programlama dillerinde yalnızca tek bir bağlanmayı destekleyen çift bağlamayı simüle eden bir mekanizmadır. Ne yazık ki, bu ifade bazı şeyleri netleştirmeyebilir, bu yüzden basit bir örnekle açıklayayım.

Kullandığınız platform olan .NET ve C # 'da, nesneler ToString()işlev kullanılarak dizelere dönüştürülebilir . Bu işlevin yaptığı, yani yürütülen kod, uyguladığınız nesnenin türüne bağlıdır (sanal bir yöntemdir). Hangi kodun yürütüldüğü bir şeye, nesnenin bir türüne bağlıdır, bu nedenle kullanılan mekanizmaya tek bağlama denir.

Ancak, her bir nesne türü için bir nesneyi bir dizeye dönüştürmek için birden fazla yolum olmasını istersem ne olur? Nesneleri dizelere dönüştürmenin iki yoluna sahip olmak istersem, yürütülmekte olan kod iki şeye bağlıdır: sadece dönüştürülecek nesne değil, aynı zamanda dönüştürülmesini istediğimiz yol?

Çift bağlanma olsaydı bu iyi bir şekilde çözülebilirdi. Ancak C # dahil olmak üzere çoğu OO dili yalnızca tek bir bağlamayı destekler.

Ziyaretçi kalıbı, sorunu çift bağlamayı iki tekli bağlamaya dönüştürerek çözmektedir.

Yukarıdaki örneğimizde, dönüştürme algoritmasını uygulayan nesnede ikinci bir sanal yöntemi çağıran dönüştürmek için nesnede sanal bir yöntem kullanır.

Ancak bu, algoritmayı uygulamak istediğiniz nesnenin bununla işbirliği yapması gerektiği anlamına gelir: pişmiş ziyaretçi kalıbını desteklemesi gerekir.

Ziyaretçi kalıbı için desteği olmayan .NET'in Windows Forms sınıflarını kullanıyor gibi görünüyorsunuz. Daha spesifik olarak, public virtual void Accept(IVisitor)belli ki sahip olmadıkları bir yönteme sahip olmaları gerekir.

Peki, alternatif nedir? .NET, sadece tekli bağlamayı desteklemekle kalmaz, aynı zamanda çiftli bağlamaya göre çok daha güvenli olan dinamik bağlamayı da destekler.

Sorununuzu çözmenize izin verecek olan bu tekniği nasıl uygulayacağınız hakkında daha fazla bilgi için (iyi anlarsam) Farewell Visitor'ye bir göz atın .

GÜNCELLEME:

Tekniği özel probleminize uygulamak için önce uzatma yönteminizi tanımlayın:

public static XmlDocument ToXml(this Control control)
{
    XmlDocument xml = new XmlDocument();
    XmlElement root = xml.CreateElement(control.GetType().Name);
    xml.AppendChild(root);

    Visit(control, xml, root);

    return xml;
}

Dinamik dağıtıcıyı oluşturun:

private static void Visit(Control control, XmlDocument xml, XmlElement root)
{
    dynamic dynamicControl = control; //notice the 'dynamic' type.
                                      //this is the key to dynamic dispatch

    VisitCore(dynamicControl, xml, root);
}

Ardından belirli yöntemleri doldurun:

private static void VisitCore(Control control, XmlDocument xml, XmlElement root)
{
    // TODO: specific Control handling
}

private static void VisitCore(ContainerControl control, XmlDocument xml, XmlElement root)
{
    // call the "base" method
    VisitCore(control as Control, xml, root);

    // TODO: specific ContainerControl handling
    // for example:
    foreach (Control child in control.Controls)
    {
        XmlElement element = xml.CreateElement(child.GetType().Name);
        root.AppendChild(element);

        // call the dynamic dispatcher method
        Visit(child, xml, element);
    }
}

private static void VisitCore(Form control, XmlDocument xml, XmlElement root)
{
    // call the "base" method
    VisitCore(control as ContainerControl, xml, root);

    // TODO: specific Form handling
}

.NET dinamik dağıtım gerçekten oldukça güçlü .. Ancak ben biraz olabilir ... iyi .. yavaş, ama bir kod birden fazla sınıf ve bir ziyaretçi ile arayüzleri sevral satırları alır tek bir satır yapar
Newtopian

Yine de, dinamik dağıtım sorunumu çözmeyecek çünkü ToXml algoritmam kalıtım zincirini oluşturan tüm türleri 'ziyaret etmemi' gerektiriyor. Örneğimde, başarılı bir XML dönüşümüne sahip olmak için bu sırayla Control, ContainerterControl ve Form'u ziyaret etmesi gerekiyor.
Nezreli

@Nezreli Sorununuzu çözebilir, nasıl olduğunu göstermek için cevabımı güncelledim.
Kris Vandermotten

Dinamik değişken tanımına yorum ekleme iznini aldım. Kodun farkına varmadan önce bana iki kez okundu ve tüm hikayenin anahtarı bu.
Cristi Diaconescu
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.