5

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

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);

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

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.

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


或者,我可以消除泛型,并拥有ExclusiveResource并且ShareableResource是抽象和公共的。
然后,这些类将由我和客户针对所需的每种类型的资源(、、...)进行FileResource extends ExclusiveResource扩展SocketResource 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);
}

但是,遵循继承方式似乎更正确。

4

1 回答 1

0

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

我这里还有两个选择。

使用泛型

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

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);
}

只有ResourceHandler, ResourceManager,ResourceState并且ResourceStrategy需要对客户公开。


使用继承

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

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 和用例。

于 2013-05-05T11:08:48.990 回答