Menggunakan pola pengunjung dengan hierarki objek besar

12

Konteks

Saya telah menggunakan dengan hierarki objek (pohon ekspresi) pola pengunjung "semu" (semu, karena di dalamnya tidak menggunakan pengiriman ganda):

 public interface MyInterface
 {
      void Accept(SomeClass operationClass);
 }

 public class MyImpl : MyInterface 
 {
      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }
 }

Desain ini, bagaimanapun dapat dipertanyakan, cukup nyaman karena jumlah implementasi MyInterface signifikan (~ 50 atau lebih) dan saya tidak perlu menambahkan operasi tambahan.

Setiap implementasi unik (ini ekspresi atau operator yang berbeda), dan beberapa komposit (yaitu, simpul operator yang akan berisi simpul operator / daun lainnya).

Traversal saat ini dilakukan dengan memanggil operasi Terima pada simpul akar pohon, yang pada gilirannya memanggil Terima pada masing-masing simpul anaknya, yang pada gilirannya ... dan seterusnya ...

Tetapi saatnya telah tiba di mana saya perlu menambahkan operasi baru , seperti pencetakan cantik:

 public class MyImpl : MyInterface 
 {
      // Property does not come from MyInterface
      public string SomeProperty { get; set; }

      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }

      public void Accept(SomePrettyPrinter printer)
      {
           printer.PrettyPrint(this.SomeProperty);
      }
 }    

Saya pada dasarnya melihat dua opsi:

  • Jaga desain yang sama, menambahkan metode baru untuk operasi saya ke setiap kelas turunan, dengan biaya pemeliharaan (bukan pilihan, IMHO)
  • Gunakan pola Pengunjung "benar", dengan biaya ekstensibilitas (bukan opsi, karena saya perkirakan akan ada lebih banyak implementasi yang datang di sepanjang jalan ...), dengan sekitar 50+ kelebihan metode Kunjungan, masing-masing cocok dengan implementasi tertentu ?

Pertanyaan

Apakah Anda akan merekomendasikan menggunakan pola Pengunjung? Apakah ada pola lain yang bisa membantu menyelesaikan masalah ini?

T. Fabre
sumber
1
Mungkin rantai dekorator akan lebih tepat?
MattDavey
beberapa pertanyaan: bagaimana perbedaan implementasi ini? apa struktur hierarki? dan apakah strukturnya selalu sama? apakah Anda selalu perlu melintasi struktur dalam urutan yang sama?
jk.
@MattDavey: jadi Anda akan merekomendasikan memiliki satu dekorator per implementasi dan operasi?
T. Fabre
2
@ T.Fabre sulit dikatakan. Ada lebih dari 50 implementator MyInterface.. apakah semua kelas memiliki implementasi unik DoSomethingdan DoSomethingElse? Saya tidak melihat di mana kelas pengunjung Anda benar-benar melintasi hierarki - sepertinya lebih seperti facadesaat ini ..
MattDavey
juga apa versi C # itu. apakah kamu punya lambda? atau LINQ? siap membantu Anda
jk.

Jawaban:

13

Saya telah menggunakan pola pengunjung untuk mewakili pohon ekspresi selama 10+ tahun pada enam proyek skala besar dalam tiga bahasa pemrograman, dan saya sangat senang dengan hasilnya. Saya menemukan beberapa hal yang membuat penerapan pola jauh lebih mudah:

Jangan gunakan kelebihan beban di antarmuka pengunjung

Masukkan tipe ke dalam nama metode, yaitu digunakan

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
}

daripada

IExpressionVisitor {
    void Visit(IPrimitiveExpression expr);
    void Visit(ICompositeExpression expr);
}

Tambahkan metode "tangkap tidak dikenal" ke antarmuka pengunjung Anda.

Itu akan memungkinkan bagi pengguna yang tidak dapat mengubah kode Anda:

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
    void VisitExpression(IExpression expr);
};

Ini akan membuat mereka membangun implementasi mereka sendiri IExpressiondan IVisitoryang "memahami" ekspresi mereka dengan menggunakan informasi tipe run-time dalam implementasi VisitExpressionmetode catch-all mereka .

Menyediakan implementasi IVisitorantarmuka tanpa-apa-apa yang baku

Ini akan memungkinkan pengguna yang perlu berurusan dengan subset jenis ekspresi membangun pengunjung mereka lebih cepat, dan membuat kode mereka kebal terhadap Anda menambahkan lebih banyak metode IVisitor. Misalnya, menulis pengunjung yang memanen semua nama variabel dari ekspresi Anda menjadi tugas yang mudah, dan kode tidak akan pecah bahkan jika Anda menambahkan banyak jenis ekspresi baru ke Anda IVisitornanti.

dasblinkenlight
sumber
2
Bisakah Anda menjelaskan mengapa Anda mengatakannya Do not use overloads in the interface of the visitor?
Steven Evers
1
Bisakah Anda menjelaskan mengapa Anda tidak merekomendasikan menggunakan kelebihan? Saya membaca di suatu tempat (di oodesign.com, sebenarnya) bahwa itu tidak terlalu penting apakah saya menggunakan kelebihan atau tidak. Apakah ada alasan khusus mengapa Anda lebih suka desain itu?
T. Fabre
2
@ T.Fabre Tidak masalah dalam hal kecepatan, tetapi itu penting dalam hal keterbacaan. Resolusi metode dalam dua dari tiga bahasa tempat saya menerapkan ini ( Java dan C #) memerlukan langkah waktu untuk memilih di antara potensi kelebihan, membuat kode dengan jumlah besar kelebihan sedikit lebih sulit untuk dibaca. Refactoring kode juga menjadi lebih mudah, karena memilih metode yang ingin Anda modifikasi menjadi tugas yang sepele.
dasblinkenlight
@SnOrfus Silakan lihat jawaban saya untuk T.Fabre di atas.
dasblinkenlight
@dasblinkenlight C # sekarang menawarkan dinamis untuk membiarkan runtime memutuskan metode kelebihan beban apa yang harus digunakan (bukan pada waktu kompilasi). Apakah masih ada alasan mengapa tidak menggunakan kelebihan beban?
Tintenfiisch