Domanda

Esiste un modo migliore di associare un elenco di classi base a un'interfaccia utente diversa dal downcasting, ad esempio:

static void Main(string[] args) {
    List<Animal> list = new List<Animal>();  
    Pig p = new Pig(5);  
    Dog d = new Dog("/images/dog1.jpg");  
    list.Add(p);  
    list.Add(d);  
    foreach (Animal a in list)   
    {  
        DoPigStuff(a as Pig);  
        DoDogStuff(a as Dog);  
    }  

}  


static void DoPigStuff(Pig p)
{
    if (p != null) 
    {  
        label1.Text = String.Format("The pigs tail is {0}", p.TailLength);
    }  
}

static void DoDogStuff(Dog d) {
    if (d != null) 
    {
        Image1.src = d.Image;
    }
}

class Animal {
    public String Name { get; set; }
}

class Pig : Animal{
    public int TailLength { get; set; }

    public Pig(int tailLength) 
    {
        Name = "Mr Pig";
        TailLength = tailLength;
    }
}

class Dog : Animal {
    public String Image { get; set; }

    public Dog(String image) 
    {
        Name = "Mr Dog";
        Image = image;
    }
}
È stato utile?

Soluzione

Di fronte a questo tipo di problema, seguo il modello di visitatore .

interface IVisitor
{
  void DoPigStuff(Piggy p);
  void DoDogStuff(Doggy d);
}

class GuiVisitor : IVisitor
{
  void DoPigStuff(Piggy p)
  {
    label1.Text = String.Format("The pigs tail is {0}", p.TailLength);
  }

  void DoDogStuff(Doggy d)
  {
    Image1.src = d.Image;
  }
}

abstract class Animal
{
    public String Name { get; set; }
    public abstract void Visit(IVisitor visitor);
}

class Piggy : Animal
{
    public int TailLength { get; set; }

    public Piggy(int tailLength) 
    {
        Name = "Mr Pig";
        TailLength = tailLength;
    }

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

class Doggy : Animal 
{
   public String Image { get; set; }

   public Doggy(String image) 
   {
     Name = "Mr Dog";
     Image = image;
   }

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

public class AnimalProgram
{
  static void Main(string[] args) {
    List<Animal> list = new List<Animal>();  
    Pig p = new Pig(5);  
    Dog d = new Dog("/images/dog1.jpg");  
    list.Add(p);  
    list.Add(d);

    IVisitor visitor = new GuiVisitor();  
    foreach (Animal a in list)   
    {
      a.Visit(visitor);
    }  
  }
}

Pertanto, il modello visitatore simula il doppio dispacciamento in un linguaggio convenzionale orientato agli oggetti a singolo invio come Java, Smalltalk, C # e C ++.

L'unico vantaggio di questo codice rispetto a jop ' s è che l'interfaccia IVisitor può essere implementata su una classe diversa in un secondo momento quando è necessario aggiungere un nuovo tipo di visitatore (come un XmlSerializeVisitor o un FeedAnimalVisitor ).

Altri suggerimenti

Perché non fare in modo che Animal includa un metodo astratto che Pig e Dog sono costretti ad attuare

public class Animal
{
    public abstract void DoStuff();
}

public Dog : Animal
{
    public override void DoStuff()
    {
        // Do dog specific stuff here
    }
}

public Pig : Animal
{
    public override void DoStuff()
    {
        // Do pig specific stuff here
    }
}

In questo modo ogni classe specifica si assume la responsabilità delle sue azioni, rendendo il tuo codice più semplice. Inoltre, non dovrai eseguire il cast all'interno del tuo ciclo foreach.

Un altro modo per farlo è quello di eseguire un typecheck prima di chiamare il metodo:

if (animal is Pig) DoPigStuff();
if (animal is Dog) DoDogStuff();

Quello che stai cercando è la spedizione multipla. NO - C # non supporta la spedizione multipla. Supporta solo la spedizione singola. C # può invocare dinamicamente un metodo solo in base al tipo di ricevitore (ovvero l'oggetto sul lato sinistro del. Nella chiamata del metodo)

Questo codice utilizza double-dispatch . Lascerò che il codice parli da solo:

class DoubleDispatchSample
{
    static void Main(string[]args)
    {
        List<Animal> list = new List<Animal>();
        Pig p = new Pig(5);
        Dog d = new Dog(@"/images/dog1.jpg");
        list.Add(p);
        list.Add(d);

        Binder binder = new Binder(); // the class that knows how databinding works

        foreach (Animal a in list)
        {
            a.BindoTo(binder); // initiate the binding
        }
    }
}

class Binder
{
    public void DoPigStuff(Pig p)
    {
        label1.Text = String.Format("The pigs tail is {0}", p.TailLength);
    }

    public void DoDogStuff(Dog d)
    {
        Image1.src = d.Image;
    }
}

internal abstract class Animal
{
    public String Name
    {
        get;
        set;
    }

    protected abstract void BindTo(Binder binder);
}

internal class Pig : Animal
{
    public int TailLength
    {
        get;
        set;
    }

    public Pig(int tailLength)
    {
        Name = "Mr Pig";
        TailLength = tailLength;
    }

    protected override void BindTo(Binder binder)
    {
        // Pig knows that it's a pig - so call the appropriate method.
        binder.DoPigStuff(this);
    }
}

internal class Dog : Animal
{
    public String Image
    {
        get;
        set;
    }

    public Dog(String image)
    {
        Name = "Mr Dog";
        Image = image;
    }

    protected override void BindTo(Binder binder)
    {
        // Pig knows that it's a pig - so call the appropriate method.
        binder.DoDogStuff(this);
    }
}

NOTA: il codice di esempio è molto più semplice di questo. Penso al doppio dispacciamento come una delle artiglierie pesanti nella programmazione in C # - lo prendo solo come ultima risorsa. Ma se ci sono molti tipi di oggetti e molti tipi diversi di associazioni che devi fare (ad es. Devi associarlo a una pagina HTML ma devi anche associarlo a un WinForms o un report o un CSV) , Alla fine refatterei il mio codice per utilizzare il doppio invio.

Non stai sfruttando appieno la tua classe di base. Se avevi una funzione virtuale nella tua classe Animal quel Dog & amp; Sostituzione del maiale, non dovrai lanciare nulla.

A meno che tu non abbia un esempio più specifico, sovrascrivi ToString ().

Penso che tu voglia una classe di visualizzazione associata a una fabbrica.

Dictionary<Func<Animal, bool>, Func<Animal, AnimalView>> factories;
factories.Add(item => item is Dog, item => new DogView(item as Dog));
factories.Add(item => item is Pig, item => new PigView(item as Pig));

Quindi DogView e PigView erediteranno AnimalView simile a:

class AnimalView {
  abstract void DoStuff();
}

Finirai per fare qualcosa del tipo:

foreach (animal in list)
  foreach (entry in factories)
    if (entry.Key(animal)) entry.Value(animal).DoStuff();

Suppongo che potresti anche dire che si tratta di un'implementazione del modello di strategia.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top