A menudo me encuentro con este problema, especialmente en Java, incluso si creo que es un problema general de OOP. Es decir: plantear una excepción revela un problema de diseño.
Supongamos que tengo una clase que tiene un String name
campo y un String surname
campo.
Luego usa esos campos para componer el nombre completo de una persona para mostrarlo en algún tipo de documento, digamos una factura.
public void String name;
public void String surname;
public String getCompleteName() {return name + " " + surname;}
public void displayCompleteNameOnInvoice() {
String completeName = getCompleteName();
//do something with it....
}
Ahora quiero fortalecer el comportamiento de mi clase arrojando un error si displayCompleteNameOnInvoice
se llama antes de que se haya asignado el nombre. Parece una buena idea, ¿no?
Puedo agregar un código de excepción al getCompleteName
método. Pero de esta manera estoy violando un contrato 'implícito' con el usuario de la clase; en general, los getters no deben lanzar excepciones si no se establecen sus valores. Ok, este no es un captador estándar ya que no devuelve un solo campo, pero desde el punto de vista del usuario, la distinción puede ser demasiado sutil para pensarlo.
O puedo lanzar la excepción desde adentro del displayCompleteNameOnInvoice
. Pero para hacerlo debería probar directamente name
o surname
campos y al hacerlo violaría la abstracción representada por getCompleteName
. Es responsabilidad de este método verificar y crear el nombre completo. Incluso podría decidir, basando la decisión en otros datos, que en algunos casos es suficiente surname
.
Así que la única posibilidad parece cambiar la semántica del método getCompleteName
a composeCompleteName
, que sugiere un comportamiento más 'activa' y, con ella, la capacidad de lanzar una excepción.
¿Es esta la mejor solución de diseño? Siempre estoy buscando el mejor equilibrio entre simplicidad y corrección. ¿Hay una referencia de diseño para este problema?
fuente
displayCompleteNameOnInvoice
puede lanzar una excepción sigetCompleteName
regresanull
, ¿no?Respuestas:
No permita que su clase se construya sin asignar un nombre.
fuente
La respuesta proporcionada por DeadMG prácticamente lo clava, pero permítanme expresarlo de manera un poco diferente: evite tener objetos con un estado no válido. Un objeto debe ser "completo" en el contexto de la tarea que cumple. O no debería existir.
Entonces, si quieres fluidez, usa algo como el Patrón de construcción . O cualquier otra cosa que sea una reificación separada de un objeto en construcción en oposición a la "cosa real", donde se garantiza que este último tiene un estado válido y es el que realmente expone las operaciones definidas en ese estado (por ejemplo
getCompleteName
).fuente
So if you want fluidity, use something like the builder pattern
- fluidez o inmutabilidad (a menos que sea de alguna manera). Si uno quiere crear objetos inmutables, pero necesita diferir la configuración de algunos valores, entonces el patrón a seguir es establecerlos uno por uno en un objeto generador y solo crear uno inmutable una vez que hayamos reunido todos los valores necesarios para el correcto estado ...No tenga un objeto con un estado no válido.
El propósito de un constructor o constructor es establecer el estado del objeto en algo que sea consistente y utilizable. Sin esta garantía, surgen problemas con un estado inicializado incorrectamente en los objetos. Este problema se agrava si se trata de concurrencia y algo puede acceder al objeto antes de que haya terminado de configurarlo.
Entonces, la pregunta para esta parte es "¿por qué estás permitiendo que el nombre o apellido sea nulo?" Si eso no es algo válido en la clase para que funcione correctamente, no lo permita. Tenga un constructor o constructor que valide adecuadamente la creación del objeto y, si no es correcto, plantee el problema en ese punto. También puede utilizar una de las
@NotNull
anotaciones que existen para ayudar a comunicar que "esto no puede ser nulo" y aplicarla en la codificación y el análisis.Con esta garantía, es mucho más fácil razonar sobre el código, lo que hace, y no tener que lanzar excepciones en lugares extraños o poner controles excesivos alrededor de las funciones getter.
Getters que hacen más.
Hay bastante por ahí sobre este tema. ¿Tienes cuánta lógica en Getters y qué debería permitirse dentro de getters y setters? desde aquí y getters y setters que realizan lógica adicional en Stack Overflow. Este es un problema que surge una y otra vez en el diseño de clase.
El núcleo de esto proviene de:
Y usted tiene razón. No deberían. Deberían parecer "tontos" al resto del mundo y hacer lo que se espera. Poner demasiada lógica allí conduce a problemas en los que se viola la ley del menor asombro. Y admitámoslo, realmente no quieres envolver a los captadores con un
try catch
porque sabemos cuánto nos encanta hacer eso en general.También hay situaciones en las que debe usar un
getFoo
método como el marco JavaBeans y cuando tiene algo de llamadas EL que espera obtener un bean (para que<% bar.foo %>
realmente llamegetFoo()
a la clase, dejando a un lado el 'debería el bean hacer la composición o debería que se deje a la vista? 'porque uno puede llegar fácilmente a situaciones en las que uno u otro claramente puede ser la respuesta correcta)Tenga en cuenta también que es posible que un getter determinado se especifique en una interfaz o que haya sido parte de la API pública expuesta anteriormente para la clase que se está refactorizando (una versión anterior solo tenía 'completeName' que se devolvió y se rompió una refactorización en dos campos).
Al final del día...
Haz lo que sea más fácil de razonar. Pasará más tiempo manteniendo este código del que dedicará a diseñarlo (aunque cuanto menos tiempo dedique a diseñar, más tiempo dedicará a mantenerlo). La elección ideal es diseñarlo de tal manera que no lleve tanto tiempo mantenerlo, pero tampoco te quedes ahí pensando durante días, a menos que esta sea realmente una elección de diseño que tendrá días de implicaciones más adelante .
Intentar mantener la pureza semántica del ser captador
private T foo; T getFoo() { return foo; }
te meterá en problemas en algún momento. A veces, el código simplemente no se ajusta a ese modelo y las contorsiones que uno atraviesa para tratar de mantenerlo así simplemente no tienen sentido ... y, en última instancia, hacen que sea más difícil de diseñar.Acepte a veces que los ideales del diseño no pueden realizarse de la forma en que los quiere en el código. Las pautas son pautas, no grilletes.
fuente
surname
oname
ser nulo es un estado válido para el objeto, trátelo en consecuencia. Pero si no es un estado válido, lo que hace que los métodos dentro de la clase arrojen excepciones, se debe evitar que se encuentre en ese estado desde el punto en que se inicializó. Puede pasar objetos incompletos, pero pasar los que no son válidos es problemático. Si un apellido nulo es excepcional, intente evitarlo más temprano que tarde.No soy un chico de Java, pero parece cumplir con las dos restricciones que presentaste.
getCompleteName
no lanza una excepción si los nombres no están inicializados, y lodisplayCompleteNameOnInvoice
hace.fuente
getCompleteName()
no tiene que preocuparse si la propiedad está realmente almacenada o si se calcula sobre la marcha.getCompleteName
, lo cual es horrible.getCompleteName()
en el resto de la clase, ni siquiera en otras partes del programa. En algunos casos, regresarnull
puede ser exactamente lo que se requiere. Pero si bien hay UN método cuya especificación es "lanzar una excepción en este caso", eso es EXACTAMENTE lo que debemos codificar.Parece que nadie está respondiendo tu pregunta.
A diferencia de lo que a la gente le gusta creer, "evitar objetos no válidos" no suele ser una solución práctica.
La respuesta es:
Si deberia.
Ejemplo fácil:
FileStream.Length
en C # /. NET , que arrojaNotSupportedException
si el archivo no es buscable.fuente
Tienes todo el nombre del cheque al revés.
getCompleteName()
no tiene que saber qué funciones lo utilizarán. Por lo tanto, no tener unname
cuando llamadisplayCompleteNameOnInvoice()
nogetCompleteName()
es responsabilidad de usted.Pero,
name
es un requisito previo claro paradisplayCompleteNameOnInvoice()
. Por lo tanto, debe asumir la responsabilidad de verificar el estado (o debe delegar la responsabilidad de verificar a otro método, si lo prefiere).fuente