Stefan Haberl
Stefan Haberl

Reputation: 10539

How to work around that there's no subpackage visibility in Java

When I'm writing a new component with DDD I try to have one single public class in a package with all collaborators of my component package protected.

That usually plays out very nicely, because there's only one single entry point, that clients of my component can use. All (internal) collaborators will be hidden.

Consider an example, where a client can drive around with my purpose built Java car. I don't want the client to see all internal parts of the vehicle, so only the Car class is exposed as public:

org.automobile
|- Car           <-- only Car is public
|- Engine        <-- all collaborators have default (package-protected) visibility
\- Battery

Now, things usually get more complicated as the domain evolves, so I want to move classes with their collaborators to subpackages to keep my structure nice and clean:

org.automobile
|- Car
|- drive.Engine           <-- how to make this only available to Car
|- drive.Spark
|- drive.Belt
|- electric.Battery       <-- and this
\- electric.BatteryCell

I would now like to make Engine and Battery visible to Car only, because the client of my component should still not be able to use these classes directly.

I know that there's no "subpackage" visibility in Java, but is there any other lightweight pattern of achieving what I want, short of using Java 9 modules?

Upvotes: 4

Views: 1854

Answers (1)

Spyros K
Spyros K

Reputation: 2605

I would say there are 2 options. Anyone feel free to chip in as this is question is interesting.

Use a single file for java code instead of packages

The idea is that you can use a single java file to declare the classes and API that are available to the Car.java (Engine and Battery). Classes declaring concepts hidden from Car (Spark, Belt, BatteryCell) should be private and static so that they do not have implicit links with the declaring class.

This approach can scale but requires more work. It is also a be helpful up to a point, it is not suitable for large code bases nor for code bases that will scale in the future to be large.

 org.automobile
|- Car
|- Engine.java           <-- how to make this only available to Car
|- Engine.java-Spark     <--private static class in File Engine.java
|- Engine.java-Belt      <--private static class in File Engine.java
|- Battery.java               <-- package private class
\- Battery.java - BatteryCell <--private static class in File Battery.java

Combine the use of abstract and protected modifiers together with subpackages

The idea is that you create public Abstract classes whose functionality is exposed by protected methods. For instance AbstractBattery and AbstractEngine. Then you can create some classes that act as "proxies", you extend the abstract classes with package private implementations in the package of Car and override the methods that you want to make available. For example Engine and Battery. Car will have access to the overridden methods of the abstract classes. Another side-effect is that you cannot have final API methods in your AbstractClasses.

This approach is more suitable for large code bases and aims to hide functionality and abstractions from unintended use. However it requires some more work. Of course it is still possible for others to extend AbstractBattery and AbstractEngine and use them.

org.automobile
|- Car
|- Engine      <-- extends AbstractEngine, is package private, overrides methods that Car needs to  access
|- Battery     <-- extends AbstractBattery,is package private, overrides methods that Car needs to access
|- drive.AbstractEngine <-- Public abstract class with no public methods, methods that are needed by Engine and Car are protected.
|- drive.Spark <-package private
|- drive.Belt <-package private
|- electric.AbstractBattery <--Public abstract class with no public methods, methods that are needed by Battery and Car are protected.
\- electric.BatteryCell <-package private

Here is an example of how this could look like:

org.automobile.engine.AbstractEngine

package org.automobile.engine;

public abstract class AbstractEngine {

    protected void startEngine(){
        System.out.println("Zoom Zooomm Zoommm");
    }
}

org.automobile.Engine

package org.automobile;

import org.automobile.engine.AbstractEngine;

final  class Engine extends AbstractEngine {

    @Override
    protected void startEngine(){
        super.startEngine();
    }
}

Upvotes: 1

Related Questions