Supongamos que tiene dos interfaces:
interface Readable {
public void read();
}
interface Writable {
public void write();
}
En algunos casos, los objetos de implementación solo pueden soportar uno de estos, pero en muchos casos las implementaciones admitirán ambas interfaces. Las personas que usan las interfaces tendrán que hacer 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();
Ninguna de estas opciones es terriblemente conveniente, aún más cuando se considera que desea esto como un parámetro de método.
Una solución sería declarar una interfaz de ajuste:
interface TheWholeShabam extends Readable, Writable {}
Pero esto tiene un problema específico: todas las implementaciones que admiten tanto Readable como Writable tienen que implementar TheWholeShabam si quieren ser compatibles con las personas que usan la interfaz. Aunque no ofrece nada aparte de la presencia garantizada de ambas interfaces.
¿Existe una solución limpia para este problema o debería elegir la interfaz de contenedor?
ACTUALIZAR
De hecho, a menudo es necesario tener un objeto que sea legible y escribible, por lo que simplemente separar las preocupaciones en los argumentos no siempre es una solución limpia.
ACTUALIZACIÓN2
(extraído como respuesta para que sea más fácil comentarlo)
ACTUALIZACIÓN3
Tenga en cuenta que el caso de uso principal para esto no son las transmisiones (aunque también deben ser compatibles). Las transmisiones hacen una distinción muy específica entre entrada y salida y existe una clara separación de responsabilidades. Por el contrario, piense en algo así como un bytebuffer donde necesita un objeto en el que pueda escribir y leer, un objeto que tiene un estado muy específico adjunto. Estos objetos existen porque son muy útiles para algunas cosas como E / S asíncronas, codificaciones, ...
ACTUALIZACIÓN4
Una de las primeras cosas que probé fue la misma que la sugerencia dada a continuación (verifique la respuesta aceptada) pero resultó ser demasiado frágil.
Supongamos que tiene una clase que debe devolver el tipo:
public <RW extends Readable & Writable> RW getItAll();
Si llama a este método, el RW genérico está determinado por la variable que recibe el objeto, por lo que necesita una forma de describir esta var.
MyObject myObject = someInstance.getItAll();
Esto funcionaría, pero una vez más lo vincula a una implementación y, en realidad, puede arrojar ideas de clase en tiempo de ejecución (dependiendo de lo que se devuelva).
Además, si desea una variable de clase del tipo RW, debe definir el genérico a nivel de clase.
fuente
Respuestas:
Sí, se puede declarar el parámetro de método como un tipo subespecificada que se extiende tanto
Readable
yWritable
:La declaración del método parece terrible, pero usarla es más fácil que tener que saber acerca de la interfaz unificada.
fuente
process(Readable readThing, Writable writeThing)
y si debe invocarlo usandoprocess(foo, foo)
.<RW extends Readable&Writable>
?process
hace varias cosas diferentes y viola el principio de responsabilidad única.Si hay un lugar donde necesita
myObject
tanto como aReadable
yWritable
puede:¿Refactorizar ese lugar? Leer y escribir son dos cosas diferentes. Si un método hace ambas cosas, tal vez no siga el principio de responsabilidad única.
Pase
myObject
dos veces, como ayReadable
como aWritable
(dos argumentos). ¿Qué le importa al método si es o no el mismo objeto?fuente
StreamWriter
vs.StreamReader
(y muchos otros)Ninguna de las respuestas actualmente aborda la situación cuando no necesita una lectura o escritura pero ambas . Necesita garantías de que al escribir en A, puede leer esos datos desde A, no escribir en A y leer desde B y solo esperar que sean realmente el mismo objeto. Los casos de uso son abundantes, por ejemplo, en todas partes usarías un ByteBuffer.
De todos modos, casi he terminado el módulo en el que estoy trabajando y actualmente he optado por la interfaz del contenedor:
Ahora al menos puedes hacer:
Mis propias implementaciones de contenedor (actualmente 3) implementan Container en lugar de las interfaces separadas, pero si alguien olvida esto en una implementación, IOUtils proporciona un método de utilidad:
Sé que esta no es la solución óptima, pero sigue siendo la mejor salida en este momento porque Container sigue siendo un caso de uso bastante grande.
fuente
Dada una instancia genérica de MyObject, siempre necesitará saber si admite lecturas o escrituras. Entonces tendrías un código como:
En el caso simple, no creo que pueda mejorar esto. Pero si
readThisReadable
quiere escribir el Leíble en otro archivo después de leerlo, se vuelve incómodo.Entonces probablemente iría con esto:
Tomando esto como un parámetro,
readThisReadable
ahorareadThisWholeShabam
puede manejar cualquier clase que implemente TheWholeShabam, no solo MyObject. Y puede escribirlo si se puede escribir y no escribirlo si no lo es. (Tenemos un verdadero "polimorfismo").Entonces el primer conjunto de código se convierte en:
Y puede guardar una línea aquí haciendo que ReadThisWholeShebam () realice la verificación de legibilidad.
Esto significa que nuestro anterior solo legible tiene que implementar isWriteable () (devolviendo falso ) y escribir () (sin hacer nada), pero ahora puede ir a todo tipo de lugares a los que no podía ir antes y todo el código que maneja TheWholeShabam los objetos se ocuparán de ello sin ningún esfuerzo adicional de nuestra parte.
Otra cosa: si puede manejar una llamada a read () en una clase que no lee y una llamada a write () en una clase que no escribe sin destruir algo, puede omitir isReadable () y isWriteable () métodos. Esta sería la forma más elegante de manejarlo, si funciona.
fuente