1

I'm new to Java. I've discovered, while trying to structure my code, that Java intimately ties source file organisation (directory structure) to package structure and package structure to external visibility of classes (a class is either visible to all other packages, or none).

This makes it quite difficult to organise the internal implementation details of my public library into logical units of related functionality while maintaining good encapsulation. JSR 294 explains it best:

Today, an implementation can be partitioned into multiple packages. Subparts of such an implementation need to be more tightly coupled to each other than to the surrounding software environment. Today designers are forced to declare elements of the program that are needed by other subparts of the implementation as public - thereby making them globally accessible, which is clearly suboptimal.

Alternately, the entire implementation can be placed in a single package. This resolves the issue above, but is unwieldy, and exposes all internals of all subparts to each other.

So my question is, what workarounds exist for this limitation, and what are the pros & cons? Two are mentioned in the JSR - use packages for logical grouping (violating encapsulation); place everything in a single package (unwieldy). Are there other pros/cons to these workarounds? Are there other solutions? (I've become vaguely aware of OSGi bundles, but I've found it hard to understand how they work and what the the pros/cons might be (perhaps that's a con). It appears to be very intrusive compared to vanilla packages, to development & deployment.

Note: I'll upvote any good answers, but the the best answer will be one that comprehensively folds in the pros & cons of others (plagiarise!).

Related (but not duplicate!) questions

Anticipating cries of 'Possible duplicate', here are similar questions that I've found on SO; I present them here for reference and also to explain why they don't answer my question.

Community
  • 1
  • 1
bacar
  • 9,761
  • 11
  • 55
  • 75
  • IMO, if you have two modules that would like "special" access to each other's non-public members, you in fact do *not* have good encapsulation. – Kirk Woll Mar 08 '12 at 00:58
  • I think the problem still exists when you consider just one module that needs such access to one other module - I agree that mutual dependency is likely to be an indicator that you should probably refactor out a third, shared module. – bacar Mar 08 '12 at 01:03
  • sorry I wasn't clear. I meant that a truly encapsulated module should *only* need access to public members of other modules. Didn't mean to imply anything about circular dependencies. – Kirk Woll Mar 08 '12 at 01:04
  • Perhaps I wasn't clear: I agree, and that's entirely the point of this question - Java doesn't have ''modules', it has packages, but code structure is intimately tied to accessibility - that is, if my 'module' (e.g. API, library) should be logically broken down into multiple packages (e.g. public-facing parts, private implementation details), Java *forces* me to make the impl details public - just so that the public parts of my library can see it - which in turn means _all clients_ can see the implementation details. – bacar Mar 08 '12 at 02:44

3 Answers3

0

Tools like ProGuard can be used to repackage a JAR, exposing only those classes you specify in the configuration file. (It does this in addition to optimizing, inlining, and obfuscating.) You might be able to set up ProGuard in e.g. a Maven or Ant build, so you write your library exposing methods as public, and then use ProGuard to eliminate them from the generated JAR.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • This sounds like it has the advantages of 'use multi packages' for the library developer, then effectively modifies it to have the encapsulation advantages of "put everything in one package" - I'm trying to find docs that say it will actually do this though :) do you know if you can persuade it to do it _without_ doing all of the obfuscation razzmatazz? – bacar Mar 08 '12 at 01:09
  • Put `-dontobfuscate` in the spec? – Louis Wasserman Mar 08 '12 at 02:08
0

I'll get the ball rolling. Steal this answer and add to it/correct it/elaborate please!

Use multiple packages for multiple logical groupings

Pros: effective logical grouping of related code.

Cons: when internal implementation detail classes in different packages need to use one another, they must be made public - even to the end user - violating encapsulation. (Work around this by using a standard naming convention for packages containing internal implementation details such as .internal or .impl).

Put everything in one package

Pros: effective encapsulation

Cons: unwieldy for development/maintenance of the library if it contains many classes

Use OSGi bundles

Pros: ? (do they fix the problem?)

Cons: appears to be very intrusive at development (for both library user and author) and deployment, compared to just deploying .jar files.

Wait for Jigsaw in Java 8

http://openjdk.java.net/projects/jigsaw/

Pros: fixes the problem for good?

Cons: doesn't exist yet, not specific release date known.

bacar
  • 9,761
  • 11
  • 55
  • 75
0

I've never found this to be a problem. The workaround (if you want to call it that) is called good API design.

If you design your library well, then you can almost always do the following:

  • Put the main public API in one package e.g. "my.package.core" or just "my.package"
  • Put helper modules in other packages (according to logical groupings), but give each one it's own public API subset (e.g. a factory class like "my.package.foobarimpl.FoobarFactory")
  • The main public API package uses only the public API of helper modules
  • Your tests should also run primarily against the public APIs (since this is what you care about in terms of regressions or functionality)

To me the "right level of encapsulation" for a package is therefore to expose enough public API that your package can be used effectively as a dependency. No more and no less. It shouldn't matter whether it is being used by another package in the same library or by an external user. If you design your packages around this principle, you increase the chance of effective re-use.

Making parts of a package "globally accessible" really doesn't do any harm as long as your API is reasonably well designed. Remember that packages aren't object instances and as a result encapsulation doesn't matter nearly as much: making elements of a package public is usually much less harmful than exposing internal implementation details of a class (which I agree should almost always be private/protected).

Consider java.lang.String for example. It has a big public API, but whatever you do with the public API can't interfere with other users of java.lang.String. It's perfectly safe to use as a dependency from multiple places at the same time. On the other hand, all hell would break loose if you allowed users of java.lang.String to directly access the internal character array (which would allow in-place mutation of immutable Strings.... nasty!!).

P.S. Honourable mention goes to OSGi because it is a pretty awesome technology and very useful in many circumstances. However its sweet spot is really around deployment and lifecycle management of modules (stopping / starting / loading etc.). You don't really need it for code organisation IMHO.

mikera
  • 105,238
  • 25
  • 256
  • 415
  • I disagree. If my.package.foobarimpl.FoobarFactory is an internal implementation detail, yet clients may end up relying on it, so I cannot change that implementation detail without breaking clients that may have started using it. It _absolutely_ matters whether it's being used by the same library (impl detail - should be allowed to change without breaking clients) or by an external user. The whole _point_ of an impl detail is that I am at liberty to change it without breaking clients. – bacar Mar 08 '12 at 03:07
  • @bacar - if that's really the case (it's an implementation detail) then it logically belong in the same package. That's what packages are for IMHO - a logical bundle of functionality that has a public API and hides it's implementation details. – mikera Mar 08 '12 at 03:20
  • Thanks - that's the problem, though: your statement is very restrictive and doesn't allow for further logicical groupings/subdivisions within the package. Consider an API whose internal implementation detail needs some classes to interact with a particular DB; a few more to deal with transport, others to handle configuration etc; if I want to group these logically into .db, .transport, .config packages then at least one class in each impl detail package must be made public. Alternatively I have to put everything in one package - unwieldy. – bacar Mar 08 '12 at 08:56