2014-02-09 50 views
1

我阅读了一些关于接口的文章,但是对于我还不够清楚。请帮助找到正确的方式处理接口。我的问题是在codesample的注释中:使用接口的正确方法?

using System; 
namespace IfTest 
{ 
    public interface ICalculator 
    { 
     void Sum(int a, int b); 
    } 

    public class MyCalc : ICalculator 
    { 
     public void Sum(int a, int b) 
     { 
      Console.WriteLine(a + b); 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      //What's the difference between 
      ICalculator mycalc; 
      mycalc = new MyCalc(); 
      mycalc.Sum(5, 5); 

      //and this. When should we use this way? 
      MyCalc mc = new MyCalc(); 
      mc.Sum(5, 5); 
     } 
    } 
} 

回答

5

随着ICalculator mycalc您声明ICalculator类型,在编译时的变量,你将只能调用一个此接口的一部分方法。您将无法调用不属于接口但仅包含实现类的方法。

使用MyCalc mc您正在声明类型为MyCalc的变量,并且在编译时您只能调用此类及其继承的接口上的所有方法。

在运行时,两者之间完全没有区别。

对接口进行编程时,建议针对对象层次结构中最抽象的可能类型进行工作。所以在这种情况下,这将是ICalculator接口。

这可以更好地分隔调用代码和实际实现。如果呼叫代码是针对接口编程的,则不再与特定的MyCalc实现绑定,该实现可以与其他实现交换。

+0

在运行时**会有所不同:第一个被编译为il“callvirt”操作,而第二个只是一个“调用”操作(速度稍快)。 – Olivier

0

如果希望程序依赖抽象合同而不是硬实现,则可以使用接口。通过使用ICalculator接口来定义一个变量(或领域),您允许程序,以提供该合同的其他实现的将来实现,

ICalculator mycalc; 
mycalc = new MyCalc(); // or mycalc = new OtherCalc(); or mycalc = new FinancialCalc(); 

在将来,当一个更有效的实现出现时,你可以快速替换mycalc与实现接口的不同类,并且程序将继续按预期工作。如果您不希望程序由于未知的副作用或由诸如MyCalc等硬性实现提供的实施细节而失败,这一点特别重要。

1

当使用Dependency injection时,接口将全面生效。假设你有一个使用计算器进行计算的方法。相反,在实例化这种方法计算的,它传递一个计算器作为参数:

public void PerformSomeCalculations(ICalculator calculator) 
{ 
    calculator.Sum(5, 5); 
    ... 
} 

这可以让你在任何时候另一种实现方式来改变计算器。方法PerformSomeCalculations不知道有关特定实现的任何信息。出于测试目的,您甚至可以将它传递给一个虚拟计算器,用于跟踪已调用的方法,以查看PerformSomeCalculations方法是否执行预期的操作。

你甚至可以提供一个计算器,例如Modular arithmetic,并比较两个非常不同的计算器的行为。

编程与接口使您的代码比编程与特定类型更灵活。编程与界面使得Unit testing更容易。

0

您通常会遇到一个班级使用其他班级的服务的情况。 “使用服务”是指调用其公共方法。例如,CalculatorUser使用MyCalc - CalculatorUser实例将在某处呼叫MyCalc.Sum()

想象一下,现在您将包含类CalculatorUserMyCalc的应用程序交给您的客户。你可以写这样的事情:

public class CalculatorUser 
{ 
    private MyCalc _myCalc; 

    public CalculatorUser(MyCalc myCalc) 
    { 
     _myCalc = myCalc; 
    } 

    public void PerformAddition(int a, int b) 
    { 
     _myCalc.Sum(a, b); 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     MyCalc calculator = MyCalc(); 
     CalculatorUser calcUser = new CalculatorUser(calculator); 
     calcUser.PerformAddition(1, 2); 

     Console.ReadKey(); 
    } 
} 

一切看起来不错,但随后一段时间后,客户回来给你带了新的要求:“我要CalculatorUser有更多的选择,我希望它能够选择在旧的简单计算器MyCalc和一个新的花式计算器之间显示操作数,操作和结果!而且,这个选择必须在运行时间中进行。“

您意识到现在您必须创建MyFancyCalc并且还要更改CalculatorUser以支持此新要求。您可能需要将MyFancyCalc类型的另一个成员添加到CalculatorUser,然后使用另一种方法PerformAdditionWithFancyCalc(),这将使用MyFancyCalc。但是如果你的客户需要10种其他类型的计算器 - 你会为每个计算器添加新的成员和方法吗?如果让用户和服务提供者紧密耦合,每次需求变化都会导致用户和解决方案的不断变化,这是因为用户不知道特定的服务提供者,而只知道它提供的服务:这些服务的名称是什么,它们的输入值的类型是什么,它们的输出类型是什么?这实际上是使服务提供商的公众界面CalculatorUser不需要知道计算器的具体实现 - MyCalcMyFancyCalc,它只需要知道它使用的任何计算器都有一个方法Sum,它接受两个int值并返回void。通过这种方式,您可以将用户从特定的计算器中解耦出来,并使其能够使用任何以接口中描述的方式实现Sum的计算器。如果您创建MyExtraFancyCalc班,则不需要更改CalculatorUser

所以,以满足新的需求(使计算器选择在运行时),你可以写这样的事情:

public interface ICalculator 
{ 
    void Sum(int a, int b); 
} 

public class MyCalc : ICalculator 
{ 
    public void Sum(int a, int b) 
    { 
     Console.WriteLine(a + b); 
    } 
} 

public class MyFancyCalc : ICalculator 
{ 
    public void Sum(int a, int b) 
    { 
     Console.WriteLine("{0} + {1} = {2}", a, b, a + b); 
    } 
} 

public class CalculatorUser 
{ 
    private ICalculator _calculator; 

    public CalculatorUser(ICalculator calculator) 
    { 
     _calculator = calculator; 
    } 

    public void PerformAddition(int a, int b) 
    { 
     _calculator.Sum(a, b); 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     bool useFancyCalculator = GetUseFancyCalculator(); 

     ICalculator calculator = CreateCalculator(useFancyCalculator); 
     CalculatorUser calcUser = new CalculatorUser(calculator); 
     calcUser.PerformAddition(1, 2); 

     Console.ReadKey(); 
    } 

    static bool GetUseFancyCalculator() 
    { 
     Console.WriteLine("Would you like to use fancy calculator? (y/n)"); 
     string choice = Console.ReadLine(); 
     return (choice == "y"); 
    } 

    static ICalculator CreateCalculator(bool createFancyCalculator) 
    { 
     ICalculator calculator = null; 

     if (createFancyCalculator) 
      calculator = new MyFancyCalc(); 
     else 
      calculator = new MyCalc(); 

     return calculator; 
    } 
} 

询问用户“你想使用花哨的计算器(Y/N) “,如果使用类型”n“,则使用旧计算器并且输出简单地为”3“,但如果答案为”y“,则使用花式计算器并且输出为”1 + 2 = 3“

此示例显示接口的强大功能(基本上就是一种称为依赖注入的简单模式)。

在现实生活中,您经常会遇到这样的情况:您的客户拥有一个应用程序(CalculatorUser),该应用程序永远不会或很少发生更改,包含各种服务提供程序实现的插件(DLL)。主应用程序会在运行时检测哪些插件可用并选择一个,具体取决于运行时中生成的用户选择(或其他条件)。

+0

你好Bojan。谢谢你的样品。这对我的理解来说似乎是一个很好的起点。我尝试了一下,发现了一种我认为的方式(至少在我看来)是有道理的。但我不确定我是否正确。即G.当我想使用CalculatorUser时,我会使用如下代码: – Ted

相关问题