Método de miembro de Java Patrón de inicialización -- java campo con design-patterns campo con constructor camp codereview Relacionados El problema

Java member method initialization pattern


2
vote

problema

Español

Para evitar el método sestreable en el dilema constructor, este enfoque delega la inicialización de las variables y las llamadas a métodos sesperados a un método miembro que devuelve la referencia a la instancia, su auto y la excepción se lanzarán si intentan usar instancia sin invocar el método de inicialización. / s para que la creación de objetos se vea así:

  SuperClass sup = new SuperClass().initialize("big"); SubClass sub = new SubClass().initialize("sato", "try initialzer"); SubClass sub2 = new SubClass().initialize("happy"); SubClass sub3 = new SubClass().initialize();   

Mi Super Clase debe implementar métodos de interfaz inicializable Tiene dos métodos:

  1. Inicializar el método en el que escribo código para hacer lo siguiente en orden

    1. Métodos de configuración de llamadas y otro código de inicialización
    2. Realice una llamada al método sobretrosque que los pone al final del método. Este método se puede sobrecargar para proporcionar versiones con los argumentos necesarios como se ilustra en un ejemplo complejo
  2. El método isInitialzed Para verificar si la inicialización se realiza o no cuando una llamada a un método en mi clase que hace referencia a una variable que se inicializa en el método de inicialización y la inicialización no se realiza, lanza mi NonInitializedInstanceException .

La subclase debe anular el método de inicializar "todas las versiones de la super clase del método inicializar" y hacer lo siguiente en orden:

  1. inicializa sus variables
  2. puede llamar al Método Super que no lanza NonInitializedInstanceException
  3. llamada super.initialize() MÉTODO
  4. Métodos de programación de llamadas "Tenga en cuenta que los súper llame a los colonos para evitar que nuestro setter haya sido inútil, deberíamos llamarlo después de la llamada de Método Super Inicialize"
  5. puede llamar al Método Super que lanza NonInitializedInstanceException

Manejo de versiones sobrecargadas de inicialización HECHO llamando a la que con más parámetros de la persona con menos parámetros.

La interfaz:

   /**  * @author ahmed mazher  * @param <T> making class initializable   *   */ public interface Initializable<T> {      /**      * do the initialization job and return reference to the implementing      * class current instance       * @return      */     T initialize();      /**      * test the variables that you initialize in the initialize method      * if it was initialized or not "or what ever test you see appropriate"      * @return      */     boolean isInitialized(); }   

EL {ABC, ["Simon", "John"]} {DEF, ["Simon"]} {XYZ, ["John"]} 0 CLASE:

  {ABC,     ["Simon", "John"]} {DEF,     ["Simon"]} {XYZ,     ["John"]} 1  

Ejemplo de usarlos:

  {ABC,     ["Simon", "John"]} {DEF,     ["Simon"]} {XYZ,     ["John"]} 2  

Un ejemplo más complicado utilizando el inicializador con argumentos:

  {ABC,     ["Simon", "John"]} {DEF,     ["Simon"]} {XYZ,     ["John"]} 3  

Sugerencia de usar el método estático patrón de fábrica. Como esperaba, el dolor estático nunca termina. Aquí está mi prueba para adaptar mi patrón en la estática. Fallé, así que no sé cómo hacer que Subclass invoque superclase y cree un método para afectar su instancia.

  {ABC,     ["Simon", "John"]} {DEF,     ["Simon"]} {XYZ,     ["John"]} 4  
Original en ingles

To avoid the overridable method call in constructor dilemma this approache delegate the variables initialization and calls to overridable methods to a member method that return reference to the instance its self and exception to be thrown if trying to use instance without invoking the initialization method/s so that the creation of objects will look like this:

SuperClass sup = new SuperClass().initialize("big"); SubClass sub = new SubClass().initialize("sato", "try initialzer"); SubClass sub2 = new SubClass().initialize("happy"); SubClass sub3 = new SubClass().initialize(); 

My super class should implement methods of initializable interface it has two methods:

  1. Initialize method in which I write code to do the following in order

    1. Call setter methods and other initialization code
    2. Make call to overridable method putting them at end of the method. This method can be overloaded to provide versions of it with necessary arguments as illustrated in complex example
  2. The isInitialzed method to check if the initialization done or not when a call to a method in my class that reference a variable that is initialized in initialize method and initialization not done, I throw my NonInitializedInstanceException.

The subclass should override the initialize method/s "all the super class versions of the initialize method" and do the following in order:

  1. Initialize its variables
  2. Can call super method that don't throw NonInitializedInstanceException
  3. Call super.initialize() method
  4. Call setter methods "note that the super call the setters so to avoid our setter been useless we should call it after super initialize method call"
  5. Can call super method that throw NonInitializedInstanceException

Handling overloaded versions of initialize done by calling the one with more parameters from the one with fewer parameters.

The interface:

 /**  * @author ahmed mazher  * @param <T> making class initializable   *   */ public interface Initializable<T> {      /**      * do the initialization job and return reference to the implementing      * class current instance       * @return      */     T initialize();      /**      * test the variables that you initialize in the initialize method      * if it was initialized or not "or what ever test you see appropriate"      * @return      */     boolean isInitialized(); } 

The NonInitializedInstanceException class:

/**  * @author Ahmed mazher   * runtime exception to be thrown by method that reference variables that  * are initialized by the initialize method of the initializable interface  * you can test if initialization was done or not by isInialized method   * of the same interface if false throw the exception   */ public class NonInitializedInstanceException extends RuntimeException {      private static final long serialVersionUID = 1L;       @Override     public String getMessage() {         return super.getMessage()                  + " you must call instance.initialize() method "                 + "before calling this method";     }  } 

Example of using them:

/**  * @author ahmed mazher  * testing the initializer pattern  */ class LearnJava {      public static void main(String args[]) {         SuperClass sup = new SuperClass().initialize();         SubClass sub = new SubClass().initialize();     }  }  class SuperClass implements Initializable<SuperClass> {      private String initializeDependentName;     private final String nonInitializeDependentVal = "well done";     @Override     public SuperClass initialize() {         //initializing super Class fields         setInitializeDependentName("super class");         //overidable method call in initializer "replacement of          //call in constructor"         overridablePrintName();         return this;     }      public final String getInitializeDependentName() throws NonInitializedInstanceException {         if (!isInitialized()) {             throw new NonInitializedInstanceException();         }         return initializeDependentName;     }      public final void setInitializeDependentName(String initializeDependentName) {         this.initializeDependentName = initializeDependentName;     }      public final void youCanCallMeBeforeInitialization(){         System.out.println(nonInitializeDependentVal);     }      public void overridablePrintName() {         System.out.println("I'm the super method and my name is " + getInitializeDependentName());     }      @Override     public boolean isInitialized() {         return initializeDependentName != null;     } //to avoid some one messing with isInitialized() make your     //private one and use it instead but don't forget to call it inside     //the body of isInitialized() to maintian integrity of inheritance hierarchy //    private boolean privateIsInitialized(){ //        return initializeDependentName != null; //    } //    @Override //    public boolean isInitialized() { //        return privateIsInitialized(); //    }  }  class SubClass extends SuperClass {      private String job;      @Override     public SubClass initialize() {         //initializing subClass fields         setJob("testing initialize method");         //call super method that throws nonInitialiezinstanceException          //before call super.initialize() method will rise the exception         //unless you overided the isInitialized method "welling to take the risk"         //------------------------------------------------------------------         //System.out.println(getName().toUpperCase());         //------------------------------------------------------------------         //call super method that not throws nonInitialiezinstanceException          //before call super.initialize() method         youCanCallMeBeforeInitialization();         setInitializeDependentName("subClass");         //calling super.initialize() method initialize super methods         super.initialize();//initialize the super class         //call super method that throws nonInitialiezinstanceException          //after call super.initialize() method will not rise the exception         //unless you overided the isInitialized method in stupid way ^_^         System.out.println(getInitializeDependentName().toUpperCase());         //calling the setter method now to change my property as calling         //it before the super initialization is useless as the super          //initialization will erase it         setInitializeDependentName("subClass");         System.out.println(getInitializeDependentName().toUpperCase());         return this;     }      public final void setJob(String job) {         this.job = job;     }      @Override     public void overridablePrintName() {         System.out.println("i'm overriden method and i do " + job);     }      @Override     public boolean isInitialized() {         //return false;//stupid version          //return true;//take the risk         return job != null && super.isInitialized();//a good one      }  } 

A more complicated example using initializer with arguments:

class LearnJava {      public static void main(String args[]) {         SuperClass sup = new SuperClass().initialize("big");         SubClass sub = new SubClass().initialize("sato", "try initializer");         SubClass sub2 = new SubClass().initialize("happy");         SubClass sub3 = new SubClass().initialize();     }  }  class SuperClass implements Initializable<SuperClass> {      private String initializeDependentName;     private final String nonInitializeDependentVal = "well done";      @Override     public SuperClass initialize() {         return initialize("i'm super with no name");     }      public SuperClass initialize(String name) {         //initializing super Class fields         setInitializeDependentName(name);         //overidable method call in initializer "replacement of          //call in constructor"         overridablePrintName();         return this;     }      public final String getInitializeDependentName() throws NonInitializedInstanceException {         if (!isInitialized()) {             throw new NonInitializedInstanceException();         }         return initializeDependentName;     }      public final void setInitializeDependentName(String initializeDependentName) {         this.initializeDependentName = initializeDependentName;     }      public final void youCanCallMeBeforeInitialization() {         System.out.println(nonInitializeDependentVal);     }      public void overridablePrintName() {         System.out.println("i'm the super method my name is " + getInitializeDependentName());     }      @Override     public boolean isInitialized() {         return initializeDependentName != null;     }      //to avoid some one messing with isInitialized() make your     //private one and use it instead but don't forget to call it inside     //the body of isInitialized() to maintian integrity of inheritance hierarchy //    private boolean privateIsInitialized(){ //        return initializeDependentName != null; //    } //    @Override //    public boolean isInitialized() { //        return privateIsInitialized(); //    } }  class SubClass extends SuperClass {      private String job;      @Override     public SubClass initialize() {         return initialize("i'm subclass no one give me name");     }      @Override     public SubClass initialize(String name) {         return initialize(name, "no one give me job");     }      public SubClass initialize(String name, String job) {         //initializing subClass fields         setJob(job);         //call super method that throws nonInitialiezinstanceException          //before call super.initialize() method will rise the exception         //unless you overided the isInitialized method "welling to take the risk"         //------------------------------------------------------------------         //System.out.println(getName().toUpperCase());         //------------------------------------------------------------------         //call super method that not throws nonInitialiezinstanceException          //before call super.initialize() method         youCanCallMeBeforeInitialization();         //calling super.initialize() method initialize super methods         super.initialize(name);//initialize the super class         //call super method that throws nonInitialiezinstanceException          //after call super.initialize() method will not rise the exception         //unless you overided the isInitialized method in stuped way ^_^         System.out.println(getInitializeDependentName().toUpperCase());         //calling the setter method now to change my property as calling         //it before the super initialization is useless as the super          //initialization will erase it         setInitializeDependentName("setter called in subclass");         System.out.println(getInitializeDependentName().toUpperCase());         return this;     }      public final void setJob(String job) {         this.job = job;     }      @Override     public void overridablePrintName() {         System.out.println("i'm overiden method my name is "                 + getInitializeDependentName()                 + " and i do " + job);     }      @Override     public boolean isInitialized() {         //return false;//stuped version          //return true;//take the risk         return job != null && super.isInitialized();//a good one      }  } 

Suggestion of using static method factory pattern. As I expected, static pain never ends. Here is my trial to adapt my pattern into static one. I failed, so I don't know how to make subClass invoke superClass and create a method to affect its instance.

class LearnJava {      public static void main(String args[]) {         SuperClass.create("nono");         SubClass.create("sato","test factory");      }  }  class SuperClass{      private String name;     protected SuperClass() {     }      public static SuperClass create(String name) {         SuperClass sup = new SuperClass();         //initializing super Class fields         sup.setName(name);         //overidable method call useless as i always call the super instance version          sup.overridablePrintName();         return sup;     }      public final String getName(){         return name;     }      public final void setName(String name) {         this.name = name;     }      public void overridablePrintName() {         System.out.println("i'm the super method my name is " + getName());     } }  class SubClass extends SuperClass {      private String job;      protected SubClass() {         super();     }      public static SubClass create(String name, String job) {         SubClass sub = new SubClass();         //initializing subClass fields         sub.setJob(job);         //call the super class initializer to affect this sub class instatnce          //seems no way to do it         sub.create(name);         SubClass.create(name);         SuperClass.create(name);         return sub;     }      public final void setJob(String job) {         this.job = job;     }      @Override     public void overridablePrintName() {         System.out.println("i'm overiden method my name is "                 + getName()                 + " and i do " + job);     } } 
        
     
     

Lista de respuestas

1
 
vote

Revisión de código inicial

  • Usted declaró su head7 como head8 y posteriormente declaramos sus métodos para lanzar esta excepción sin marcar. Cambie el tipo de Excetpión a head9 o elimine el head0 de los métodos.
  • No estoy seguro de cómo una clase puede depender de su nombre. Si quiso decir que debe especificarse head1 probablemente sea más adecuado
  • Basado en la API en sí no está claro si se puede invocar un método mientras la inicialización o no. Dudo que un caso real de negocios incluiría un nombramiento como head2 o head3 . Por lo tanto, una sobrecarga de la documentación es obligatoria que conduce a una API que podría ser más difícil y más desalentada de usar.

    Tampoco estoy totalmente seguro de si en el tiempo de inicialización dado previniendo los métodos de sobreridrado, las invocaciones son necesarias, ya que se ejecuta primero el constructor de la super clase, lo que no hace nada, seguido del constructor secundario, que tampoco hace nada. En un sentido Java, el objeto ahora está completamente inicializado. Luego, se invoca el método de los niños head44 que invoca el método de super inicialización en algún momento. Pero como el niño ya está inicializado en un sentido de Java, invocando cualquier otro método dentro del método del inicializador es admisible. Una llamada de método de sobreirrido dentro del método de inicialización siempre devolverá el estado de sobreridrado correcto.

Descripción del problema

El problema real que intenta resolver es establecer el estado dentro del constructor que aún no está disponible, ya que la clase principal podría invocar el método sestrado, pero como el niño aún no está inicializado, el método anulado no devuelve lo que puede Se le asume que se devuelva, dejando el objeto actualmente inicializado en un estado inconsistente.

Su código inicializa un nuevo objeto y luego invoca head5 que es básicamente un método de fábrica que es similar a un constructor, pero en lugar de devolver una referencia de producto, devuelve la referencia de sí misma. Como ya tiene un objeto válido a través de la creación de instancias a través de head6 Lanzar un head7 puede prevenir su mayor uso del objeto, aunque los métodos sobrevulsibles siempre establecerán el valor correcto ya que el objeto ya estaba creado.

Enfoques alternativos

Como solicitó enfoques alternativos, intentaré representar otras dos formas de solucionar el método sestruzable en el problema del constructor:

  • objeto de parámetro
  • patrón de constructor

objeto de parámetro

En Java, el constructor se usa generalmente para inicializar el estado del objeto. Es posible retrasar la inicialización a un método adicional, pero solo es útil en ciertas circunstancias como DE / Serialización o algunas iniciales perezosas imo.

Si no necesita inicialización perezosa, puede estar mejor con un objeto de parámetros, que puede ser un mapa simple o una clase de parámetro especializada.

  head8  

Una clase heredada podría parecer esto:

  head9  

Aquí, primero los campos del padre se inicializan y para ninguno de los campos finales, incluso se puede actualizar como con // OP's head = malloc(sizeof(struct node)); // Alternate head = malloc(sizeof *head); 0 en el siguiente ejemplo.

Este enfoque también permite establecer argumentos a través de la reflexión fácilmente. Un método de muestra que se podría poner en la clase principal puede parecer el siguiente a continuación:

  // OP's head = malloc(sizeof(struct node));  // Alternate head = malloc(sizeof *head); 1  

En el constructor respectivo, entonces necesita cambiar el código a algo como:

  // OP's head = malloc(sizeof(struct node));  // Alternate head = malloc(sizeof *head); 2  

En lugar de un mapa, también puede usar un objeto de parámetro personalizado que contiene los receptores y los restadores respectivos. Tenga en cuenta que no manejo todo tipo de excepciones aquí, ya que esto solo debería ser un tutorial corto en los objetos de parámetros en lugar de la reflexión.

Este enfoque establece el valor de campo directamente o invoca un método final definitivo y, por lo tanto, no se puede reutilizar por los niños.

Patrón de constructor

Según una respuesta bien aceptada la invocación Los métodos exagerados dentro del constructor pueden arreglarse fácilmente con un patrón de constructor.

En comparación con los mapas de parámetros genéricos, los constructores permiten el tiempo de seguridad de compilación, especialmente si el objeto de parámetro está unido a objetos en lugar de tipos más específicos. Además de eso, la reflexión también tiene su precio y, si crea objetos a menudo, esto puede disminuir la solicitud notablemente.

Los constructores pueden usar ciertos valores predeterminados para Campos para configurar y solo sobrescribir aquellos que se configuran explícitamente. Además, los constructores generalmente crean solo instancias completamente inicializadas. Los constructores son especialmente útiles si tiene objetos que permiten inyectar múltiples valores dentro del constructor. Además de minimizar la sobrecarga de recordar todo tipo de constructores, también permiten agrupar ciertos parámetros lógicamente. Considere algún constructor de forma, por ejemplo. Sin embargo, podría hacer algo como // OP's head = malloc(sizeof(struct node)); // Alternate head = malloc(sizeof *head); 3 , lógicamente más expresivo sería usar algo como // OP's head = malloc(sizeof(struct node)); // Alternate head = malloc(sizeof *head); 4 . Los editores modernos de Java también proporcionan soporte de finalización de códigos para los constructores y, por lo tanto, proporcionan una interfaz fluida para configurar los parámetros necesarios. Por último, pero no menos importante, en el uso de los constructores, generalmente puede asignar los valores en el tiempo de construcción a los campos finales y, por lo tanto, evitar la reasignación de otras referencias a este campo.

Mientras que los constructores no se usan a menudo en combinación con la herencia, es posible con algunas generas. Tiendo a mantener los parámetros necesarios que no tienen valores predeterminados dentro del constructor del constructor y los parámetros opcionales o los que el valor se puede sobrescribir como métodos de configuración de constructor típico.

El siguiente código de ejemplo presenta un escenario donde un constructor de base se extiende por los constructores infantiles. El código también está disponible en GitHub < / a>. Un constructor básico tiene una forma como esta:

  // OP's head = malloc(sizeof(struct node));  // Alternate head = malloc(sizeof *head); 5  

// OP's head = malloc(sizeof(struct node)); // Alternate head = malloc(sizeof *head); 66 es el tipo genérico del parámetro opcional, mientras que // OP's head = malloc(sizeof(struct node)); // Alternate head = malloc(sizeof *head); 7 es el tipo genérico del constructor real. Para invocar el constructor de concreto en los constructores infantiles en lugar de que el constructor de base se pasa el tipo genérico como valor de retorno en los constructores. Especificando el tipo generado El constructor produce a través del tipo genérico tendría sentido, aunque en la versión actual de Java (Java 7 y 8) en el uso del opperador de diamantes, Java es incapaz de resolver los tipos correctos. Para evitar esta limitación en lugar de devolver algún tipo T del formulario // OP's head = malloc(sizeof(struct node)); // Alternate head = malloc(sizeof *head); 8 El tipo de retorno se basa en el tipo que el producto constructor se asigna a través de // OP's head = malloc(sizeof(struct node)); // Alternate head = malloc(sizeof *head); 9 .

El long00 sucursal en la el repositorio de GitHUB también muestra cómo resolver este problema, aunque en lugar de un simple operador de diamantes se debe proporcionar una definición genérica bastante compleja para ejecutar las pruebas sin advertencias del editor.

Extensión de producto y constructor se vea así:

  long01  

long02 , long03 , long04 998877665554433105 En esta muestra son solo clases simples del formulario: < / p>

  long06  

Para proporcionar alguna salida de prueba y mostrar la funcionalidad del patrón. long07 es solo una estafa de long08 que define los colonos para long09 y long10 en lugar de long11 y long12 .

El código a continuación es solo un simple escenario de escaparate:

  long13  

Eclipse e IntelliJ actúan de manera diferente en este código, pero ambos editores (además de compilar y ejecutar el código manualmente) producen la misma salida.

El código anterior debe producir la siguiente salida:

  long14  

Mientras que el constructor tiene un par de ventajas, el requisito de código casi duplicado es seguro de una sobrecarga y, por lo tanto, puede desalentarse de usar, especialmente para los objetos que no vienen con muchos parámetros. Algunos también argumentan que los colonos que deben invocarse para pasar valores al objeto, lo que puede resultar en una larga cadena de invocaciones, conduce al código que es difícil de leer y, por lo tanto, también está desalentador. Con el formato de código adecuado, aunque encuentro el código a menudo más ReadBlac en comparación con invocar los colonos en el objeto real.

Ciertos Setters (Producto y Constructor) pueden ser reemplazados por Lombok Anotaciones que pueden ayudar a limpiar aún más el código y De este modo, reduzca el número de líneas de código real.

 

Initial code review

  • You declared your NonInitializedInstanceException as RuntimeException and later declared your methods to throw this unchecked exception. Either change the excetpion type to Exception or remove the throws NonInitializedInstanceException from the methods.
  • I'm not sure how a class can be dependent on its name. If you meant that it needs to be specified requiredName would probably more suitable
  • Based on the API itself it is not clear if a method may be invoked while initialization or not. I doubt that a real business case would include a naming like youCanCallMeBeforeInitialization or overridablePrintName. Therefore a documentation overhead is mandatory which leads to an API that might be harder and more discouraging to use.

    I'm also not fully sure if at the given initialization time preventing overriden method invocations are necessary as first the super class constructor is executed, which does nothing, followed by the child constructor, which also does nothing. In a Java sense the object is now fully initialized. Afterwards the childs initialize method is invoked which invokes the super initialization method at some point. But as the child is already initialized in a Java sense, invoking any other method within the initializer method is admissible. An overriden method call within the initialization method will thus always return the correct overriden state.

Problem description

The actual problem you try to solve is to set state within the constructor which may not yet be available as the parent class might invoke the overridable method, but as the child is not yet initialized, the overridden method does not return what may be assumed to be returned leaving the currently initialized object in an inconsistent state.

Your code initializes a new object and afterwards invokes initialize which is basically a factory method which is similar to a builder but instead of returning a product reference it returns the reference of itself. As you already have a valid object through instantiating via new SuperClass() throwing a NonInitializedInstanceException may prevent further usage of the object, though overridable methods will always set the correct value as the object was already created.

Alternative approaches

As you asked for alternative approaches, I'll try to depict two other ways to fix the overridable method in constructor issue:

  • Parameter object
  • Builder pattern

Parameter object

In Java the constructor is usually used to initialize the object state. Delaying the initialization to a further method is possible but only useful in certain circumstances like de/serialization or some lazy initializations IMO.

If you don't need lazy initialization you may be better off with a parameter object, which can be a simple map or a specialized parameter class.

public class Sample {     protected final SomeType someField;     protected SomeOtherType someOtherField;      public Sample(Map<String, Object> parameters) {         if (parameters != null) {             if (parameters.contains(KEY) {                 this.someField = (SomeType)parameters.get(KEY);             } else if (parameters.contains(OTHER_KEY)) {                 this.someOtherField = (SomeOtherType)parameters.get(OTHER_KEY);             }         } else {             throw new IllegalArgumentException("Could not initialize field 'someField'!");         }     } } 

An inheriting class could look like this:

public class SomeSample extends Sample {      private final YetSomeOtherType yetSomeOtherField;      public SomeSample(Map<String, Object> parameters) {         super(parameters);         if (parameters.contains(YET_OTHER_KEY)) {             this.yetSomeOtherField = (YetSomeOtherType)parameters.get(YET_OTHER_KEY);             this.someOtherField = null; // admissible         } else {             throw new IllegalArgumentException("Could not initialize field 'someOtherField'!");         }     } } 

Here, first the fields of the parent are initialized and for none final fields, the may even be updated like with someOtherField in the example below.

This approach also allows to set arguments via reflection easily. A sample method which could be put into the parent class may look like the one below:

protected final void setRequiredField(Field field, Map<String, Object> parameters, String key) {     if (null == field) {         throw new InvalidArgumentException("Invalid field to inject value into for key '"                                             + key + "' provided");     }     if (null == key || "".equals(key)) {         throw new InvalidArgumentException("Could not inject value into field '"                                             + field.getName() + "' as key was invalid");     }                 if (null == parameters || parameters.isEmpty()) {         throw new InvalidArgumentException("Invalid parameter object provided. Could not inject value with key '"                                             + key + "' into field '" + field.getName() + "'!");     }      if (parameters.contains(key)) {         field.set(this, parameters.get(key));     } else {         throw new IllegalArgumentException("Could not set value for field '"                                             + field.getName() + "' as provided parameters did not contain key: '"                                             + key + "'");     } } 

In the respective constructor you then need to change the code to something like:

public Sample(Map<String, Object> parameters) {     setRequiredField(getClass().getDeclaredField("someField"), parameters, KEY); }  ...  public SomeSample(Map<String, Object> parameters) {     super(parameters);     setRequiredField(getClass().getDeclaredField("someOtherField"), parameters, OTHER_KEY); } 

Instead of a map you can also use a custom parameter object which contains respective getters and setters. Note that I don't handle all kinds of exceptions here as this should only be a short tutorial on parameter objects rather then reflection.

This approach either sets the field value directly or invokes a final and therefore none overridable setter method, which also can be reused by children.

Builder pattern

According to a well-accepted answer the invocation of overridable methods within the constructor can easily get fixed using a builder pattern.

Compared to generic parameter maps, builders allow for compile time safetiness, especially if the parameter object is bound to objects rather then more specific types. In addition to that, reflection also has its price and if you create objects often, this may slow down the application notably.

Builders may use certain default values for fields to set and only overwrite those that are explicitly set. Also, builders usually create only fully initialized instances. Builders are especially useful if you have objects which allow to inject multiple values within the constructor. Besides minimizing the overhead of remembering all kinds of constructors they also allow to group certain parameters logically. Consider some shape builder for instance. you could do something like withPosX(x).withPosY(y).withColor(yellow)... though, logically more expressive would be to use something like withPosition(x, y).withColor(yellow).... Modern Java editors also provide code completion support for builder setters and therefore provide a fluent interface for setting needed parameters. Last but not least, on using builders you usually can assign the values upon build-time to final fields and therefore prevent reassigning other references to this field.

While builders are not used often in combination with inheritance, it is possible with some overhead though. I tend to keep necessary parameters which have no default values within the constructor of the builder and optional parameters or ones which value can be overwritten as typical builder setter methods.

The following sample code presents a scenario where a base builder is extended by child builders. The code is also available on Github. A basic builder has a form like this:

public abstract class Base<Z> {     public static abstract class Builder<B extends Builder<? extends B, Z>, Z> {         // necessary parameters         protected final String a;         protected final String b;         protected final String c;         // optional parameters         protected Z z = null;          public Builder(String a, String b, String c) {             this.a = a;             this.b = b;             this.c = c;         }          @SuppressWarnings("unchecked")         public B withOptionalZ(Z z) {             this.z = z;             return (B)this;         }          public abstract <T> T build();     }      protected final String name;     protected final String a;     protected final String b;     protected final String c;     protected Z z = null;      protected Base(String name, String a, String b, String c) {         this(name, a, b, c, null);     }      protected Base(String name, String a, String b, String c, Z z) {         this.name = name;         this.a = a;         this.b = b;         this.c = c;         this.z = z;     }      public String getA() {         return a;     }      public String getB() {         return b;     }      public String getC() {         return c;     }      protected abstract String getContent();      @Override     public String toString() {         return name+"[A: " + a + ", B: " + b + ", C: " + c                 + (z != null ? ", Z: " + z.toString() : "") + getContent() +"]";     } } 

Z is the generic type of the optional parameter whereas B is the generic type of the actual builder. In order to invoke the concrete builder on child builders rather then the base builder the generic type is passed as return value on the builder setters. Specifying the generated type the builder produces via the generic type would make sense, though in the current version of Java (Java 7 and 8) on using the diamond opperator, Java is incapable of resolving the correct types. To bypass this limitation instead of returning some type T of the form ? extends Param<Z> the return type is based on the type the builder product is assigned to via <T> T.

The genericReturnType branch in the Github repository also showcases how to solve this issue, though instead of a simple diamond operator a quite complex generic definition has to be provided in order to execute the tests without editor warnings.

A product and builder extension do look like this:

public class SampleA<D,E,Z> extends Base<Z> {     @SuppressWarnings("unchecked")     public static class Builder<B extends SampleA.Builder<? extends B, D, E, Z>,                                 D,E,Z>             extends Base.Builder<SampleA.Builder<B,D,E,Z>, Z> {         protected D d;         protected E e;          public Builder(String a, String b, String c) {             super(a, b, c);         }          public B withD(D d) {             this.d = d;             return (B)this;         }          public B withE(E e) {             this.e = e;             return (B)this;         }          @Override         public <T> T build() {             return (T) new SampleA<>("Test A", a, b, c, z, d, e);         }     }      protected final D d;     protected final E e;      protected SampleA(String name, String a, String b, String c, Z z, D d, E e) {         super(name, a, b, c, z);         this.d = d;         this.e = e;     }      public D getD() {         return d;     }      public E getE() {         return e;     }      @Override     protected String getContent() {         return ", D: " + d + ", E: " + e;     } } 

D, E, F and G in this sample are just simple classes of the form:

public class D {      @Override     public String toString() {         return "D";     } } 

to provide some test output and showcase the functionality of the pattern. SampleB is just a ripoff of SampleA which defines setters for F and G rather then D and E.

The code below is just a simple showcase scenario:

public class Main {     public static void main(String ... args)     {         // sample builder invocation         SampleA<D,E,?> a = new SampleA.Builder<>("a","b","c")             .withD(new D()).withE(new E()).build();         // sample with invoking setter for optional parameter         SampleB<F,G,String> b = new SampleB.Builder<>("a","b","c")             .withF(new F()).withG(new G()).withOptionalZ("z").build();         // assigning the product of the builder to a parent type         Base<String> c = new SampleA.Builder<>("a","b","c")             .withD(new D()).withE(new E()).withOptionalZ("z").build();         // omitted generic type by intention         Base d = new SampleB.Builder<>("a","b","c")             .withF(new F()).withG(new G()).build();          test(a);         test(b);         test(c);         test(d);          test(new SampleA.Builder<>("a","b","c")             .withD(new D()).withE(new E()));         test(new SampleB.Builder<>("a","b","c")             .withF(new F()).withG(new G()).withOptionalZ("z"));         testBase(new SampleA.Builder<>("a","b","c")             .withD(new D()).withE(new E()).withOptionalZ("z"));         testBase(new SampleB.Builder<>("a","b","c")             .withF(new F()).withG(new G()));     }      public static void test(SampleA<?,?,?> sample)     {         System.out.println("Test for SampleA: " + sample.toString());     }      public static void test(SampleB<?,?,?> sample)     {         System.out.println("Test for SampleB: " + sample.toString());     }      public static void test(Base<?> base)     {         System.out.println("Test for Base: " + base.toString());     }      public static void test(SampleA.Builder<?,?,?,?> builder)     {         System.out.println("Test for BuilderA: " + builder.build().toString());     }      public static void test(SampleB.Builder<?,?,?,?> builder)     {         System.out.println("Test for BuilderB: " + builder.build().toString());     }      public static void testBase(Base.Builder<?,?> builder)     {         System.out.println("Test for Base builder: " + builder.build().toString());     } } 

Eclipse and IntelliJ act differently on this code but both editors (as well as compiling and executing the code manually) produce the same output.

The code above should produce the following output:

Test for SampleA: Sample A[A: a, B: b, C: c, D: D, E: E] Test for SampleB: Sample B[A: a, B: b, C: c, Z: z, F: F, G: G] Test for Base: Sample A[A: a, B: b, C: c, Z: z, D: D, E: E] Test for Base: Sample B[A: a, B: b, C: c, F: F, G: G] Test for BuilderA: Sample A[A: a, B: b, C: c, D: D, E: E] Test for BuilderB: Sample B[A: a, B: b, C: c, Z: z, F: F, G: G] Test for Base builder: Sample A[A: a, B: b, C: c, Z: z, D: D, E: E] Test for Base builder: Sample B[A: a, B: b, C: c, F: F, G: G] 

While builder have a couple of advantages, the almost duplicate code requirement is for sure an overhead and therefore may be discouraging to use, especially for objects that do not come with a lot of parameters. Some also argue that the setters that need to be invoked to pass values to the object, which may result in a long chain of invocations, leads to code that is hard to read and therefore is also discouraging. With proper code formatting though I find the code often more readabl compared to invoking setters on the real object.

Certain setters (Product and Builder) may be replaced by Lombok annotations which may help to clean the code further and thus reduce the number of actual lines of code.

 
 
     
     

Relacionados problema

3  Construyendo una función de distribución acumulada de un histograma  ( Constructing a cumulative distribution function from a histogram ) 
Durante algún proceso de funcionamiento, se acumulará un histograma de valores. Cuando se hace, el CDF se derivará de ella y se utilizará para obtener los c...

6  Argumentos en los constructores campos a juego  ( Arguments in constructors matching fields ) 
Bloqueado . Esta pregunta y sus respuestas son bloqueadas porque la pregunta es off-topic pero tiene importancia histórica. Actualmente no está a...

7  Solución alternativa para constructor sobrecargado en PHP  ( Workaround for overloaded constructor in php ) 
Tengo una clase cuyo propósito es mostrar un comentario. Me gustaría poder instanciarlo al pasar un objeto 998877666555443319 template <std::size_t N> cla...

1  Establecimiento de parámetros para un informe de ventas, con valores de booleano y fecha predeterminados  ( Establishing parameters for a sales report with default boolean and date values ) 
La idea es que estoy obteniendo los parámetros de una solicitud 99887766655443311 appendSoldier2 y appendSoldier3 . appendSoldier4 El problema es qu...

5  Conexión de base de datos MySQL en el constructor  ( Mysql database connection in the constructor ) 
Soy un principiante absoluto en PHP OOP en busca del "Santo Grial" de la base de datos de MySQL una vez y reutilizando esta conexión para todo el sitio. cl...

3  Cambiando el estado de otra entidad en el método del constructor  ( Changing the state of another entity into the constructor method ) 
Tengo una clase de reloj que se hereda de Pyglet.clock.clock y se comporta como un singleton. en el método del constructor Tengo la siguiente línea: pygl...

45  Objetos instantáneos con muchos atributos  ( Instantiating objects with many attributes ) 
Tengo una clase con bastantes atributos, la mayoría de los cuales se conocen cuando creo una instancia del objeto. Así que paso todos los valores en el constr...

2  Subclajusing Uibutton  ( Subclassing uibutton ) 
Necesito SUBCLASS UIButton , sin embargo, el diseño de UIButton hace que esto sea realmente doloroso. Esto se debe a que para crear un botón que realmente ...

7  Constructores: muchos parámetros o ninguno y uso de métodos establecidos  ( Constructors lots of paramters or none and use of set methods ) 
En su opinión, cuál es la mejor manera de construir un objeto, dado los dos ejemplos a continuación: Opción 1: muchos parámetros private string firstna...

41  Conexión de la base de datos en constructor y destructor  ( Database connection in constructor and destructor ) 
Estoy jugando con diferentes maneras de hacer la interacción de la base de datos en PHP, y una de las ideas con las que he estado jugando está conectando a la...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos