Qual é a melhor prática - métodos auxiliares como instância ou estática?


48

Essa pergunta é subjetiva, mas fiquei curioso em saber como a maioria dos programadores aborda isso. O exemplo abaixo está em pseudo-C #, mas isso também deve se aplicar a Java, C ++ e outras linguagens OOP.

De qualquer forma, ao escrever métodos auxiliares em minhas classes, eu os declaro estáticos e apenas passo os campos se o método auxiliar precisar deles. Por exemplo, dado o código abaixo, prefiro usar a Chamada de Método # 2 .

class Foo
{
  Bar _bar;

  public void DoSomethingWithBar()
  {
    // Method Call #1.
    DoSomethingWithBarImpl();

    // Method Call #2.
    DoSomethingWithBarImpl(_bar);
  }

  private void DoSomethingWithBarImpl()
  {
    _bar.DoSomething();
  }

  private static void DoSomethingWithBarImpl(Bar bar)
  {
    bar.DoSomething();
  }
}

Minha razão para fazer isso é que deixa claro (pelo menos aos meus olhos) que o método auxiliar tem um possível efeito colateral em outros objetos - mesmo sem ler sua implementação. Eu acho que posso rapidamente entender métodos que usam essa prática e, assim, me ajudam a depurar as coisas.

O que você prefere fazer em seu próprio código e quais são seus motivos?


3
Eu removeria o C ++ desta pergunta: no C ++, obviamente, você o tornaria um método livre, porque não há sentido em ser arbitrariamente escondido em um objeto: ele desativa o ADL.
Matthieu M.

11
@MatthieuM .: Método livre ou não, não importa. A intenção da minha pergunta era perguntar se há alguma vantagem em declarar métodos auxiliares como exemplos. Portanto, em C ++, a escolha pode ser método de instância versus método / função livre. Você tem um bom argumento sobre ADL. Então você está dizendo que prefere usar métodos gratuitos? Obrigado!
Ilian

2
para C ++, métodos gratuitos são recomendados. Eles aumentam o encapsulamento (indiretamente, como sendo capaz de acessar apenas a interface pública) e ainda funcionam bem por causa do ADL (especialmente em modelos). No entanto, não sei se isso é aplicável ao Java / C #, o C ++ difere muito do Java / C #, portanto, espero que as respostas sejam diferentes (e, portanto, não acho que pedir as 3 linguagens faça sentido).
Matthieu M.


11
Sem tentar agravar ninguém, nunca ouvi uma boa razão para não usar métodos estáticos. Eu os uso sempre que posso, pois eles tornam mais óbvio o que o método faz.
Sridhar Sarnobat

Respostas:


23

Isso realmente depende. Se os valores em que seus ajudantes operam são primitivos, os métodos estáticos são uma boa opção, como Péter apontou.

Se eles são complexos e, em seguida SÓLIDO aplica-se, mais especificamente, o S , o eu e o D .

Exemplo:

class CookieJar {
      function takeCookies(count:Int):Array<Cookie> { ... }
      function countCookies():Int { ... }
      function ressuplyCookies(cookies:Array<Cookie>
      ... // lot of stuff we don't care about now
}

class CookieFan {
      function getHunger():Float;
      function eatCookies(cookies:Array<Cookie>):Smile { ... }
}

class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieJar;
      function makeEveryBodyAsHappyAsPossible():Void {
           //perform a lot of operations on jake, jane and the cookies
      }
      public function cookieTime():Void {
           makeEveryBodyAsHappyAsPossible();
      }
}

Isso seria sobre o seu problema. Você pode criar makeEveryBodyAsHappyAsPossibleum método estático, que terá os parâmetros necessários. Outra opção é:

interface CookieDistributor {
    function distributeCookies(to:Array<CookieFan>):Array<Smile>;
}
class HappynessMaximizingDistributor implements CookieDistributor {
    var jar:CookieJar;
    function distributeCookies(to:Array<CookieFan>):Array<Smile> {
         //put the logic of makeEveryBodyAsHappyAsPossible here
    }
}
//and make a change here
class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieDistributor;

      public function cookieTime():Void {
           cookies.distributeCookies([jake, jane]);
      }
}

Agora OurHousenão é necessário conhecer os meandros das regras de distribuição de cookies. Agora, ele deve apenas um objeto que implemente uma regra. A implementação é abstraída para um objeto, cuja única responsabilidade é aplicar a regra. Este objeto pode ser testado isoladamente. OurHousepode ser testado usando um mero mock do CookieDistributor. E você pode facilmente decidir alterar as regras de distribuição de cookies.

No entanto, tome cuidado para não exagerar. Por exemplo, ter um sistema complexo de 30 classes age como a implementação de CookieDistributor, onde cada classe apenas cumpre uma tarefa minúscula, realmente não faz sentido. Minha interpretação do SRP é que ele não apenas determina que cada classe possa ter apenas uma responsabilidade, mas também que uma única responsabilidade deve ser realizada por uma única classe.

No caso de primitivas ou objetos que você usa como primitivas (por exemplo, objetos representando pontos no espaço, matrizes ou algo assim), as classes auxiliares estáticas fazem muito sentido. Se você tem a opção, e isso realmente faz sentido, você pode realmente considerar adicionar um método à classe que representa os dados, por exemplo, é sensato que um Pointtenha um addmétodo. Novamente, não exagere.

Portanto, dependendo do seu problema, existem diferentes maneiras de solucioná-lo.


18

É um idioma bem conhecido declarar métodos de classes de utilidade static; portanto, essas classes nunca precisam ser instanciadas. Seguir esse idioma facilita a compreensão do seu código, o que é uma coisa boa.

Porém, existe uma séria limitação a essa abordagem: esses métodos / classes não podem ser facilmente zombados (embora o AFAIK, pelo menos para C #, exista estruturas de zombaria que podem alcançar isso, mas elas não são comuns e pelo menos algumas são comercial). Portanto, se um método auxiliar tiver alguma dependência externa (por exemplo, um banco de dados), o que dificulta o teste de unidade - assim seus chamadores -, é melhor declará-lo não estático . Isso permite a injeção de dependência, facilitando assim os chamadores do método ao teste de unidade.

Atualizar

Esclarecimento: o acima fala sobre classes de utilidade, que contêm apenas métodos auxiliares de baixo nível e (normalmente) nenhum estado. Os métodos auxiliares nas classes não utilitárias com estado são um problema diferente; desculpas por interpretar mal o OP.

Nesse caso, sinto um cheiro de código: um método da classe A que opera principalmente em uma instância da classe B pode realmente ter um lugar melhor na classe B. Mas se eu decidir mantê-lo onde está, prefiro a opção 1, como é mais simples e mais fácil de ler.


Você não pode simplesmente passar a dependência para o método auxiliar estático?
Ilian

11
@JackOfAllTrades, se o único objetivo do método é lidar com um recurso externo, quase não há sentido em injetar essa dependência. Caso contrário, pode ser uma solução (embora, no último caso, o método provavelmente quebre o Princípio de Responsabilidade Única, sendo candidato à refatoração).
Péter Török

11
as estatísticas são facilmente 'zombadas' - o Microsoft Moles ou Fakes (dependendo do VS2010 ou VS2012 +) permite substituir um método estático pela sua própria implementação em seus testes. Torna obsoletas as antigas estruturas de simulação. (ele também faz zombarias tradicionais e também pode zombar de classes concretas). É livre de MS através do gerenciador de extensão.
gbjbaanb 11/09

4

O que você prefere fazer em seu próprio código

Prefiro o número 1 ( this->DoSomethingWithBarImpl();), a menos que o método auxiliar não precise acessar os dados / implementação da instância static t_image OpenDefaultImage().

e quais são suas razões para fazê-lo?

interface pública e implementação privada e membros são separados. os programas seriam muito mais difíceis de ler se eu optasse pelo segundo. com # 1, eu sempre tenho os membros e a instância disponíveis. comparado a muitas implementações baseadas em OO, eu costumo usar muito encapsualtion e muito poucos membros por classe. no que diz respeito aos efeitos colaterais - eu estou bem com isso, geralmente há validação de estado que amplia as dependências (por exemplo, argumentos que seria necessário passar).

O número 1 é muito mais simples de ler, manter e possui boas características de desempenho. é claro, haverá casos em que você poderá compartilhar uma implementação:

static void ValidateImage(const t_image& image);
void validateImages() const {
    ValidateImage(this->innerImage());
    ValidateImage(this->outerImage());
}

Se o nº 2 fosse uma boa solução comum (por exemplo, o padrão) em uma base de código, eu ficaria preocupado com a estrutura do design: as classes estão fazendo muito? não há encapsulamento suficiente ou reutilização suficiente? o nível de abstração é alto o suficiente?

por fim, o número 2 parece redundante se você estiver usando linguagens OO nas quais a mutação é suportada (por exemplo, um constmétodo).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.