¿Cómo evitar la sobrecarga excesiva de métodos?

16

Tenemos muchos lugares en el código fuente de nuestra aplicación, donde una clase tiene muchos métodos con los mismos nombres y diferentes parámetros. Esos métodos siempre tienen todos los parámetros de un método 'anterior' más uno más.

Es el resultado de una larga evolución (código heredado) y este pensamiento (creo):

" Hay un método M que hace la cosa A. Necesito hacer A + B. OK, lo sé ... Agregaré un nuevo parámetro a M, crearé un nuevo método para eso, moveré el código de M al nuevo método con un parámetro más, haga A + B allí y llame al nuevo método desde M con un valor predeterminado del nuevo parámetro " .

Aquí hay un ejemplo (en lenguaje similar a Java):

class DocumentHome {

  (...)

  public Document createDocument(String name) {
    // just calls another method with default value of its parameter
    return createDocument(name, -1);
  }

  public Document createDocument(String name, int minPagesCount) {
    // just calls another method with default value of its parameter
    return createDocument(name, minPagesCount, false);
  }

  public Document createDocument(String name, int minPagesCount, boolean firstPageBlank) {
    // just calls another method with default value of its parameter
    return createDocument(name, minPagesCount, false, "");
  }

  public Document createDocument(String name, int minPagesCount, boolean firstPageBlank, String title) {
    // here the real work gets done
    (...)
  }

  (...)
}

Siento que esto está mal. No solo eso no podemos seguir agregando nuevos parámetros como este para siempre, sino que el código es difícil de extender / cambiar debido a todas las dependencias entre los métodos.

Aquí hay algunas formas de hacerlo mejor:

  1. Introducir un objeto de parámetro:

    class DocumentCreationParams {
    
      String name;
      int minPagesCount;
      boolean firstPageBlank;
      String title;
    
      (...)
    }
    
    class DokumentHome {
    
      public Document createDocument(DocumentCreationParams p) {
        // here the real work gets done
        (...)
      }
    }
    
  2. Establezca los parámetros para el DocumentHomeobjeto antes de llamarcreateDocument()

      @In
      DocumentHome dh = null;
    
      (...)
    
      dh.setName(...);
      dh.setMinPagesCount(...);
      dh.setFirstPageBlank(...);
    
      Document newDocument = dh.createDocument();
    
  3. Separe el trabajo en diferentes métodos y llámelos según sea necesario:

      @In
      DocumentHome dh = null;
    
      Document newDocument = dh.createDocument();
      dh.changeName(newDocument, "name");
      dh.addFirstBlankPage(newDocument);
      dh.changeMinPagesCount(new Document, 10);
    

Mis preguntas:

  1. ¿El problema descrito es realmente un problema?
  2. ¿Qué opinas sobre las soluciones sugeridas? ¿Cuál preferirías (según tu experiencia)?
  3. ¿Se te ocurre alguna otra solución?
Ytus
fuente
1
¿A qué idioma se dirige o es solo algo así?
Knerd
Ningún lenguaje particular, solo general. No dude en mostrar cómo las funciones en otros idiomas pueden ayudar con esto.
Ytus
como dije aquí programmers.stackexchange.com/questions/235096/… C # y C ++ tienen algunas características.
Knerd
Está bastante claro que esta pregunta se aplica a todos los idiomas que admiten este tipo de sobrecarga de métodos.
Doc Brown
1
@DocBrown está bien, pero no todos los idiomas admiten las mismas alternativas;)
Knerd

Respuestas:

20

Tal vez intente el patrón constructor ? (nota: resultado de Google bastante aleatorio :)

var document = new DocumentBuilder()
                   .FirstPageBlank()
                   .Name("doc1final(2).doc")
                   .MinimumNumberOfPages(4)
                   .Build();

No puedo dar un resumen completo de por qué prefiero el constructor sobre las opciones que ofrece, pero ha identificado un gran problema con mucho código. Si cree que necesita más de dos parámetros para un método, es probable que su código esté estructurado incorrectamente (¡y algunos argumentarían uno!).

El problema con un objeto params es (a menos que el objeto que cree sea de alguna manera real) simplemente suba el problema un nivel y termine con un grupo de parámetros no relacionados que forman el 'objeto'.

Sus otros intentos me parecen como alguien que busca el patrón de construcción pero no llega a llegar allí :)

Froome
fuente
Gracias. Su respuesta puede ser la solución num. 4 sí. Estoy buscando más respuestas de esta manera: "En mi experiencia, esto conduce a ... y se puede solucionar ...". o 'Cuando veo esto en el código de mi colega, le sugiero que ... en su lugar'.
Ytus
No sé ... me parece que solo estás moviendo el problema. Por ejemplo, si puedo construir un documento en diferentes partes de la aplicación, es mejor organizar y probar esto aislando la construcción de este documento en una clase separada (como usar a DocumentoFactory). Tener un lugar builderdiferente es difícil controlar los cambios futuros en la construcción del documento (como agregar un nuevo campo obligatorio al documento, por ejemplo) y agregar código adicional en las pruebas para satisfacer las necesidades del creador de documentos en las clases que están usando el constructor.
Dherik
1

Usar un objeto de parámetro es una buena manera de evitar la sobrecarga (excesiva) de métodos:

  • limpia el código
  • separa los datos de la funcionalidad
  • hace que el código sea más fácil de mantener

Sin embargo, no iría demasiado lejos con eso.

Tener una sobrecarga aquí y allá no es algo malo. Es compatible con el lenguaje de programación, así que úselo para su ventaja.

No tenía conocimiento del patrón de construcción, pero lo he usado "por accidente" en algunas ocasiones. Lo mismo se aplica aquí: no exagere. El código en su ejemplo se beneficiaría de él, pero pasar mucho tiempo implementándolo para cada método que tiene un solo método de sobrecarga no es muy eficiente.

Solo mis 2 centavos.

Robar
fuente
0

Sinceramente, no veo un gran problema con el código. En C # y C ++ puede usar parámetros opcionales, eso sería una alternativa, pero que yo sepa, Java no admite ese tipo de parámetros.

En C #, puede hacer que todas las sobrecargas sean privadas y un método con parámetros opcionales es público para llamar a las cosas.

Para responder a su pregunta, parte 2, tomaría el objeto de parámetro o incluso un diccionario / HashMap.

Al igual que:

class DokumentHome {

  public Document createDocument(Map<string, object> params) {
    if (params.containsKey("yourkey")) {
       // do that
    }
    // here the real work gets done
    (...)
  }
}

Como descargo de responsabilidad, primero soy un programador de C # y JavaScript y luego un programador de Java.

Knerd
fuente
44
Es una solución, pero no creo que sea una buena solución (al menos, no en un contexto donde se espera la seguridad de tipos).
Doc Brown
Así es. Debido a casos como ese, me gustan los métodos sobrecargados o los parámetros opcionales.
Knerd
2
Usar un diccionario como parámetro es una manera fácil de reducir la cantidad de parámetros aparentes a un método, pero oculta las dependencias reales del método. Esto obliga a la persona que llama a buscar en otro lugar lo que debe estar exactamente en el diccionario, ya sea en comentarios, otras llamadas al método o la implementación del método en sí.
Mike Partridge
Use el diccionario solo para parámetros verdaderamente opcionales , por lo que los casos de uso básicos no necesitan leer demasiada documentación.
user949300
0

Creo que este es un buen candidato para el patrón de construcción. El patrón de construcción es útil cuando desea crear objetos del mismo tipo, pero con diferentes representaciones.

En su caso, tendría un constructor con los siguientes métodos:

SetTitle (Document document) { ... }
SetFirstPageBlank (Document document) { ... }
SetMinimumPageCount (Document document) { ... }

Luego puede usar el constructor de la siguiente manera:

_builder.SetTitle(document);
_builder.SetFirstPageBlank(document);

En la nota al margen, no me importan algunas sobrecargas simples: qué diablos, .NET framework las usa en todas partes con ayudantes HTML. Sin embargo, volvería a evaluar lo que estoy haciendo si tengo que pasar más de dos parámetros a cada método.

CodeART
fuente
0

Creo que la buildersolución puede funcionar en la mayoría de los escenarios, pero en casos más complejos, su constructor también será complejo de configurar , porque es fácil cometer algunos errores en el orden de los métodos, qué métodos deben exponerse o no , etc. Entonces, muchos de nosotros preferiremos una solución más simple.

Si solo crea un generador simple para crear un documento y difunde este código en diferentes partes (clases) de la aplicación, será difícil:

  • organizar : tendrás muchas clases construyendo un documento de diferentes maneras
  • Mantener : cualquier cambio en la creación de documentos (como agregar un nuevo archivo obligatorio) lo llevará a una cirugía de escopeta
  • prueba : si está probando una clase que crea un documento, necesitará agregar un código repetitivo solo para satisfacer la instanciación del documento.

Pero esto no responde a la pregunta OP.

La alternativa a la sobrecarga

Algunas alternativas:

  • Cambiar el nombre del nombre del método : si el mismo nombre de método está creando cierta confusión, trate de cambiar el nombre de los métodos para crear un nombre significativo mejor que createDocument, como: createLicenseDriveDocument, createDocumentWithOptionalFields, etc. Por supuesto, esto puede conducir a que los nombres de métodos gigantes, así que esto no es Una solución para todos los casos.
  • Utiliza métodos estáticos . Este enfoque es algo similar si se compara con la primera alternativa anterior. Puede utilizar un nombres significativos para cada caso y una instancia del documento a partir de un método estático en Document, como: Document.createLicenseDriveDocument().
  • Cree una interfaz común : puede crear un método único llamado createDocument(InterfaceDocument interfaceDocument)y crear diferentes implementaciones para InterfaceDocument. Por ejemplo: createDocument(new DocumentMinPagesCount("name")). Por supuesto, no necesita una sola implementación para cada caso, porque puede crear más de un constructor en cada implementación, agrupando algunos campos que tengan sentido en esa implementación. Este patrón se llama constructores telescópicos .

O simplemente quédese con la solución de sobrecarga . Aun siendo, a veces, una solución fea, no hay muchos inconvenientes en su uso. En ese caso, prefiero usar métodos de sobrecarga en una clase separada, como una DocumentoFactoryque se puede inyectar como dependencia de las clases que necesitan crear documentos. Puedo organizar y validar los campos sin la complejidad de crear un buen generador y mantener el código en un solo lugar también.

Dherik
fuente