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

47

Esta questão é subjetiva, mas eu estava curioso para saber como a maioria dos programadores aborda isso. O exemplo abaixo está no pseudo-C #, mas isso deve se aplicar também a Java, C ++ e outras linguagens OOP.

De qualquer forma, ao escrever métodos auxiliares em minhas classes, tenho a tendência de declará-los como estáticos e apenas passar os campos se o método auxiliar precisar deles. Por exemplo, dado o código abaixo, prefiro usar o Método de Chamada # 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 para os 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 acelerar rapidamente os métodos que usam essa prática e, assim, ajudar-me a depurar as coisas.

Qual você prefere fazer no seu próprio código e quais são suas razões para fazê-lo?

    
por Ilian 02.10.2011 / 15:40
fonte

3 respostas

23

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

Se eles são complexos, então SOLID se aplica, mais especificamente, o S , o eu e o < href="http://en.wikipedia.org/wiki/Dependency_inversion_principle"> 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 tornar makeEveryBodyAsHappyAsPossible um método estático, que receberá 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, OurHouse não precisa saber sobre as complexidades das regras de distribuição de cookies. Só deve agora um objeto, que implementa uma regra. A implementação é abstraída em um objeto, cuja única responsabilidade é aplicar a regra. Este objeto pode ser testado isoladamente. OurHouse pode ser testado usando apenas uma simulação 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 agem como a implementação de CookieDistributor , onde cada classe apenas cumpre uma pequena tarefa, realmente não faz sentido. Minha interpretação do SRP é que ele não apenas dita que cada classe pode ter apenas uma responsabilidade, mas também que uma única responsabilidade deve ser executada por uma única classe.

No caso de primitivos ou objetos que você usa como primitivos (por exemplo, objetos representando pontos no espaço, matrizes ou algo assim), as classes auxiliares estáticas fazem muito sentido. Se você tiver a escolha e realmente fizer sentido, poderá considerar a possibilidade de adicionar um método à classe que representa os dados, por exemplo, É sensato que um Point tenha um método add . Mais uma vez, não exagere.

Então, dependendo do seu problema, existem diferentes maneiras de fazer isso.

    
por 02.10.2011 / 16:22
fonte
18

É um idioma bem conhecido declarar métodos de classes de utilitários static , portanto, essas classes nunca precisam ser instanciadas. Seguir esse idioma torna seu código mais fácil de entender, o que é uma coisa boa.

Existe uma limitação séria a esta abordagem: tais métodos / classes não podem ser facilmente ridicularizados (embora AFAIK pelo menos para C # existam frameworks de zombaria que podem conseguir até isso, mas eles não são banal e pelo menos alguns deles são comerciais). Então, se um método auxiliar tiver alguma dependência externa (por exemplo, um banco de dados) que o torne - assim seus chamadores - difícil de testar em unidade, é melhor declará-lo não estático . Isso permite a injeção de dependência, tornando assim os chamadores do método mais fáceis para o teste de unidade.

Atualizar

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

Neste 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 estiver, prefiro opção 1, como é mais simples e mais fácil de ler.

    
por 02.10.2011 / 15:45
fonte
4

Which do you prefer to do in your own code

Eu prefiro # 1 ( this->DoSomethingWithBarImpl(); ), a menos que, é claro, o método auxiliar não precise de acesso aos dados / implementação da instância static t_image OpenDefaultImage() .

and what are your reasons for doing so?

interface pública e implementação privada e membros são separados. programas seria muito mais difícil de ler se eu optasse por # 2. com # 1, sempre tenho os membros e a instância disponíveis. Em comparação com muitas implementações baseadas em OO, eu uso muito encapsualtion e pouquíssimos membros por classe. no que diz respeito aos efeitos colaterais - eu estou bem com isso, muitas vezes há validação de estado que estende as dependências (por exemplo, argumentos que seria necessário passar).

# 1 é muito mais simples de ler, manter e possui boas características de desempenho. Claro, haverá casos em que você pode compartilhar uma implementação:

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

Se # 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 insuficiente? o nível de abstração é alto o suficiente?

por fim, # 2 parece redundante se você estiver usando idiomas OO nos quais a mutação é suportada (por exemplo, um método const ).

    
por 02.10.2011 / 16:08
fonte