FriendPackages

From APIDesign

Jump to: navigation, search

Common object oriented languages offer some kind of encapsulation, which often takes form of having public, protected and private access modifiers. Indeed, designers soon found that this is not enough and as such C++ has friend keyword and Java adds additional package private access type.

The problem with the Java approach is that it generally dictates the files layout of your friends. Their sources need to be placed in the same directory. Many people do not like that, as this mixes the classes representing the API with the classes that provide their implementation. This is not perfect. Common wisdom and also Chapter 8 advice, that it is preferable to code against interfaces and not the implementation. The Chapter 8 explains that this old good advice can have various meanings and take multiple forms. One of such forms is the practise of putting the implementation and interfaces sources in two distinct disk locations. This is exactly against the requirements of the Java's package private access modifier, making it useless for establishing friendship between distinct API and implementation packages.

This often leads people to open the APIs up and use public or protected modifiers, sometimes annotating the method with a javadoc note do not call me, I am part of the implementation. This is unfortunate as it pollutes the APIs with useless implementation details, distracts the API users and potentially creates security holes in the API.

One of the solutions is to add yet another modifiers to Java and introduce concept of friend packages. This may happen occasionally. Meanwhile let's learn how to use simple API design pattern that makes connecting two packages possible even now.

Friend Packages

Imagine you have an API class called Item with public getter and setter while its constructor and its addChangeListener methods are dedicated only for friends. Then just write:

does not exists: design.less.friend.Item

(package org.apidesign.friendpackage.api)

This is regular Java code relying on the standard public and package private modifiers. This is exactly the code that you would write, if the friend code would reside in the same package as the API. As we want to use distinct packages, let us take another step and teleport the API to another friend package with implementation classes. This can be done by defining an accessor class in such package:

does not exists: design.less.friend.Accessor

(package org.apidesign.friendpackage.impl)

This class defines an internal, friend API specifying what is the functionality that our implementation classes want to get from our API interfaces (in addition to regular access to all public elements). In this particular case it is the access to constructor via the newItem method and listener manipulation via addChangeListener method. Please note that these methods are abstract, without implementation and that this internal API is waiting for someone to register the implementation by calling the setDefault static method.

Who can provide meaningful implementation of this class? Well, only someone who can call the package private methods from the Item class. Does a class like that exist? Indeed, it does, any class in the same package as Item can do that. So just write:

does not exists: design.less.friend.AccessorImpl

(package org.apidesign.friendpackage.api)

Place this non-public AccessorImpl next to the Item class, so they are friends in the classical Java sense. Now all our intrinsic classes are ready. We have the public API class Item with hooks for friends, we have the API for friends in the Accessor class and we also have a provider of this friend API. Now we need to bind this all together. As soon as the Item class is loaded, we need to register the implementation. In Java this can be done with the use of static initializer block in the API class:

does not exists: design.less.friend.Item.static

(package org.apidesign.friendpackage.api)

And this is all (especially if we want as clueless understanding as possible). We have connected two Java packages together and provided an API that allows them to perform a privileged communication. As such any code in the same package as the Accessor class can call:

does not exists: design.less.friend.use

(package org.apidesign.friendpackage.impl)

Moreover, because all the methods in the Accessor class are protected and there is just one reasonable subclass, only code in this one package can make the calls, no code in other packages has any chance to access these methods. As such the solution is safe and creates a secure teleport between the API package and the implementation one, allowing us to separate interfaces and implementation as advised by Code Against Interfaces, Not Implementations wisdom.

Personal tools
buy