CodeGym /课程 /C# SELF /接口契约

接口契约

C# SELF
第 23 级, 课程 3
可用

1. 引言

所以我们已经发现接口其实就是很强的契约。那么接口级编程到底是啥?其实就是一种设计哲学,意思是:“依赖抽象,不要依赖具体实现。”

再来个比喻。你想煮咖啡,你买了台咖啡机。你其实不在乎它是具体哪款“超级无敌机V3000”还是“巨能Barista 5000”,对吧?你只关心这玩意能“做咖啡”——也就是实现了ICoffeeMaker这个“契约”。你只要按下“做咖啡”按钮,它就能搞定。至于它是用咖啡豆、胶囊还是别的啥,对你来说根本不用管。

在代码里,这就意味着你的方法里不用死盯着Document或者Image这种具体类型,而是要用IPrintable。你的代码就能和任何实现了这个接口的对象打交道,根本不用知道它具体是啥类型。

接口契约——就是保证每个实现接口的类都必须实现接口里所有成员(方法、属性、事件)。对程序来说,任何实现了接口的对象都支持一套“能力”,你完全可以放心用,根本不用管它内部怎么实现的。

比如你和同事约好总是在大厅的红沙发见面。他怎么去——坐电梯、走楼梯还是爬墙——你都不用管。关键是:他一定会到。接口就是代码里的“红沙发”。

例子


public interface IPrintable
{
    void Print();
}

契约:谁实现了IPrintable,谁就得能把自己打印到屏幕上。怎么打印——那是细节。

2. 契约作为系统各部分的交互点

实际怎么用

一个系统可能有几十个类,都是不同的人、不同时间、不同目的写的。但只要大家都遵守同一个契约(也就是实现同一个接口),你就能统一地用它们。

真实应用例子:想象一下客户数据库。有Customer类、Employee类、Contractor类。每个类存信息的方式都不一样,但只要它们都实现了比如IContactInfo接口,这个接口要求有GetEmail()和GetPhoneNumber()方法,那外部代码根本不用管对象是什么类型——只要能拿到email和电话就行。


public interface IContactInfo
{
    string GetEmail();
    string GetPhoneNumber();
}

public class Customer : IContactInfo
{
    public string Email { get; set; }
    public string Phone { get; set; }

    public string GetEmail() => Email;
    public string GetPhoneNumber() => Phone;
}

// Employee和Contractor同理...

现在如果你要打印所有公司有联系的人的数据(不管是谁),你只要遍历IContactInfo列表,调用方法——就全搞定了。

3. “接口级编程”

接口级编程——就是写代码时只依赖接口(契约),不依赖具体类。只要类实现了接口,怎么实现都行,只要满足接口要求就OK。

为啥这么重要?

  • 可扩展性:加新类型很容易,老代码不用动。
  • 可测试性:测试时可以随便用mock(假对象)替换。
  • 灵活性:实现可以天天变,接口一直不变。
  • 架构清晰:模块之间耦合度低,复用性高。

例子——熟悉的程序:“银行账户”

之前的例子里我们有个抽象类BankAccount,有个抽象方法Withdraw(),具体类型(SavingsAccount、CheckingAccount)实现细节。

现在我们加个接口——比如用来输出余额信息:


public interface IBalanceReporter
{
    void ReportBalance();
}

public abstract class BankAccount : IBalanceReporter
{
    public double Balance { get; set; }

    public abstract void Withdraw(double amount);

    public void ReportBalance()
    {
        Console.WriteLine($"当前余额: {Balance} 欧元");
    }
}

现在所有用IBalanceReporter的代码都能调用ReportBalance(),不用管具体账户类型。

4. 通过契约实现通用处理和多态

例子:通用处理器

只要用接口,你就能写出和对象类型无关的通用方法:


static void PrintAllBalances(IBalanceReporter[] accounts)
{
    foreach (var reporter in accounts)
    {
        reporter.ReportBalance();
    }
}

这个列表里可以放任何实现了IBalanceReporter的对象:SavingsAccount、CheckingAccount,甚至MockAccountForTesting。没有黑魔法,全部都能用。

图解:接口契约带来的好处


+-------------------+      实现           +-------------------+
|   BankAccount     |  <---------------- |  IBalanceReporter |
|  (SavingsAccount) |                    |-------------------|
|  (CheckingAccount)|                    | + ReportBalance() |
+-------------------+                    +-------------------+
         |                                   ^
         |                                   |
         +-----------+-----------------------+
                     |
        任何实现了契约的其他类
图:接口契约作为交互点

5. 契约、业务逻辑和架构设计

接口契约把类应该做什么和怎么做分得很清楚。所以搞架构的人都喜欢先用接口来设计系统逻辑。经常是先有接口(契约),实现可以后面再写。

生活中的例子:支付系统

电子钱包、银行卡、PayPal、加密货币……每种都有自己的细节,但如果抽象出来做个IPaymentProvider接口:


public interface IPaymentProvider
{
    void Pay(decimal amount);
    bool Refund(decimal amount);
}

和这个接口打交道的代码根本不用管是卡还是账户。对架构和实际开发都很方便:要支持新支付方式,老代码不用动。

6. 把业务逻辑提取到契约里

契约可以把主要的业务规则提到接口层,细节(比如额度检查、返现啥的)留给具体类去实现。

再来个例子


public interface ILogger
{
    void LogInfo(string message);
    void LogError(string message);
}

public class ConsoleLogger : ILogger
{
    public void LogInfo(string message)  => Console.WriteLine($"INFO: {message}");
    public void LogError(string message) => Console.WriteLine($"ERROR: {message}");
}

public class FileLogger : ILogger
{
    public void LogInfo(string message)  => /* 写入文件 */;
    public void LogError(string message) => /* 写入文件 */;
}

然后客户端代码就像这样(根本不用管用的是什么logger):


void DoWork(ILogger logger)
{
    logger.LogInfo("工作开始了。");
    // ... 做点啥 ...
    logger.LogError("工作出错了。");
}

这就是接口级编程:客户端代码只依赖契约(接口),不依赖具体实现。

7. 错误和常见坑

新手常犯的错:代码死绑具体类,不用接口。这样会有一堆问题:

  • 想换东西或测试很难——得改一堆代码。
  • 加新类型要到处改。
  • 模块之间耦合太紧。

反过来,如果一开始就用接口来设计系统、参数都用接口传递,就能做到低耦合和高灵活。

最重要的建议:多想想模块之间是“契约”在交互,而不是具体实现。

2
任务
C# SELF, 第 23 级, 课程 3
已锁定
基于接口的编程
基于接口的编程
2
任务
C# SELF, 第 23 级, 课程 3
已锁定
通用处理器通过接口
通用处理器通过接口
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION