You are indeed using the template pattern. There are two main things you could do to "hide" details from outside users:
1) Understand your access modifiers:
Access Levels
Modifier | Class | Package | Subclass | World |
--------------------------------------------------
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
no modifier | Y | Y | N | N |
private | Y | N | N | N |
Short of determined hackers bullying their way in past these protections with reflection, these can keep casual coders from accidentally calling methods that are best kept out of their way. The coders will appreciate this. No one wants to use a cluttered API.
Currently b.doWork()
is protected
. So it would only be visible in your main()
if your main()
is in class A
(it's not), subclass B
(it's not), or in the same package (not clear, you didn't post any clues about the package(s) your code resides in).
This begs the question, who are you trying to protect from seeing b.doWork()
? If the package you're creating is something only you are developing it might not be so bad. If many people are stomping around in this package messing with many other classes it's not likely they will be intimately familiar with classes A
and B
. This is the case where you don't want everything hanging out where everyone can see it. Here it would be nice to only allow class and subclass access. But there is no modifier that allows subclass access without also allowing package access. As long as you're subclassing protected
is the best you can do. With that in mind, don't go creating packages with massive numbers of classes. Keep packages tight and focused. That way most people will be working outside the package where things look nice and simple.
In a nutshell, if you'd like to see main()
prevented from calling b.doWork()
put main()
in a different package.
package some.other.package
public final class Main {
...
}
2) The value of this next piece of advice will be hard to understand with your current code because it's about resolving issues that will only come up if your code is expanded on. But it really is closely tied to the idea of protecting people from seeing things like b.doWork()
What does "program to interfaces, not implementations" mean?
In your code what this means is it would be better if main looked like this:
public static void main(String[] args) {
A someALikeThingy = new B(); // <-- note use of type A
someALikeThingy.work(); //The name is silly but shows we've forgotten about B
//and know it isn't really just an A :)
}
Why? What does using type A
vs type B
matter? Well A
is closer to being an interface. Note, here I don't just mean what you get when you use the keyword interface
. I mean type B
is a concrete implementation of type A
and if I don't absolutely have to write code against B
I really would rather not because who knows what weird non A
things B
has. This is hard to understand here because the code in main is short & sweet. Also, the public interfaces of A
and B
aren't all that different. They both only have one public method work()
. Imagine that main()
was long and convoluted, Imagine B
had all sorts of non A
methods hanging off it. Now imagine you need to create a class C
that you want main to deal with. Wouldn't it be comforting to see that while main was fed a B
that as far as every line in main knows that B
is just some simple A
like thing. Except for the part after the new
this could be true and save you from doing anything to main()
but updating that new
. Indeed, isn't this why using a strongly typed language can sometimes be nice?
<rant>
If you think even updating that part after new
with the B()
is too much work you're not alone. That particular little obsession is what gave us the dependency injection movement that spawned a bunch of frameworks that were really more about automating object construction than a simple injection that any constructor can let you do.
</rant>
Update:
I see from your comments that you are reluctant to create small tight packages. In that case consider abandoning an inheritance based template pattern in favor of a composition based strategy pattern. You still get polymorphism. It just doesn't happen for free. Little more code writing and indirection. But you get to change state even at run time.
This will seem counter intuitive because now doWork()
has to be public. What kind of protection is that? Well now you can hide B
entirely behind or even inside AHandler
. This is some human friendly facade class that holds what used to be your template code but only exposes work()
. A
can just be an interface
so the AHandler
still doesn't know it has a B
. This approach is popular with the dependency injection crowd but certainly doesn't have universal appeal. Some do it blindly every time which annoys many. You can read more about it here:
Prefer composition over inheritance?