¿Usar el método constructor o setter?

16

Estoy trabajando en un código de interfaz de usuario donde tengo una Actionclase, algo como esto:

public class MyAction extends Action {
    public MyAction() {
        setText("My Action Text");
        setToolTip("My Action Tool tip");
        setImage("Some Image");
    }
}

Cuando se creó esta clase de acción, se suponía que la Actionclase no sería personalizable (en cierto sentido, su texto, información sobre herramientas o imagen no se cambiarán en ninguna parte del código). Ahora, necesitamos cambiar el texto de acción en alguna ubicación del código. Entonces, le sugerí a mi compañero de trabajo que eliminara el texto de acción codificado del constructor y lo aceptara como argumento, para que todos se vean obligados a pasar el texto de acción. Algo como este código a continuación:

public class MyAction extends Action {
    public MyAction(String actionText) {
        setText(actionText);
        setTooltip("My Action tool tip"); 
        setImage("My Image"); 
    }
}

Él, sin embargo, piensa que dado que el setText()método pertenece a la clase base, se puede usar de manera flexible para pasar el texto de acción donde sea que se cree una instancia de acción. De esa manera, no hay necesidad de cambiar la MyActionclase existente . Entonces su código se vería así.

MyAction action = new MyAction(); //this creates action instance with the hardcoded text
action.setText("User required new action text"); //overwrite the existing text.

No estoy seguro si esa es una forma correcta de lidiar con el problema. Creo que en el caso mencionado anteriormente, el usuario de todos modos va a cambiar el texto, entonces, ¿por qué no forzarlo mientras construye la acción? El único beneficio que veo con el código original es que el usuario puede crear la clase de acción sin pensar mucho en configurar el texto.

zswap
fuente
1
¿El lenguaje que está utilizando no permite sobrecargar los constructores?
Mat
1
Estoy usando Java. Entonces, sí lo permite y creo que podría ser una forma de lidiar con esto
zswap
2
Notaría que si no tiene una forma pública de establecer miembros de la clase después del hecho, su clase es efectivamente inmutable . Al permitir un setter público, su clase ahora se vuelve mutable , y es posible que deba tener esto en cuenta si dependía de la inmutabilidad.
cbojar
Diría que si necesita configurarse para que su objeto sea válido, póngalo en cada constructor ... si es opcional (tiene un valor predeterminado razonable) y no le importa la inmutabilidad, póngalo en un setter. Debería ser imposible crear una instancia de su objeto en un estado no válido o, después de la instanciación, ponerlo en un estado no válido siempre que sea posible.
Bill K

Respuestas:

15

El único beneficio que veo con el código original es que el usuario puede crear la clase de acción sin pensar mucho en configurar el texto.

Eso en realidad no es un beneficio, para la mayoría de los propósitos es un inconveniente y en los casos restantes lo llamaría un empate. ¿Qué pasa si alguien olvida llamar a setText () después de la construcción? ¿Qué pasa si ese es el caso en algún caso inusual, tal vez un controlador de errores? Si realmente desea forzar el ajuste del texto, debe forzarlo en el momento de la compilación, ya que solo los errores en tiempo de compilación son realmente fatales . Todo lo que sucede en tiempo de ejecución depende de la ruta de código particular que se ejecute.

Veo dos caminos claros hacia adelante:

  1. Use un parámetro constructor, como sugiere. Si realmente lo desea, puede pasar nullo una cadena vacía, pero el hecho de que no está asignando un texto es explícito en lugar de implícito. Es fácil ver la existencia de un nullparámetro y ver que probablemente haya pensó algo en él, pero no es tan fácil ver la falta de una llamada al método y determinar si la falta fue intencional o no. Para un caso simple como este, este es probablemente el enfoque que tomaría.
  2. Use un patrón de fábrica / constructor. Esto puede ser excesivo para un escenario tan simple, pero en un caso más general es muy flexible ya que le permite establecer cualquier número de parámetros y verificar las condiciones previas antes o durante la creación de instancias del objeto (si construir el objeto es una operación grande y / o la clase se puede usar de más de una manera, esto puede ser una gran ventaja). Particularmente en Java, también es un idioma común, y seguir patrones establecidos en el lenguaje y el marco que está utilizando rara vez es algo malo.
un CVn
fuente
10

La sobrecarga de constructores sería una solución simple y directa aquí:

public class MyAction extends Action {
    public MyAction(String actionText) {
        setText(actionText);
        setTooltip("My Action tool tip"); 
        setImage("My Image"); 
    }
    public MyAction() {
        this("My Action Text");
    }
}

Es mejor que llamar .setTextmás tarde, porque de esta manera no es necesario sobrescribir nada,actionText puede ser lo que se pretende desde el principio.

A medida que su código evoluciona y necesitará aún más flexibilidad (lo que seguramente sucederá), se beneficiará del patrón de fábrica / generador sugerido por otra respuesta.

janos
fuente
¿Qué sucede cuando quieren personalizar una segunda propiedad?
Kevin Cline
3
Para una segunda, tercera, ... propiedad, puede aplicar la misma técnica, pero cuantas más propiedades desee personalizar, más difícil será. En algún momento tendrá más sentido implementar un patrón de fábrica / constructor, como dijo @ michael-kjorling en su respuesta.
janos
6

Agregue un método fluido 'setText':

public class MyAction ... {
  ...
  public MyAction setText(String text) { ... ; return this; }
}

MyAction a = new MyAction().setText("xxx");

¿Qué podría ser más claro que eso? Si decide agregar otra propiedad personalizable, no hay problema.

Kevin Cline
fuente
+1, estoy de acuerdo, y agregué otra respuesta que se complementa con más propiedades. Creo que la API fluida es más clara de entender cuando tiene más de 1 propiedad individual como ejemplo.
Machado
¡Me gustan las interfaces fluidas, especialmente para los constructores que producen objetos inmutables! Cuantos más parámetros, mejor funciona. Pero mirando el ejemplo específico en esta pregunta, supongo que setText()se define en la clase de Acción de la que MyAction hereda. Es probable que ya tenga un tipo de retorno nulo.
GlenPeterson
1

Tal como dijo Kevin Cline en su respuesta, creo que el camino a seguir es crear una API fluida . Solo me gustaría agregar que la API fluida funciona mejor cuando tiene más de una propiedad que puede usar.

Hará que su código sea más legible y, desde mi punto de vista, más fácil y, aham , "sexy" escribirlo.

En su caso, sería así (perdón por cualquier error tipográfico, ha pasado un año desde que escribí mi último programa Java):

 public class MyAction extends Action {
    private String _text     = "";
    private String _tooltip  = "";
    private String _imageUrl = "";

    public MyAction()
    {
       // nothing to do here.
    }

    public MyAction text(string value)
    {
       this._text = value;
       return this;
    }

    public MyAction tooltip(string value)
    {
       this._tooltip = value;
       return this;
    }

    public MyAction image(string value)
    {
       this._imageUrl = value;
       return this;
    }
}

Y el uso sería así:

MyAction action = new MyAction()
    .text("My Action Text")
    .tooltip("My Action Tool tip")
    .image("Some Image");
Machado
fuente
Mala idea, ¿qué pasa si se olvidan de establecer texto o algo importante?
Prakhar
1

El consejo para usar constructores o constructores está bien en general, pero, en mi experiencia, pierde algunos puntos clave para las Acciones, que

  1. Posiblemente necesite ser internacionalizado
  2. Es probable que el marketing cambie en el último minuto.

Sugiero encarecidamente que el nombre, la información sobre herramientas, el icono, etc. se lean desde un archivo de propiedades, XML, etc. Por ejemplo, para la acción Abrir archivo, puede pasar Propiedades y buscará

File.open.name=Open
File.open.tooltip=Open a file
File.open.icon=somedir/open.jpg

Este es un formato bastante fácil de traducir al francés, para probar un nuevo ícono mejor, etc. Sin tiempo de programador ni recompilación.

Esto es solo un esbozo, queda mucho para el lector ... Busque otros ejemplos de internacionalización.

user949300
fuente
0

Es inútil llamar a setText (actionText) o setTooltip ("Mi sugerencia de herramienta de acción") dentro del constructor; es más fácil (y gana más rendimiento) si simplemente inicializa el campo correspondiente directamente:

    public MyAction(String actionText) {
        this.actionText = actionText;
    }

Si cambia actionText durante el tiempo de vida del objeto correspondiente MyAction, debe colocar un método setter; si no, inicialice el campo solo en el constructor sin proporcionar un método de establecimiento.

Dado que la información sobre herramientas y la imagen son constantes, trátelas como constantes; tener campos:

private (or even public) final static String TOOLTIP = "My Action Tooltip";

En realidad, al diseñar objetos comunes (no beans u objetos que representan estrictamente estructuras de datos) es una mala idea proporcionar setters y getters, ya que rompen la encapsulación.

m3th0dman
fuente
44
Cualquier compilador competente a mitad de camino y JITter deben incorporar las llamadas setText (), etc., por lo que la diferencia de rendimiento entre una llamada a la función para realizar una asignación y tener esa asignación en lugar de la llamada a la función, debe ser insignificante a lo sumo, y más probable que no cero.
un CVn
0

Creo que esto es cierto si vamos a hacer una clase de acción genérica (como actualización, que se utiliza para actualizar Empleado, Departamento ...). Todo depende del escenario. Si se crea una clase de acción específica (como actualizar empleado) (se usa en muchos lugares de la aplicación - Actualizar empleado) con la intención de mantener el mismo texto, información sobre herramientas e imagen en cada lugar de la aplicación (para un punto de vista coherente). Por lo tanto, la codificación puede hacerse para texto, información sobre herramientas e imagen para proporcionar el texto, información sobre herramientas e imagen predeterminados. Aún para dar más flexibilidad, para personalizarlos, debe tener los métodos de establecimiento correspondientes. Teniendo en mente solo el 10% de los lugares, tenemos que cambiarlo. Tomar texto de acción cada vez del usuario puede causar texto diferente cada vez para la misma acción. Como 'Actualizar Emp', 'Actualizar Empleado', 'Cambiar Empleado' o 'Editar Empleado'.

Arenoso
fuente
Creo que el constructor sobrecargado aún debería resolver el problema. Porque en todos los casos de "10%", primero creará una Acción con texto predeterminado y luego cambiará el texto de acción utilizando el método "setText ()". ¿Por qué no establecer el texto apropiado al construir la acción?
zswap
0

Piense en cómo se usarán las instancias y use una solución que guíe, o incluso obligue, a los usuarios a usar esas instancias de la manera correcta, o al menos mejor. Un programador que use esta clase tendrá muchas otras cosas de las que preocuparse y pensar. Esta clase no debe agregarse a la lista.

Por ejemplo, si se supone que la clase MyAction es inmutable después de la construcción (y posiblemente otra inicialización), no debería tener un método de establecimiento. Si la mayoría de las veces usará el "Mi texto de acción" predeterminado, debería haber un constructor sin parámetros, más un constructor que permita un texto opcional. Ahora el usuario no necesita pensar en usar la clase correctamente el 90% del tiempo. Si el usuario generalmente debería pensar un poco en el texto, omita el constructor sin parámetros. Ahora el usuario se ve obligado a pensar cuando es necesario y no puede pasar por alto un paso necesario.

Si un MyAction instancia necesita ser mutable después de la construcción completa, entonces necesita un setter para el texto. Es tentador omitir la configuración del valor en el constructor (principio DRY - "No se repita") y, si el valor predeterminado es generalmente lo suficientemente bueno, lo haría. Pero si no es así, requerir el texto en el constructor obliga al usuario a pensar cuándo debería.

Tenga en cuenta que estos usuarios no son tontos . Simplemente tienen demasiados problemas reales de los que preocuparse. Al pensar en la "interfaz" de su clase, también puede evitar que se convierta en un problema real, y uno innecesario.

RalphChapin
fuente
0

En la siguiente solución propuesta, la superclase es abstracta y tiene los tres miembros establecidos en un valor predeterminado.

La subclase tiene diferentes constructores para que el programador pueda instanciarla.

Si se utiliza el primer constructor, todos los miembros tendrán los valores predeterminados.

Si se utiliza el segundo constructor, le da un valor inicial al miembro actionText dejando a otros dos miembros con el valor predeterminado ...

Si se utiliza un tercer constructor, lo instancia con un nuevo valor para actionText y toolTip, dejando imageURl con el valor predeterminado ...

Y así.

public abstract class Action {
    protected String text = "Default action text";
    protected String toolTip = "Default action tool tip";
    protected String imageURl = "http://myserver.com/images/default.png";

    .... rest of code, I guess setters and getters
}

public class MyAction extends Action {


    public MyAction() {

    }

    public MyAction(String actionText) {
        setText(actionText);
    }

    public MyAction(String actionText, String toolTip_) {
        setText(actionText);
        setToolTip(toolTip_);   
    }

    public MyAction(String actionText, String toolTip_; String imageURL_) {
        setText(actionText);
        setToolTip(toolTip_);
        setImageURL(imageURL_);
    }


}
Tulains Córdova
fuente