【问题标题】:Java - Using Generics or InheritanceJava - 使用泛型或继承
【发布时间】:2013-05-04 21:27:52
【问题描述】:

我有一个接口,Resource,它应该包装 something 并在被包装的对象上公开一些操作。
我的第一个方法是编写以下内容,同时牢记 Strategy 模式。

interface Resource<T> {
    ResourceState read();
    void write(ResourceState);
}

abstract class AbstractResource<T> implements Resource<T> {
    // This is where the Strategy comes in.
    protected AbstractResource(ResourceStrategy<T> strat) {
        // ...
    }

    // Both the read and write implementations delegate to the strategy.
}

class ExclusiveResource<T> extends AbstractResource<T> { ... }
class ShareableResource<T> extends AbstractResource<T> { ... }

上述两种实现在使用的锁定方案上有所不同(常规锁或读写锁)。

还有一个ResourceManager,一个负责管理这些事情的实体。 我对客户使用的想法是:

ResourceManager rm = ...
MyCustomObject o = ...
MyCustomReadWriteStrategy strat = ...
rm.newResourceFor(o, "id", strat);

这样,客户端会知道资源,但不必直接处理资源(因此是包私有类)。另外,我可以自己实现一些公共资源,比如套接字,而客户端只会要求它们(ie,我必须写一个SocketStrategy implements ResourceStrategy&lt;Socket&gt;)。

ResourceManager rm = ...
rm.newSocketResource("id", host, port);

要访问资源,他会向经理请求处理程序。这是由于每个线程都有一些特定的访问权限,因此管理器会创建一个具有适当访问权限的处理程序。

// This is in the ResourceManager class.
public ResourceHandler getHandlerFor(String id) {
    if (!canThreadUseThisResource(id)) throw ...;
    if (isThreadReaderOnly()) {
         return new ResourceReadHandler( ... );
    } else {
         return new ResourceWriteHandler( ... );
    }
}

这就是问题所在。
这种方法对我来说似乎干净和清晰,它对用户来说似乎也很直观。 但是,正如暗示的那样,管理器保留了从标识符到资源的映射。这将如何声明,以及管理器如何从地图中检索资源?

Map<String, Resource<?>> map;
// Can I go around without any specific cast? Not sure yet.
Resource<?> r = map.get(id);
// This could have an enum ResourceType, to check if thread has privileges
// for the specific type.

这种设计是否可以接受和/或遵循良好做法?


或者,我可以清除泛型,让 ExclusiveResourceShareableResource 抽象和公开。
然后,这些类将由我和客户针对所需的每种类型的资源(FileResource extends ExclusiveResourceSocketResource extends ExclusiveResource、...)进行扩展。
这可能会消除对策略模式的需求,但会将我的包更多地暴露给用户。

这些替代方案中哪一个是最正确的,或被广泛接受为良好做法?


编辑:经过一番思考,我认为我可以从Resource 接口中删除泛型,因为这是造成问题的原因,并将其保留在AbstractResource 及其子类中.后者仍然可以让我对所使用的策略进行编译时验证。

public <T> void newExclusiveResourceFor(
        T obj, String id, ResourceStrategy<T> strat) {
    ExclusiveResource<T> r = new ExclusiveResource<>(obj, strat);
    map.put(id, r);
}

不过,遵循继承的方式似乎更正确。

【问题讨论】:

  • 投反对票有什么特别的原因吗?让我知道这个问题是否可以改进。
  • Resource> 带走了泛型的好处。它可以容纳任何东西。在我看来,你可以在这里消灭泛型。
  • @dkaustubh 我明白你的意思,我同意。这里的泛型只是一种确保用户定义的资源接收适当策略的方法,因为用户不能直接访问资源。但我想按照继承方式,我可以实现相同的目标,同时使用适当的工具。
  • 我对@9​​87654336@ 泛型声明感到有些困惑,因为它的成员都没有使用T - 该接口是否有充分的理由成为泛型?
  • @PaulBellora 事实上,我认为没有,正如我刚才在问题更新中指出的那样。最好将泛型从接口中取出,即使它保留在实现中。

标签: java generics inheritance architecture


【解决方案1】:

正如dkaustubhPaul Bellora 所建议的那样,Resource 接口中的泛型没有合理的理由。起初我完全没有注意到这一点,因为我希望实现是通用的,所以我认为接口也应该是通用的。事实并非如此。

我这里还有两个选择。

使用泛型

我应该删除界面中的泛型。然后,我会得到以下结果。

interface Resource {
    ResourceState read();
    void write(ResourceState);
    void dispose();
}

abstract class AbstractResource<T> implements Resource {
    /* This is where the Strategy comes in.
     * The generic ensures compile-time verification of the
     * strategy's type. */
    protected AbstractResource(ResourceStrategy<T> strat) {
        // ...
    }

    // Both the read and write implementations delegate to the strategy.
}

class ExclusiveResource<T> extends AbstractResource<T> { ... }
class ShareableResource<T> extends AbstractResource<T> { ... }

// This is the behaviour the client implements, for custom resources.
public abstract class ResourceStrategy<T> {
    public abstract ResourceState read(T obj);
    public abstract void write(ResourceState state);
    public abstract void dispose(T obj);
}

只有 ResourceHandlerResourceManagerResourceStateResourceStrategy 需要对客户公开。


使用继承

使用继承,我可以实现相同的结果,但需要进行一些权衡。

public interface Resource {
    ResourceState read();
    void write(ResourceState);
    void dispose();
}

/* These implement only the locking schemes. */
abstract class ExclusiveResource implements Resource { ... }
abstract class ShareableResource implements Resource { ... }

/* The user extends these for custom content and behaviour. */
public abstract class CustomExclusiveResource
        extends ExclusiveResource { ... }
public abstract class CustomShareableResource
        extends ShareableResource { ... }

资源现在对客户端公开。


结论

  1. 这两种方法都存在滥用资源的方法,绕过预期的合同和线程权限。这两种方法在这里是平等的。

  2. 使用泛型,客户端不需要知道资源的内部表示,因为管理器在后台创建资源。通过继承,资源创建发生在客户端,因此管理器的 API 将更改为接受提供的资源。

  3. 尽管Resources 不是公开的,但使用泛型,客户需要了解策略。有了继承,这些就消失了,而是将public 状态分配给资源。

  4. 使用策略,可以在运行时更改行为,或者对同一种资源有不同的行为。没有它们,客户端需要处理资源,然后使用实现不同行为的另一个子类重新创建它。
    例如:小文件可以完全读入内存,而大文件可能需要适当大小的缓冲区。

除非缺少其他内容,否则可能只是选择问题,并考虑所需的 API 和用例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-06
    • 1970-01-01
    • 2021-10-11
    • 1970-01-01
    相关资源
    最近更新 更多