接口先行:软件开发中的设计原则

在软件开发的过程中,经常会遇到需要构建基础架构的情况。虽然这可能不是整个应用程序的关键部分,但至少对于应用程序的某个部分来说,它是基础性的。通常会戴上思考帽,规划一下即将编写的代码,然后立即开始编码。如果采用的是测试驱动开发(TDD),可能会开始编写测试用例,但无论采用什么方法,很快就会开始编写一些类。但是,不应该这样做。

从接口开始

在看来,如果正在编写的代码是应用程序基础架构的一部分,应该从接口开始。如果已经开始翻白眼,心里抱怨说现在不得不编写一个类,然后有一个接口只是重新定义了方法,让代码膨胀,那么请深呼吸。以下是理由:

接口定义了类的输入和输出。如果只关注这一点,将看到其他开发者如何与类交互。如果其他人可以扩展工作,通过提供接口而不是强迫他们实现一个具体类,提供了更多的灵活性。

有一个叫做控制反转的高级编程主题,可以通过首先从接口开始,更容易地实现它,而可能在未来的道路上需要它。接口非常适合分层架构。

这不是一个很长的列表,也许实际上在告诉深呼吸的时候就已经完成了。也许对添加一些方法签名到文件中造成的代码膨胀感到非常沮丧,以至于甚至无法专注于刚才说的话。没关系,等冷静下来后,可以回来再试一次。

他人如何交互

接口定义了类将实现的暴露部分。如果已经对如何制作类并猜测应该制作什么公开/内部/受保护/私有有困难,这可能会对有所帮助。在接口中放置的任何东西都必须对类外部的其他人可见,因为接口实现具有公共成员。想要实现的所有其他函数呢?开始思考“嗯...也许这对类外部有用...”。但现在的问题是,有多有用?如果它那么有用,那么它是否足够基础,可以成为接口的一部分?如果不是,它应该被限定在类内部,而不是类外部。

如果读过关于什么是一个好的API的帖子,提到了很多关于接口的要点。如果认为这不值得几行代码的膨胀,可能是正确的。坚持下去。

扩展工作

所以整个API设置得像一个老板一样。认为一切都很好,因为它完成了工作并且通过了所有的测试。太棒了。但这只是今天很棒。明天有人需要扩展工作。有人想现在把代码作为一个参数提供他们自己的对象,假设。这是写的签名:

C# void CoolestFunctionEver(MyConcreteClass input);

不是因为一切都工作而感到兴奋,这并没有错。应该为此感到骄傲——恭喜。但现在,如果想为函数提供输入,必须像这样扩展类:

C# public NicksConcreteClass : MyConcreteClass { // all the good stuff inside here }

这看起来可能并不那么糟糕...但如果有另一个类已经存在,并且已经拥有大部分(如果不是全部的话)信息和功能,基本上必须复制它,或者创建某种复制构造函数来让它工作。如果在原始方法签名中这样做了,并且考虑了接口:

C# void CoolestFunctionEver(IMyInterface input);

那么世界将变得更加美好。可以利用现有的类,实现接口,然后把引用直接作为参数插入。不需要添加任何胶带或胶水来让它工作。它只是很好地工作。

如果还没有看到这个价值,会认为可能没有在大型项目中编写足够的代码。这并不是说听起来像是一个混蛋或任何东西,但这不是不寻常的情况,不幸的是,这比定义一个接口会导致更多的代码膨胀。

反转控制

如果还没有听说过这个,它将真正打开眼睛,让看到更好的代码设计。控制反转(IoC)让其他人“注入”他们自己的类和依赖项到现有的代码中。唯一能让这个很好地工作的方法是如果有接口。

如果函数操作在具体类上,那么它们依赖于这些具体类。依赖导致耦合和代码不是非常可扩展。可能还没有看到这种情况会出现,但让试着提供一个例子。

让假设有一个分层应用程序,有一个表示层,应用程序层和一个数据层。假设有整个应用程序工作,并且在数据层有一个基于MySQL的模型。一切都很好。有一天,有人走过来说“MySQL对客户来说一直很好,但为了进入这个其他市场细分,用户需要能够使用SQLite”。(好吧,也许这有点牵强,但仍然...)想,“没问题!没有使用MySQL的任何花哨的东西,所以实现SQLite将是5分钟的工作!”。但然后意识到它...到处都使用数据层访问MySQL模型类。到处都是。需要能够使用任何一个,所以不能只是用即将创建的SQLite模型替换它。哦哦。

如果从一开始就使用接口,这将是一件轻而易举的事情。字面上如果有一个在办公室附近,因为那将是一个5分钟的修复,让有很多时间散步。如果所有的代码引用模型而不是引用一个漂亮的干净的模型接口,比如说,IModel,那么可以在少数几个地方“注入”新模型。在少数几个地方实际上去初始化具体模型类,可以添加逻辑来做MySQL或SQLite,然后其他地方只看到它作为一个IModel。他们实际上不知道底层实现是什么,他们不在乎!

这一点,在看来,值得接口定义的“代码膨胀”。它可以节省几个小时和一瓶Advil的成本。

层层叠叠

这一点与IoC点和关于扩展工作的点有点联系。如果有一个分层架构,那么需要将代码分成功能不同的部分。表示层负责渲染事物,使它们对用户交互变得漂亮。应用程序层做繁重的工作,数据层做所有那些没有人想去想的低级事情。:) 接口有助于提供一个很好的抽象层。如果在另一个项目中声明类实现接口,或者想要在层之间移动它的定义,这不会对依赖接口的代码产生任何影响,只要接口定义不移动。

为什么这很好?嗯,为了夸大观点,让假设有人是一个绝对的美女,并决定在表示层中实现惊人的数据模型。太好了。好吧,尽管它在错误的位置,但它工作得很好,而且工作得很好。如果数据模型接口位于正确的位置,每个人都在使用接口,那么将数据模型类移动到正确的位置的工作就很小了。剪切它,将其移动到正确的项目/层,并更改一个/几个初始化引用的地方。所有引用接口的代码都可以绝对不受影响。谈论bauss模式重构。只是将大部分数据层在项目和层之间移动,而不必担心破坏太多代码。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485