Minimal API v. Convenience
Asked Answered
N

9

10

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().

Google Collections FAQ

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.

Notogaea answered 1/1, 2010 at 4:50 Comment(3)
Since google is god, go with google.Inverson
Your example is borderline. Fluent interfaces change the scenario a bit. Calling V value = originalMap.inverse().get() is arguably better than BiMap<V,K> tempMap = originalMap.inverse(); V value = tempMap.get(); Also method combination is better than having to create another separate function with more code than just calls to the methods of the class.Zetland
This is one of the problems that Scala's "traits" were designed to solve (javaforyou.wordpress.com/2009/07/11/traits-in-scala-deep-dive). Sadly, the Scala tools are still rubbish....Pitapat
N
4

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?

Naresh answered 1/1, 2010 at 4:57 Comment(1)
+1 I agree that the plausibility of a more efficient implementation is of prime importance in deciding whether to offer a technically-redundant operation on an interface. In BiMap's case, it's implausible for getKeyForValue() to be able to do anything that inverse().get() can't do, so we cut it.China
N
4

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.

Neutrophil answered 1/1, 2010 at 5:34 Comment(0)
A
2

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

Acarpous answered 9/8, 2010 at 20:27 Comment(0)
I
1

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.

Imperfection answered 1/1, 2010 at 4:59 Comment(0)
C
1

One solution is to provide both an interface and an abstract implementation that implements the convenience methods. For an example, compare

interface List ...

and

class AbstractList implements List ...

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.

Cis answered 1/1, 2010 at 5:13 Comment(0)
Z
0

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.

Zetland answered 1/1, 2010 at 5:3 Comment(0)
C
0

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.

Chromite answered 1/1, 2010 at 5:9 Comment(0)
C
0

Another reference point: Java's List has both add() and addAll().

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.

Chemotherapy answered 1/1, 2010 at 5:20 Comment(0)
L
0

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().

Lutz answered 1/1, 2010 at 13:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.