拆分大型接口


9

我正在使用一个带有大约50种方法的大型接口来访问数据库。该界面是由我的一位同事编写的。我们讨论了这一点:

我:50种方法太多了。这是代码气味。
同事:我该怎么办?您需要数据库访问权限-您拥有它。
我:是的,但是目前还不清楚,将来很难维护。
同事:好的,您是对的,不好。界面应该如何?
我:有5种方法返回的对象各有10种方法怎么样?

嗯,但这不一样吗?这真的会导致更加清晰吗?值得付出努力吗?

我时不时地处于需要界面的情况,而想到的第一件事就是一个大型界面。是否有通用的设计模式?


更新(响应SJuan的评论):

“种类的方法”:它是用于从数据库中获取数据的接口。所有方法都具有形式(伪代码)

List<Typename> createTablenameList()

方法和表并不完全是1-1关系,而是更多地关注您总是从数据库中获得某种列表的事实。


12
缺少相关信息(您拥有哪种方法)。无论如何,我的猜测是:如果只按数字除法,那么您的同事是正确的,那么您就没有任何改善。一个可能的划分标准将由返回的“实体”(几乎等同于一个表)(所以a UserDao和a CustomerDao和a ProductDao
SJuan76 2014年

实际上,某些表在语义上接近于形成“ clique”的其他表。方法也是如此。
TobiMcNamobi 2014年

有可能看到代码吗?我知道它已经4岁了,您现在可能已经解决了:D但我很想考虑这个问题。我以前解决过类似的问题。
clankill3r

@ clankill3r实际上,我再也无法访问使我发布上述问题的特定界面了。不过,这个问题更为普遍。想象一个数据库,其中包含大约50个表,并且每个表都采用类似List<WeatherDataRecord> createWeatherDataTable() {db.open(); return db.select("*", "tbl_weatherData");}
TobiMcNamobi

Answers:


16

是的,有50种方法是一种代码气味,但是一种代码气味意味着需要重新审视它,而不是它会自动出错。如果每个使用该类的客户端都可能需要全部50种方法,则可能没有理由将其拆分。但是,这不太可能。我的观点是,任意拆分一个接口要比根本不拆分它更糟。

没有一个单一的模式可以解决此问题,但是描述所需状态的原则是接口隔离原则(SOLID中的“ I”),该原则规定不应强迫任何客户端依赖于不使用的方法。

ISP描述确实提示您如何修复它:查看客户端。通常,仅通过查看一个类,似乎一切都属于同一类,但是当您查看使用该类的客户时,就会出现明显的分歧。设计界面时,请始终首先考虑客户端。

确定是否应该在何处拆分接口的另一种方法是进行第二种实现。通常最终会发生的是您的第二种实现不需要很多方法,因此显然应该将它们拆分为各自的接口。


这个和utnapistim的答案确实很棒。
TobiMcNamobi 2014年

13

同事:好的,您是对的,不好。界面应该如何?

我:有5种方法返回的对象各有10种方法怎么样?

这不是一个很好的标准(实际上,该语句中根本没有任何标准)。您可以按以下方式对它们进行分组(例如,假设您的应用程序是金融交易应用程序):

  • 功能(插入,更新,选择,事务,元数据,模式等)
  • 实体(用户DAO,存款DAO等)
  • 应用领域(财务交易,用户管理,总计等)
  • 抽象级别(所有表访问代码是一个单独的模块;所有选择的API在各自的层次结构中,事务支持是独立的,模块中的所有转换代码,模块中的所有验证代码,等等)

嗯,但这不一样吗?这真的会导致更加清晰吗?值得付出努力吗?

当然,如果您选择正确的标准。如果您不这样做,绝对不是:)。

一些例子:


3

我时不时地处于需要界面的情况,而想到的第一件事就是一个大界面。是否有通用的设计模式?

这是一种称为单片类的设计反模式。在一个类或接口中有50个方法可能违反了SRP。之所以出现整体类,是因为它试图成为所有人的一切。

DCI解决了方法膨胀问题。本质上,一个类的许多职责可以分配给仅在特定上下文中相关的角色(分担给其他类)。角色的应用可以通过多种方式来实现,包括混合器或装饰器。这种方法使课程保持重点和精益。

5种返回对象(每个对象都有10种方法)的方法怎么样?

这建议在实例化对象本身时实例化所有角色。但是,为什么要实例化您可能不需要的角色?而是在实际需要的上下文中实例化一个角色。

如果发现对DCI的重构并不明显,则可以采用更简单的访问者模式。它提供了类似的好处,而无需强调用例上下文的创建。

编辑:我对此的想法已经改变了一些。我提供了一个替代答案。


1

在我看来,其他所有答案都没有抓住重点。关键是接口应该理想地定义行为的原子块。就是SOLID中的I。

一个班级应该负一个责任,但这仍然可以包括多种行为。为了坚持使用典型的数据库客户端对象,它可以提供完整的CRUD功能。那将是四种行为:创建,读取,更新和删除。在纯SOLID环境中,数据库客户端将不实现IDatabaseClient,而是实现ICreator,IReader,IUpdater和IDeleter。

这将带来许多好处。首先,仅需阅读类声明,即可立即了解有关该类的很多知识,该类实现的接口说明了整个故事。其次,如果将客户端对象作为参数传递,则现在有不同的有用选项。它可以作为IReader传递,并且可以确信被叫方只能阅读。可以分别测试不同的行为。

但是,当涉及测试时,通常的做法是在一个类上打一个接口,该类是完整类接口的1对1副本。如果只进行测试,那么这可能是有效的方法。它使您可以快速制作假人。但这几乎不是SOLID,而是真正出于专用目的而滥用接口。

因此,是的,有50种方法是一种气味,但它的意图和目的取决于它的好坏与否。这当然不是理想的。


0

数据访问层往往将许多方法附加到一个类上。如果您曾经使用Entity Framework或其他ORM工具,则将看到它们生成100多种方法。我假设您和您的同事正在手动实施它。没必要有代码气味,但是看起来并不漂亮。不知道您的领域,很难说。


方法或属性?
JeffO 2014年

0

我几乎对所有带有FP和OOP的api都使用协议(如果需要,可以称其为接口)。(还记得矩阵吗?没有具体概念!)当然有具体的类型,但是在程序的范围内,每种类型都被认为是在某种上下文中起作用的某种类型。

这意味着可以将整个程序中传递给对象,函数等的对象视为具有命名行为集的抽象实体。可以将对象视为扮演某种协议集的角色。一个人(具体类型)可以是一个男人,一个父亲,一个丈夫,一个朋友和一个雇员,但是我无法想象有很多功能会认为该实体的总和超过2。

我想一个复杂的对象可能会遵守许多不同的协议,但是您仍然很难获得50方法的api。大多数协议有1或2种方法,可能是3种,但从来没有50种!任何拥有50种方法的实体都应该由一堆较小的组件组​​成,每个组件都有自己的职责。整个实体将提供一个更简单的接口,该接口抽象出其中的所有api。

与其从对象和方法的角度思考,不如从抽象和契约以及主题在某种情况下所扮演的角色开始思考。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.