Hook protection in pattern template

I am using a template template in Java. Assume the following code snippet:

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 believe the problem is that you can easily accidentally call b.doWork() , rather than always calling b.work() . What is the most elegant solution, if possible, to "hide" the hooks?

+5
source share
2 answers

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

If for some reason you cannot do this, you can use the interface and make your class B complete class that actually descends from A Something like that:

 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(); } } 

Thus, protected methods remain hidden in an anonymous inner class that extends from A , which is a private end member of B The key is that B does not extend A , but implements the Worker method of the work() interface, delegating an anonymous inner class. Thus, even when the B work() method is called from a class belonging to the same package, protected methods will not be visible.

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

Output:

 doPrepare doWork 
+2
source

You really use a template template. There are two main things you can do to “hide” information from external users:

1) Understand 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 | 

With the exception of certain hackers who mock these defenses with reflection, they can deter random coders from accidentally calling methods that are best kept in their path. Encoders will appreciate it. No one wants to use a cluttered API.

b.doWork() is currently protected . This way, it will only be visible in your main() , if your main() is in class A (it is not), subclass B (it is not), or in the same package (it is not clear you did not publish any hints about the package ( -ah) where your code is located).

This begs the question, who are you trying to protect against b.doWork() monitoring? If the package you create is what you are developing, it may not be so bad. If many people stomp in this package, fiddling with many other classes, they are unlikely to be closely familiar with classes A and B This is the case when you do not want everyone to hang where everyone can see it. It would be nice to allow access only to the class and subclass. But there is no modifier that allows access to subclasses without permission to access the package. As long as you subclass protected , this is the best you can do. With this in mind, do not create packages with a huge number of classes. Keep packaging tight and focused. Thus, most people will work outside the package, where everything looks beautiful and simple.

In short, if you want to see that main() failed to call b.doWork() put main() in another package.

 package some.other.package public final class Main { ... } 

2) The meaning of this next tip will be difficult to understand with the help of your current code, because it concerns the solution of problems that will appear only when expanding your code. But it really is closely related to the idea of ​​protecting people from seeing things like b.doWork()

What is a program for interfaces, not implementations? mean?

In your code, this means that it would be better if the main thing 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 type A vs type B matter? Good A closer to the interface. Notice, here I do not just mean what you get when you use the interface keyword. I mean, type B is a concrete implementation of type A , and if I don’t need to write code against B , I really would prefer not because who knows what strange things A n't for B This is hard to understand here because the code is mostly short and sweet. In addition, the public interfaces A and B not that different. Both of them have only one publicly available work() method. Imagine that main() was long and confusing. Imagine that B had all sorts of non- A methods depending on it. Now imagine that you need to create a class C that you want to handle. Wouldn't it be nice to see that while main was served B , that since each line in the main knows that B is just A simple thing. Except for the part after new , this may be true and save you from doing anything before main() , but updating this is new . Indeed, does this not mean that using a strongly typed language can be enjoyable?

pompous
If you think that even updating this part after new with B() is too much work, you are not alone. This little obsession is what made us addicted to injections , which spawned a bunch of frameworks that were really more about building automation than a simple injection that any constructor could allow you.
& L; / & bombastic GT;

Update:

From your comments you can see that you are reluctant to create small tight packages. In this case, consider abandoning the inheritance-based template template in favor of a composition-based strategy template. You are still getting polymorphism. It just doesn't happen for free. Some more coding and indirection. But you can change the state even during operation.

This will look intuitive, because now doWork() should be publicly available. What kind of protection is this? Now you can hide B completely behind or even inside AHandler . This is some kind of human-friendly facade class that contains what used to be your template, but only provides work() . A might just be an interface , so AHandler still doesn't know that it has B This approach is popular among the crowd of dependency injections, but, of course, does not have universal appeal. Some do it blindly every time, which annoys many. You can read more about it here: Prefer composition over inheritance?

+3
source

Source: https://habr.com/ru/post/1243478/


All Articles