最少的 API 与便利性
我正在尝试设计将在我的应用程序内部使用的界面。我以 Google 为榜样,努力减少公共 API 的混乱。但是,有一些根据最小方法定义的便捷方法。在方便和整洁之间寻求平衡时,我应该考虑哪些因素?
Google 示例:在 HashBiMap
中 (文档):
为什么BiMap没有 getKeyForValue() 方法?
我们确实考虑过(Doug Lea 甚至 半开玩笑地建议命名它 泰格()!)。但你并不真正需要它; 只需调用 inverse().get() 即可。
Set 上的示例 接口:
add()
和 remove()
是最小方法,而 addAll()
和 removeAll()
> 是为了方便。 addAll()
可以用 add()
来实现,因此它并没有真正为客户端提供使用 Set
的新功能。但它确实清理了客户端代码。
我考虑过创建一个包含更多便利方法的 Utility
类。但随后我就摆脱了 OOP,并且必须在每次调用中将正在操作的对象作为参数包含在内。尽管我猜想这遵循了 Java 的 Collections
类的示例。
I am trying to design the interface that will be used internally for my application. Following Google's example, I strive to reduce public API clutter. However, there are some convenience methods that are defined in terms of the minimal methods. What factors should I consider as I seek a balance between convenience and tidiness?
Google example: in HashBiMap
(doc):
Why does BiMap have no
getKeyForValue() method?We did think about it (Doug Lea even
half-jokingly suggested naming it
teg()!). But you don't really need it;
just call inverse().get().
An example of this on the Set
interface: add()
and remove()
are minimal methods, whereas addAll()
and removeAll()
are for convenience. addAll()
could be implemented in terms of add()
, so it's not really giving the client new capabilities for working with a Set
. But it does clean up client code.
I have considered making a Utility
class that would include more convenience methods. But then I'm getting away from OOP, and I have to include the object being operated on as an argument in every call. Although I guess that follows the example of Java's Collections
class.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
只要类有机会(即使现在不能)以比客户端更有效的方式实现该 API,我肯定会提供额外的 API。 (例如,
Set.removeAll()
。)一般来说,每当它清理客户端代码时,我都会提供额外的 API。您能否提供一个 Google API 未提供看似有用的便利方法以支持客户端进行多次调用的示例?
I'd definitely supply the additional APIs whenever there's a chance that the class could (even if it doesn't today) implement that API in a more efficient manner than the client. (For example,
Set.removeAll()
.) And in general I'd supply the additional APIs whenever it cleans up client code.Could you provide an example of a Google API not providing a seemingly-useful convenience method in favor of having the client make multiple calls?
提供更多方法会使重写虚拟方法变得更加困难/危险。
例如考虑 add() 和 addAll()。 addAll() 是否调用 add()?它可以(它可以是一个简单的包装器,依次为每个元素调用 add() ),但它不是必须的。因此,如果您然后进行子类化并添加一些新的不变量(例如,您可能使 add() 将内容添加到单独的容器中以存储插入顺序,或者其他什么,容器有许多在不同应用程序中有用的变体) ,您现在必须知道 addAll() 是否调用 add()。如果确实如此,那就太好了,您的子类将保持正确的行为。但其实没必要!
当然,您可以通过适当的文档解决所有这些问题。但这让危险的事情变得更容易做。
一般来说,更好的方法是使类接口最小化、正交且完整,然后添加这些方便的实用方法非成员非友元。通过这样做,很明显他们只能调用公共接口,从而避免了整个问题。
有时会出现这样的情况:将实用程序设置为方法(而不是非成员非友元)会提供一些实现优势。排序就是一个例子;一般来说,排序(数组、双端队列、向量等)应该是非成员非友元,但对于链表来说,将 sort() 作为方法有一个特殊的优势。具体来说,一种方法可以操纵节点链接,从而使用就地合并排序——这对于任何合理的链表接口来说都是困难或不可能的。在这些特殊情况下,我建议使实用程序方法不可重写,并明确指示它们调用哪些方法(以及,在有意义的情况下,按什么顺序)。这最大限度地提高了子类不会破坏事物的机会。
Offering more methods makes overriding virtual methods more difficult/dangerous.
Consider for example add() and addAll(). Does addAll() call add()? It could (it could be a simple wrapper that calls add() for each element in turn), but it doesn't have to. So if you then subclass, and add some new invariant (perhaps, for example, you make add() add things to a separate container to store the insertion order, or whatever, there are many variations on containers that are useful in different applications), you now have to know if addAll() calls add(). If it does, great, your subclass maintains the correct behaviour. But it doesn't have to!
Sure, you can solve all this through appropriate documentation. But it makes dangerous things easier to do.
A better approach in general is to make the class interface minimal, orthogonal, and complete, and then add make these convenience utility methods non-member non-friends. By doing this, it is explicitly clear that they can only call the public interface, thereby avoiding the entire problem.
Occasionally a situation arises where making a utility a method (rather than a non-member non-friend) affords some implementation superiority. An example of this is sorting; generally sorting (of arrays, deques, vectors, etc.) should be a non-member non-friend, but for linked lists there is a particular advantage to making sort() a method. Specifically, a method can manipulate node links and thus use an in-place merge sort--something difficult or impossible for any reasonable linked list interface. In these exceptional cases I would suggest making the utility methods non-overridable, and explicitly indicating which methods they call (and, where it makes sense, in which order). This maximizes the chance that subclasses won't break things.
我会借用 John F. 的回答:
我想要所有我认为有用的便捷方法,而没有其他我不需要的方法。 Firefox 通过插件来适应这种事情。该浏览器支持基本浏览器应有的功能;然而,根据我个人的喜好,我可以通过插件来增强它。我也以同样的眼光看待便利方法。
允许我添加我喜欢的任何模块,这样我就可以拥有我想要的便捷方法。
这有很多方面:
http://martinfowler.com/bliki/HumaneInterface.html
I'll piggyback on John F.'s answer:
I want all the convenience methods I find useful without all the others I don't. Firefox, accommodates this sort of thing with plugins. The browser supports what a basic browser should; however, to my own personal preferences, I can beef it up with plugins. I see convenience methods in the same light.
Allow me to add in whatever modules I like so I can have just the convenience methods I want.
There are many sides to this one:
http://martinfowler.com/bliki/HumaneInterface.html
有几种可能的方法。我在其他地方看到的一种做法是拥有一个最小的核心 API,然后是一个“扩展”或“实用程序”API,这使得核心更加方便,但不能保证也得到支持或根本不得到支持。
一般来说,一旦您的开发者社区变得足够大,人们就会为您的 API 编写自己的扩展、帮助程序和实用程序。
There are a few possible approaches to this. One I've seen used elsewhere is to have a minimal core API, and then an "extensions" or "utilities" API which makes the core more convenient, but which is not guaranteed to be supported as well or at all.
Generally, once your developer community gets big enough, people write their own extensions, helpers, and utilities for your API anyway.
一种解决方案是提供一个接口和一个实现便利方法的抽象实现。例如,比较
和
java.util
包中的 。因此,客户端可以从抽象类派生子类并仅实现抽象方法。但就我个人而言,我不会因为将便利方法放入实用程序类中而感到羞耻。你无法用蹩脚的语言来编写纯面向对象的程序。 Java 这里缺少的是 traits 或扩展方法。据我所知,Java 7 的扩展方法正在讨论中。
One solution is to provide both an interface and an abstract implementation that implements the convenience methods. For an example, compare
and
in the
java.util
package. So client can subclass from the abstract class and just implement the abstract method.Personally however I would not feel ashamed to put the convenience methods in a utility class. You cannot program pure OO in a broken language. What Java misses here is either traits or extensions methods. As far I know, extensions methods are being discussed for Java 7.
只要实用方法确实有用,而不仅仅是您想象中的虚构(“如果狼人征服美国,有一天它可能会有用”),我会将它们添加到原始类中。
按照您的示例,addAll() 和removeAll() 是合理的实用程序方法,应该添加到Set 中。但是 addEven() 和 removeEven() 是不合理的,即使它们在某些特定情况下可能有用。
那么,如何判断哪些方法是合理的呢?只有两条路:经验和经验。
您必须在现实生活场景中尝试您的课程,看看哪些实用方法在一般情况下真正有用。
As long as the utility methods are really useful and not just figments of your imagination ("it might be useful someday if the werewolves conquer USA"), I would add them to the original classes.
Following your example, addAll() and removeAll() are utility methods that are reasonable and should be added to Set. But addEven() and removeEven() are not reasonable, even if they might be useful in some particular case.
Now, how to detect which methods are reasonable? Only two ways: experience and experience.
You have to try your classes on real life scenarios to see which utility methods are really useful in the general case.
如果不查看源代码,我会猜测 ArrayList.addAll 首先确保容量足够大,以便在操作发生时不会调整数组的大小。这对于实用程序类来说是不可能的,因为它是内部实现细节。
因此,答案取决于您是否需要类的内部能够执行实用程序方法。如果不是,那么就有充分的理由将其移出类,否则它应该成为类的一部分。
Without looking at the source I would guess that ArrayList.addAll first ensures that the capacity is large enough so that the array is not resized while the operation happens. This would not be possible for a utility class since it is an internal implementation detail.
So the answer depends on if you require the internals of the class to be able to do the utility method or not. If not then there is a strong argument for moving it out of the class, otherwise it should be part of the class.
另一个参考点:Java的
List
同时具有add()
和addAll()
。我认为重要的是自己清楚哪些是基本的,哪些是方便的。也分别称为原子(不能进一步细分)和化合物(可以通过原子方法组合而成)。
了解基本方法对于证明您的代码非常有用;并确保您的用户确实可以使用您的代码执行任何操作,因此它是完整(例如,确保没有仅在便捷方法中可用的功能)。
换句话说:图书馆的目的是有用。方便的方法使它更可用 - 但如果该库一开始就没有用,那么它们就没有帮助。基本方法有助于确保您的代码完整(数学完美的一个方面) - 但如果您的库一开始就没有用,那么它们也没有帮助。
换句话说:100% 专注于使其有用,并让可用性和完整性由此产生。
Another reference point: Java's
List
has bothadd()
andaddAll()
.I think the important thing is to be clear in your own mind which are fundamental and which are convenience. Also known as atomic (can't be further subdivided), and compound (can be formed by combining atomic methods), respectively.
Being aware of the fundamental methods is very useful for proving things about your code; and for ensuring that your users really can do anything with your code, so it is complete (e.g. ensure there is no feature that is only available within convenience methods).
Put another way: the purpose of your library is to be useful. Convenience methods make it more usable - but they don't help if the library isn't useful in the first place. Fundamental methods help ensure your code is complete (an aspect of mathematical perfection) - but again they don't help if your library isn't useful in the first place.
In other words: 100% focus on making it useful, and let usability and completeness flow from there.
我首选的方式是让 API 实现一个提供核心命令子集的接口,从而提供更简化的 API,同时为用户提供更多功能(如果用户愿意)。这样,如果用户想要简单,可以通过接口访问它,如果他们想要访问 addAllUsersExceptOnesNamedJeff(),则可以通过实际的类访问它。
My preferred way to provide a more simplified API yet give the user more power if they want it is to make the API implement an interface that gives a subset of the core commands. In this way the users can access it through the interface if they want simple, and through the actual class if they want access to addAllUsersExceptOnesNamedJeff().