Today, we continue our series on controlling visibility in Java. As I told you before, there are two ways to controll visibility: at the level of classes or at the level of packages.
At class level, controlling visibility is done as given in the previous post. We use the modifiers private, protected or public to set the visibility of class members. Class members are fields, methods and inner classes. This give us very fine-grained control, which can be a good and a bad thing.
Sometimes, controlling visibility at class level becomes pain-stacking micromanagement and we wish we could just turn everything public. This is where package level visibility joins the game.
At package level, controlling visibility is done completely different. Let A, B, C be packages of layer Below and X be a package of layer Above. How do we controll the visibility of classes and methods in Below such that Above can not see them?
The solution is to introduce yet another package and use interfaces.
We create interfaces for all to-be-public classes and methods and put them in a new package D, together with a Facade (185) that is an Abstract Factory (87) and a Layer Singeltion (127). The responsability of the facade is to serve as factory for the interfaces defined in D.
Now, we define that code from Above must not (darf nicht, des englische “must not” kann für Deutschsprecher verwirrend sein) directly import members of A, B or C. Rather, all access from Above to Below must be done using package D only.
That way, we get complete controll over the visibility of Below.
Let me give a quick example, where classes A.Below and X.Client are given as follows
package A; // belongs to layer Below
public class Foo {
public void useMe() { };
public void doNotUseMe() { };
public Foo self() { return this };
}
package X; // belongs to layer Above
public class Client {
public void main(String[] argv) {
A.Foo foo = new A.Foo();
foo.useMe();
// architecture violation, but no compile error!
foo.doNotUseMe();
}
}
The method A.Foo.doNotUseMe() is supposed to be private to layer Below, but with the present settings we can call it nevertheless. Given that we can not make the method private because of internal uses by packages A, B and C, we must resort to the architecture level to controll its visibility. Therefore, we introduce a new package D as follows
package D; // belongs to layer Below
public interface IFoo {
public void useMe();
public IFoo self();
}
public class BelowFactory {
public IFoo createFoo() {
return new A.Foo();
}
}
Now, we can rewrite our client and A.Foo.doNotUseMe() is really hidden!
package X; // belongs to layer Above
public class Client {
public void main(String[] argv) {
D.BelowFactory factory = new D.BelowFactory();
D.IFoo foo = factory.createFoo();
foo.useMe();
// architecture violation AND compile error!
// foo.doNotUseMe();
}
}
An important issue are return types. To demonstrate that, the Method A.Foo.self() has been included in the example. As you can see, the interface specifies its return type as D.IFoo whereas the implementations returns A.Foo. That is, the return type of the overridding method is more specific than the overriden method. This is call co-variance.
It is very important to use co-variance whenever a method has an internal return type! Without co-variance we could write in the client the following, and thus violate the architecture.
foo.self().doNotUseMe(); // architecure violation
With co-variance, this does not compile.
A final word regarding naming conventions. Architectural visibility splits each class in two files: a public interface and its internal implementation … and both can not have the same name. Given a class Example, the most common naming conventions are
- interface
IExample and implementation Example
- interface
Example and implementation ExampleImpl
- interface
Example and implementation MyExample
- interface
Example and implementation AbbrExample, where Abbr is the abbreviation of the layer’s name
Please keep in mind that, given such a small example as above, the forces present in a huge architecure are not present and the propose solution may look unnecessary complex. Thus, you must just believe me that it is indeed a very common case that you want to controll visbility with a mechanism other than method modifiers. You will see yourself when you work for the first on a system with 1000 classes or more.
admin Java