我读了一些关于接口的文章,但对我来说还不够清楚。请帮助找到使用接口的正确方法。我的问题在codeample的评论中:
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);
}
}
}
答案 0 :(得分:5)
使用ICalculator mycalc
声明类型为ICalculator
的变量,在编译时,您只能调用属于此接口的方法。您将无法调用不属于接口但仅属于实现类的方法。
使用MyCalc mc
声明类型为MyCalc
的变量,在编译时,您只能调用此类及其继承的接口上的所有方法。
在运行时,两者之间根本没有区别。
对接口进行编程时,建议使用对象层次结构中最抽象的可能类型。所以在这种情况下,这将是ICalculator
接口。
这样可以更好地区分调用代码和实际实现。如果调用代码是针对接口编程的,则它不再依赖于可以与其他实现交换的特定MyCalc
实现。
答案 1 :(得分:1)
使用Dependency injection时接口完全生效。假设您有一个使用计算器进行计算的方法。而不是在此方法中实例化计算器,将其作为参数传递给计算器:
public void PerformSomeCalculations(ICalculator calculator)
{
calculator.Sum(5, 5);
...
}
这允许您随时通过其他实现更改计算器。方法PerformSomeCalculations
对具体实现一无所知。出于测试目的,您甚至可以传递一个虚拟计算器来跟踪已调用的方法,以查看方法PerformSomeCalculations
是否完成预期的操作。
你甚至可以提供一个例如Modular arithmetic的计算器,并比较两个非常不同的计算器的行为。
编程与接口使您的代码比编程与特定类型更灵活。编程与接口使Unit testing更容易。
答案 2 :(得分:0)
当您希望程序依赖于抽象合同而不是硬实现时,可以使用接口。通过使用ICalculator
接口来定义变量(或字段),您允许程序的未来实现提供该合同的其他实现,
ICalculator mycalc;
mycalc = new MyCalc(); // or mycalc = new OtherCalc(); or mycalc = new FinancialCalc();
将来,当更高效的实现出现时,您可以快速将mycalc
替换为实现该接口的其他类,并且您的程序将继续按预期运行。如果您不希望程序因未知的副作用或由MyCalc
等硬实现提供的实现细节而失败,这一点尤为重要。
答案 3 :(得分:0)
您通常会遇到一个类使用其他类的服务的情况。 “使用服务”意味着调用其公共方法。例如,CalculatorUser
使用MyCalc
- CalculatorUser
实例将在某处调用MyCalc.Sum()
。
现在想象一下,您向客户提供包含类CalculatorUser
和MyCalc
的应用程序。你可以这样写:
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
有更多的选择:我希望它能够在旧的简单计算器{{}之间进行选择1}}和一个新的,花哨的计算器,显示操作数,操作和结果!此外,这个选择必须在运行时进行。“
您意识到现在必须创建MyCalc
并更改MyFancyCalc
才能支持此新要求。您可能希望将另一个CalculatorUser
类型的成员添加到MyFancyCalc
,然后将另一个方法CalculatorUser
添加到PerformAdditionWithFancyCalc()
。但是,如果您的客户需要10种其他类型的计算器,您会为每种计算器添加新成员和方法吗?如果您使用户和服务提供商保持紧密耦合,那么每个需求的变化都会导致用户不断变化,解决方案就是让用户不了解特定的服务提供商,而只了解它提供的服务:这些服务的名称是什么,什么是输入值的类型,它们的输出类型是什么?这实际上是使服务提供商的公共接口的原因。 MyFancyCalc
无需知道计算器的具体实现 - CalculatorUser
或MyCalc
,所有它必须知道的是它使用的任何计算器都有一个接受两个的方法MyFancyCalc
Sum
值并返回int
。通过这种方式,您可以将用户与特定计算器分离,并使其能够使用任何以接口中描述的方式实现void
的计算器。如果您创建Sum
课程,则无需更改MyExtraFancyCalc
。
因此,为了满足新的要求(在运行时选择计算器),您可以编写如下内容:
CalculatorUser
用户被问到“你想用花式计算器吗?(y / n)”如果类型为“n”,则使用旧计算器,输出只是“3”,但如果答案是“y”,花式计算器使用并输出“1 + 2 = 3”
此示例显示了接口的功能(基本上是一个简单的模式称为依赖注入)。
在现实生活中,您经常会遇到这样的情况:您的客户拥有一个消费者(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;
}
}
)的应用程序,该应用程序从不或很少更改和插件(DLL)包含服务提供商的各种实现。主应用程序在运行时检测哪些插件可用并选择一个,具体取决于运行时生成的用户选择(或其他一些标准)。