2013-04-24 58 views
1

我的代码中有太多downcasts。在C++中,我可以使用模板来避免向下转换。但在C#中以下示例的最佳实现是什么?如何避免函数中太多downcasts

class Pet { bool mIsDog; } 
class Dog : Pet // mIsDog = true 
class Cat : Pet // mIsDog = false 

class Owner //There are many different owner classes, may come from different dlls 
{ 
    private List<Food> mFoods; 

    private void FeedDog(Dog dog) 
    { 
     ... //Use mFoods, which is invisible to dog 
    } 

    void HandlePet(Pet pet) 
    { 
     if(pet.mIsDog) //In c++ I can use templates to avoid downcasting 
      FeedDog((Dog)pet); 
     else 
      FeedCat((Cat)pet); 

     ... //code handling general pet 

     while(pet.IsUnhappy()) 
     { 
      if(pet.mIsDog) //In c++ I can use templates to avoid downcasting 
       PlayWithDog((Dog)pet); 
      else 
       PlayWithCat((Cat)pet); 
     } 

     ... //a lot of code handling general pet or specific pet (cat or dog) 
    } 
} 

注意功能HandlePet有压痕的多层次非常复杂的逻辑,因此很难将其拆分成多个独立的功能。


,我不作BeFedWith或BePlayedWith宠物类中的虚函数的是,我可以有很多不同的用户类别,例如,BoyOwner,GirlOwner,WomanOwner,ManOwner,每一个都有自己的方式来养活的原因宠物。宠物是一个普通的类,被许多其他类使用,但所有者类只与宠物交互。另外,FeedDog等函数需要访问Owner类的私有成员。

+6

是否有任何理由认为'Pet'不是带'Play'和'Feed'方法的抽象类? – 2013-04-24 15:06:14

+0

您可以为您的Pet类添加一个抽象的BeFed(或类似命名)方法,并从您的所有者类中调用该方法,那么您不需要获取具体类,只需(现在是抽象的)'Pet'类就足够了。 (编辑:他说什么!) – 2013-04-24 15:07:25

+4

在'Pet'中有'mIsDog'是一个非常非常糟糕的设计决定。 – 2013-04-24 15:08:37

回答

0

如果必须这样做有什么建议,保持饲料等主人不是宠物的方法的一部分,那么你可以利用动态绑定来调用正确的所有者方法:

using System; 

class Pet { } 
class Dog : Pet { } 
class Cat : Pet { } 

class Owner 
{ 
    public void HandlePet(Pet pet) 
    { 
     HandlePet((dynamic)pet); 
    } 

    private void HandlePet(Cat pet) 
    { 
     Console.WriteLine("Stroke the cat softly whispering Please don't scratch me."); 
    } 

    private void HandlePet(Dog pet) 
    { 
     Console.WriteLine("Stroke the dog firmly saying Who's a good boy?"); 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var owner = new Owner(); 

     Console.WriteLine("Handle dog first"); 
     owner.HandlePet(new Dog()); 

     Console.WriteLine("Handle cat second"); 
     owner.HandlePet(new Cat()); 

     Console.ReadKey(); 
    } 
} 

输出:

先处理狗
中风狗坚定地说谁是好孩子?
把手猫秒
抚摸着猫轻声低语请不要抓我。


如果能避免的话,那么我会穿上宠物类的方法,如下:
一种方法是使宠物一个抽象类,它有EatFoodPlay抽象方法。该DogCat类会实现这些方法:

using System; 

abstract class Pet 
{ 
    public abstract void EatFood(); 
    public abstract void Play(); 

    public void BrushHair() 
    { 
     Console.WriteLine("Hair brushed!"); 
    } 
} 
class Dog : Pet 
{ 
    public override void EatFood() 
    { 
     Console.WriteLine("Eating dog food..."); 
    } 

    public override void Play() 
    { 
     Console.WriteLine("Run around manically barking at everything."); 
    } 
} 
class Cat : Pet 
{ 
    public override void EatFood() 
    { 
     Console.WriteLine("Eating cat food..."); 
    } 

    public override void Play() 
    { 
     Console.WriteLine("Randomly choose something breakable to knock over."); 
    } 
} 

class Owner 
{ 
    public void HandlePet(Pet pet) 
    { 
     pet.EatFood(); 
     pet.Play(); 
     pet.BrushHair(); 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var owner = new Owner(); 

     Console.WriteLine("Handle dog first"); 
     owner.HandlePet(new Dog()); 

     Console.WriteLine("Handle cat second"); 
     owner.HandlePet(new Cat()); 

     Console.ReadKey(); 
    } 
} 

输出:

手柄狗第一
吃狗粮......
运行周围的一切狂躁乱叫。
发刷!
第二把手猫
吃猫食...
随机选择一些易碎的东西。
发刷!


如果你想使用访问者模式,尝试下面的代码:

using System; 

public interface IPetVisitor 
{ 
    void Visit(Dog dog); 
    void Visit(Cat cat); 
} 

public interface IPetAcceptor<T> 
{ 
    void Accept(T visitor); 
} 

public abstract class Pet : IPetAcceptor<IPetVisitor> 
{ 
    public abstract void Accept(IPetVisitor visitor); 
} 
public class Dog : Pet 
{ 
    public override void Accept(IPetVisitor visitor) 
    { 
     visitor.Visit(this); 
    } 
} 
public class Cat : Pet 
{ 
    public override void Accept(IPetVisitor visitor) 
    { 
     visitor.Visit(this); 
    } 
} 

class Owner 
{ 
    // Private variable on owner class 
    private int HandCount = 2; 

    // Pet handler is an inner class so we can access the enclosing class' private member. 
    public class PetHandler : IPetVisitor 
    { 
     private Owner Owner; 

     public PetHandler(Owner owner) 
     { Owner = owner; } 

     public void Visit(Dog dog) 
     { 
      Console.WriteLine("Pet the dog with {0} hands", Owner.HandCount); 
     } 

     public void Visit(Cat cat) 
     { 
      Console.WriteLine("Pet the cat with {0} hands", Owner.HandCount); 
     } 
    } 
    private PetHandler PetHandlerInstance; 

    public Owner() 
    { 
     PetHandlerInstance = new PetHandler(this); 
    } 

    public void HandlePet(IPetAcceptor<IPetVisitor> pet) 
    { 
     pet.Accept(PetHandlerInstance); 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var owner = new Owner(); 

     Console.WriteLine("Handle dog first"); 
     owner.HandlePet(new Dog()); 

     Console.WriteLine("Handle cat second"); 
     owner.HandlePet(new Cat()); 

     Console.ReadKey(); 
    } 
} 
+0

'动态'似乎是我想要的。它的工作方式与C++的模板类似。但为什么它是一个糟糕的设计? – Fan 2013-04-24 15:34:08

+1

例如,如果您忘记使用接受狗的HandlePet方法,或者添加第三种宠物,会发生什么情况? – 2013-04-24 15:35:12

+0

它给我一个运行时错误。太糟糕了,它不能像C++那样在编译期间检查它。 – Fan 2013-04-24 15:41:20

0

好吧,向下转换被认为是不好的设计... 你应该将常用的方法移动到基类,并使它们抽象或有一个接口来做到这一点。

如果您有:

public interface Pet{ 
    void Feed(); 
    void Play(); 
} 

public class Dog:Pet{ 
    public void Feed(){ 
     ...dog feeding code... 
    } 
    public void Play(){ 
     ...dog playing code... 
    } 
} 
public class Cat:Pet{ 
    public void Feed(){ 
     ...cat feeding code... 
    } 
    public void Play(){ 
     ...cat playing code... 
    } 
} 

那么你handlePet功能成为:

void HandlePet(Pet pet){ 
    pet.Feed(); 
    while(pet.isUnhappy()) 
     pet.Play(); 
} 
+0

Feed和PlayWith需要使用类Owner的私有成员。另外,所有者就像一个单独的模块,它可能会或可能不会被编译,因此我想将大部分内容(Feed,Play)保留在其内部。 – Fan 2013-04-24 15:14:16

+1

为了避免使用沮丧,你可能会去在c#中搜索动态关键字,但仍然是一个非常糟糕的设计选择,我不知道如果动态是正确的方式,反正另一件事,而不是使用变量mIsDog,你可以使用if (宠物是狗)来检查宠物是否是实施狗的类 – 2013-04-24 15:24:13

+0

@FabioMarcolini:参见我的使用'dynamic'的示例。仍然认同它并不理想。 – 2013-04-24 15:29:18

1

你正在试图解决的问题是基于对象类型如何调度,当你不在你设计对象的时候知道所有可能需要的不同操作。 (其他答案建议将所有必需的操作放在基类中;这有时不可能并导致过度耦合)。

答案是不测试对象的类型和强制转换;如果有的话,你应该很少这么做。一个好的解决方案是使用Visitor pattern:当您使对象可见时,您可以通过实现操作来添加在对象类型上分派的新操作,而不是通过更改对象本身。