2013-04-24 67 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:當您使對象可見時,您可以通過實現操作來添加在對象類型上分派的新操作,而不是通過更改對象本身。