4.4.1 定义和实现接口
下面开发一个遵循接口继承规范的小例子来说明如何定义和使用接口。这个例子建立在银行账户的基础上。假定编写代码,最终允许在银行账户之间进行计算机转账业务。许多公司可以实现银行账户,但它们都是彼此赞同表示银行账户的所有类都实现接口IBankAccount。该接口包含一个用于存取款的方法和一个返回余额的属性。这个接口还允许外部代码识别由不同银行账户执行的各种银行账户类。我们的目的是允许银行账户彼此通信,以便在账户之间进行转账业务,但还没有介绍这个功能。
为了使例子简单一些,我们把例子的所有代码都放在同一个源文件中,但实际上不同的银行账户类会编译到不同的程序集中,而这些程序集位于不同银行的不同机器上。第37章在讨论远程通信时,将介绍位于不同机器上的.NET程序集如何通信。但那些内容对于这里的例子来说过于复杂了。为了保留一定的真实性,我们为不同的公司定义不同的命名空间。
首先,需要定义IBank接口:
namespace Wrox.ProCSharp
{
public interface IBankAccount
{
void PayIn(decimal amount);
bool Withdraw(decimal amount);
decimal Balance
{
get;
}
}
}
|
注意,接口的名称为IBankAccount。接口名称传统上以字母I开头,以便知道这是一个接口。
注意:
如第2章所述,在大多数情况下,.NET用法规则不鼓励采用所谓的Hungarian表示法,在名称的前面加一个字母,表示对象的类型,接口是Hungarian表示法推荐采用的几种名称之一。
现在可以编写表示银行账户的类了。这些类不必彼此相关,它们可以是完全不同的类。但它们都表示银行账户,因为它们都实现了IBankAccount接口。
下面是第一个类,一个由Royal Bank of Venus运行的存款账户:
namespace Wrox.ProCSharp.VenusBank
{
public class SaverAccount : IBankAccount
{
private decimal balance;
public void PayIn(decimal amount)
{
balance += amount;
}
public bool Withdraw(decimal amount)
{
if (balance >= amount)
{
balance -= amount;
return true;
}
Console.WriteLine("Withdrawal attempt failed.");
return false;
}
public decimal Balance
{
get
{
return balance;
}
}
public override string ToString()
{
return String.Format("Venus Bank Saver: Balance = {0,6:C}", balance);
}
}
}
|
这个类的实现代码的作用一目了然。其中包含一个私有字段balance,当存款或取款时就调整这个字段。如果因为账户中的金额不足而取款失败,就会显示一个错误消息。还要注意,因为我们要使代码尽可能简单,所以不实现额外的属性,例如账户持有人的姓名。在现实生活中,这是最基本的信息,但对于本例来说,这是不必要的。
在这段代码中,唯一有趣的是类的声明:
public class SaverAccount : IBankAccount |
SaverAccount派生于一个接口IbankAccount,我们没有明确指出任何其他基类(当然这表示SaverAccount直接派生于System.Object)。另外,从接口中派生完全独立于从类中派生。
SaverAccount派生于IBankAccount,表示它获得了IBankAccount的所有成员,但接口并不实际实现其方法,所以SaverAccount必须提供这些方法的所有实现代码。如果没有提供实现代码,编译器就会产生错误。接口仅表示其成员的存在性,类负责确定这些成员是虚拟还是抽象的(但只有在类本身是抽象的,这些成员才能是抽象的)。在本例中,接口方法不必是虚拟的。
为了说明不同的类如何实现相同的接口,下面假定Planetary Bank of Jupiter还实现一个类Gold Account来表示其银行账户:
namespace Wrox.ProCSharp.JupiterBank
{
public class GoldAccount : IBankAccount
{
// etc
}
}
|
这里没有列出GoldAccount类的细节,因为在本例中它基本上与SaverAccount的实现代码相同。GoldAccount与VenusAccount没有关系,它们只是碰巧实现相同的接口而已。
有了自己的类后,就可以测试它们了。首先需要一些using语句:
using System; using Wrox.ProCSharp; using Wrox.ProCSharp.VenusBank; using Wrox.ProCSharp.JupiterBank; |
然后需要一个Main()方法:
namespace Wrox.ProCSharp
{
class MainEntryPoint
{
static void Main()
{
IBankAccount venusAccount = new SaverAccount();
IBankAccount jupiterAccount = new GoldAccount();
venusAccount.PayIn(200);
venusAccount.Withdraw(100);
Console.WriteLine(venusAccount.ToString());
jupiterAccount.PayIn(500);
jupiterAccount.Withdraw(600);
jupiterAccount.Withdraw(100);
Console.WriteLine(jupiterAccount.ToString());
}
}
}
|
这段代码(如果下载本例子,它在BankAccounts.cs文件中)的执行结果如下:
C:>BankAccounts Venus Bank Saver: Balance = £100.00 Withdrawal attempt failed. Jupiter Bank Saver: Balance = £400.00 |
在这段代码中,一个要点是把引用变量声明为IBankAccount引用的方式。这表示它们可以指向实现这个接口的任何类的实例。但我们只能通过这些引用调用接口的方法—— 如果要调用由类执行的、不在接口中的方法,就需要把引用强制转换为合适的类型。在这段代码中,我们调用了ToString()(不由IBankAccount实现),但没有进行任何显式转换,这只是因为ToString()是一个System.Object方法,C#编译器知道任何类都支持这个方法(换言之,从接口到System.Object的数据类型转换是隐式的)。第6章将介绍强制转换的语法。
接口引用完全可以看做是类引用—— 但接口引用的强大之处在于,它可以引用任何实现该接口的类。例如,我们可以构造接口数组,其中的每个元素都是不同的类:
IBankAccount[] accounts = new IBankAccount[2]; accounts[0] = new SaverAccount(); accounts[1] = new GoldAccount(); |
但注意,如果编写了如下代码,就会生成一个编译错误:
accounts[1] = new SomeOtherClass();// SomeOtherClass does NOT implement
// IBankAccount: WRONG!! |
这会导致一个如下所示的编译错误:
Cannot implicitly convert type 'Wrox.ProCSharp.SomeOtherClass' to 'Wrox.ProCSharp.IBankAccount' |
| 回书目 上一节 下一节 |