空接口以合并多个接口


20

假设您有两个接口:

interface Readable {
    public void read();
}

interface Writable {
    public void write();
}

在某些情况下,实现对象只能支持其中之一,但在很多情况下,实现将支持两个接口。使用界面的人员将必须执行以下操作:

// can't write to it without explicit casting
Readable myObject = new MyObject();

// can't read from it without explicit casting
Writable myObject = new MyObject();

// tight coupling to actual implementation
MyObject myObject = new MyObject();

这些选项都不是十分方便,甚至在考虑将其作为方法参数时更是如此。

一种解决方案是声明一个包装接口:

interface TheWholeShabam extends Readable, Writable {}

但这有一个特定的问题:同时支持Readable和Writable的所有实现必须实现TheWholeShabam,以使其与使用该界面的人员兼容。即使除了保证两个接口的存在之外,它什么也没有提供。

有没有解决此问题的解决方案,还是应该使用包装器接口?

更新

实际上,经常需要使对象既可读又可写,因此仅将参数中的关注点分开并不总是一个干净的解决方案。

更新2

(被提取为答案,因此更易于评论)

更新3

请注意,此操作的主要用例不是流(尽管也必须支持它们)。流在输入和输出之间做出了非常具体的区分,并且职责之间有明确的区分。而是想像一个字节缓冲区,您需要一个可以写入和读取的对象,一个附加了非常特定状态的对象。存在这些对象是因为它们对于异步I / O,编码,

更新4

我尝试的第一件事与以下建议相同(请检查接受的答案),但事实证明它太脆弱了。

假设您有一个必须返回类型的类:

public <RW extends Readable & Writable> RW getItAll();

如果调用此方法,则常规RW由接收对象的变量确定,因此您需要一种描述此var的方法。

MyObject myObject = someInstance.getItAll();

这将起作用,但是再次将其与实现联系在一起,并且实际上可能在运行时抛出classcastexceptions(取决于返回的内容)。

另外,如果要使用RW类型的类变量,则需要在类级别定义泛型。


5
这句话是“整个shebang”
凯文·克莱恩

这是一个很好的问题,但是我认为使用Readable和'Writable'作为示例界面在一定程度上使浑水不堪,因为它们通常是不同的角色……
vaughandroid

@Basueta虽然命名已简化,但可读性和可写性实际上很好地传达了我的用例。在某些情况下,您只想读,在某些情况下,您只想写;在某些情况下,您需要读和写。
nablex

我想不出我需要一个既可读又可写的流的时间,从其他人的答案/评论来看,我并不是唯一的一个。我只是说选择一对争议较小的接口可能更有用……
vaughandroid13年

@Baqueta与java.nio *软件包有什么关系?如果您坚持使用流,则用例确实仅限于使用ByteArray * Stream的地方。
nablex

Answers:


19

是的,您可以将您的方法参数声明为扩展了Readable和的未指定类型Writable

public <RW extends Readable & Writable> void process(RW thing);

方法声明看起来很糟糕,但是使用它比知道统一接口要容易。


2
我在这里更喜欢Konrads的第二种方法:process(Readable readThing, Writable writeThing)如果必须使用调用它process(foo, foo)
约阿希姆·绍尔

1
语法不正确<RW extends Readable&Writable>吗?

1
@JoachimSauer:为什么您会喜欢一种方法,而该方法比仅是视觉上丑陋的方法容易打破?如果我调用process(foo,bar)且foo和bar不同,则该方法可能会失败。
迈克尔·肖

@MichaelShaw:我的意思是当它们是不同的对象时它不会失败。为什么要这样 如果确实如此,那么我认为这样process做会做很多不同的事情,并且违反了单一责任原则。
Joachim Sauer

@JoachimSauer:为什么不会失败?for(i = 0; j <100; i ++)循环不如for(i = 0; i <100; i ++)有用。for循环读取和写入相同的变量,并且这样做不会违反SRP。
萧伯纳

12

如果有一个地方,你需要myObject同时作为ReadableWritable您可以:

  • 重构那个地方?阅读和写作是两回事。如果一种方法兼有,那么它可能就不遵循单一责任原则。

  • 传递myObject两次,作为Readable和作为Writable(两个参数)。该方法在乎是同一对象还是什么不是对象?


1
当您将其用作参数时,这可能会起作用,并且它们是单独的关注点。但是有时您确实希望同时具有可读写的对象(出于相同的原因,例如,您想使用ByteArrayOutputStream)
nablex 2013年

怎么来的?顾名思义,输出流将写入-可以读取的输入流。同样在C#中-有一个StreamWritervs. StreamReader(还有很多其他)
Konrad Morawski

您写入ByteArrayOutputStream以获得字节数(toByteArray())。这相当于写+读。接口背后的实际功能大体相同,但方式更为通用。有时您只希望读或写,有时您会两者都想要。另一个示例是ByteBuffer。
nablex

2
在第二点上我有点后退,但是经过一会儿的思考,这似乎并不是一个坏主意。您不仅要分离读写,而且要使功能更灵活,并减少其变异的输入状态。
Phoshi

2
@Phoshi问题是关注点并不总是分开的。有时您想要一个既可以读写的对象,又想要保证它是同一对象(例如ByteArrayOutputStream,ByteBuffer等)
nablex 2013年

4

当您不需要可读性可写性但两者都需要时,当前没有答案可以解决这种情况。您需要保证在写入A时,您可以从A读取该数据,而不是写入A并从B读取数据,只是希望它们实际上是同一对象。用例很多,例如在所有要使用ByteBuffer的地方。

无论如何,我几乎已经完成了我正在处理的模块,目前我选择了包装器接口:

interface Container extends Readable, Writable {}

现在您至少可以执行以下操作:

Container container = IOUtils.newContainer();
container.write("something".getBytes());
System.out.println(IOUtils.toString(container));

我自己的容器实现(当前为3个)都实现了Container而不是单独的接口,但是如果有人在实现中忘记了这一点,IOUtils提供了一种实用程序方法:

Readable myReadable = ...;
// assuming myReadable is also Writable you can do this:
Container container = IOUtils.toByteContainer(myReadable);

我知道这不是最佳解决方案,但由于容器仍然是一个相当大的用例,因此它仍然是目前的最佳解决方案。


1
我认为这绝对好。比其他答案中提出的其他一些方法更好。
汤姆·安德森

0

给定一个通用的MyObject实例,您总是需要知道它是否支持读取或写入。因此,您将获得如下代码:

if (myObject instanceof Readable)  {
    Readable  r = (Readable) myObject;
    readThisReadable( r );
}

在简单的情况下,我认为您无法对此进行改进。但是,如果readThisReadable要在读取文件后将Readable 写入另一个文件,它将变得很尴尬。

所以我可能会这样做:

interface TheWholeShabam  {
    public boolean  isReadable();
    public boolean  isWriteable();
    public void     read();
    public void     write();
}

readThisReadable现在readThisWholeShabam,将此参数作为参数可以处理实现TheWholeShabam的任何类,而不仅仅是MyObject。 而且它可以写,如果它是可写的,如果它是不是不会写。(我们有真正的“多态性”。)

因此,第一组代码变为:

TheWholeShabam  myObject = ...;
if (myObject.isReadable()
    readThisWholeShebam( myObject );

您可以通过readThisWholeShebam()进行可读性检查,在此处保存一行。

这确实意味着我们以前的Readable-only必须实现isWriteable()(返回false)和write()(不执行任何操作),但是现在它可以遍及以前无法企及的所有地方,并且可以处理TheWholeShabam的所有代码对象将处理它,而无需我们进一步努力。

另一件事:如果您可以处理不读取的类中对read()的调用,以及不浪费内容就不进行写入的类中对write()的调用,则可以跳过isReadable()和isWriteable () 方法。如果可行,这将是最优雅的处理方式。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.