Снижение C# при привязке к интерфейсу
Вопрос
Есть ли лучший способ привязки списка базового класса к пользовательскому интерфейсу, кроме понижающего приведения, например:
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;
}
}
Решение
Столкнувшись с подобной проблемой, я следую шаблон посетителя.
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);
}
}
}
Таким образом, шаблон посетителя имитирует двойную отправку в обычном объектно-ориентированном языке с одной отправкой, таком как Java, Smalltalk, C# и C++.
Единственное преимущество этого кода перед прыгатьДело в том, что интерфейс IVisitor можно реализовать в другом классе позже, когда вам понадобится добавить новый тип посетителя (например, XmlSerializeVisitor или КормаЖивотноеПосетитель).
Другие советы
Почему бы не включить в Animal абстрактный метод, который вынуждены реализовать Pig и Dog?
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
}
}
Таким образом, каждый конкретный класс берет на себя ответственность за свои действия, что упрощает ваш код.Вам также не нужно будет выполнять приведение внутри цикла foreach.
Другой способ сделать это — выполнить проверку типа перед вызовом метода:
if (animal is Pig) DoPigStuff();
if (animal is Dog) DoDogStuff();
То, что вам нужно, — это множественная отправка.НЕТ. C# не поддерживает множественную отправку.Он поддерживает только однократную отправку.C# может динамически вызывать метод только в зависимости от типа получателя (т. е.объект в левой части .в вызове метода)
Этот код использует двойная отправка.Я позволю коду говорить сам за себя:
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);
}
}
ПРИМЕЧАНИЕ:Ваш пример кода намного проще, чем этот.Я считаю двойную диспетчеризацию одной из тяжелых артиллерийских средств в программировании на C# — я использую ее только в крайнем случае.Но если существует много типов объектов и много разных типов привязок, которые вам нужно сделать (например,вам нужно привязать его к HTML-странице, но вам также необходимо привязать его к WinForms, отчету или CSV), в конечном итоге я бы реорганизовал свой код, чтобы использовать двойную отправку.
Вы не в полной мере используете преимущества своего базового класса.Если бы в вашем классе Animal была виртуальная функция, которая переопределяла Dog и Pig, вам не нужно было бы ничего приводить.
Если у вас нет более конкретного примера, просто переопределите ToString().
Я думаю, вам нужен класс представления, связанный с фабрикой.
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));
Тогда ваши DogView и PigView унаследуют AnimalView, который выглядит примерно так:
class AnimalView {
abstract void DoStuff();
}
В конечном итоге вы сделаете что-то вроде:
foreach (animal in list)
foreach (entry in factories)
if (entry.Key(animal)) entry.Value(animal).DoStuff();
Думаю, можно также сказать, что это реализация шаблона стратегии.