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?