关于c#:使用接口变量


Using Interface variables

我仍在努力更好地理解接口。我知道它们是什么,以及如何在类中实现它们。

我不明白的是,当您创建一个属于您的接口类型的变量时:

1
IMyInterface somevariable;

你为什么要这样做?我不理解imyinterface如何像类一样使用……例如调用方法,因此:

1
somevariable.CallSomeMethod();

为什么要使用imyinterface变量来执行此操作?


您没有创建接口的实例-您正在创建实现接口的某个实例。

接口的要点是,它保证任何时候实现它的东西都将提供它内部声明的方法。

因此,现在,使用您的示例,您可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MyNiftyClass : IMyInterface
{
    public void CallSomeMethod()
    {
        //Do something nifty
    }
}

MyOddClass : IMyInterface
{
    public void CallSomeMethod()
    {
        //Do something odd
    }
}

现在你有了:

1
2
IMyInterface nifty = new MyNiftyClass()
IMyInterface odd = new MyOddClass()

调用CallsomeMethod方法现在可以做一些漂亮的事情,也可以做一些奇怪的事情,当您使用imyinterface作为类型传入时,这变得特别有用。

1
2
3
4
public void ThisMethodShowsHowItWorks(IMyInterface someObject)
{
    someObject.CallSomeMethod();
}

现在,根据您是用一个漂亮的类还是一个奇怪的类调用上面的方法,您会得到不同的行为。

1
2
3
4
5
6
7
8
9
10
11
12
public void AnotherClass()
{
    IMyInterface nifty = new MyNiftyClass()
    IMyInterface odd = new MyOddClass()

    // Pass in the nifty class to do something nifty
    this.ThisMethodShowsHowItWorks(nifty);

    // Pass in the odd class to do something odd
    this.ThisMethodShowsHowItWorks(odd);

}

编辑

这解决了我认为您的预期问题——为什么要将变量声明为接口类型?

这就是为什么要使用:

1
IMyInterface foo = new MyConcreteClass();

优先考虑:

1
MyConcreteClass foo = new MyConcreteClass();

希望在声明方法签名时使用接口的原因很清楚,但这就留下了关于局部作用域变量的问题:

1
2
3
4
5
6
7
8
public void AMethod()
{
    // Why use this?
    IMyInterface foo = new MyConcreteClass();

    // Why not use this?
    MyConcreteClass bar = new MyConcreteClass();
}

通常没有技术上的原因来选择接口。我通常使用接口是因为:

  • 我通常注入依赖项,因此需要多态性
  • 使用接口清楚地说明了我只使用接口成员的意图

技术上需要接口的地方之一是使用多态性的地方,比如使用工厂创建变量,或者(如我上面所说)使用依赖注入。

借用伊托尔森的一个例子,使用具体的声明,您不能这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void AMethod(string input)
{              
    IMyInterface foo;

    if (input =="nifty")
    {
        foo = new MyNiftyClass();
    }
    else
    {
        foo = new MyOddClass();
    }
    foo.CallSomeMethod();
}


因为:

1
2
public void ReadItemsList(List<string> items);
public void ReadItemsArray(string[] items);

可以变成这样:

1
public void ReadItems(IEnumerable<string> items);

编辑

这样想:

You have to be able to do this.

而不是:

You have to be this.

本质上,这是方法与其调用方之间的契约。


我处于同样的位置,花了几天时间来弄明白为什么我们必须使用接口变量。

1
 IDepartments rep = new DepartmentsImpl();

为什么不

1
 DepartmentsImpl rep = new DepartmentsImpl();

设想一下,如果一个类实现两个接口,其中包含一个具有相同签名的成员,那么在该类上实现该成员将导致两个接口将该成员用作其实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Test
{
static void Main()
{
    SampleClass sc = new SampleClass();
    IControl ctrl = (IControl)sc;
    ISurface srfc = (ISurface)sc;

    // The following lines all call the same method.
    sc.Paint();
    ctrl.Paint();
    srfc.Paint();
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface IControl
{
  void Paint();
}
 interface ISurface
{
  void Paint();
}
 class SampleClass : IControl, ISurface
 {
   // Both ISurface.Paint and IControl.Paint call this method.
 public void Paint()
 {
    Console.WriteLine("Paint method in SampleClass");
 }

}

1
2
3
4
 // Output:
 // Paint method in SampleClass
// Paint method in SampleClass
// Paint method in SampleClass

但是,如果两个接口成员不执行相同的功能,这可能导致一个或两个接口的实现不正确。

1
2
3
4
5
6
7
8
9
10
11
public class SampleClass : IControl, ISurface
{
    void IControl.Paint()
    {
        System.Console.WriteLine("IControl.Paint");
    }
    void ISurface.Paint()
    {
        System.Console.WriteLine("ISurface.Paint");
    }
}

类成员IControl.paint仅通过IControl接口和ISurface可用。paint仅通过ISurface可用。两种方法实现都是独立的,并且都不能直接在类上使用。例如:

1
2
3
   IControl c = new SampleClass();
   ISurface s = new SampleClass();
   s.Paint();

如果我错了,请纠正我,因为我还在学习这个接口概念。


使用接口变量是允许编写处理程序方法的唯一方法,该方法可以接受来自具有不同基类的对象的数据。

这几乎是任何人都能明白的。


假设你有船、车、卡车、飞机。

这些都共享一个公共方法takemethere(字符串目的地)

您将拥有一个接口:

1
2
3
4
public interface ITransportation
{
    public void TakeMeThere(string destination);
}

那么你的班级:

1
2
3
4
5
6
7
public class Boat : ITransportation
{
   public void TakeMeThere(string destination) // From ITransportation
   {
       Console.WriteLine("Going to" + destination);
   }
}

你在这里说的是,我的班轮也会按运输部告诉我的做。

当你想为运输公司制作软件的时候。你可以有一个方法

1
2
3
4
Void ProvideServiceForClient(ITransportation transportationMethod, string whereTheyWantToGo)
{
      transportationMethod.TakeMeThere(whereTheyWantToGo); // Cause ITransportation has this method
}

所以他们想要哪种交通方式并不重要,因为我们知道它可以带我去


我相信每个人都在回答使用接口的多态性原因,而DavidHall部分地谈到了为什么您将它作为接口而不是实际的对象名来引用。当然,仅限于接口成员等是有帮助的,但另一个答案是依赖注入/实例化。

当您设计应用程序时,如果您使用依赖注入,那么它通常更干净、更容易管理,并且更灵活。如果你从未做过,一开始感觉是倒退的,但当你开始回溯的时候,你会希望你做过。

依赖注入通常通过允许类实例化和控制依赖项来工作,而您只需要依赖所需对象的接口。

例子:

首先将应用程序分层。一级逻辑,二级接口,三级依赖注入。(每个人都有自己的方式,这只是为了展示)。

在逻辑层中,您引用接口和依赖层,然后最终仅基于外部对象的接口创建逻辑。

我们走到这里:

1
2
3
4
5
6
7
8
public IEmployee GetEmployee(string id)
{
    IEmployee emp = di.GetInstance<List<IEmployee>>().Where(e => e.Id == id).FirstOrDefault();

    emp?.LastAccessTimeStamp = DateTime.Now;

    return emp;
}

注意上面我们如何使用di.getInstance从依赖项中获取对象。该层中的代码永远不会知道或关心Employee对象。事实上,如果它在其他代码中发生变化,在这里它将永远不会影响我们。如果IEemployee的接口发生更改,那么我们可能需要更改代码。

关键是,iemployee emp=永远不会真正知道实际对象是什么,但知道接口以及如何使用它。考虑到这一点,当您想要使用一个接口而不是一个对象时,因为我们永远不知道或无法访问该对象。

这是总结..希望有帮助。


不,这是不可能的。设计师没有提供一种方法。当然,这也是常识。因为接口只包含抽象方法,而且抽象方法没有(实现代码的)主体,所以我们无法创建对象。

假设即使它是允许的,它的用途是什么。用对象调用抽象方法不会产生任何目的,因为没有输出。抽象方法没有功能。那么,Java设计和编码中接口的使用是什么呢?它们可以用作原型,您可以从中轻松地开发新类。它们像其他类的模板一样工作,这些类实现接口,就像构建建筑的蓝图一样。


使用了一个接口,因此您不需要担心什么类实现了该接口。这一点很有用的一个例子是,当您有一个工厂方法返回一个具体的实现时,根据您运行的环境可能会有所不同。它还允许API设计者定义API,同时允许第三方以他们认为合适的任何方式实现API。Sun用Java的加密API来实现这一点。

1
2
3
4
5
6
7
8
9
10
11
public interface Foo {

}

public class FooFactory {
    public static Foo getInstance() {
        if(os == 'Windows') return new WinFoo();
        else if(os == 'OS X') return new MacFoo();
        else return new GenricFoo();
    }
}

使用工厂的代码只需要了解foo,而不需要了解任何特定的实现。


这不是特定于C,因此我建议移动到其他标志。关于你的问题,我们选择接口的主要原因是提供两个组件之间的协议(可以是dll、jar或任何其他组件)。请参阅以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
 public class TestClass
    {
        static void Main()
        {
            IMyInterface ob1, obj2;
            ob1 = getIMyInterfaceObj();
            obj2 = getIMyInterfaceObj();
            Console.WriteLine(ob1.CallSomeMethod());
            Console.WriteLine(obj2.CallSomeMethod());
            Console.ReadLine();

        }

        private static bool isfirstTime = true;
        private static IMyInterface getIMyInterfaceObj()
        {
            if (isfirstTime)
            {
                isfirstTime = false;
                return new ImplementingClass1();
            }
            else
            {
                return new ImplementingClass2();
            }
        }
    }
    public class ImplementingClass1 : IMyInterface
    {
        public ImplementingClass1()
        {

        }


        #region IMyInterface Members

        public bool CallSomeMethod()
        {
            return true;
        }

        #endregion
    }

    public class ImplementingClass2 : IMyInterface
    {
        public ImplementingClass2()
        {

        }
        #region IMyInterface Members

        public bool CallSomeMethod()
        {
            return false;
        }

        #endregion
    }
    public interface IMyInterface
    {
        bool CallSomeMethod();

    }

这里的主要方法不知道类的情况,但是它仍然能够使用接口获得不同的行为。


例如,您有两个类:BookNewspaper。您可以阅读其中的每一个,但是这两个从一个普通的超类继承是没有意义的。因此,它们都将实现IReadable接口:

1
2
3
4
public interface IReadable
{
    public void Read();
}

现在假设您正在编写一个应用程序,该应用程序将为用户阅读书籍和报纸。用户可以从列表中选择一本书或一份报纸,该项目将被用户阅读。

应用程序中读取给用户的方法将把这个BookNewspaper作为参数。在代码中可能是这样:

1
2
3
4
public static void ReadItem(IReadable item)
{
    item.Read();
}

因为参数是一个IReadable,我们知道对象有方法Read(),所以我们调用它来读取给用户。不管这是一个BookNewspaper还是其他实现IReadable的东西。单个类通过实现Read()方法来精确地实现每个项的读取方式,因为对于不同的类,它很可能是不同的。

BookRead()可能如下:

1
2
3
4
5
6
7
8
9
10
11
public void Read()
{
    this.Open();
    this.TurnToPage(1);
    while(!this.AtLastPage)
    {
        ReadText(this.CurrentPage.Text);
        this.TurnPage();
    }
    this.Close();
}

NewspaperRead()可能有点不同:

1
2
3
4
5
6
7
8
9
10
public void Read()
{
    while(!this.OnBackPage)
    {
        foreach(Article article in this.CurrentPage.Articles)
        {
            ReadText(article.Text);
        }
    }
}

重点是,由接口类型的变量所包含的对象被保证具有一组特定的方法,即使对象的可能类没有以任何其他方式相关。这允许您编写应用于各种类的代码,这些类具有可以对其执行的公共操作。


接口的目的是定义多个对象之间的契约,独立于特定的实现。

因此,当您有一个内部ISomething和一个特定的实现时,通常会使用它。

1
class Something : ISomething

因此,当您实例化一个契约时,将使用接口variable:

1
2
ISomething myObj = new Something();
myObj.SomeFunc();

您还应该阅读接口C#

更新:

我将通过一个(真实的)例子来解释为变量而不是类本身使用接口的逻辑:

我有一个通用的库交互:

1
2
3
4
Interface IRepository {
    void Create();
    void Update();
}

我有两个独立的实现:

1
2
class RepositoryFile : interface IRepository {}
class RepositoryDB : interface IRepository {}

每个类都有一个完全不同的内部实现。

现在我有了另一个对象,一个日志记录程序,它使用一个已经建立的存储库来完成他的写作。这个对象不关心存储库是如何实现的,所以他只是实现:

1
void WriteLog(string Log, IRepository oRep);

顺便说一句,这也可以通过使用标准类继承来实现。但是使用接口和类继承之间的区别是另一个讨论。

有关抽象类和接口之间区别的更详细的讨论,请参见此处。


这是面向对象编程中的一个基本概念——多态性。(维基百科)

简短的回答是,通过使用类A中的接口,可以给类A提供IMyInterface的任何实现。

这也是松耦合的一种形式(维基百科),在那里你有许多类,但它们并不明确地相互依赖——只依赖于它们提供的一组属性和方法(接口)的抽象概念。