13 Temmuz 2020 Pazartesi

GoF - Visitor Örüntüsü

Giriş
Bu örüntü, Tell Don't Ask prensibine daha uygun. Bu prensip aynı zamanda bazı programlama dillerindeki Multiple dispatch eksikliğini de kapatıyor. Konuyu anlatan bir yazı burada.

Klasik Visitor Örüntüsü
İki kısımdan oluşur
1. Visited Class : Kendini visitor'a teslim eden sınıf
2. Visitor Class : Hesaplamaların yapıldığı sınıf

1. Ziyaret Edilen Sınıf - Visited/Visitable Class
Bu sınıf sadece basit bir Accept() metodu tanımlar.

Örnek
Şöyle yaparız
@Override
public void accept(DisplayVisitor v) {
  v.visit(this); //Kendini visitor'a teslim et
}
Tabii bu accept metodu virtual olduğu için bize bir sınıf hiyerarşisi gerekiyor. Şöyle yaparız. Ziyaret edilen sınıflar ortak bir arayüzden türerler (R).
              R                <-- interface
            /   \
           /     \
          /       \
        BR         RR          <-- abstract classes
      / | \       / | \
     /  |  \     /  |  \
  BRA BRB BRC  RRA RRB RRC     <-- classes
2. Visitor Sınıfı/Arayüzü - Visitor Class
Visitor arayüzü, ziyaret edilen tüm tipler için abstract metodlar içerir. Daha sonra bu arayüzü gerçekleştiren bir Visitor sınıf kodlanır.
              V                <-- interface
              | 
              VA               <-- class
2.1 Visitor Sınıfında Overload Metod İsimleri
Visitor sınıflarda metodların isimlerini overload etmek gerekiyor. Bu tür kodlar pek okunaklı olmuyor.
Örnek
Burada metod isimleri overload ediliyor.
class IVisitor
{
public:

  virtual void visit (VisitableA& a ) = 0;
  virtual void visit (VisitableB& b ) = 0;

};
Örnek
Elimizde şöyle bir kod olsun. Burada metod isimleri overload edilmiyor. Ancak bu sefer de homojen yapıyı kaybediyoruz.
interface Animal {
  void accept(AnimalVisitor visitor);
}

class Dog implements Animal {
  void accept(AnimalVisitor visitor) {
    visitor.visitDog(this);
  }    
}

class Cat implements Animal {
  void accept(AnimalVisitor visitor) {
    visitor.visitCat(this);
  }    
}
Visitor mecburen şöyle oluyor.
interface AnimalVisitor {
  // These methods could be just called "visit" and rely on overloading,
  void visitDog(Dog dog);
  void visitCat(Cat cat);
}

class MakeSoundVisitor implements AnimalVisitor {
  void visitDog(Dog dog) {
    // In a real case you'd obviously do something with the dog object
    System.out.println("bark! bark bark!!");
  }

  void visitCat(Cat cat) {
    System.out.println("meow meeeoooww!!");
  }
}
Şöyle yaparız.
var makeSoundVisitor = new MakeSoundVisitor();
var cat = new Cat();
var dog = new Dog();

cat.accept(makeSoundVisitor);
dog.accept(makeSoundVisitor);
Diğer

Visitor ve Iterator + Filter + Walker
Bu yöntemi gömülü projede kullandım.
1. Iterator Kullanımı
Iterator veri yapısını kolayca dolaşmamızı sağlar, gerekirse visit edilen nesneyi silme imkanı verir. Mesela bir listeyi dolaşarak zamanı gelmiş nesneleri paketlemek ve bayatlamış olanları silmek için kullanılabilir.

2. Filtre Kullanımı
Eger visit kuralları karışık ise, visitor kuralları Filter nesnesine sorabilir. Filter'dan geçen nesneler işlenir.

3. Walker Kullanımı
Elimizde bir çok container varsa ve bunların hepsini dolaşmak istersek bir Walker kullanılır. Walker container'ları gerekirse farklı sırada dolaşma imkanı da tanır.
4. Visitor Arayüzü
Klasik kullanımda vistor sadece Accept () çağrısı yapar. Bu kullanımda ise, iterator'ün dolaşmaya devam edip etmeyeceği, visit edilen nesnenin silinip silinmeyeceği, visit işleminin başarılı olup olmadığı gibi birden çok parametre dönebilir.

C++'a Mahsus Visitor Örüntüsü
C++ ile bu örüntüyü kodlarken, IMyVisitor şeklinde bir arayüz yapmaya gerek yok. Basit bir functor yazılabilir. Visitor şöyle olabilir
template<typename T>
struct ElementPrinter {

  void operator()(const T& elem) {
    std::cout << elem << std::endl;
  }
};
Uğranan sınıf bir liste ise, her bir elemana functor std::_for_each() ile geçilir.
class MyClass {

  template<typename Functor>
  void visitInts(Functor f) {
    std::for_each(myInts.begin(), myInts.end(), f);
  }
};
Kullanım şekli aşağıdaki gibi olur.
MyClass m;
m.visitInts(ElementPrinter());

C# Visitor Örüntüsü
C#'ta dynamic kelimesi ile C++'taki gibi nesnenin gerçek tipi kullanılabilir. Böylece tüm ziyaret edilen nesnelerin bir arayüzden kalıtması gerekmez.

Örnek
Şöyle yaparız
var analyzer = new ConcreteAnalyzer();
dynamic res = analyzer.PerformAction(input);
ProcessResult(res); 

void ProcessResult(ConcreteAnalysisResult cr) {
...
}
void ProcessResult(SomeOtherConcreteAnalysisResult cr) {
...
}
void ProcessResult(AnalysisResult cr) {
...
}
Örnek
Şöyle yaparız.  Burada composite bir yapının nasıl visitor ile kullanılacağı gösteriliyor. Amaç composite'i XML'e çevirmek. Önce XML metodu yazılır.
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;
}
Visit metodu nesnenin gerçek sınıfını dynamic kelimesi ile alır.
private static void Visit(Control control, XmlDocument xml, XmlElement root)
{
    dynamic dynamicControl = control;
    VisitCore(dynamicControl, xml, root);
}
VisitCore metodu her sınıf tipi için özelleştirilir. İkinci VisitCore metodunda composite'in for döngüsü ile dolaşıldığı görülebilir.
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
}









Hiç yorum yok:

Yorum Gönder