FriendPackages
From APIDesign
Line 1: | Line 1: | ||
- | wikipedia::Object-oriented_programming_language|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'' constructs and [[Java]] adds additional ''package private'' access type. | + | Common [[wikipedia::Object-oriented_programming_language|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'' constructs 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. They 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 implement them. And as common wisdom and [[Code Against Interfaces, Not Implementations|Chapter 8]] advice it is preferable to ''code against interfaces and not the implementation''. The [[Code Against Interfaces, Not Implementations|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 implies that the [[Java]]'s ''package private'' access modifier is useless for making these two sets of classes be friends. | The problem with the [[Java]] approach is that it generally dictates the files layout of your friends. They 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 implement them. And as common wisdom and [[Code Against Interfaces, Not Implementations|Chapter 8]] advice it is preferable to ''code against interfaces and not the implementation''. The [[Code Against Interfaces, Not Implementations|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 implies that the [[Java]]'s ''package private'' access modifier is useless for making these two sets of classes be friends. |
Revision as of 11:40, 24 October 2008
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 constructs 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. They 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 implement them. And as common wisdom and Chapter 8 advice 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 implies that the Java's package private access modifier is useless for making these two sets of classes be friends.
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 he is little API design pattern that makes that possible 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:
Code from Item.java:
See the whole file.public final class Item { private int value; private ChangeListener listener; /** Only friends can create instances. */ Item() { } /** Anyone can change value of the item. */ public void setValue(int newValue) { value = newValue; ChangeListener l = listener; if (l != null) { l.stateChanged(new ChangeEvent(this)); } } /** Anyone can get the value of the item. */ public int getValue() { return value; } /** Only friends can listen to changes. */ void addChangeListener(ChangeListener l) { assert listener == null; listener = l; } }
This is a regular Java code relying on the standard 'public and package private modifiers. No surprises so far, the next quest is to teleport the API to the friend package with implementation classes. This can be done by defining an accessor class in such package:
Code from Accessor.java:
See the whole file.public abstract class Accessor { private static volatile Accessor DEFAULT; public static Accessor getDefault() { Accessor a = DEFAULT; if (a == null) { throw new IllegalStateException("Something is wrong: " + a); } return a; } public static void setDefault(Accessor accessor) { if (DEFAULT != null) { throw new IllegalStateException(); } DEFAULT = accessor; } public Accessor() { } protected abstract Item newItem(); protected abstract void addChangeListener(Item item, ChangeListener l); }
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 and listener via addChangeListener methods. 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 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:
Code from AccessorImpl.java:
See the whole file.final class AccessorImpl extends Accessor { protected Item newItem() { return new Item(); } protected void addChangeListener(Item item, ChangeListener l) { item.addChangeListener(l); } }
Now 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:
Code from Item.java:
See the whole file.static { Accessor.setDefault(new AccessorImpl()); }
And this is all (especially if we want as clueless understanding as possible). We have just bind 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:
Code from AccessorTest.java:
See the whole file.Item item = Accessor.getDefault().newItem(); assertNotNull("Some item is really created", item); Accessor.getDefault().addChangeListener(item, this);
Because all the methods in the Accessor class are protected, only code in this one package can make the calls, no code in other packages has any chance to call these methods. As such the solution is safe and creates a secure teleport between the API package and the implementation one.