Classe com método único - melhor abordagem?


172

Digamos que eu tenha uma classe destinada a executar uma única função. Depois de executar a função, ela pode ser destruída. Existe alguma razão para preferir uma dessas abordagens?

// Initialize arguments in constructor
MyClass myObject = new MyClass(arg1, arg2, arg3);
myObject.myMethod();

// Pass arguments to method
MyClass myObject = new MyClass();
myObject.myMethod(arg1, arg2, arg3);

// Pass arguments to static method
MyClass.myMethod(arg1, arg2, arg3);

Eu estava sendo intencionalmente vago sobre os detalhes, para tentar obter orientações para diferentes situações. Mas eu realmente não tinha em mente funções simples da biblioteca como Math.random (). Estou pensando mais em classes que executam alguma tarefa específica e complexa, mas requerem apenas um método (público) para fazê-lo.

Respostas:


264

Eu adorava classes utilitárias preenchidas com métodos estáticos. Eles fizeram uma grande consolidação de métodos auxiliares que, de outra forma, ficariam por aí causando redundância e manutenção no inferno. São muito fáceis de usar, sem instanciação, sem descarte, apenas esquecem. Eu acho que essa foi minha primeira tentativa involuntária de criar uma arquitetura orientada a serviços - muitos serviços sem estado que apenas fizeram seu trabalho e nada mais. À medida que o sistema cresce, os dragões estão chegando.

Polimorfismo
Digamos que temos o método UtilityClass.SomeMethod que felizmente vibra. De repente, precisamos alterar ligeiramente a funcionalidade. A maior parte da funcionalidade é a mesma, mas precisamos alterar algumas partes. Se não fosse um método estático, poderíamos criar uma classe derivada e alterar o conteúdo do método conforme necessário. Como é um método estático, não podemos. Claro, se precisarmos adicionar funcionalidades antes ou depois do método antigo, podemos criar uma nova classe e chamar a antiga dentro dela - mas isso é nojento.

Problemas de interface
Os métodos estáticos não podem ser definidos por meio de interfaces por razões lógicas. E como não podemos substituir métodos estáticos, as classes estáticas são inúteis quando precisamos transmiti-las pela interface. Isso nos torna incapazes de usar classes estáticas como parte de um padrão de estratégia. Podemos corrigir alguns problemas passando delegados em vez de interfaces .

Testes
Isso basicamente anda de mãos dadas com os problemas de interface mencionados acima. Como nossa capacidade de trocar implementações é muito limitada, também teremos problemas para substituir o código de produção pelo código de teste. Novamente, podemos agrupá-los, mas isso exigirá que alteremos grandes partes do nosso código apenas para poder aceitar wrappers em vez dos objetos reais.

Promove blobs
Como os métodos estáticos são geralmente usados ​​como métodos de utilidade e métodos de utilidade geralmente têm finalidades diferentes, acabaremos rapidamente com uma classe grande preenchida com funcionalidade não coerente - idealmente, cada classe deve ter uma única finalidade no sistema . Prefiro ter cinco vezes mais aulas, desde que seus objetivos sejam bem definidos.

Creep do parâmetro
Para começar, esse método estático bonitinho e inocente pode ter um único parâmetro. À medida que a funcionalidade cresce, alguns novos parâmetros são adicionados. Em breve, são adicionados parâmetros opcionais, portanto criamos sobrecargas do método (ou apenas adicionamos valores padrão, nos idiomas que os suportam). Em pouco tempo, temos um método que leva 10 parâmetros. Somente os três primeiros são realmente necessários, os parâmetros 4-7 são opcionais. Mas se o parâmetro 6 for especificado, é necessário preencher também de 7 a 9 ... Se tivéssemos criado uma classe com o único objetivo de fazer o que esse método estático fazia, poderíamos resolver isso incorporando os parâmetros necessários no construtor e permitindo que o usuário defina valores opcionais por meio de propriedades ou métodos para definir vários valores interdependentes ao mesmo tempo. Além disso, se um método cresceu para essa quantidade de complexidade,

Exigindo que os consumidores criem uma instância de classes sem motivo
Um dos argumentos mais comuns é: por que exigir que os consumidores de nossa classe criem uma instância para invocar esse método único, sem ter utilidade para a instância posteriormente? Criar uma instância de uma classe é uma operação muito barata na maioria dos idiomas; portanto, a velocidade não é um problema. Adicionar uma linha de código extra ao consumidor é um custo baixo para estabelecer as bases de uma solução muito mais sustentável no futuro. E, finalmente, se você deseja evitar a criação de instâncias, basta criar um wrapper singleton da sua classe que permita uma reutilização fácil - embora isso exija a exigência de que sua classe não tenha estado. Se não for apátrida, você ainda poderá criar métodos de empacotamento estático que lidam com tudo, enquanto ainda oferece todos os benefícios a longo prazo. Finalmente,

Apenas um Sith lida com absolutos.
É claro que há exceções ao meu desagrado por métodos estáticos. As verdadeiras classes de utilidade que não apresentam nenhum risco de inchaço são excelentes casos para métodos estáticos - System.Convert como exemplo. Se o seu projeto é único, sem requisitos para manutenção futura, a arquitetura geral realmente não é muito importante - estática ou não estática, não importa - a velocidade de desenvolvimento, no entanto.

Padrões, normas, normas!
O uso de métodos de instância não o impede de usar métodos estáticos e vice-versa. Desde que haja um raciocínio por trás da diferenciação e seja padronizado. Não há nada pior do que examinar uma camada de negócios que se estende a diferentes métodos de implementação.


Como Mark diz, polimorfismo, poder passar os métodos como parâmetros de 'estratégia' e ser capaz de configurar / definir opções são todos os motivos para usar uma instância . Por exemplo: um ListDelimiter ou DelimiterParser pode ser configurado para quais delimitadores usar / aceitar, para aparar o espaço em branco dos tokens analisados, se você deve colocar a lista entre colchetes, como tratar uma lista em branco / nula ... etc.
Thomas W

4
"À medida que o sistema cresce ..." você refatora?
Rodney Gitzel

3
@ user3667089 Argumentos gerais necessários para a classe existir logicamente, aqueles que eu passaria no construtor. Eles normalmente seriam usados ​​na maioria dos métodos. Argumentos específicos do método que eu passaria nesse método específico.
Mark S. Rasmussen

1
Realmente, o que você está fazendo com uma classe estática está voltando ao C bruto, não orientado a objetos (embora gerenciado pela memória) - todas as funções estão em um espaço global, não há this, você precisa gerenciar o estado global etc. se você gosta de programação procedural, faça isso. Mas saiba que você perde muitos dos benefícios estruturais do OO.
Engineer

1
A maioria desses motivos está relacionada ao gerenciamento incorreto de código, e não ao uso de métodos estáticos. No F # e em outras linguagens funcionais, usamos basicamente métodos estáticos (funções sem estado de instância) em todos os lugares e não temos esses problemas. Dito isto, o F # fornece um paradigma melhor para o uso de funções (funções são de primeira classe, funções podem ser curry e parcialmente aplicadas), o que as torna mais viáveis ​​do que em C #. Uma razão maior para usar classes é porque é para isso que o C # é criado. Todas as construções de Injeção de Dependência no .NET Core giram em torno de classes de instância com deps.
23619 Justin J Stark

89

Eu prefiro o caminho estático. Como a Classe não está representando um objeto, não faz sentido fazer uma instância dele.

Classes que existem apenas para seus métodos devem ser deixadas estáticas.


19
-1 "Classes que existem apenas para seus métodos devem ser deixadas estáticas."
Rookian 13/08/11

8
@Rookian, por que você não concorda com isso?
Jjnguy

6
um exemplo seria o padrão do repositório. A classe contém apenas métodos. Seguir sua abordagem não permite o uso de interfaces. => aumentar acoplamento
Rookian

1
@Rook, em geral, as pessoas não devem criar classes que são usadas apenas para métodos. No exemplo que você deu, os métodos estáticos não são uma boa ideia. Mas para métodos utilitários simples, o caminho estático é o melhor.
jjnguy

9
Absolutamente correto :) Com a sua condição "para métodos simples de utilidade", eu concordo totalmente com você, mas você fez uma regra geral na sua resposta que está errada.
Rookian

18

Se não houver motivo para criar uma instância da classe para executar a função, use a implementação estática. Por que fazer com que os consumidores dessa classe criem uma instância quando não é necessária.


16

Se você não precisar salvar o estado do objeto, não será necessário instancia-lo em primeiro lugar. Eu usaria o método estático único para o qual você passa parâmetros.

Eu também alertaria contra uma classe gigante de Utils que possui dezenas de métodos estáticos não relacionados. Isso pode ficar desorganizado e pesado com pressa. É melhor ter muitas classes, cada uma com poucos métodos relacionados.


6

Eu diria que o formato do método estático seria a melhor opção. E também tornaria a classe estática, para que você não precise se preocupar em criar acidentalmente uma instância da classe.


5

Realmente não sei qual é a situação aqui, mas gostaria de colocá-la como método em uma das classes às quais arg1, arg2 ou arg3 pertencem - Se você puder dizer semanticamente que uma dessas classes seria dona do método método.


4

Eu sugeriria que é difícil responder com base nas informações fornecidas.

Minha intuição é que, se você tiver apenas um método e jogar a classe fora imediatamente, faça dela uma classe estática que aceita todos os parâmetros.

Obviamente, é difícil dizer exatamente por que você precisa criar uma única classe apenas para esse método. É a situação típica da "classe Utilitários", como a maioria está assumindo? Ou você está implementando algum tipo de classe de regra, da qual pode haver mais no futuro.

Por exemplo, faça com que essa classe seja plugável. Em seguida, você deseja criar uma interface para o seu método único e, em seguida, todos os parâmetros passados ​​para a interface, e não para o construtor, mas não deseja que seja estático.


3

Sua turma pode se tornar estática?

Nesse caso, eu a tornaria uma classe 'Utilities' na qual colocaria todas as minhas classes de uma função.


2
Eu discordo um pouco. Nos projetos em que estou envolvido, sua classe Utilities pode ter centenas de métodos não relacionados.
Paul Tomblin 15/10/08

3
@Paul: Geralmente aceito. Eu odeio ver aulas com dezenas (ou sim, centenas) de métodos totalmente independentes. Se for necessário adotar essa abordagem, pelo menos divida-os em conjuntos de utilitários menores e relacionados (EG, FooUtilities, BarUtilities, etc.).
John Rudy

9
uma classe - uma responsabilidade.
Andreas Petersson

3

Se esse método for sem estado e você não precisar contorná-lo, faz mais sentido defini-lo como estático. Se você precisar repassar o método, considere usar um delegado em vez de uma de suas outras abordagens propostas.


3

Para aplicativos e internalajudantes simples , eu usaria um método estático. Para aplicativos com componentes, estou adorando o Managed Extensibility Framework . Aqui está um trecho de um documento que estou escrevendo para descrever os padrões que você encontrará nas minhas APIs.

  • Serviços
    • Definido por uma I[ServiceName]Serviceinterface.
    • Exportado e importado pelo tipo de interface.
    • A implementação única é fornecida pelo aplicativo host e consumida internamente e / ou por extensões.
    • Os métodos na interface de serviço são seguros para threads.

Como um exemplo artificial:

public interface ISettingsService
{
    string ReadSetting(string name);

    void WriteSetting(string name, string value);
}

[Export]
public class ObjectRequiringSettings
{
    [Import]
    private ISettingsService SettingsService
    {
        get;
        set;
    }

    private void Foo()
    {
        if (SettingsService.ReadSetting("PerformFooAction") == bool.TrueString)
        {
            // whatever
        }
    }
}

2

Eu faria apenas tudo no construtor. igual a:

new MyClass(arg1, arg2, arg3);// the constructor does everything.

ou

MyClass my_object(arg1, arg2, arg3);

E se você precisar retornar algo além de um objeto do tipo MyClass?
Bill o Lagarto

13
Considero uma prática ruim colocar lógica de execução em um construtor. Bom desenvolvimento é sobre semântica. Semanticamente, existe um construtor para construir um objeto, não para executar outras tarefas.
Mstrobl 15/10/08

bill, se você precisar de um valor de retorno, passe a referência ao valor-ret: MyClass myObject (arg1, arg2, arg3, retvalue); mstrobl, se o objeto não é necessário, por que criá-lo? e esse truque pode realmente ajudá-lo em alguns casos.

Se um objeto não tem estado, ou seja, nenhum vars, instancia-lo não produzirá nenhuma alocação - nem no heap nem na pilha. Você pode pensar em objetos em C ++ como apenas chamadas de função C com um primeiro parâmetro oculto apontando para uma estrutura.
Mstrobl 15/10/08

mstrobl, como uma função ac em esteróides porque você pode definir funções privadas que o ctor pode usar. você também pode usar as variáveis ​​de membro da classe para ajudar a passar dados para as funções privadas.

0

Uma questão mais importante a considerar é se o sistema estaria em execução em um ambiente multithread e se seria seguro para threads ter um método ou variáveis ​​estáticas ...

Você deve prestar atenção ao estado do sistema.


0

Você pode evitar a situação todos juntos. Tente refatorar para que você obtenha arg1.myMethod1(arg2, arg3). Troque arg1 por arg2 ou arg3 se fizer mais sentido.

Se você não tem controle sobre a classe arg1, decore-a:

class Arg1Decorator
    private final T1 arg1;
    public Arg1Decorator(T1 arg1) {
        this.arg1 = arg1;
    }
    public T myMethod(T2 arg2, T3 arg3) {
        ...
    }
 }

 arg1d = new Arg1Decorator(arg1)
 arg1d.myMethod(arg2, arg3)

O raciocínio é que, em OOP, dados e métodos de processamento desses dados pertencem um ao outro. Além disso, você obtém todas as vantagens que Mark mencionou.


0

Eu acho que, se as propriedades da sua classe ou a instância da classe não serão usadas nos construtores ou nos seus métodos, não será sugerido que os métodos sejam projetados como padrão 'estático'. O método estático deve ser sempre pensado da maneira 'ajuda'.


0

Dependendo se você deseja fazer apenas algo ou fazer e retornar algo, você pode fazer isso:

public abstract class DoSomethingClass<T>
{
    protected abstract void doSomething(T arg1, T arg2, T arg3);
}

public abstract class ReturnSomethingClass<T, V>
{
    public T value;
    protected abstract void returnSomething(V arg1, V arg2, V arg3);
}

public class DoSomethingInt extends DoSomethingClass<Integer>
{
    public DoSomethingInt(int arg1, int arg2, int arg3)
    {
        doSomething(arg1, arg2, arg3);
    }

    @Override
    protected void doSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        // ...
    }
}

public class ReturnSomethingString extends ReturnSomethingClass<String, Integer>
{
    public ReturnSomethingString(int arg1, int arg2, int arg3)
    {
        returnSomething(arg1, arg2, arg3);
    }

    @Override
    protected void returnSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        String retValue;
        // ...
        value = retValue;
    }
}

public class MainClass
{
    static void main(String[] args)
    {
        int a = 3, b = 4, c = 5;

        Object dummy = new DoSomethingInt(a,b,c);  // doSomething was called, dummy is still around though
        String myReturn = (new ReturnSomethingString(a,b,c)).value; // returnSomething was called and immediately destroyed
    }
}
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.