繁体   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