Básicamente necesito ejecutar diferentes acciones dada una cierta condición. El código existente se escribe de esta manera.
Interfaz base
// DoSomething.java
interface DoSomething {
void letDoIt(String info);
}
Implementación de la primera clase obrera.
class DoItThisWay implements DoSomething {
...
}
Implementación de la segunda clase obrera.
class DoItThatWay implements DoSomething {
...
}
La clase principal
class Main {
public doingIt(String info) {
DoSomething worker;
if (info == 'this') {
worker = new DoItThisWay();
} else {
worker = new DoItThatWay();
}
worker.letDoIt(info)
}
Este código funciona bien y es fácil de entender.
Ahora, debido a un nuevo requisito, necesito pasar una nueva información que solo tiene sentido DoItThisWay
.
Mi pregunta es: ¿es bueno el siguiente estilo de codificación para manejar este requisito?
Use una nueva variable de clase y método
// Use new class variable and method
class DoItThisWay implements DoSomething {
private int quality;
DoSomething() {
quality = 0;
}
public void setQuality(int quality) {
this.quality = quality;
};
public void letDoIt(String info) {
if (quality > 50) { // make use of the new information
...
} else {
...
}
} ;
}
Si lo hago de esta manera, necesito hacer el cambio correspondiente a la persona que llama:
class Main {
public doingIt(String info) {
DoSomething worker;
if (info == 'this') {
int quality = obtainQualityInfo();
DoItThisWay tmp = new DoItThisWay();
tmp.setQuality(quality)
worker = tmp;
} else {
worker = new DoItThatWay();
}
worker.letDoIt(info)
}
¿Es un buen estilo de codificación? ¿O puedo simplemente lanzarlo?
class Main {
public doingIt(String info) {
DoSomething worker;
if (info == 'this') {
int quality = obtainQualityInfo();
worker = new DoItThisWay();
((DoItThisWay) worker).setQuality(quality)
} else {
worker = new DoItThatWay();
}
worker.letDoIt(info)
}
java
coding-style
Anthony Kong
fuente
fuente
quality
al constructorDoItThisWay
?DoItThisWay
yDoItThatWay
se realiza una vez en el constructor deMain
.Main
es una clase de larga vida ydoingIt
se llama muchas veces una y otra vez.setQuality
método se llamará varias veces durante la vida útil delDoItThisWay
objeto?Respuestas:
Supongo que
quality
debe establecerse junto con cadaletDoIt()
llamada en aDoItThisWay()
.El problema que veo surgir aquí es el siguiente: está introduciendo el acoplamiento temporal (es decir, ¿qué sucede si olvida llamar
setQuality()
antes de llamarletDoIt()
a unDoItThisWay
?). Y las implementaciones paraDoItThisWay
yDoItThatWay
son divergentes (uno debe habersetQuality()
llamado, el otro no).Si bien esto puede no causar problemas en este momento, puede volver a perseguirte eventualmente. Puede valer la pena echarle otro vistazo
letDoIt()
y considerar si la información de calidad podría ser parte de la información queinfo
usted pasa; Pero esto depende de los detalles.fuente
Desde mi punto de vista, ninguna de sus versiones es. Primero tener que llamar
setQuality
antes de queletDoIt
se pueda llamar es un acoplamiento temporal . Estás atrapado viendoDoItThisWay
como un derivado deDoSomething
, pero no lo es (al menos no funcionalmente), es más bien algo así como unEsto haría
Main
más bien algo comoPor otro lado, podría pasar los parámetros a las clases directamente (suponiendo que esto sea posible) y delegar la decisión de cuál usar en una fábrica (esto haría que las instancias sean intercambiables nuevamente y ambas puedan derivarse
DoSomething
). Esto haría que seMain
vea asíSoy consciente de que escribiste eso
Pero podría almacenar en caché las piezas que son costosas de crear en la fábrica y pasarlas también al constructor.
fuente
DoItThisWay
requiere que se establezca la calidad antes de llamar,letDoIt
se comporta de manera diferente. Esperaría queDoSomething
a funcione de la misma manera (sin establecer la calidad antes) para todos los derivados. ¿Esto tiene sentido? Por supuesto, esto es algo borroso, ya que si llamamossetQuality
desde un método de fábrica, el cliente sería agnóstico acerca de si la calidad debe establecerse o no.letDoIt()
es (sin información adicional) "Puedo ejecutar correctamente (hacer lo que haga) si me lo proporcionaString info
". Exigir quesetQuality()
se llame de antemano fortalece la condición previa de la llamadaletDoIt()
y, por lo tanto, sería una violación de LSP.letDoIt
" no arrojará ninguna excepción, y olvidarse de llamar "setQuality" haríaletDoIt
una excepción, entonces estaría de acuerdo, tal implementación violaría el LSP. Pero esos me parecen suposiciones muy artificiales.Supongamos que
quality
no se puede pasar al constructor, ysetQuality
se requiere la llamada a .Actualmente, un fragmento de código como
es demasiado pequeño para invertir demasiado en ello. En mi humilde opinión, se ve un poco feo, pero no es realmente difícil de entender.
Los problemas surgen cuando tal fragmento de código crece y debido a la refactorización terminas con algo como
Ahora, no ves de inmediato si ese código sigue siendo correcto, y el compilador no te lo dirá. Por lo tanto, introducir una variable temporal del tipo correcto y evitar el reparto es un poco más seguro, sin ningún esfuerzo adicional real.
Sin embargo, en realidad le daría
tmp
un mejor nombre:Tales nombres ayudan a hacer que el código incorrecto parezca incorrecto.
fuente
DoItThisWay
obtiene parámetros más específicos; según lo que sugiere, el nombre debe cambiarse cada vez. Es mejor utilizar nombres para variables que aclaren el tipo de objeto, no los nombres de métodos disponibles.workerThisWay
convierta en algo asíusingQuality
. En ese pequeño fragmento de código es más seguro ser más específico. (Y, si hay una frase mejor dentro de su dominio que "usingQuality", úsela.Una interfaz común donde la inicialización varía según los datos de tiempo de ejecución generalmente se presta al patrón de fábrica.
Entonces DoThingsFactory puede preocuparse por obtener y establecer la información de calidad dentro del
getDoer(info)
método antes de devolver el objeto concreto DoThingsWithQuality emitido a la interfaz DoSomething.fuente