Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protecting hooks in template pattern

I am implementing template pattern in Java. Let us assume the following piece of code:

public abstract class A {

  public final void work() {
    doPrepare();
    doWork();
  }

  protected abstract void doPrepare();

  protected abstract void doWork();
}

public final class B extends A {

  @Override 
  protected abstract void doPrepare() { /* do stuff here */ }

  @Override 
  protected abstract void doWork() { /* do stuff here */ }
}

public final class Main {

  public static void main(String[] args) {
    B b = new B();
    b.work();
  }
}

I consider it a problem that one is easily able to accidentally call b.doWork() instead of always calling b.work(). What is the most elegant solution, if possible, to "hide" the hooks?

like image 857
Peter Lényi Avatar asked Feb 20 '16 21:02

Peter Lényi


People also ask

What is hook in template method?

the template method. A hook is a method that is declared in the abstract class, but only given an empty or default implementation. – Gives the subclasses the ability to “hook into” the algorithm at various points, if they wish; they can ignore the hook as well.

Which of the following classes is an implementation of template method pattern?

The Abstract Class declares methods that act as steps of an algorithm, as well as the actual template method which calls these methods in a specific order.

Can method using template is essential in programming?

The template method is used for the following reasons : Let subclasses implement varying behavior (through method overriding) Avoid duplication in the code, the general workflow structure is implemented once in the abstract class's algorithm, and necessary variations are implemented in the subclasses.

What is a hook method in Java?

A hook is a method of interposing a piece of code in front of another piece of code, so that the first piece of code executes before the second piece of code, giving the first piece of code an opportunity to monitor and/or filter the behavior of the second piece of code.


2 Answers

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?

like image 199
MagicWindow Avatar answered Oct 29 '22 07:10

MagicWindow


The easiest and more obvious thing you could do would be to use your A -> B hierarchy from another package.

If for some reason you can't do it, then you could use an interface and make your B class wrap the class that actually descends from A. Something like this:

public interface Worker {

    void work();
}

public abstract class A implements Worker {

    @Override
    public final void work() {
        doPrepare();
        doWork();
    }

    protected abstract void doPrepare();

    protected abstract void doWork();
}

public static class B implements Worker {  // does not extend A, but implements Worker

    private final A wrapped = new A() { // anonymous inner class, so it extends A

        @Override
        protected void doPrepare() {
            System.out.println("doPrepare");
        }

        @Override
        protected void doWork() {
            System.out.println("doWork");
        }
    };

    @Override
    public void work() { // delegate implementation to descendant from A
        this.wrapped.work();
    }
}

This way, protected methods remain hidden in an anonymous inner class that extends from A, which is a private final member of B. The key is that B doesn't extends A, but implements the Worker interface's work() method by delegating to the anonymous inner class. So, even in the case B's work() method is being invoked from a class that belongs to the same package, protected methods won't be visible.

B b = new B();
b.work(); // this method is accessible via the Work interface

Output:

doPrepare
doWork
like image 27
fps Avatar answered Oct 29 '22 05:10

fps