代码之家  ›  专栏  ›  技术社区  ›  whiskeysierra

对于组合和标记接口是否有解决方法?

  •  6
  • whiskeysierra  · 技术社区  · 14 年前

    java.io.Serializable )和几个包装器(适配器、装饰器、代理等)。但是,当您将一个可序列化实例包装到另一个实例(不可序列化)中时,就会失去功能。同样的问题也发生在java.util.RandomAccess 可以通过列表实现。有没有一个很好的OOP方法来处理它?

    4 回复  |  直到 14 年前
        1
  •  7
  •   Dimitris Andreou    14 年前

    这是最近关于番石榴邮件列表的讨论-我的回答涉及到这个,相当基本的问题。

    http://groups.google.com/group/guava-discuss/browse_thread/thread/2d422600e7f87367/1e6c6a7b41c87aac

    其要点是: 当您希望包装对象时,不要使用标记接口 . (好吧,这很一般-怎么说 你知道你的对象不会被客户包装吗?)

    ArrayList . 它实现了 RandomAccess ,显然。然后决定为创建一个包装器 List 实现随机访问!

    如果你只有一个标记接口的话,这个工作“很好”!但是如果包装的对象可以序列化呢?如果它是,比如说,“不可变的”(假设你有一个类型来表示它)?还是同步的(同样的假设)。

    正如我在对邮件列表的回答中所指出的那样,这种设计缺陷也表现在旧版本中 java.io 包裹。假设你有办法接受 InputStream BufferedInputStream 为你?哦,那很简单!你只要检查一下 stream instanceof BufferedInputStream ,如果没有,你自己包起来!但是没有。流可能在链的某个地方有缓冲,但是你可能会得到一个包装器, 不是BufferedInputStream的实例 . 因此,“这个流被缓冲”的信息丢失了(也许你不得不悲观地浪费内存来再次缓冲它)。

    适当地

    interface YourType {
      Set<Capability> myCapabilities();
    }
    
    enum Capability {
      SERIALIAZABLE,
      SYNCHRONOUS,
      IMMUTABLE,
      BUFFERED //whatever - hey, this is just an example, 
               //don't throw everything in of course!
    }
    

    编辑: 应该注意的是,我使用枚举只是为了方便。可以通过接口 Capability 以及一组实现它的开放式对象(可能是多个枚举)。

    因此,当你包装一个这样的对象时,你会得到一组功能,你可以很容易地做出决定 .

    显然,它有它的缺点,所以它只在您真正感受到包装器隐藏表示为标记接口的功能的痛苦的情况下使用。例如,假设您编写了一段接受列表的代码,但它必须是随机访问的 可序列化。按照通常的方法,这很容易表达:

    <T extends List<Integer> & RandomAccess & Serializable> void method(T list) { ... }
    

    void method(YourType object) {
      Preconditions.checkArgument(object.getCapabilities().contains(SERIALIZABLE));
      Preconditions.checkArgument(object.getCapabilities().contains(RANDOM_ACCESS));
      ...
    }
    

    编辑: 另一个缺点是,没有明确的 类型 对于每个功能,我们没有合适的位置来放置表示此功能所提供内容的方法。这在讨论中不太重要,因为我们讨论的是 标记 接口,即不通过其他方法表达的功能,但我提到它是为了完整性。

    PS:顺便说一句,如果你浏览一下Guava的collections代码,你会真正感受到这个问题所带来的痛苦。是的,一些优秀的人正试图把它隐藏在美好的抽象背后,但是潜在的问题仍然是痛苦的。

        2
  •  5
  •   mkadunc    14 年前

    public interface Wrapper {
        boolean isWrapperFor(Class<?> iface);
    }
    

    其实现如下所示:

    public boolean isWrapperFor(Class<?> cls) {
        if (wrappedObj instanceof Wrapper) {
            return ((Wrapper)wrappedObj).isWrapperFor(cls);
        }
        return cls.isInstance(wrappedObj);
    }
    

    这就是我的工作方式 java.sql.Wrapper . 如果接口不仅仅是一个标记,实际上还具有一些功能,则可以添加一个方法来展开:

    <T> T unwrap(java.lang.Class<T> cls)
    
        3
  •  1
  •   Tom Hawtin - tackline    14 年前

    RandomAccess instanceof 检查并创建相关类的实例。类的数量随着标记呈指数增长(尽管您可以使用 java.lang.reflect.Proxy

    Serializable 没那么糟。如果间接类实现 可序列化 如果目标类是 可序列化 如果不是的话也不行。

        4
  •  1
  •   mdma    14 年前

    1. 使包装器实现接口,如果在编译时知道包装对象是否也实现接口。如果直到运行时才知道包装对象是否将实现接口,则可以使用工厂方法来创建包装。这意味着您可以为实现的接口的可能组合使用单独的包装器类(对于一个接口,您需要两个包装器,一个有一个没有。用于2个接口、4个包装器等。)

    2. 从包装器中公开包装的对象,这样客户机就可以遍历链并使用 instanceof

    3. 有一个专用的方法来检索接口,由包装器和包装对象实现。例如。 asSomeInterface()

    4. 为每个接口创建一个包装器类-包装器照常实现-它实现接口并委托给该接口的另一个实现。一个包装对象可以实现多个接口,因此通过使用动态代理将代理实现的接口方法委托给适当的包装实例,可以将多个包装实例组合成一个逻辑实例。代理实现的接口集必须没有任何共同的方法签名。

    微软烘焙 aggregation ( Wikipedia )组件对象模型(COM)。大多数人似乎都没有使用它,但对COM对象实现者来说却造成了相当大的复杂性,因为每个对象都必须遵守一些规则。包装对象是通过让包装对象知道它们是包装器来封装的,必须维护一个指向包装器的指针,这是在实现QueryInterface(松散地)时使用的 运算符

    我还没有看到一个干净、易于理解/实现和正确封装的解决方案。COM聚合可以工作并提供完整的封装,但这是您为实现的每个对象支付的成本,即使它从未在聚合中使用。