0

Assume that there is a library and it provides an interface to its users in order to callback them. Users implement this interface and receive notifications from the library.

Let's say, ICallback is the interface, and Notify1(arg1), ..., Notify5(arg5) are the interface methods.

interface ICallback;
+ Notify1(arg1)
+ ...
+ Notify5(arg5)

This library also provides a concrete class of the ICallback and distributes this class with the library package.

class CallbackAdapter : implements ICallback
+ Notify1(arg1)
+ ...
+ Notify5(arg5)

The owner of the library calls this concrete class as "adapter". Users are encouraged to use this class instead of interface because it is claimed that;

a. You may not want to implement all notify methods 1 to 5 because you want to keep your code clean. If you extend this adapter class instead of implementing interface directly, then you can select methods to override. (This is the main motivation written in the class document)

b. If the interface is changed, say Notify6() is added, then user don't have to change anything in the client code. No compilation error occurs when version is bumped. (This is an extra motivation suggested by people who extends adapter class)

Note that, some overridden methods of CallbackAdapter class aren't just empty methods, they contain code, and do some work with objects provided by library (args).

This design really disturbs me. Firstly, I will explain why I'm not comfortable with it and then I will suggest a solution to above motivations. Finally, the question will be asked at the end.

1. Favor object composition over class inheritance

When user code extends CallbackAdapter, there will be coupling between user code and an external class. This can break the user code easily since encapsulation is terribly broken by inheriting an external class. Anyone can have a look at Effective Java 1st Edition, Item 14 for more details.

2. Ambiguous adapter pattern

I think the adapter pattern is misused here unless this isn't another pattern with an "adapter" postfix in the name.

As far as I know, the adapter pattern is used when there is an external alternative implementation that we want to use but our interface doesn't match to use alternative solution directly. Hence, we write an adapter to gain capabilities of alternative implementation (adaptee).

For all the adapter examples that I've seen, there is an adaptation to a concrete class, a class which does a real job and have a capability. However, for the given example, the adaptation is against an interface but not a concrete class.

Is this a valid adapter as we know it? I don't think so.

There is a statement at applicability section of adapter pattern in the book of GoF Design Patterns:

Use the Adapter pattern when you want to use an existing class, and its interface does not match the one you need.

I think developers misinterpreted the word "interface" in this statement. Author mentions of adaptee's interface which eventually declares the methods of concrete adaptee class. It seems that developers thought like this: There will be a class that a user created, this class will implement the interface provided by us (as library developers), user will want to use this existing class now and then, one day we will change interface, and won't match with user code, so that we must provide an adapter, and distribute this adapter with our library.

I have just tried to understand the motivation of this adapter design. Above reasoning may be wrong but this doesn't make code safer, it's still insecure because of 1.

3. Distribution of adapter class

If there will be an adapter class, it shouldn't be in the library package, it should be in the user package, this makes more sense to me because user adapts his code to work with new implementations.

4. It's not good to break contract silently

Interfaces define behavior and are used to make contracts among participants. If a contract ends and a new contract starts, I think both sides must be aware of this change. Breaking a contract silently as given in the above example, may produce undefined behavior that we can't notice at compile time but encounter on run time.

Solutions to Motivations

a. Just override methods and keep them empty if you don't want to do anything with them.

If user can work without new method, Notify6(), this smells like a large interface problem. A segregation may be considered.

If you insist to have such a feature, you can design a callback register mechanism. Users can register any method they want. May register any of them, all of them or none of them. Keep function objects in the library, and callback registered functions. This seems a better OOP design compared to using inheritance.

b. Just avoid silent contract breaks. If an interface changes, it's more secure to see compilation errors and solve them one by one at compile time.

Discussed design is currently in use in a widely used open source project. I've explained thoughts in my mind about it. All of them seems sensible to me.

I don't think discussed motivations are huge gain. I can't understand why does someone take the risks given at 1 and 4. Help me to understand advantages of such design. What am I missing here?

6
  • 3
    I'm less interested in whether this interface meets the textbook definitions of the Gang of Four, and more interested in whether or not this interface will meet your specific requirements for ease of use and maintenance. Will it? Commented May 23, 2020 at 15:55
  • @RobertHarvey Both solutions seem to meet requirements but one is more secure in my opinion. Commented May 23, 2020 at 16:06
  • Can you provide a little more concrete example? I'm having a hard time imagining why a callback would possibly have more than one method.
    – Telastyn
    Commented May 23, 2020 at 16:10
  • 1
    @Telastyn There are different kind notify methods, like NotifyError, NotifyData, NotifySomethingElse.. Commented May 23, 2020 at 16:12
  • Why wouldn't you put each of those callbacks on its own interface? Commented May 23, 2020 at 20:41

1 Answer 1

3

The Adapter suffix is likely inspired by a similar pattern used throughout Java AWT/Swing for listener interfaces, such as MouseListener and MouseAdapter. You're right that this is not the typical GoF Adapter pattern, but looking past the name, there is a bigger design smell in having two types that are effectively identical.

You may not want to implement all notify methods 1 to 5 because you want to keep your code clean. ... If the interface is changed, say Notify6() is added, then user don't have to change anything in the client code.

There are two ways to solve this:

  • Divide the interface into more granular ones so that clients can choose which ones they need. If you need to add a new notification, create a new interface.
  • Provide only a base class with empty methods so that clients can override the ones they need. If you need to add a new notification, just add a new empty method.

Providing both an interface and a base class doesn't really protect you, because clients might still directly implement the interface and you won't be able to add new methods without breaking them.

Favor object composition over class inheritance ... When user code extends CallbackAdapter, there will be coupling between user code and an external class.

This is not a composition vs. inheritance problem. Whether you use an interface or a base class, you will be inheriting from it and thus be coupled to it. In fact, the very distinction between "interface" and "class" is language-specific.

It's not good to break contract silently ... Interfaces define behavior and are used to make contracts among participants.

Notifications are optional by definition, so adding a new one shouldn't break any contract. Creating a new interface or adding an empty method to a base class are both valid mechanisms to do this (this choice should be informed by other design goals).

5
  • Thanks for this nice and informative answer. All methods of the awt adapter classes are empty including MouseAdapter. These empty methods may be innocent and acceptable. However, when you start to add code to these adapter methods, as noted at the post, things are getting messy. Commented May 24, 2020 at 9:45
  • This is not a composition vs. inheritance problem. If there wouldn't any code in adapter class (as in awt adapters), then I'll say yes you're right but there is code in the adapter (of discussed library). Let's think this way; awt developers added a new method named mouseJumped(event) and write some code on event object in this new method and assume that this code affects rest of the user code. For awt example, all methods takes isolated event objects, changing the event object doesn't affect the rest, such a nice design but the library that we're discussing, this isn't the case. Commented May 24, 2020 at 9:46
  • Creating a new interface or adding an empty method to a base class are both valid mechanisms to do this. If methods are not empty, this is a code smell, do you agree with me? Thanks again for information you gave. Commented May 24, 2020 at 9:46
  • Additionally, key events of awt adapter seem immutable which is a very nice design. If a user stores this object anywhere, this doesn't harm to anyone. Commented May 24, 2020 at 11:40
  • 1
    It does seem odd for a notification interface to include code in the base class. If this is code that the library requires to function properly, then the developer of the library runs the risk that the client never calls the base method.
    – casablanca
    Commented May 24, 2020 at 19:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.