【问题标题】:What is the best way to work with many interfaces?使用许多接口的最佳方式是什么?
【发布时间】:2012-02-20 13:05:53
【问题描述】:

我有很多模型类(约 1000 个)实现任意数量的 5 个接口。所以我有实现一个的类和实现四个或五个的类。

这意味着我可以对这五个接口进行任何排列。在经典模型中,我必须实现 32-5 = 27 个“元接口”,将接口“连接”到一个包中。通常,这不是问题,因为IB 通常会扩展IA 等,但就我而言,这五个接口是正交/独立的。

在我的框架代码中,我的方法需要实现任意数量的这些接口的实例。所以假设我们有类X和接口IAIBICIDIEX 实现 IAIDIE

情况变得更糟,因为其中一些接口有formal type parameters

我现在有两个选择:

  1. 我可以定义一个接口IADE(或者更确切地说是IPersistable_MasterSlaveCapable_XmlIdentifierProvider;下划线只是为了您的阅读乐趣)

  2. 我可以将泛型类型定义为<T extends IPersistable & IMasterSlaveCapable & IXmlIdentifierProvider>,这样可以方便地根据需要混合和匹配接口。

  3. 我可以使用这样的代码:IA a = ...; ID d = (ID)a; IE e = (IE)e,然后使用具有正确类型的局部变量来调用方法,即使这三个变量都在同一个实例上工作。或者在每隔一个方法调用中使用一次强制转换。

第一个解决方案意味着我得到了很多名称非常不可读的空接口。

第二种使用一种“临时”类型。 Oracle 的 javac 有时会在 Eclipse 正确处理时绊倒它们。

最后一个解决方案使用强制转换。纳夫说。

问题:

  1. 有没有更好的解决方案来混合任意数量的接口?

  2. 是否有任何理由避免解决方案 #2 为我提供的临时类型(Oracle 的 javac 中的缺点除外)?

注意:我知道编写无法使用 Oracle 的 javac 编译的代码是有风险的。我们知道我们可以应对这种风险。

[编辑] 我在这里尝试尝试的似乎有些混乱。我的模型实例可以具有以下特征之一:

  • 它们可以是“主从能力”(想想克隆)
  • 它们可以有一个 XML 标识符
  • 它们可能支持树操作(父/子)
  • 他们可能支持修订
  • 等。 (是的,模型比这更复杂)

现在我有了在树上运行的支持代码。树的扩展是具有修订的树。但我也有没有树的修订。

当我在代码中添加修订树管理器中的子项时,我知道每个实例都必须实现ITtreeIRevisionable,但两者没有通用接口,因为它们是完全独立的问题。

但是在实现中,我需要在树的节点上调用方法:

public void addChild( T parent, T child ) {
    T newRev = parent.createNewRevision();
    newRev.addChild( foo );
    ... possibly more method calls to other interfaces ...
}

如果createNewRevision 在接口IRevisionable 中并且addChild 在接口ITree 中,我有哪些选项可以定义T

注意:假设我有几个其他接口以类似的方式工作:有很多地方它们是独立的,但有些代码需要看到它们的混合。 IRevisionableTree 不是解决方案,而是另一个问题。

我可以为每个调用转换类型,但这似乎很笨拙。创建接口的所有排列会很无聊,而且似乎没有合理的模式来压缩巨大的接口名称。泛型提供了一个很好的出路:

public
<T extends IRevisionable & ITree>
void addChild( T parent, T child ) { ... }

这并不总是适用于 Oracle 的 javac,但它看起来紧凑且有用。任何其他选项/cmets?

【问题讨论】:

    标签: java generics interface


    【解决方案1】:

    松散耦合的能力可能很有趣。一个example here。 这是一种完全不同的方法;解耦事物而不是打字。 基本上接口是隐藏的,实现为委托字段。

    IA ia = x.lookupCapability(IA.class);
    if (ia != null) {
        ia.a();
    }
    

    它适合这里,因为许多接口都希望解耦,并且您可以更轻松地组合相互依赖的接口的情况 (if (ia != null &amp;&amp; ib != null) ...)。

    【讨论】:

    【解决方案2】:

    如果你有方法(半码)

    void doSomething(IA & ID & IE thing);
    

    那么我主要关心的是:doSomething 不能更好地定制吗?拆分功能会更好吗?还是界面本身定制不当?

    我曾多次偶然发现类似的事情,每次都证明最好让后退一步并重新考虑逻辑的完整划分 - 不仅因为你提到的东西,而且由于其他问题。

    由于您非常抽象地提出了您的问题(即没有一个明智的例子),我无法告诉您这是否也适用于您的情况。

    【讨论】:

    • 我有一个Tree 的接口,它得到IA,但在实现中,我知道在这个阶段,thing 还必须实现IDIEthing 应该使用什么抽象类型?
    • @AaronDigulla:像 TreeIAthing 这样的抽象东西不会有帮助 - 这太遥远了为了真正了解您的问题领域并提炼出新的、有用的抽象。
    【解决方案3】:

    我会避免所有试图表示组合的“人工”接口/类型。这只是糟糕的设计......如果再添加 5 个接口会怎样?组合的数量激增。

    您似乎想知道某个实例是否实现了某些接口。合理的选择是:

    • 使用instanceof - 没有羞耻感
    • 使用反射通过object.getClass().getInterfaces() 发现接口 - 您也许可以编写一些通用代码来处理内容
    • 使用反射通过object.getClass().getMethods() 发现方法并调用与接口的已知方法列表匹配的方法(这种方法意味着您不必关心它实现了什么 - 听起来很简单,因此听起来不错想法)

    您没有向我们提供确切为什么您想知道的上下文,因此很难说“最佳”方法是什么。

    已编辑

    好的。由于添加了您的额外信息,它开始变得有意义。这里最好的方法是使用回调:不是传入父对象,而是传入一个接受“子”的接口。

    这是访问者模式的简化版本。您的调用代码知道它正在调用什么以及它如何处理孩子,但是导航和/或决定添加孩子的代码没有调用者的上下文。

    您的代码看起来像这样(警告:可能无法编译;我只是输入了它):

    public interface Parent<T> {
        void accept(T child);
    }
    
    // Central code - I assume the parent is passed in somewhere earlier
    public void process(Parent<T> parent) {
        // some logic that decides to add a child
        addChild(parent, child);
    }
    
    public void addChild(Parent<T> parent, T child ) {
        parent.accept(child);
    }
    
    // Calling code
    final IRevisionable revisionable = ...;
    someServer.process(new Parent<T> {
        void accept(T child) {
            T newRev = revisionable.createNewRevision();
            newRev.addChild(child);
        }
    }
    

    您可能需要处理各种事情,但我希望您能理解我的意思。

    【讨论】:

    • 我的情况正好相反:我知道在代码中的某个时刻,foo 必须实现IAIDIE(否则,它无法做到那么远) .现在我需要调用所有三个接口中的方法。 foo 应该得到什么类型?
    • if (foo instanceof IA &amp;&amp; foo instanceof ID &amp;&amp; foo instanceof IE) { foo.doSomething();} 有什么问题?你能提供更多的商业背景吗?你的问题都是关于一种特定的方法 - 你想要做什么(用简单的英语 - 不是代码)?
    【解决方案4】:

    其实解决方案1是一个很好的解决方案,但你应该找到一个更好的命名。

    您实际上会为实现IPersistable_MasterSlaveCapable_XmlIdentifierProvider 接口的类命名什么?如果您遵循良好的命名约定,它应该有一个源自模型实体的有意义的名称。可以给接口取同名前缀I

    我不认为拥有许多接口是不利的,因为这样您可以编写模拟实现以用于测试目的。

    【讨论】:

    • 实现这个接口的类叫做OrgUnit, User, ClientInfo, ...
    • 所以你命名你的接口:IOrgUnitIUserIClientInfo,等等。这样你就可以有像OrgUnitMockUserMockClientInfoMock这样的模拟实现来实现这些接口。
    • 那将给我 1000 个接口。
    • 您已经有 1000 个课程。通常为每个类定义一个接口是一个很好的设计策略,通过模拟类来简化测试
    【解决方案5】:

    我的情况正好相反:我知道在代码中的某个点, foo 必须实现 IA、ID 和 IE(否则,它无法实现 远的)。现在我需要调用所有三个接口中的方法。什么样的 foo 应该得到吗?

    你能通过传递(例如)三个对象来完全绕过这个问题吗?所以而不是:

    doSomethingWithFoo(WhatGoesHere foo);
    

    你会的:

    doSomethingWithFoo(IA foo, ID foo, IE foo);
    

    或者,您可以创建一个实现所有接口的代理,但允许您禁用某些接口(即调用“错误”接口会导致 UnsupportedOperationException)。

    最后一个疯狂的想法 - 可能可以为适当的接口创建动态代理,将其委托给您的实际对象。

    【讨论】:

    • 我不喜欢“一个实例变成三个”,因为最终,有人会为其中一个“foo”分配一个新值,或者调用具有多个实例的方法。动态代理也太慢了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-31
    • 2016-03-22
    • 2016-05-06
    • 1970-01-01
    • 2011-12-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多