【问题标题】:Protecting hooks in template pattern模板模式中的保护钩子
【发布时间】:2016-06-02 10:35:31
【问题描述】:

我正在用 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() 的问题。如果可能的话,“隐藏”钩子的最优雅的解决方案是什么?

【问题讨论】:

  • "很容易意外调用 b.doWork()"。真的吗?它受到保护,因此只有 A 或 B 实例可以调用它。
  • 不幸的是,由于它受到保护,包中的所有类都可以调用它。拆分包是一种方法,但我宁愿避免使用 1 类或 2 类包。
  • 不!您正在描述默认访问修饰符(即无)。受保护的意思是“只有我或扩展我的类可以使用它”
  • @OskarHýbl 不,你错了。 protected 表示同一包中的子类和所有类。
  • 哦,我的错。我站得更正了。但是,如果你只能从同一个包中调用受保护的函数,那么你真的没有人想保护它。

标签: java oop design-patterns access-modifiers


【解决方案1】:

您确实在使用模板模式。您可以做两件事来向外部用户“隐藏”详细信息:

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() 在类A(不是),子类B(不是),或者在同一个包中(不清楚,你没有发布有关您的代码所在包的任何线索)。

这就引出了一个问题,你想保护谁不看到b.doWork()?如果您正在创建的包是只有您正在开发的东西,它可能不会那么糟糕。如果很多人在这个包中跺脚,与许多其他类混在一起,那么他们不太可能对类AB 非常熟悉。在这种情况下,您不希望所有东西都挂在每个人都可以看到的地方。在这里只允许类和子类访问会很好。但是没有修饰符允许子类访问而不允许包访问。只要你继承protected 就是你能做的最好的。考虑到这一点,不要去创建包含大量类的包。保持包装紧凑和专注。这样一来,大多数人都会在看起来不错且简单的包之外工作。

简而言之,如果您希望阻止 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 中的代码又短又甜。此外,AB 的公共接口 并没有什么不同。他们都只有一个公共方法work()。想象一下main() 又长又复杂,想象一下B 有各种各样的非A 方法挂在它上面。现在假设您需要创建一个您希望 main 处理的类 C。看到 main 被喂了一个 B ,就 main 中的每一行都知道 B 只是一些简单的 A 之类的东西,这不是很令人欣慰吗。除了new 之后的部分之外,这可能是真的,并且可以让您免于对main() 做任何事情,而是更新new。确实,这难道不是为什么使用强类型语言有时会很好吗?


如果您认为即使在使用B() 更新new 之后的那部分工作量太大,您并不孤单。正是这种特别的小痴迷给了我们dependency injection movement,它催生了一堆框架,这些框架实际上更多的是关于自动化对象构造,而不是任何构造函数都可以让你做的简单注入。

更新:

我从您的 cmets 看到您不愿意创建小而紧的包。在这种情况下,考虑放弃基于继承的模板模式,转而采用基于组合的策略模式。你仍然得到多态性。它只是不会免费发生。更多的代码编写和间接。但是即使在运行时您也可以更改状态。

这似乎违反直觉,因为现在doWork() 必须是公开的。那是一种什么样的保护?好吧,现在您可以将B 完全隐藏在AHandler 后面甚至内部。这是一些对人类友好的外观类,它包含曾经是您的模板代码但只公开work()A 可以只是一个interface,所以AHandler 仍然不知道它有一个B。这种方法在依赖注入人群中很受欢迎,但肯定没有普遍吸引力。有些人每次都盲目地这样做,这让很多人感到烦恼。你可以在这里读更多关于它的内容: Prefer composition over inheritance?

【讨论】:

  • 感谢您提供详尽的回答。我将尝试解决所有想法:1)访问修饰符和包结构是我在发布问题之前很久就考虑的第一件事。我的包裹已经很小了,进一步拆分它会给我 1 类或 2 类包裹,正如我在之前的一个 cmets 中提到的那样。所以这不是我目前的解决方案,但将来可能适用于其他人。
  • 2) 至于接口方式,它有两个缺点。首先,它不适用于泛型(我可以提供一个示例)。其次,它会阻止你写:Result r = new B(args).work();,我认为这比有点强迫的A a = new B(args); Result r = a.work(); 更好。这又是我尝试过的东西,但仍然在寻找更好的东西,尽管它可能对某些人来说是可以接受的。
  • 3) 组合才是真正的答案。由于我的类的语义,我直接跳到继承,忘记考虑这个选项。我将 Schaffner 先生的回答标记为正确,因为它提供了一个示例,并保持对钩子的有限(受保护)访问;但我建议其他人查看您发布的链接。
  • @PeterLényi 我基本上同意这个答案的概念。特别是,对接口进行编程并倾向于组合而不是继承。不过,在我的回答中,我直奔主题。
【解决方案2】:

您可以做的最简单和更明显的事情是使用另一个包中的 A -&gt; B 层次结构。

如果由于某种原因你不能这样做,那么你可以使用一个接口并让你的B 类包装实际上从A 继承的类。像这样的:

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 扩展的匿名内部类中,它是B 的私有最终成员。关键是B没有继承A,而是通过委托给匿名内部类实现Worker接口的work()方法。因此,即使在从属于同一包的类调用 Bwork() 方法的情况下,受保护的方法也不会可见。

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

输出:

doPrepare
doWork

【讨论】:

  • 我已经尝试并调整了我的代码以遵循这种方法,它就像一个魅力,谢谢。它也适用于泛型。我发现的唯一缺点是,对于抽象类 A 中的每个方法,您都需要提供一个包装器;使用继承时可以避免的事情。但这绝对是一个不错的权衡。
猜你喜欢
  • 1970-01-01
  • 2017-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-14
  • 2016-04-06
  • 2016-03-12
  • 2018-07-13
相关资源
最近更新 更多