簡體   English   中英

如何使用接口作為方法參數但仍訪問非接口方法?

[英]How to use interface as a method parameter but still access non-interface methods?

我已經花費了數小時試圖找到一種方法來實現此目的,但到目前為止,我還沒有找到一個好的解決方案,所以我希望有人可以幫助我指出正確的方向。

我目前有一個C#Winforms項目,該項目具有一個帶有多個子類的抽象基類。 大多數方法是相同的,但是每個子類都有一些特定的其他方法。

我希望能夠聲明一次使用過的類的類型,並將該類型傳遞給我的所有其他方法,而不必通過到處手動檢查類型,例如“如果Class Type = A則執行此操作,如果Class Type = B則執行此操作, 等等”。

問題是我需要傳遞基類或接口來完成此任務。 但是,這樣做意味着我無法再訪問特定於子類的屬性或方法,並且我不知道如何解決/解決此問題。

這是我要執行的操作的簡化示例:

接口和類結構

public interface IAnimal
    {
        string NameOfAnimal { get; set; }

        void Eat();
    }

public abstract class Animal : IAnimal
    {
        public abstract string NameOfAnimal { get; set; }

        public abstract void Eat();
    }

public class Bird : Animal
{
    public Bird()
    {
        NameOfAnimal = "Bob the Bird";
        NumberOfFeathers = 100;
    }

    // Interface members
    public override string NameOfAnimal { get; set; }

    public override void Eat()
    {
        System.Windows.Forms.MessageBox.Show("Eating like a Bird");
    }

    // Bird specific properties and methods
    public int NumberOfFeathers { get; protected set; }
    public string SomeOtherBirdSpecificProperty { get; protected set; }

    public void Fly()
    {
        // Fly like a bird
    }
}

public class Fish : Animal
{
    public Fish()
    {
        NameOfAnimal = "Jill the Fish";
        DoesFishHaveSharpTeeth = true;
    }

    // Interface members
    public override string NameOfAnimal { get; set; }

    public override void Eat()
    {
        System.Windows.Forms.MessageBox.Show("Eating like a Fish");
    }

    // Fish specific properties
    public bool DoesFishHaveSharpTeeth { get; protected set; }
    public string SomeOtherFishSpecificProperty { get; protected set; }
}

主要表格代碼

    private void button1_Click(object sender, EventArgs e)
    {
        IAnimal myCustomAnimal = null;
        string animalTheUserSelected = "Bird";

        // Only want to manually specify this once
        switch (animalTheUserSelected)
        {
            case "Bird":
                myCustomAnimal = new Bird();
                break;

            case "Fish":
                myCustomAnimal = new Fish();
                break;

            default:
                break;
        }

        DoSomethingWithACustomAnimal(myCustomAnimal);
    }

    private void DoSomethingWithACustomAnimal(IAnimal myAnimal)
    {
        // This works fine
        MessageBox.Show(myAnimal.NameOfAnimal);
        myAnimal.Eat();

        // This doesn't work
        MessageBox.Show(myAnimal.NumberOfFeathers);
        myAnimal.Fly();
    }

我知道為什么我在主要形式代碼中遇到這個問題……編譯器尚不知道將哪種類型的動物傳遞給它,因此它也不知道顯示什么。 但是,我不知道該怎么辦才能解決此問題。

我試過了:

  • 將所有動物特定的屬性放在界面中。 這可行,但是違反了一些OOP原則。 魚沒有羽毛等,因此這些特定屬性不屬於其中。

  • 通過類似“ If Type = Fish do abc Else If Type = Bird do def”之類的方法手動檢查類型。 這也有效,但違反了DRY原則,因為我在各處重復自己。 同樣,通過許多使用動物的方法,這將是未來維護的噩夢。

  • 將IAnimal顯式轉換為特定動物,例如((Bird)myCustomAnimal).NumberOfFeathers。 這也可以,但是我不知道在編譯時使用什么強制類型轉換。 除非用戶在運行時選擇動物,否則這是未知的。

所以我只是想知道如何解決這個問題?

更具體地說,我想知道如何重新設計上面的代碼,以便可以同時進行以下工作:

A)一次只聲明一種動物類型,然后在任何地方都傳遞該類型(無需在每種方法中進行大量的手工檢查就可以知道它是什么類型)

並且

B)我仍然需要某種方式來手動訪問動物特定的屬性,例如someBird.NumberOfFeathers。

有任何想法嗎?

這實際上取決於您要做什么以及您希望實現什么。 到目前為止,您已經嘗試過的所有方法都是可行的方法,但是如果沒有更多的上下文,很難給出一個一般性的答案。 這取決於您要執行的操作的正確抽象級別。

要記住的一件事是,您可以根據需要使一個類實現盡可能多的接口。 因此,您可以執行以下操作:

public interface IAnimal
{
    string NameOfAnimal { get; set; }
    void Eat();
}

public interface IFly
{
    void Fly();
}

public interface IHaveFeathers
{
    int NumberOfFeathers { get; set; }
}

然后您的Bird類可以是:

public Bird : Animal, IFly, IHaveFeathers
{
    // implementation
}

現在,使用一種方法,您可以執行以下操作:

private void DoSomethingWithACustomAnimal(IAnimal myAnimal)
{
    // This works fine
    MessageBox.Show(myAnimal.NameOfAnimal);
    myAnimal.Eat();

    var feathered = myAnimal as IHaveFeathers;
    if (feathered != null)
    {
        MessageBox.Show(feathered.NumberOfFeathers);
    }

    var flier = myAnimal as IFly;
    if (flier != null)
    {
        flier.Fly();
    }
}

要考慮的另一件事是如何將您需要的內容抽象到更高的層次。 所以你需要Fly ,但是為什么呢? 不能飛行的Animal會怎樣? 通過Fly ,您真的只是在嘗試Move嗎? 然后,也許您可​​以這樣做:

public interface IAnimal
{
    string NameOfAnimal { get; set; }
    void Eat();
    void Move();
}

在您的Bird您可以執行以下操作:

public Bird : Animal, IFly, IHaveFeathers
{
    public override void Move()
    {
        Fly();
    }

    public void Fly()
    {
        // your flying implementation
    }
    // rest of the implementation...
}

一種解決方案是使用訪問者模式定義要在動物實例上執行的操作。

首先,您將定義一個訪客界面,該界面為層次結構中的每種動物提供一種方法。

public interface IAnimalVisitor
{
    void VisitBird(Bird bird);
    void VisitFish(Fish fish);
}

然后,您需要修改動物類和接口,以包含接受訪客的方法,如下所示:

public interface IAnimal
{
    string NameOfAnimal { get; set; }
    void Accept(IAnimalVisitor visitor);
}

您實際的動物類現在看起來像這樣:

public class Bird : IAnimal
{
    public Bird()
    {
        NameOfAnimal = "Bob the Bird";
        NumberOfFeathers = 100;
    }

    public string NameOfAnimal { get; set; }
    public int NumberOfFeathers { get; protected set; }

    public void Accept (IAnimalVisitor visitor)
    {
        visitor.VisitBird(this);
    }
}

public class Fish : IAnimal
{
    public Fish()
    {
        NameOfAnimal = "Jill the Fish";
        DoesFishHaveSharpTeeth = true;
    }

    public string NameOfAnimal { get; set; }
    public bool DoesFishHaveSharpTeeth { get; protected set; }

    public void Accept (IAnimalVisitor visitor)
    {
        visitor.VisitFish(this);
    }
}

現在,對於您想對每種動物進行的任何操作,都需要定義IAnimalVisitor接口的實現。 在您的示例中,您顯示了顯示有關動物的信息的消息框,因此執行IAnimalVisitor接口的實現看起來像這樣:

public class AnimalMessageBoxes : IAnimalVisitor
{
    private void VisitAnimal(IAnimal animal)
    {
        MessageBox.Show(animal.NameOfAnimal);
    }

    public void VisitBird(Bird bird)
    {
        visitAnimal(bird);
        MessageBox.Show(bird.NumberOfFeathers);
    }

    public void VisitFish(Fish fish)
    {
        visitAnimal(fish);
        MessageBox.Show(fish.DoesFishHaveSharpTeeth);
    }
}

現在,您只需要將訪客帶到您的動物,就會顯示正確的信息。 您的事件處理代碼現在看起來像這樣:

string animalTheUserSelected = "Bird";
IAnimal myCustomAnimal = null;

switch (animalTheUserSelected)
{
    case "Bird":
        myCustomAnimal = new Bird();
            break;

    case "Fish":
        myCustomAnimal = new Fish();
            break;

     default:
        break;
}

AnimalMessageBoxes msgBoxes = new AnimalMessageBoxes();
myCustomAnimal.Accept(msgBoxes);

如果要對動物實例執行其他操作(將它們保存到文件,生成UI,播放聲音...),則只需定義一個新的IAnimalVisitor實現即可提供所需的行為。

為了平衡起見,我會說這可能不是一個合適的設計,因為它增加了一些額外的復雜性。 每個“操作”都需要您實現一個訪問者,並且在層次結構中添加其他動物需要您更新訪問者界面及其所有實現,以解決新情況。

根據您的觀點,這可能是好是壞。 一些人認為以上幾點是不好的,也是避免訪問者模式並使用已經建議的其他方法的原因。 其他人(如我)認為以上幾點是一件好事; 現在,僅當您為層次結構中的每個動物提供實現並將您的操作分成小的專用類時,才可以編譯代碼。

我的建議是嘗試使用我提供的SSCCE,並進一步研究訪問者模式,以確定該解決方案是否可以滿足您的要求。

我了解您要達到的目標,但是您的方法是錯誤的。

這樣想:您的“主要”班級/形式真的需要知道是鳥還是狗? 答案是不”。 您通過接口公開了一些常用屬性(我建議在此處使用基類!)。 其他一切都特定於給定的動物。 最簡單的方法是使用DoAnimalSpecificStuff()方法擴展您的接口,該方法將執行特定的操作。

關於演示,您應該看一下MVP和MVVM模式。

PS使用工廠模式進行動物創作!

如果要訪問特定的屬性/方法,則必須將IAnimal對象顯式IAnimal為特定的類型。

您可以使用as運算符,然后檢查轉換是否成功,例如:

Bird b = myAnimal as Bird;
if(b != null)
{
     MessageBox.Show(b.NumberOfFeathers);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM