1

Suppose I have two different classes that need to print different things: enter image description here

OrderManager wants to print the order history, and MenuManager wants to print perhaps a menu. Since both want to do a similar function (but actual implementation is different), is it advisable to use an interface like this? The problem is that the function "print" is not very well-defined and self-explanatory as opposed to "printOrderHistory", and I can only implement the method with the exact name as defined in the interface.

Is there a workaround to this or am I using the interface concept wrongly? In fact, nothing seems to be stopping me from just defining their own print functions for each class without an interface...

Ymi
  • 609
  • 6
  • 18

2 Answers2

3

This is the purpose of an interface! It defines a set of operations, and any class that implements this interface needs to implement these operations. This allows then to refer to a Printable object and call print(), knowing that it will perform as relevant for that object.

Your UML diagram, is therefore ambiguous: as OrderManager realizes (in java, "implements") Printable it should have its own print(). There are three solutions in UML and in Java:

  1. Comply to the interface, as your diagram promises, and rename printOrderHistory() into print(). This makes sense if there is only one meaningfull way to print and order manager, and if there are no other constraints.

  2. Add a print() operation (in java, "method") to OrderManager(), that just forwards the call to printOrderHistory(). But this looks like overcomplicated. It's justifiable only if the order manager would really e a printable, and if print() would add some relevant functionality such as selecting the right printing function depending on some factors.

  3. Use the adapter pattern and leave the OrderManager clean, using an intermediate object for the dirty things. This makes especially sense if the name of OrderManager's printing function is constrained, or if there are different printing functions for different purposes. THis is the most flexible approach

Here a possible diagram for solution 3: UML diagram with an adapter using composition over inheritance

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Class PrintableOrderManager should implement print() method not printOrderHistory(). Schouldn't it? – Stefan D. Jan 14 '22 at 10:25
  • @StefanD. Of course! Thank you very much for pointing it out. – Christophe Jan 14 '22 at 10:38
  • For the adapter pattern, is this understanding correct: inside printOrderHistory(), we initialize a PrintableOrderManager and pass on relevant attributes from OrderManager, and then use that instantiated PrintableOrderManager to call its print function? All this just so that I can have a function named "printOrderHistory" instead of "print" – Ymi Jan 14 '22 at 12:22
  • See https://stackoverflow.com/questions/28930476/is-there-a-convention-for-showing-overridden-methods-in-uml-static-class-diagram regarding overridden operations. – qwerty_so Jan 14 '22 at 15:37
  • 1
    @qwerty_so Interesting. In the adapter, `print()` is not really overridden, since the class does not inherit anything. But your remark would be very relevant for GoF's original adapter: it is based on an abstract class rather than on an interface (*at the time the book was written, Java did not exist and interfaces -- that James Gosling borrowed from Objective-C protocol, according to his foundational Java white paper -- were not yet mainstream)*. – Christophe Jan 14 '22 at 16:14
  • @Yui Yes that's the principle, except that you would not initialize an order manager inside printOrderHistory, but when you create the adapter. And this would be done outside these classes. Imagine you have a class `MonthlyReport` that prints a collection of `Printable`. In this case you'd append to that collection either objects that are natively printables, or -- in the case of your the order manager a printable adapter. THis is of course overhead comapred to solution 1 (i.e. just comply to the interface). – Christophe Jan 14 '22 at 17:04
  • Option 2 is also lighter (in fact the object plays the role of its own adapter): no need to instantiate any additional objects. But it comes at the cost of a light overhead (compared to 1), and it cannot cope with more complex scenarios (e.g. print differnt things for different context). If you want keep things as simple as possible but it's just about a self explaining name, go for solution 1, and explain in the javadoc commends that prinf() prints the order history. – Christophe Jan 14 '22 at 17:10
1

The idea with interfaces is to have a contract to fullfill. That means, that you have to you use method names and parameters defined in the interface when you build an implementation.

From my personal experience, it's always a bad idea to force an interface on classes, that are not similar, as in they do have a different purpose. While you could work with print() in both classes, it only makes sense if they are exchangeable in your code.