保护模板模式中的钩子
Protecting hooks in template pattern
我正在 Java 中实现模板模式。让我们假设以下代码:
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();
}
}
我认为这是一个很容易不小心调用 b.doWork()
而不是总是调用 b.work()
的问题。如果可能的话,"hide" 钩子最优雅的解决方案是什么?
您确实在使用模板模式。对于来自外部用户的 "hide" 详细信息,您可以做两件主要的事情:
1) 了解您的 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 |
除非有坚定的黑客通过反射欺负他们的方式来绕过这些保护,这些可以防止临时编码人员意外调用最好远离他们的方法。编码人员将对此表示赞赏。没有人愿意使用杂乱无章的 API.
目前 b.doWork()
是 protected
。所以它只会在你的 main()
中可见,如果你的 main()
在 class A
中(不是),subclass B
(它是不),或者在同一个包中(不清楚,你没有post任何关于你的代码所在包的线索)。
这引出了一个问题,你想保护谁不被看到 b.doWork()
?如果您正在创建的包只是您正在开发的东西,那么它可能还不错。如果许多人在这个包中四处走动,与许多其他 classes 混在一起,那么他们不太可能非常熟悉 classes A
和 B
。在这种情况下,您不希望所有内容都挂在每个人都能看到的地方。在这里最好只允许 class 和 subclass 访问。但是没有允许 subclass 访问但也不允许包访问的修饰符。只要你是 subclassing protected
就是你能做的最好的。考虑到这一点,不要创建包含大量 classes 的包。保持包裹紧凑和集中。这样一来,大多数人将在看起来不错且简单的程序包之外工作。
简而言之,如果您想看到 main()
无法调用 b.doWork()
,请将 main()
放入不同的包中。
package some.other.package
public final class Main {
...
}
2) 下一条建议的价值将很难用您当前的代码来理解,因为它是关于解决只有在扩展您的代码时才会出现的问题。但它确实与保护人们不看到像 b.doWork()
这样的东西的想法密切相关
What does "program to interfaces, not implementations" mean?
在您的代码中,这意味着如果 main 看起来像这样会更好:
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 :)
}
为什么?使用类型 A
与类型 B
有什么关系? A
更接近于 接口 。请注意,这里我不仅仅指使用关键字 interface
时得到的结果。我的意思是类型 B
是类型 A
的具体实现,如果我不是绝对必须针对 B
编写代码,我真的宁愿不写,因为谁知道什么奇怪的非 A
东西B
了。这在这里很难理解,因为 main 中的代码短小精悍。此外,A
和 B
的 public 接口 并没有什么不同。他们都只有一个 public 方法 work()
。想象一下 main()
又长又复杂,想象一下 B
有各种非 A
的方法。现在假设您需要创建一个您希望 main 处理的 class C
。看到 main 被输入 B
而 main 中的每一行都知道 B
只是一些简单的 A
之类的东西,这难道不是令人欣慰的吗?除了 new
之后的部分,这可能是正确的,并且可以让您免于对 main()
做任何事情,而是更新 new
。确实,这不是为什么使用强类型语言有时会很好吗?
<咆哮>
如果您认为即使在 new
之后用 B()
更新那部分工作量太大,您并不孤单。这种特别的小痴迷让我们 dependency injection movement 产生了一堆框架,这些框架实际上更多地是关于自动化对象构造,而不是任何构造函数都可以让你做的简单注入。
更新:
我从你的评论中看出你不愿意创建小而紧的包。在那种情况下,考虑放弃基于继承的模板模式,转而采用基于组合的策略模式。你仍然会得到多态性。它只是不会免费发生。更多的代码编写和间接。但是你甚至可以在 运行 时间改变状态。
这似乎违反直觉,因为现在 doWork()
必须是 public。那是什么样的保护?那么现在您可以将 B
完全隐藏在 AHandler
后面,甚至隐藏在 AHandler
里面。这是一些人性化的外观 class,它包含曾经是您的模板代码但只公开 work()
。 A
可以只是一个 interface
,所以 AHandler
仍然不知道它有一个 B
。这种方法在依赖注入人群中很受欢迎,但肯定没有普遍的吸引力。有些人每次都盲目地这样做,这让很多人感到恼火。你可以在这里读更多关于它的内容:
Prefer composition over inheritance?
您可以做的最简单和更明显的事情是使用另一个包中的 A -> B
层次结构。
如果由于某种原因你不能这样做,那么你可以使用一个接口,让你的 B
class 包装实际上来自 [=15= 的 class ].像这样:
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();
}
}
这样,受保护的方法仍然隐藏在从 A
扩展的匿名内部 class 中,它是 B
的私有最终成员。关键是 B
没有扩展 A
,而是通过委托给匿名内部 class 来实现 Worker
接口的 work()
方法。因此,即使 B
的 work()
方法是从属于同一包的 class 调用的,受保护的方法也不会可见。
B b = new B();
b.work(); // this method is accessible via the Work interface
输出:
doPrepare
doWork
我正在 Java 中实现模板模式。让我们假设以下代码:
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();
}
}
我认为这是一个很容易不小心调用 b.doWork()
而不是总是调用 b.work()
的问题。如果可能的话,"hide" 钩子最优雅的解决方案是什么?
您确实在使用模板模式。对于来自外部用户的 "hide" 详细信息,您可以做两件主要的事情:
1) 了解您的 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 |
除非有坚定的黑客通过反射欺负他们的方式来绕过这些保护,这些可以防止临时编码人员意外调用最好远离他们的方法。编码人员将对此表示赞赏。没有人愿意使用杂乱无章的 API.
目前 b.doWork()
是 protected
。所以它只会在你的 main()
中可见,如果你的 main()
在 class A
中(不是),subclass B
(它是不),或者在同一个包中(不清楚,你没有post任何关于你的代码所在包的线索)。
这引出了一个问题,你想保护谁不被看到 b.doWork()
?如果您正在创建的包只是您正在开发的东西,那么它可能还不错。如果许多人在这个包中四处走动,与许多其他 classes 混在一起,那么他们不太可能非常熟悉 classes A
和 B
。在这种情况下,您不希望所有内容都挂在每个人都能看到的地方。在这里最好只允许 class 和 subclass 访问。但是没有允许 subclass 访问但也不允许包访问的修饰符。只要你是 subclassing protected
就是你能做的最好的。考虑到这一点,不要创建包含大量 classes 的包。保持包裹紧凑和集中。这样一来,大多数人将在看起来不错且简单的程序包之外工作。
简而言之,如果您想看到 main()
无法调用 b.doWork()
,请将 main()
放入不同的包中。
package some.other.package
public final class Main {
...
}
2) 下一条建议的价值将很难用您当前的代码来理解,因为它是关于解决只有在扩展您的代码时才会出现的问题。但它确实与保护人们不看到像 b.doWork()
What does "program to interfaces, not implementations" mean?
在您的代码中,这意味着如果 main 看起来像这样会更好:
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 :)
}
为什么?使用类型 A
与类型 B
有什么关系? A
更接近于 接口 。请注意,这里我不仅仅指使用关键字 interface
时得到的结果。我的意思是类型 B
是类型 A
的具体实现,如果我不是绝对必须针对 B
编写代码,我真的宁愿不写,因为谁知道什么奇怪的非 A
东西B
了。这在这里很难理解,因为 main 中的代码短小精悍。此外,A
和 B
的 public 接口 并没有什么不同。他们都只有一个 public 方法 work()
。想象一下 main()
又长又复杂,想象一下 B
有各种非 A
的方法。现在假设您需要创建一个您希望 main 处理的 class C
。看到 main 被输入 B
而 main 中的每一行都知道 B
只是一些简单的 A
之类的东西,这难道不是令人欣慰的吗?除了 new
之后的部分,这可能是正确的,并且可以让您免于对 main()
做任何事情,而是更新 new
。确实,这不是为什么使用强类型语言有时会很好吗?
<咆哮>
如果您认为即使在 new
之后用 B()
更新那部分工作量太大,您并不孤单。这种特别的小痴迷让我们 dependency injection movement 产生了一堆框架,这些框架实际上更多地是关于自动化对象构造,而不是任何构造函数都可以让你做的简单注入。
更新:
我从你的评论中看出你不愿意创建小而紧的包。在那种情况下,考虑放弃基于继承的模板模式,转而采用基于组合的策略模式。你仍然会得到多态性。它只是不会免费发生。更多的代码编写和间接。但是你甚至可以在 运行 时间改变状态。
这似乎违反直觉,因为现在 doWork()
必须是 public。那是什么样的保护?那么现在您可以将 B
完全隐藏在 AHandler
后面,甚至隐藏在 AHandler
里面。这是一些人性化的外观 class,它包含曾经是您的模板代码但只公开 work()
。 A
可以只是一个 interface
,所以 AHandler
仍然不知道它有一个 B
。这种方法在依赖注入人群中很受欢迎,但肯定没有普遍的吸引力。有些人每次都盲目地这样做,这让很多人感到恼火。你可以在这里读更多关于它的内容:
Prefer composition over inheritance?
您可以做的最简单和更明显的事情是使用另一个包中的 A -> B
层次结构。
如果由于某种原因你不能这样做,那么你可以使用一个接口,让你的 B
class 包装实际上来自 [=15= 的 class ].像这样:
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();
}
}
这样,受保护的方法仍然隐藏在从 A
扩展的匿名内部 class 中,它是 B
的私有最终成员。关键是 B
没有扩展 A
,而是通过委托给匿名内部 class 来实现 Worker
接口的 work()
方法。因此,即使 B
的 work()
方法是从属于同一包的 class 调用的,受保护的方法也不会可见。
B b = new B();
b.work(); // this method is accessible via the Work interface
输出:
doPrepare
doWork