简体   繁体   English

如何使用接口作为方法参数但仍访问非接口方法?

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

I've spent hours trying to find a way to implement this and so far I haven't found a good solution, so I'm hoping someone could please help point me in the right direction. 我已经花费了数小时试图找到一种方法来实现此目的,但到目前为止,我还没有找到一个好的解决方案,所以我希望有人可以帮助我指出正确的方向。

I currently have a C# Winforms project that has an abstract base class with several child classes. 我目前有一个C#Winforms项目,该项目具有一个带有多个子类的抽象基类。 Most of the methods are the same but each child class has a few additional methods specific to it. 大多数方法是相同的,但是每个子类都有一些特定的其他方法。

I want to be able to declare a type of the class being used once and pass that type to all my other methods without having to manually check the type everywhere by going "If Class Type=A Do This Else If Class Type=B Do That, and so on". 我希望能够声明一次使用过的类的类型,并将该类型传递给我的所有其他方法,而不必通过到处手动检查类型,例如“如果Class Type = A则执行此操作,如果Class Type = B则执行此操作, 等等”。

The problem is that I need to pass the base class or interface to accomplish this. 问题是我需要传递基类或接口来完成此任务。 However, by doing so it means I can no longer access the properties or methods specific to the child classes and I don't know how to fix/workaround this. 但是,这样做意味着我无法再访问特定于子类的属性或方法,并且我不知道如何解决/解决此问题。

Here's a simplified example of what I'm trying to do: 这是我要执行的操作的简化示例:

Interface and Class Structure 接口和类结构

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; }
}

Main Form Code 主要表格代码

    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();
    }

I understand why I'm having that issue in the main form code... the compiler doesn't know which type of animal is being passed to it yet so it doesn't know what to show. 我知道为什么我在主要形式代码中遇到这个问题……编译器尚不知道将哪种类型的动物传递给它,因此它也不知道显示什么。 However, I don't know what I should do to fix this. 但是,我不知道该怎么办才能解决此问题。

I've tried: 我试过了:

  • Putting all the animal-specific properties in the interface. 将所有动物特定的属性放在界面中。 This works but violates several OOP principles. 这可行,但是违反了一些OOP原则。 A fish doesn't have feathers, etc. so these specific properties don't belong there. 鱼没有羽毛等,因此这些特定属性不属于其中。

  • Manually checking the type everywhere by going something like "If Type=Fish do abc Else If Type=Bird do def". 通过类似“ If Type = Fish do abc Else If Type = Bird do def”之类的方法手动检查类型。 This also works but violates the DRY principle because I'm repeating myself everywhere. 这也有效,但违反了DRY原则,因为我在各处重复自己。 Also with a lot of methods using animals, this will be a nightmare to maintain in the future. 同样,通过许多使用动物的方法,这将是未来维护的噩梦。

  • Explicitly casting IAnimal to a specific animal like ((Bird)myCustomAnimal).NumberOfFeathers. 将IAnimal显式转换为特定动物,例如((Bird)myCustomAnimal).NumberOfFeathers。 This also works but I don't know what cast to use at compile-time. 这也可以,但是我不知道在编译时使用什么强制类型转换。 This won't be known until the user selects an animal at run-time. 除非用户在运行时选择动物,否则这是未知的。

So I'm just wondering how I can solve this issue? 所以我只是想知道如何解决这个问题?

More specifically, I'm wondering how I can re-design the above code so that I can both: 更具体地说,我想知道如何重新设计上面的代码,以便可以同时进行以下工作:

A) Explicitly declare a type of animal only once and pass that type everywhere (without having to do lots of manual checks in every method to see what type it is before doing something with it) A)一次只声明一种动物类型,然后在任何地方都传递该类型(无需在每种方法中进行大量的手工检查就可以知道它是什么类型)

and also 并且

B) Somehow still have a way to manually access the animal-specific properties like someBird.NumberOfFeathers when I need to. B)我仍然需要某种方式来手动访问动物特定的属性,例如someBird.NumberOfFeathers。

Any ideas? 有任何想法吗?

It really depends on exactly what you are trying to do and what you are hoping to achieve. 这实际上取决于您要做什么以及您希望实现什么。 All the things you've tried so far are possible approaches, but without more context it's hard to give a general answer. 到目前为止,您已经尝试过的所有方法都是可行的方法,但是如果没有更多的上下文,很难给出一个一般性的答案。 It depends on what the right level of abstraction is for what you are trying to do. 这取决于您要执行的操作的正确抽象级别。

One thing to remember is that you can have a class implement as many interfaces as you need. 要记住的一件事是,您可以根据需要使一个类实现尽可能多的接口。 So you could do something like: 因此,您可以执行以下操作:

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

public interface IFly
{
    void Fly();
}

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

Then you Bird class can be: 然后您的Bird类可以是:

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

And now in a method you can do something like: 现在,使用一种方法,您可以执行以下操作:

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();
    }
}

The other thing to think about is how to abstract what you need to a higher level. 要考虑的另一件事是如何将您需要的内容抽象到更高的层次。 So you need to Fly , but why? 所以你需要Fly ,但是为什么呢? What happens with the Animal that can't fly? 不能飞行的Animal会怎样? By Fly are you really just trying to Move ? 通过Fly ,您真的只是在尝试Move吗? Then perhaps you could do: 然后,也许您可​​以这样做:

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

And in your Bird you can do this: 在您的Bird您可以执行以下操作:

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

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

One solution would be to use the visitor pattern to define the operations you want to perform on your animal instances. 一种解决方案是使用访问者模式定义要在动物实例上执行的操作。

First you would define a visitor interface which provides a method for each type of animal you have in your hierarchy. 首先,您将定义一个访客界面,该界面为层次结构中的每种动物提供一种方法。

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

And then you would need to modify your animal classes and interfaces to include a method which accepts a visitor, like so: 然后,您需要修改动物类和接口,以包含接受访客的方法,如下所示:

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

Your actual animal classes now look something like this: 您实际的动物类现在看起来像这样:

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);
    }
}

Now for anything you want to do with each of your animals you will need to define an implementation of the IAnimalVisitor interface. 现在,对于您想对每种动物进行的任何操作,都需要定义IAnimalVisitor接口的实现。 In your example you displayed message boxes that showed information about the animal so an implementation of the IAnimalVisitor interface that does that could look like this: 在您的示例中,您显示了显示有关动物的信息的消息框,因此执行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);
    }
}

Now you just need to pass your visitor to your animal and the correct information will be displayed. 现在,您只需要将访客带到您的动物,就会显示正确的信息。 Your event handling code now looks something like this: 您的事件处理代码现在看起来像这样:

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);

If you want to do something else to your animal instances (save them to a file, generate a UI, play sounds...) you just need to define a new IAnimalVisitor implementation that provides your desired behaviour. 如果要对动物实例执行其他操作(将它们保存到文件,生成UI,播放声音...),则只需定义一个新的IAnimalVisitor实现即可提供所需的行为。

For the sake of balance I will say that this might not be an appropriate design as it adds some additional complexity; 为了平衡起见,我会说这可能不是一个合适的设计,因为它增加了一些额外的复杂性。 each 'operation' requires you to implement a visitor and the addition of another animal to your hierarchy requires you to update your visitor interface and all of it's implementations to account for the new case. 每个“操作”都需要您实现一个访问者,并且在层次结构中添加其他动物需要您更新访问者界面及其所有实现,以解决新情况。

Depending on your point of view this can be either good or bad. 根据您的观点,这可能是好是坏。 Some consider the points above to be bad and a reason to avoid the visitor pattern and to use the other methods already suggested. 一些人认为以上几点是不好的,也是避免访问者模式并使用已经建议的其他方法的原因。 Others (like me) consider the points above to be a good thing; 其他人(如我)认为以上几点是一件好事; your code will now only compile when you provide an implementation for each animal in your hierarchy and your operations are separated into small, dedicated classes. 现在,仅当您为层次结构中的每个动物提供实现并将您的操作分成小的专用类时,才可以编译代码。

My suggestion would be to try the SSCCE I have provided and research the visitor pattern further to decide if this solution is acceptable for your requirements. 我的建议是尝试使用我提供的SSCCE,并进一步研究访问者模式,以确定该解决方案是否可以满足您的要求。

I understand, what you are trying to achive, but your approach is just wrong. 我了解您要达到的目标,但是您的方法是错误的。

Think it this way: does your "Main" class/form realy need to know if there is a bird or a dog? 这样想:您的“主要”班级/形式真的需要知道是鸟还是狗? The answer is "NO". 答案是不”。 There are some common properties which you exposed via an interface (i suggest using a base class here!). 您通过接口公开了一些常用属性(我建议在此处使用基类!)。 Everything else is specific to the given animal. 其他一切都特定于给定的动物。 The easiest approach is extending your interface with a DoAnimalSpecificStuff() method - which would perform the specific opperations. 最简单的方法是使用DoAnimalSpecificStuff()方法扩展您的接口,该方法将执行特定的操作。

When it comes to presentation, you should take a look at the MVP and MVVM Patterns. 关于演示,您应该看一下MVP和MVVM模式。

PS use a Factory Pattern for animal creation! PS使用工厂模式进行动物创作!

You have to explicitly cast the IAnimal object to specific type, if you want to access specific properties/methods. 如果要访问特定的属性/方法,则必须将IAnimal对象显式IAnimal为特定的类型。

You can use as operator and then check if the cast was successful like: 您可以使用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