Como evitar a sobrecarga excessiva de métodos?

16

Temos muitos lugares no código fonte do nosso aplicativo, onde uma classe tem muitos métodos com os mesmos nomes e parâmetros diferentes. Esses métodos sempre têm todos os parâmetros de um método 'anterior' mais um.

É o resultado de longa evolução (código legado) e esse pensamento (acredito):

" Existe um método M que faz a coisa A. Eu preciso fazer A + B. OK, eu sei ... vou adicionar um novo parâmetro a M, criar um novo método para isso, mover o código de M para o novo método com mais um parâmetro, faça o A + B ali e chame o novo método de M com um valor padrão do novo parâmetro " .

Aqui está um exemplo (em linguagem semelhante 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
    (...)
  }

  (...)
}

Eu sinto que isso está errado. Não apenas não podemos continuar adicionando novos parâmetros como esse para sempre, mas o código é difícil de estender / alterar por causa de todas as dependências entre os métodos.

Aqui estão algumas maneiras de como fazer isso melhor:

  1. Introduzir um 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. Defina os parâmetros para o DocumentHomeobjeto antes de chamarmoscreateDocument()

      @In
      DocumentHome dh = null;
    
      (...)
    
      dh.setName(...);
      dh.setMinPagesCount(...);
      dh.setFirstPageBlank(...);
    
      Document newDocument = dh.createDocument();
    
  3. Separe o trabalho em diferentes métodos e chame-os conforme necessário:

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

Minhas perguntas:

  1. O problema descrito é realmente um problema?
  2. O que você acha das soluções sugeridas? Qual você prefere (com base na sua experiência)?
  3. Você consegue pensar em outra solução?
Ytus
fonte
1
Qual idioma você está alvejando ou é apenas algo genérico?
Knerd
Nenhuma linguagem em particular, apenas geral. Sinta-se à vontade para mostrar como os recursos em outros idiomas podem ajudar com isso.
Ytus
como eu disse aqui programmers.stackexchange.com/questions/235096/… C # e C ++ têm alguns recursos.
Knerd
É bem claro que essa pergunta se aplica a todos os idiomas que suportam esse tipo de sobrecarga de método.
Doc Brown
1
@DocBrown ok, mas não todas as línguas suporta as mesmas alternativas;)
Knerd

Respostas:

20

Talvez tente o padrão do construtor ? (nota: resultado bastante aleatório do Google :)

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

Não posso descrever detalhadamente por que prefiro o construtor sobre as opções que você fornece, mas você identificou um grande problema com muito código. Se você acha que precisa de mais de dois parâmetros para um método, provavelmente seu código está estruturado incorretamente (e alguns argumentariam um!).

O problema com um objeto params é (a menos que o objeto que você crie seja de alguma forma real), você apenas eleva o problema para um nível superior e acaba com um cluster de parâmetros não relacionados formando o 'objeto'.

Suas outras tentativas me parecem alguém buscando o padrão do construtor, mas não chegando lá :)

Froome
fonte
Obrigado. Sua resposta pode ser a solução num. 4 sim. Estou procurando mais respostas dessa maneira: 'Na minha experiência, isso leva a ... e pode ser corrigido ...'. ou "Quando vejo isso no código do meu colega, sugiro que ele ... em vez disso".
Ytus
Eu não sei ... parece-me que você está apenas mudando o problema. Por exemplo, se eu puder criar um documento em diferentes partes do aplicativo, é melhor organizar e testar isso isolando essa construção de documento em uma classe separada (como usar a DocumentoFactory). Tendo builderem lugares diferentes, é difícil controlar as mudanças futuras na construção do documento (como adicionar um novo campo obrigatório ao documento, por exemplo) e adicionar código extra nos testes para satisfazer apenas as necessidades do construtor de documentos nas classes que estão usando o construtor.
Dherik
1

Usar um objeto de parâmetro é uma boa maneira de evitar sobrecarga (excessiva) de métodos:

  • limpa o código
  • separa os dados da funcionalidade
  • torna o código mais sustentável

Eu não iria muito longe com isso.

Ter uma sobrecarga aqui e não é uma coisa ruim. É suportado pela linguagem de programação, portanto, use-o para sua vantagem.

Eu desconhecia o padrão do construtor, mas o usei "por acidente" em algumas ocasiões. O mesmo se aplica aqui: não exagere. O código no seu exemplo se beneficiaria com isso, mas gastar muito tempo implementando-o para cada método que possui um único método de sobrecarga não é muito eficiente.

Apenas meus 2 centavos.

Roubar
fonte
0

Sinceramente, não vejo um grande problema com o código. Em C # e C ++, você pode usar parâmetros opcionais, isso seria uma alternativa, mas até onde eu sei, Java não suporta esse tipo de parâmetros.

Em C #, você pode tornar todas as sobrecargas privadas e um método com parâmetros opcionais é público para chamar as coisas.

Para responder à sua pergunta, parte 2, eu pegaria o objeto de parâmetro ou mesmo um dicionário / HashMap.

Igual a:

class DokumentHome {

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

Como aviso, sou primeiro um programador em C # e JavaScript e depois um programador em Java.

Knerd
fonte
4
É uma solução, mas não acho que seja uma boa solução (pelo menos, não em um contexto em que se espera segurança tipográfica).
Doc Brown
Está certo. Por causa de casos como esse, gosto de métodos sobrecarregados ou parâmetros opcionais.
Knerd 07/04
2
Usar um dicionário como parâmetro é uma maneira fácil de reduzir o número de parâmetros aparentes para um método, mas obscurece as dependências reais do método. Isso força o chamador a procurar em outro lugar o que exatamente precisa estar no dicionário, seja nos comentários, em outras chamadas ao método ou na própria implementação do método.
Mike Partridge
Use o dicionário apenas para parâmetros verdadeiramente opcionais, para que casos de uso básicos não precisem ler muita documentação.
user949300
0

Eu acho que este é um bom candidato para o padrão do construtor. O padrão do construtor é útil quando você deseja criar objetos do mesmo tipo, mas com representações diferentes.

No seu caso, eu teria um construtor com os seguintes métodos:

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

Em seguida, você pode usar o construtor da seguinte maneira:

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

Por outro lado, não me importo com poucas sobrecargas simples - o que diabos, o .NET framework as usa em todo o lugar com os auxiliares de HTML. No entanto, eu reavaliaria o que estou fazendo se tiver que passar mais de dois parâmetros para cada método.

CodeART
fonte
0

Acho que a buildersolução pode funcionar na maior parte dos cenários, mas em casos mais complexos, o construtor também será complexo de configurar , porque é fácil cometer alguns erros na ordem dos métodos, quais métodos precisam ser expostos ou não , etc. Muitos de nós preferem uma solução mais simples.

Se você acabou de criar um construtor simples para criar um documento e espalhar esse código em diferentes partes (classes) do aplicativo, será difícil:

  • organizar : você terá muitas aulas criando um documento de diferentes maneiras
  • manter : qualquer alteração na instanciação do documento (como adicionar um novo arquivo obrigatório) o levará a uma cirurgia de espingarda
  • teste : se você estiver testando uma classe que cria um documento, precisará adicionar um código padrão apenas para satisfazer a instanciação do documento.

Mas isso não responde à pergunta do OP.

A alternativa à sobrecarga

Algumas alternativas:

  • Renomeie o nome do método : se o mesmo nome do método é criar alguma confusão, tentar renomear os métodos para criar um nome significativo melhor do que createDocument, como: createLicenseDriveDocument, createDocumentWithOptionalFields, etc. Claro, isso pode levar você a nomes de métodos gigantes, de modo que este não é uma solução para todos os casos.
  • Use métodos estáticos . Essa abordagem é parecida se comparada à primeira alternativa acima. Você pode usar um nomes significativos para cada caso e instanciar o documento a partir de um método estático em Document, como: Document.createLicenseDriveDocument().
  • Crie uma interface comum : você pode criar um único método chamado createDocument(InterfaceDocument interfaceDocument)e criar diferentes implementações para InterfaceDocument. Por exemplo: createDocument(new DocumentMinPagesCount("name")). Obviamente, você não precisa de uma única implementação para cada caso, porque é possível criar mais de um construtor em cada implementação, agrupando alguns campos que fazem sentido nessa implementação. Esse padrão é chamado de construtores telescópicos .

Ou apenas fique com a solução de sobrecarga . Mesmo sendo, às vezes, uma solução feia, não há muitas desvantagens em usá-la. Nesse caso, prefiro usar métodos de sobrecarga em uma classe separada, como uma DocumentoFactoryque possa ser injetada como dependência de classes que precisam criar documentos. Posso organizar e validar os campos sem a complexidade de criar um bom construtor e manter o código em um único local também.

Dherik
fonte