65.9K
CodeProject 正在变化。 阅读更多。
Home

API:别忘了非公开 API!

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (1投票)

2013年8月14日

CPOL

8分钟阅读

viewsIcon

12086

别忘了非公开 API

Image courtesy of FreeDigitalPhotos.net

背景

面向对象编程的角度来看,应用程序编程接口(API)通常指的是其他开发者与你的类和接口的公共成员进行交互的方式。当然,API 也可以用来描述如何与 Web 服务(或其他类型的服务)进行交互,但在此讨论中,我将范围限定在接口和类。将 API 的定义限制在 `public` 成员(或 C# 中的“public”在其他语言中的等效项)忽略了它所包含的一个重要部分。这篇帖子的目的是阐明,在我看来,为什么我认为忽略非 `public` API 会导致糟糕的框架和 API 设计。

API 和受众

我之前写过我认为什么 makes A Good API,并且在Code Project 上关于同一篇文章有一些评论,这让我思考到这个话题:受众。当你编写许多人认为的良好、干净的代码时,有很多事情需要考虑。不可能取悦所有人,因为每个人都有某种他们遵循的最佳实践、指南或约定,因为他们认为那是最好的。所以,我不会告诉你某种方式是最好的……我只是想让你意识到你的受众,这样你就能做出更好的决定。

好吧,好吧……那么我所说的受众是什么意思呢?我将把你的受众(API 的消费者)泛化为两个不同的类别。第一类是那些将使用实现你的接口和具体类的引用的开发者。他们将使用你定义的实例。例如,让我们考虑一个内置于.NET framework 的东西:List<T> 类。你的第一类 API 消费者将仅仅使用框架提供的这个类实例。他们将创建新的实例并将它们传递给函数使用,或者创建返回 `List<T>` 实例的属性,或者声明 `List<T>` 类型的变量,等等……他们将其按原样使用,正如框架提供的。

第二类 API 消费者是那些将扩展你的接口和类的人。一个很好的例子是EventArgs 类。这个类非常基础;它什么都不做!任何想使用 `EventArgs` 类的人基本上都需要创建自己的继承自 `EventArgs` 的类。另一个例子是Exception 类。同样,这个类是为了让人们用他们自己的实现来扩展它而构建的。我猜这两个例子都相当原始,因为基类没有提供太多功能,但如果你的类想让子类覆盖默认行为怎么办?我稍后会给出一些更具体的例子。

受众的意图

定义了这两种通用的消费者类型后,就更容易考虑人们可能如何以不同的方式使用你的 API。第一类消费者希望能够轻松调用你定义的方法,并轻松创建你的 API 的核心对象/接口。这意味着他们需要提供的输入应该非常基础,所以可以使用内置接口,或者你定义的易于创建的其他类。这些消费者还想要信息丰富的返回值和类。为什么?因为这让他们的生活更轻松!如果他们只需要提供少量信息就能得到很多反馈,那么他们就能利用他们拥有的数据做更多的事情。他们不需要(而且肯定也不想)以复杂的方式调用 10 个不同的东西来获取少量数据。

第二类消费者则采取完全相反的观点。原因如下。在第一种情况下,第一组消费者只想向方法传递最少的信息,而第二类消费者则希望传递大量信息。这类消费者需要完成某项工作或返回某些数据,所以他们获得的信息越多,就越容易完成工作。同样,他们希望方法的返回值尽可能简单。为什么?这让他们的工作更容易!如果他们负责从你接口定义的方法中返回一个极其复杂的类,而该方法的输入只是最少的信息,这使得第二类消费者的工作非常困难。

记住这些相似点和区别的最佳方法是,根据你所谈论的 API 消费者类型,数据流是相反的。第一种类型的消费者希望付出一点,得到很多;第二种类型的消费者希望付出很多,得到一点。有道理吧?每个人都想做容易的事。

这里的重点是,取决于你认为你的 API 的主要受众是谁,它将影响你的结构方式。这正是为什么忽略非公开 API 会是一个巨大的错误。忽略 API 的这部分会使整个东西难以扩展,因为你的基类不能轻易地在其之上构建。如果你忽略了第二类 API 消费者的需求,你实际上会让他们的生活非常困难。

示例:WinForms

如果你进行过 C# 的桌面应用程序开发,你很可能使用过(或至少听说过)WinForms。一些刚开始进行桌面应用程序开发的人可能已经开始使用Windows Presentation Foundation(WPF),但相同的概念也适用于这里。在我看来,WinForms 是一个很好的 API 示例,它同时拥有公共和非公共组件,并且是为两种受众类型设计的。让我们从第一类 API 消费者开始。

如果你接触过 WinForms,你很可能熟悉 Visual Studio 中提供的 Windows Form Designer。如果你是……那么恭喜你!你就是我描述的第一类 API 消费者。通过使用内置类,如 `Button`、`TextBox` 和 `Label`,你正在使用框架提供的开箱即用组件,并使用这些控件提供的公共 API。你将使用诸如这些控件上提供的 `Text` 属性,并通过它们的事件(即挂接到点击事件或文本更改事件)与它们进行交互。你将使用 `public` API。这没什么不对!

//
// MyButton
//
this.MyButton.Location = new System.Drawing.Point(146, 84);
this.MyButton.Name = "MyButton";
this.MyButton.Size = new System.Drawing.Size(75, 23);
this.MyButton.TabIndex = 0;
this.MyButton.Text = "Click Me!";
this.MyButton.UseVisualStyleBackColor = true;
this.MyButton.Click += new System.EventHandler(this.MyButton_Click);

private void MyButton_Click(object sender, EventArgs e)
{
    // do stuff!
}

现在,WinForms 的创建者并非愚蠢。他们知道他们无法提供你可能需要的每一种控件。他们让 WinForms 的 API 对其基类的非公共成员提供了很好的支持!那么,我说的这个是什么意思?

假设我们想要一个我们自己的炫酷按钮类。因为我们的按钮很炫酷,我们总是想在用户点击它时告诉他们它有多炫酷。现在,如果框架的 API 设计得很糟糕,你可能被迫从头开始编写一个按钮。考虑到你已经获得的内置控件的复杂性,这相当乏味。对于这个例子,你当然可以创建一个 `button` 并将一个事件处理程序挂接到点击事件(使用 `public` API),但如果你想在整个用户界面中到处重用它怎么办?你会想拥有你自己的 `FancyButton` 类,它内置了这个行为,以便你可以轻松重用它。没问题。

private class FancyButton : Button
{
    protected override void OnClick(EventArgs e)
    {
        base.OnClick(e);
        this.Text = "The fancy button was clicked.";
    }
}

WinForms 中的非公共 API 让你能够访问基类的内置行为。你不需要挂接事件来完成工作,你实际上可以覆盖 `OnClick` 方法,甚至阻止点击事件的发生!对非公共 API 的关注允许开发者在不从头设计的情况下扩展内置类。

摘要

编写一个好的 API 需要大量的实践和经验。关于什么构成一个设计良好的 API,也有很多不同的意见。在我看来,你需要仔细考虑你的 API 将如何被使用。考虑我定义的两种通用的 API 消费者类型:使用你定义的接口和类的公共部分的消费者,以及想要扩展你定义的类以提供他们自己相关实现的消费者。这两种消费者类型想要的东西非常不同,为了满足第二类消费者,你绝对不能忘记非公开 API。

一些思考题

  • 我如何才能最好地猜测开发者将如何使用我的 API?
  • 我正在为我的框架和 API 提供基类。人们可以通过继承轻松地扩展它们吗?人们会**想要**扩展它们吗?
  • 什么能让我的公共 API 对其他开发者更容易使用?
  • 什么能让我的非公开 API 对其他开发者更容易构建我的基类?

文章 API: Don’t Forget About The Non-Public API! 首次出现在 Dev Leader

© . All rights reserved.