Suponha que você tenha duas interfaces:
interface Readable {
public void read();
}
interface Writable {
public void write();
}
Em alguns casos, os objetos de implementação podem suportar apenas um deles, mas em muitos casos as implementações suportam as duas interfaces. As pessoas que usam as interfaces terão que fazer algo como:
// 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();
Nenhuma dessas opções é extremamente conveniente, ainda mais quando se considera que você deseja isso como um parâmetro de método.
Uma solução seria declarar uma interface de agrupamento:
interface TheWholeShabam extends Readable, Writable {}
Mas isso tem um problema específico: todas as implementações que suportam Readable e Writable precisam implementar o TheWholeShabam se quiserem ser compatíveis com as pessoas que usam a interface. Mesmo que não ofereça nada além da presença garantida de ambas as interfaces.
Existe uma solução limpa para esse problema ou devo usar a interface do wrapper?
ATUALIZAR
De fato, muitas vezes é necessário ter um objeto que seja legível e gravável, de modo que simplesmente separar as preocupações dos argumentos nem sempre seja uma solução limpa.
UPDATE2
(extraído como resposta, para ficar mais fácil comentar)
UPDATE3
Por favor, tome cuidado para que a principal base de dados para isso não seja fluxos (embora eles também devam ser suportados). Os fluxos fazem uma distinção muito específica entre entrada e saída e há uma clara separação de responsabilidades. Em vez disso, pense em algo como um bytebuffer no qual você precisa de um objeto no qual possa escrever e ler, um objeto que tenha um estado muito específico anexado. Esses objetos existem porque são muito úteis para algumas coisas como E / S assíncrona, codificações, ...
UPDATE4
Uma das primeiras coisas que tentei foi a mesma sugerida abaixo (verifique a resposta aceita), mas provou ser muito frágil.
Suponha que você tenha uma classe que precise retornar o tipo:
public <RW extends Readable & Writable> RW getItAll();
Se você chamar esse método, o RW genérico será determinado pela variável que recebe o objeto, portanto, você precisará de uma maneira de descrever essa var.
MyObject myObject = someInstance.getItAll();
Isso funcionaria, mas, mais uma vez, vincula-o a uma implementação e pode realmente lançar as exceções de classes de classe em tempo de execução (dependendo do que é retornado).
Além disso, se você deseja uma variável de classe do tipo RW, precisa definir o genérico no nível da classe.
fonte
Respostas:
Sim, você pode declarar seu parâmetro de método como um tipo subespecificado que estende ambos
Readable
eWritable
:A declaração do método parece terrível, mas usá-la é mais fácil do que saber sobre a interface unificada.
fonte
process(Readable readThing, Writable writeThing)
e se você deve invocá-lo usandoprocess(foo, foo)
.<RW extends Readable&Writable>
?process
faz várias coisas diferentes e viola o princípio de responsabilidade única.Se houver um local em que você precise
myObject
como aReadable
eWritable
você pode:Refatorar esse lugar? Ler e escrever são duas coisas diferentes. Se um método faz as duas coisas, talvez não siga o princípio da responsabilidade única.
Passe
myObject
duas vezes, comoReadable
e comoWritable
(dois argumentos). O que o método se importa se é ou não o mesmo objeto?fonte
StreamWriter
vs.StreamReader
(e muitos outros)Atualmente, nenhuma das respostas aborda a situação quando você não precisa de um texto legível ou gravável, mas ambos . Você precisa de garantias de que, ao escrever para A, possa ler esses dados de A, não para A e ler de B e apenas torcer para que sejam realmente o mesmo objeto. Usecases são abundantes, por exemplo, em todos os lugares que você usaria um ByteBuffer.
De qualquer forma, eu quase terminei o módulo em que estou trabalhando e atualmente optei pela interface do wrapper:
Agora você pode pelo menos fazer:
Minhas próprias implementações de contêiner (atualmente 3) todas implementam Contêiner em oposição às interfaces separadas, mas se alguém esquecer isso em uma implementação, o IOUtils fornece um método utilitário:
Sei que essa não é a solução ideal, mas ainda é a melhor saída no momento, porque o Container ainda é um caso de uso bastante grande.
fonte
Dada uma instância genérica do MyObject, você sempre precisará saber se ela suporta leituras ou gravações. Então você teria um código como:
No caso simples, não acho que você possa melhorar isso. Mas se você
readThisReadable
deseja gravar o Readable em outro arquivo depois de lê-lo, fica estranho.Então eu provavelmente iria com isso:
Tomando isso como um parâmetro,
readThisReadable
agorareadThisWholeShabam
, pode lidar com qualquer classe que implemente TheWholeShabam, não apenas MyObject. E pode escrevê-lo se for gravável e não escrevê-lo se não for. (Temos um verdadeiro "polimorfismo".)Portanto, o primeiro conjunto de códigos se torna:
E você pode salvar uma linha aqui fazendo com que readThisWholeShebam () faça a verificação de legibilidade.
Isso significa que nosso antigo Readable-only precisa implementar isWriteable () (retornando false ) e write () (sem fazer nada), mas agora ele pode ir a todos os tipos de lugares que não podia ir antes e todo o código que lida com TheWholeShabam objetos irão lidar com isso sem nenhum esforço adicional de nossa parte.
Outra coisa: se você pode lidar com uma chamada para read () em uma classe que não lê e uma chamada para write () em uma classe que não escreve sem lixeira, você pode pular isReadable () e isWriteable () métodos. Essa seria a maneira mais elegante de lidar com isso - se funcionar.
fonte