Está tudo bem para uma classe usar seu próprio método público?

23

fundo

Atualmente, tenho uma situação em que tenho um objeto que é transmitido e recebido por um dispositivo. Esta mensagem possui várias construções, da seguinte maneira:

public void ReverseData()
public void ScheduleTransmission()

O ScheduleTransmissionmétodo precisa chamar o ReverseDatamétodo sempre que for chamado. No entanto, há momentos em que precisarei chamar ReverseDataexternamente (e devo adicionar completamente fora do namespace ) de onde o objeto é instanciado no aplicativo.

Quanto ao "recebimento", quero dizer que ReverseDataserá chamado externamente em um object_receivedmanipulador de eventos para reverter os dados.

Questão

É geralmente aceitável que um objeto chame seus próprios métodos públicos?

Snoop
fonte
5
Não há nada errado em chamar um método público por si próprio. Mas, com base nos nomes dos métodos, ReverseData () me parece um pouco perigoso por ser um método público se reverter os dados internos. E se ReverseData () chamado fora do objeto e depois chamado novamente com o ScheduleTransmission ().
Kaan
@ Kaan Estes não são os nomes reais dos meus métodos, mas estão intimamente relacionados. De fato, "dados reversos" apenas inverte 8 bits da palavra total e é feito quando recebemos e transmitimos.
Snoop
A questão ainda permanece. E se esses 8 bits forem revertidos duas vezes antes do agendamento da transmissão? Parece um enorme buraco na interface pública. Pensar na interface pública como mencionei na minha resposta pode ajudar a trazer esse problema à tona.
Daniel T.
2
@StevieV Acredito que isso apenas amplifique a preocupação que Kaan suscita. Parece que você está expondo um método que altera o estado do objeto, e o estado depende principalmente de quantas vezes o método é chamado. Isso cria um pesadelo ao tentar rastrear qual é o estado do objeto em todo o seu código. Parece-me mais que você se beneficiaria com tipos de dados separados desses estados conceituais, para poder dizer o que é isso em qualquer parte do código, sem precisar se preocupar com isso.
Jpmc26 15/03
2
@StevieV algo como Data e SerializedData. Dados é o que o código vê e SerializedData é o que é enviado pela rede. Em seguida, faça os dados reversos (ou melhor, serializar / desserializar dados) se transformarem de um tipo para outro.
csiz

Respostas:

33

Eu diria que não é apenas aceitável, mas incentivado, especialmente se você planeja permitir extensões. Para oferecer suporte a extensões da classe em C #, você precisa sinalizar o método como virtual, de acordo com os comentários abaixo. Você pode documentar isso, no entanto, para que alguém não fique surpreso ao substituir ReverseData () altera a maneira como ScheduleTransmission () funciona.

Realmente se resume ao design da classe. ReverseData () parece um comportamento fundamental da sua classe. Se você precisar usar esse comportamento em outros lugares, provavelmente não deseja ter outras versões. Você só precisa ter cuidado para não permitir que detalhes específicos de ScheduleTransmission () vazem para ReverseData (). Isso criará problemas. Mas como você já está usando isso fora da classe, provavelmente já pensou nisso.

JimmyJames
fonte
Uau, parecia realmente estranho para mim, mas isso ajuda. Gostaria de ver também o que outras pessoas dizem. Obrigado.
Snoop
2
"para que alguém não fique surpreso ao substituir ReverseData () muda a maneira como ScheduleTransmission () funciona" : não, na verdade não. Pelo menos não em C # (observe que a pergunta possui uma tag C #). A menos que ReverseData()seja abstrato, não importa o que você faça na classe filho, é sempre o método original que será chamado.
Arseni Mourzenko 14/03/16
2
@ MAINMA OU virtual... E se você está substituindo o método, por definição, ele precisa ser virtualou abstract.
Pokechu22
1
@ Pokechu22: de fato, virtualtambém funciona. Em todos os casos, a assinatura, de acordo com o OP, é public void ReverseData(), portanto, a parte da resposta sobre substituir coisas é um pouco enganadora.
Arseni Mourzenko 14/03
@ MainMa, você está dizendo que, se um método de classe base chama um método virtual, ele não se comporta da maneira virtual usual, a menos que fosse abstract? Procurei respostas no abstractvs virtuale não vi o mencionado: bastante abstrato significa apenas que não há versão dada nesta base.
JDługosz 15/03
20

Absolutamente.

A visibilidade de um método tem como único objetivo permitir ou negar acesso a um método fora da classe ou dentro de uma classe filho; métodos públicos, protegidos e privados sempre podem ser chamados dentro da própria classe.

Não há nada errado em chamar métodos públicos. A ilustração na sua pergunta é o exemplo perfeito de uma situação em que ter dois métodos públicos é exatamente o que você deve fazer.

No entanto, você pode observar cuidadosamente os seguintes padrões:

  1. Um método Hello()chama World(string, int, int)assim:

    Hello()
    {
        this.World("Some magic value here", 0, 100);
    }

    Evite esse padrão. Em vez disso, use argumentos opcionais . Argumentos opcionais facilitam a descoberta: o chamador que digita Hello(não necessariamente sabe que existe um método que possibilita chamar o método com valores padrão. Argumentos opcionais também são auto-documentados. World()não mostra ao chamador quais são os valores padrão reais.

  2. Um método Hello(ComplexEntity)chama World(string, int, int)assim:

    Hello(ComplexEntity entity)
    {
        this.World(entity.Name, entity.Start, entity.Finish);
    }

    Em vez disso, use sobrecargas . Mesmo motivo: melhor descoberta. O chamador pode ver imediatamente todas as sobrecargas através do IntelliSense e escolher a correta.

  3. Um método simplesmente chama outros métodos públicos sem agregar nenhum valor substancial.

    Pense duas vezes. Você realmente precisa deste método? Ou você deve removê-lo e permitir que o chamador invoque os diferentes métodos? Uma dica: se o nome do método não parecer correto ou difícil de encontrar, você provavelmente deverá removê-lo.

  4. Um método valida a entrada e chama o outro método:

    Hello(string name, int start, int end)
    {
        if (name == null) throw new ArgumentNullException(...);
        if (start < 0) throw new OutOfRangeException(...);
        ...
        if (end < start) throw new ArgumentException(...);
    
        this.World(name, start, end);
    }

    Em vez disso, Worlddeve validar seus próprios parâmetros ou ser privado.

Arseni Mourzenko
fonte
Os mesmos pensamentos se aplicariam se ReverseData fosse realmente interno e usado em todo o espaço de nomes contendo instâncias de "objeto"?
Snoop
1
@ StevieV: sim, é claro.
Arseni Mourzenko 14/03/16
@MainMa Eu não entendo o que é a razão por trás pontos 1 e 2
Kapol
@ Kapol: obrigado pelo seu feedback. Editei minha resposta adicionando explicações sobre os dois primeiros pontos.
Arseni Mourzenko 15/03/19
Mesmo no caso 1, geralmente prefiro sobrecargas do que argumentos opcionais. Se as opções são tais que o uso de sobrecargas não é uma opção ou produz um número especialmente grande de sobrecargas, eu criaria um tipo personalizado e o usaria como argumento (como na sugestão 2), ou refatoraria o código.
27716 Brian As
8

Se algo é público, pode ser chamado a qualquer momento por qualquer sistema. Não há razão para que você também não possa ser um desses sistemas!

Em bibliotecas altamente otimizadas, você deseja copiar o idioma apresentado java.util.ArrayList#ensureCapacity(int).

garantirCapacidade

  • ensureCapacity é público
  • ensureCapacity tem todos os limites necessários de verificação e valores padrão, etc.
  • ensureCapacity chamadas ensureExplicitCapacity

asseguraCapacityInternal

  • ensureCapacityInternal é privado
  • ensureCapacityInternal possui verificação mínima de erros, porque todas as entradas vêm de dentro da classe
  • ensureCapacityInternal TAMBÉM chama ensureExplicitCapacity

assegureCapacidadeExplicita

  • ensureExplicitCapacity também é privado
  • ensureExplicitCapacitynão tem verificação de erro
  • ensureExplicitCapacity faz trabalho real
  • ensureExplicitCapacitynão é chamado de qualquer lugar, exceto por ensureCapacityeensureCapacityInternal

Dessa maneira, o código em que você confia obtém acesso privilegiado (e mais rápido!) Porque você sabe que suas entradas são boas. O código no qual você não confia passa por um certo rigor para verificar sua integridade e bomba, fornecer padrões ou manipular informações incorretas. Ambos canalizam para o local que realmente funciona.

No entanto, isso é usado no ArrayList, uma das classes mais usadas no JDK. É muito possível que seu caso não exija esse nível de complexidade e rigor. Para encurtar a história, se todas as ensureCapacityInternalchamadas fossem substituídas por ensureCapacitychamadas, o desempenho ainda seria muito, muito bom. Esta é uma microoptimização provavelmente feita apenas após uma análise extensiva.

corsiKa
fonte
3

Várias boas respostas já foram dadas, e eu também concordaria com elas que sim, um objeto pode chamar seus métodos públicos de outros métodos. No entanto, há uma pequena ressalva de design que você terá que observar.

Os métodos públicos geralmente têm um contrato de "levar o objeto em um estado consistente, fazer algo sensato, deixar o objeto em um estado consistente (possivelmente diferente)". Aqui, "consistente" pode significar, por exemplo, que o Lengthde a List<T>não é maior que o seu Capacitye que a referência aos elementos nos índices de 0para Length-1não será lançada.

Mas, dentro dos métodos do objeto, o objeto pode estar em um estado inconsistente; portanto, quando você chama um de seus métodos públicos, pode fazer uma coisa muito errada, porque não foi escrito com essa possibilidade em mente. Portanto, se você planeja chamar seus métodos públicos de outros métodos, verifique se o contrato deles é "pegue o objeto em algum estado, faça algo sensato, deixe o objeto em um (possivelmente diferente) (talvez inconsistente - mas apenas se o estado inconsistente) ".

Joker_vD
fonte
1

Outro exemplo ao chamar método público dentro de outro método público é totalmente adequado é uma abordagem CanExecute / Execute. Eu o uso quando preciso de validação e preservação invariável .

Mas, em geral, sou sempre cauteloso com isso. Se um método a()é chamado de método interno b(), significa que esse método a()é um detalhe de implementação b(). Muitas vezes, indica que eles pertencem a diferentes níveis de abstração. E o fato de ambos serem públicos me faz pensar se é uma violação do princípio de responsabilidade única ou não.

Zapadlo
fonte
-5

Desculpe, mas vou ter que discordar da maioria das outras respostas 'sim, você pode' e dizer o seguinte:

Eu desencorajaria uma classe chamando um método público de outro

Existem alguns problemas em potencial com essa prática.

1: Loop infinito na classe herdada

Portanto, sua classe base chama method1 do method2, mas você ou outra pessoa herda dele e oculta o method1 com um novo método que chama method2.

2: Eventos, registro etc.

por exemplo, eu tenho um método Add1 que dispara um evento '1 added!' Eu provavelmente não quero que o método Add10 levante esse evento, grave em um log ou o que seja, dez vezes.

3: rosqueamento e outros impasses

Por exemplo, InsertComplexData abre uma conexão db, inicia uma transação, bloqueia uma tabela e chama InsertSimpleData, com abre uma conexão, inicia uma transação, aguarda que a tabela seja desbloqueada ....

Tenho certeza de que há mais motivos, uma das outras respostas foi tocada em 'você edita o método1 e fica surpreso com o método2 começa a se comportar de maneira diferente'

Geralmente, se você tem dois métodos públicos que compartilham código, é melhor fazê-los chamar um método privado em vez de um chamar o outro.

Editar ----

Vamos expandir o caso específico no OP.

não temos muitos detalhes, mas sabemos que ReverseData é chamado por algum manipulador de eventos, bem como o método ScheduleTransmission.

Suponho que os dados reversos também alterem o estado interno do objeto

Dado este caso, eu pensaria que a segurança do thread seria importante e, portanto, minha terceira objeção à prática se aplica.

Para tornar o thread ReverseData seguro, você pode adicionar um bloqueio. Mas se o ScheduleTransmission também precisar ser seguro para threads, você desejará compartilhar o mesmo bloqueio.

A maneira mais fácil de fazer isso é mover o código ReverseData para um método privado e fazer com que ambos os métodos públicos o chamem. Você pode colocar a instrução lock nos Métodos Públicos e compartilhar um objeto de bloqueio.

Obviamente, você pode argumentar "isso nunca vai acontecer!" ou "Eu poderia programar o bloqueio de outra maneira", mas o ponto sobre as boas práticas de codificação é estruturar bem seu código em primeiro lugar.

Em termos acadêmicos, eu diria que isso viola o L em sólido. Os métodos públicos são mais do que acessíveis publicamente. Eles também são modificáveis ​​por seus herdeiros. Seu código deve ser fechado para modificação, o que significa que você precisa pensar no trabalho que realiza nos métodos público e protegido.

Aqui está outra: você também potencialmente viola o DDD. Se seu objeto é um objeto de domínio, seus métodos públicos devem ser termos de domínio, que significam algo para os negócios. Nesse caso, é muito improvável que "compre uma dúzia de ovos" seja o mesmo que "compre 1 ovo 12 vezes", mesmo que comece dessa maneira.

Ewan
fonte
3
Esses problemas parecem mais uma arquitetura mal planejada do que uma condenação geral do princípio do design. (Talvez chamar "1 adicionado" seja sempre bom. Se não for, devemos programar de forma diferente. Talvez se dois métodos estiverem tentando bloquear exclusivamente o mesmo recurso, eles não devem ligar um para o outro ou os escrevemos mal .) Em vez de apresentar más implementações, você pode se concentrar em argumentos mais sólidos que abordam o acesso a métodos públicos como um princípio universal. Por exemplo, dado um bom código puro, por que é ruim?
doppelgreener
Exatamente, se não for, você não tem um lugar para colocar o evento. Da mesma forma com # 3 tudo é um untill multa de seus métodos abaixo na cadeia precisa de uma transação, então você está ferrado
Ewan
Todos esses problemas refletem mais o código em si como de baixa qualidade do que o princípio geral sendo falho. Todos os três casos são apenas códigos incorretos. Eles podem ser resolvidos por alguma versão de "não faça isso, faça isso" (talvez perguntando "o que você quer exatamente?") E não ponha em questão o princípio geral de uma classe que chama seu próprio público métodos. O vago potencial de um loop infinito é o único aqui que aborda o assunto por uma questão de princípio e, mesmo assim, não é abordado completamente.
doppelgreener
a única coisa que o 'código' no meu exemplo compartilha é um método público chamando outro. Se você acha que o seu 'código ruim' é bom! Essa é a minha afirmação
Ewan
O fato de você poder usar um martelo para quebrar sua própria mão não significa que o martelo esteja ruim. Isso significa que você não precisa fazer coisas que vão quebrar sua mão.
doppelgreener