2010-01-15 702 views
10

Escribo una pequeña biblioteca de estructuras de datos en C# y me encuentro con un problema de arquitectura. Esencialmente tengo una clase que implementa el patrón de visitante, y hay muchas implementaciones posibles de visitantes:¿Cómo simulo clases anónimas en C#

public interface ITreeVisitor<T, U> 
{ 
    U Visit(Nil<T> s); 
    U Visit(Node<T> s); 
} 

public abstract class Tree<T> : IEnumerable<T> 
{ 
    public readonly static Tree<T> empty = new Nil<T>(); 
    public abstract U Accept<U>(ITreeVisitor<T, U> visitor); 
} 

public sealed class Nil<T> : Tree<T> 
{ 
    public override U Accept<U>(ITreeVisitor<T, U> visitor) { return visitor.Visit(this); } 
} 

public sealed class Node<T> : Tree<T> 
{ 
    public Tree<T> Left { get; set; } 
    public T Value { get; set; } 
    public Tree<T> Right { get; set; } 

    public override U Accept<U>(ITreeVisitor<T, U> visitor) { return visitor.Visit(this); } 
} 

Cada vez que desea pasar de un visitante, tengo que crear una clase de visitantes, implementar la interfaz, y pase en este aspecto:

class InsertVisitor<T> : ITreeVisitor<T, Tree<T>> where T : IComparable<T> 
{ 
    public T v { get; set; }; 

    public Tree<T> Visit(Nil<T> s) 
    { 
     return new Node<T>() { Left = Tree<T>.empty, Value = v, Right = Tree<T>.empty }; 
    } 

    public Tree<T> Visit(Node<T> s) 
    { 
     switch (v.CompareTo(s.Value)) 
     { 
      case -1: return new Node<T>() { Left = Insert(v, s.Left), Value = s.Value, Right = s.Right }; 
      case 1: return new Node<T>() { Left = s.Left, Value = s.Value, Right = Insert(v, s.Right) }; 
      default: return s; 
     } 
    } 
} 

public static Tree<T> Insert<T>(T value, Tree<T> tree) where T : IComparable<T> 
{ 
    return tree.Accept<Tree<T>>(new InsertVisitor<T>() { v = value }); 
} 

no me gusta escribir código repetitivo que mucho, ya que se pone muy complicado cuando se tiene un número no trivial de las implementaciones de los visitantes.

Quiero escribir algo similar a anonymous classes Java (código de concepto):

public static Tree<T> Insert<T>(T v, Tree<T> tree) where T : IComparable<T> 
{ 
    return tree.Accept<Tree<T>>(new InsertVisitor<T>() 
     { 
      public Tree<T> Visit(Nil<T> s) { return new Node<T>() { Left = Tree<T>.empty, Value = v, Right = Tree<T>.empty }; } 
      public Tree<T> Visit(Node<T> s) 
      { 
       switch (v.CompareTo(s.Value)) 
       { 
        case -1: return new Node<T>() { Left = Insert(v, s.Left), Value = s.Value, Right = s.Right }; 
        case 1: return new Node<T>() { Left = s.Left, Value = s.Value, Right = Insert(v, s.Right) }; 
        default: return s; 
       } 
      } 
     }; 
} 

¿Hay alguna manera de simular las clases anónimas con las implementaciones de interfaz en C#?

+2

posible que desee para explicar lo que es una interfaz en el anonimato. Me temo que no tengo ni idea de lo que significa. – Noldorin

+2

@Noldorin: anonymous * interface * no es la mejor opción de palabras, lo que quiero decir es anónimo * clase *. Hay una característica en Java donde puedes implementar interfaces sobre la marcha sin necesidad de una clase con nombre. Me gustaría hacer algo similar en C#. – Juliet

+0

¿Seguro que no puede arreglárselas con los delegados? –

Respuesta

7

Puede cambiar la parte de "comportamiento" de la clase desde los métodos definidos en una interfaz a delegados llamados en el momento apropiado, y crear un nuevo visitante pasando nuevos delegados, alistando funciones anónimas para hacer el trabajo de clases anónimas

código Boceto (no probado, se pueden limpiar en su caso):

class CustomVisitor<T> : ITreeVisitor<T, Tree<T>> where T : IComparable<T> 
{ 
    public T v { get; set; }; 
    public Func<Nil<T>, Tree<T>> VisitNil { get; set; } 
    public Func<Node<T>, Tree<T>> VisitNode { get; set; } 

    public Tree<T> Visit(Nil<T> s) { return VisitNil(s); } 
    public Tree<T> Visit(Node<T> s) { return VisitNode(s); } 
} 

public static Tree<T> Insert<T>(T v, Tree<T> tree) where T : IComparable<T> 
{ 
    return tree.Accept<Tree<T>>(new CustomVisitor<T> { 
     VisitNil = s => 
      return new Node<T>() { Left = Tree<T>.empty, Value = v, Right = Tree<T>.empty }; } 
     VisitNode = s => 
     { 
      switch (v.CompareTo(s.Value)) 
      { 
       case -1: return new Node<T>() { Left = Insert(v, s.Left), Value = s.Value, Right = s.Right }; 
       case 1: return new Node<T>() { Left = s.Left, Value = s.Value, Right = Insert(v, s.Right) }; 
       default: return s; 
      } 
     } 
    }); 
} 
+0

+1, + respuesta: oh wow, eso fue más simple de lo que pensé :) Estaba preocupado de que tuviera que volver a escribir mi código usando 'if (tree is Nil) {...} else {...} 'solo para mantener toda la lógica de cruce de árbol contenida en el mismo método. ¡Muy apreciado! – Juliet

+1

Este es un buen ejemplo de cómo los conceptos de programación funcional pueden agregar valor a los lenguajes orientados a objetos. =) –

Cuestiones relacionadas