Como o conceito de uma classe muda ao passar dados para o construtor em vez de parâmetros de método?

12

Digamos que estamos fazendo um analisador. Uma implementação pode ser:

public sealed class Parser1
{
    public string Parse(string text)
    {
       ...
    }
}

Ou podemos passar o texto para o construtor:

public sealed class Parser2
{
    public Parser2(string text)
    {
       this.text = text;
    }

    public string Parse()
    {
       ...
    }
}

O uso é simples nos dois casos, mas o que significa habilitar a entrada de parâmetro em Parser1comparação com o outro? Que mensagem eu enviei a um colega programador quando ele olha para a API? Além disso, existem vantagens / desvantagens técnicas em certos casos?

Outra pergunta surge quando percebo que uma interface não teria sentido na segunda implementação:

public interface IParser
{
    string Parse();
}

... onde uma interface na primeira poderia servir a pelo menos algum propósito. Isso significa algo em particular, que uma classe é "interfacável" ou não?

ciscoheat
fonte
É a primeira vez que vejo algumas perguntas e respostas úteis sobre como a semântica do OO realmente expressa intenção. Até agora, tudo foi apenas açúcar sintático, para mim.

Respostas:

11

Semanticamente falando, no OOP, você deve passar apenas ao construtor um conjunto de parâmetros necessários para criar a classe - da mesma forma que quando você chama um método, deve passar apenas os parâmetros necessários para executar sua lógica de negócios.

Os parâmetros que você precisa passar no construtor são parâmetros que não possuem valor padrão sensível e se sua classe é imutável (ou mesmo a struct), então todas as propriedades padrão não-passadas devem ser passadas.

Em relação aos seus dois exemplos:

  • se você passar textpara o construtor, isso sugere que a Parser2classe será criada especificamente para analisar essa instância do texto posteriormente. Será um analisador específico. Normalmente, esse processo ocorre quando a construção da classe é muito cara ou sutil; um RegEx pode ser compilado no construtor; portanto, depois de manter uma instância, você pode reutilizá-la sem ter que pagar o custo da compilação; outro exemplo é a inicialização do PRNG - é melhor se isso for feito raramente.
  • se você passar textpara o método, ele sinaliza que Parser1pode ser reutilizado para analisar diferentes textos por chamadas.
Sklivvz
fonte
3
Eu acrescentaria que há um sinal fraco de que o Parser1 mantém o estado - ou seja, que uma determinada sequência de texto pode produzir resultados diferentes, dependendo do que foi feito anteriormente nessa instância. Isso não é necessariamente verdade, mas pode ser.
jmoreno
8

Bem, vamos lembrar o que significa passar uma variável como parâmetro construtor: Você inicializa um objeto para usar suas variáveis ​​de instância nos métodos do objeto. O ponto é que você provavelmente deseja usá-lo em mais de um método, pois deseja ter uma alta coesão em sua classe.

Passar um parâmetro diretamente para um método significa, de certa forma, enviar uma mensagem para um objeto e provavelmente receber uma resposta. Com isso, o cliente deseja que o objeto entregue um serviço para ele.

Portanto, concluindo, esses são dois meios muito diferentes de passar parâmetros e você deve escolher se seu objeto deve fornecer um serviço ou fornecer alguma funcionalidade inerentemente enquanto gerencia algumas informações internamente.

McMannus
fonte
+1. Coesão é a maneira como eu decido se ele pertence ao método ou construtor.
jhewlett
4

O uso é simples nos dois casos, mas o que significa ativar a entrada de parâmetro no Parser1, em comparação com o outro?

É uma mudança fundamental no design. E o design deve transmitir intenção e significado. Você precisa ter objetos separados para cada string que deseja analisar? Em outras palavras, por que precisamos de uma instância do analisador com stringX e outra instância com stringY? O que há com a análise e a sequência especificada de que os dois devem viver e morrer juntos? Assumindo que a "implementação [de análise] subjacente" (como diz Robert Harvey) não muda, parece não haver sentido. E mesmo assim é questionável IMHO.

Como o conceito de uma classe muda ao passar dados para o construtor em vez de parâmetros de método?

Os parâmetros do construtor dizem que essas coisas são necessárias para um objeto. O estado adequado não é garantido sem eles. Além disso, sei como / por que um analisador é fundamentalmente diferente de outro.

Os parâmetros do construtor me impedem de saber muito sobre como usar a classe. Se, em vez disso, devo definir determinadas propriedades - como eu sei disso? Uma lata inteira de vermes se abre. Quais propriedades? Em que ordem? Antes de usar quais métodos? e assim por diante.

Outra pergunta surge quando percebo que uma interface não teria sentido na segunda implementação:

Uma interface, como na API, são os métodos e propriedades expostos ao código do cliente. Não se embrulhe public interface { ... }exclusivamente. Portanto, o significado da interface está no dilema de parâmetro entre o construtor e o método vs, NOT public interface Iparservspublic sealed class Parser

A sealedturma é estranha. Se eu estou pensando em diferentes implementações de analisador - você mencionou "Iparser" -, herança é meu primeiro pensamento. É apenas uma extensão conceitual natural do meu pensamento. Ou seja, todos os ParserXs são fundamentalmente Parsers. Como mais dizer isso? ... Um Shepard alemão é um cachorro (herança), mas eu posso treinar meu papagaio para latir (agir como um cachorro - "interface"); mas Polly não é um cachorro, apenas fingindo, tendo aprendido um subconjunto de dogness. Classes, abstratas ou não, servem perfeitamente bem como interfaces .

radarbob
fonte
Se ele anda como um analisador e fala como um analisador, é ... um pato!
2

A segunda versão da classe pode ser imutável.

A interface ainda pode ser usada para fornecer a capacidade de trocar a implementação subjacente.

Robert Harvey
fonte
Também não pode ser imutável na primeira versão, passando dados dentro da classe de maneira funcional?
Ciscoheat
2
Absolutamente. Mas o padrão geral de imutabilidade é definir os membros da classe usando o construtor e ter propriedades somente leitura. Para programação funcional, você nem precisa de uma aula.
Robert Harvey
Scylla e Charybdis: escolho OO ou dados imutáveis? Eu nunca ouvi isso assim antes.
2

Parser1

Construir com um construtor padrão e passar o texto de entrada para um método implica que o Parser1 é reutilizável.

Parser2

Passar o texto de entrada para o construtor implica que um novo Parser2 deve ser criado para cada sequência de entrada.

Mike Partridge
fonte
Claro, se o levarmos ao próximo nível, o que concluir sobre um objeto reutilizável em comparação com um objeto não reutilizável?
Ciscoheat
Eu não assumiria mais do que isso, em vez de adiar a documentação.
precisa
Um "objeto reutilizável" em que sua identidade é alterada é não sequitur. E com estruturas gerenciadas, o que significa que você nem precisa jogar fora as coisas, apenas substituí-las ou deixá-las fora do escopo, é certamente desnecessário. Construa para longe!