Va bene per una classe utilizzare il proprio metodo pubblico?


23

sfondo

Attualmente ho una situazione in cui ho un oggetto che è sia trasmesso che ricevuto da un dispositivo. Questo messaggio ha diversi costrutti, come segue:

public void ReverseData()
public void ScheduleTransmission()

Il ScheduleTransmissionmetodo deve chiamare il ReverseDatametodo ogni volta che viene chiamato. Tuttavia, ci sono volte in cui dovrò chiamare ReverseDataesternamente (e dovrei aggiungere completamente al di fuori dello spazio dei nomi ) da cui viene istanziato l'oggetto nell'applicazione.

Per quanto riguarda la "ricezione", intendo che ReverseDataverrà chiamato esternamente in un object_receivedgestore di eventi per annullare l'inversione dei dati.

Domanda

È generalmente accettabile che un oggetto chiami i propri metodi pubblici?


5
Non c'è nulla di sbagliato nel chiamare un metodo pubblico da solo. Ma, in base ai nomi dei tuoi metodi, ReverseData () mi sembra un po 'pericoloso essere un metodo pubblico se inverte i dati interni. Cosa succederebbe se ReverseData () chiamasse all'esterno dell'oggetto e poi richiamasse nuovamente con ScheduleTransmission ().
Kaan,

@Kaan Questi non sono i veri nomi dei miei metodi, ma strettamente correlati. In effetti "dati inversi" inverte solo 8 bit della parola totale e viene fatto quando riceviamo e trasmettiamo.
Snoop

La domanda è ancora valida. Cosa succede se quegli 8 bit vengono invertiti due volte prima che la trasmissione sia programmata? Questo sembra un enorme buco nell'interfaccia pubblica. Pensare all'interfaccia pubblica come menziono nella mia risposta potrebbe aiutare a mettere in luce questo problema.
Daniel T.

2
@StevieV Credo che ciò amplifichi solo la preoccupazione che Kaan solleva. Sembra che tu stia esponendo un metodo che cambia lo stato dell'oggetto e lo stato dipende principalmente da quante volte viene chiamato il metodo. Questo crea un incubo nel tentativo di tenere traccia di quale sia lo stato dell'oggetto nel codice. Mi sembra più che tu trarrai beneficio da tipi di dati separati da questi stati concettuali, quindi puoi dire che cosa è in ogni dato pezzo di codice senza doverti preoccupare.
jpmc26

2
@StevieV qualcosa come Data e SerializedData. I dati sono ciò che vede il codice e SerializedData è ciò che viene inviato attraverso la rete. Quindi fare in modo che i dati inversi (o piuttosto serializzare / deserializzare i dati) si trasformino da un tipo all'altro.
CSI

Risposte:


33

Direi che non è solo accettabile, ma incoraggiato soprattutto se prevedi di consentire le estensioni. Per supportare le estensioni della classe in C #, è necessario contrassegnare il metodo come virtuale in base ai commenti seguenti. Potresti voler documentare questo, tuttavia, in modo che qualcuno non sia sorpreso quando sovrascrive ReverseData () cambia il modo in cui ScheduleTransmission () funziona.

Dipende davvero dal design della classe. ReverseData () sembra un comportamento fondamentale della tua classe. Se è necessario utilizzare questo comportamento in altri luoghi, probabilmente non si desidera avere altre versioni di esso. Devi solo stare attento a non lasciare che i dettagli specifici di ScheduleTransmission () perdano in ReverseData (). Ciò creerà problemi. Ma dal momento che lo stai già utilizzando al di fuori della classe, probabilmente lo hai già pensato.


Wow, mi è sembrato davvero strano ma questo aiuta. Mi piacerebbe anche vedere cosa dicono alcune altre persone. Grazie.
Snoop

2
"in modo che qualcuno non sia sorpreso quando si ignora ReverseData () cambia il modo in cui ScheduleTransmission () funziona" : no, non proprio. Almeno non in C # (nota che la domanda ha un tag C #). A meno che non ReverseData()sia astratto, qualunque cosa tu faccia nella classe figlio, sarà sempre chiamato il metodo originale.
Arseni Mourzenko,

2
@MainMa Oppure virtual... E se stai ignorando il metodo, allora per definizione deve essere virtualo abstract.
Pokechu22

1
@ Pokechu22: anzi, virtualfunziona anche. In tutti i casi, la firma, secondo OP, è public void ReverseData(), quindi la parte della risposta sull'override delle cose è leggermente fuorviante.
Arseni Mourzenko,

@MainMa stai dicendo che se un metodo di classe base chiama un metodo virtuale non si comporta nel solito modo virtuale a meno che non lo fosse abstract? Ho cercato risposte su abstractvs virtuale non ho visto quello menzionato: piuttosto astratto significa solo che non esiste una versione data su questa base.
JDługosz,

20

Assolutamente.

La visibilità di un metodo ha il solo scopo di consentire o negare l'accesso a un metodo esterno alla classe o all'interno di una classe figlio; i metodi pubblici, protetti e privati ​​possono sempre essere chiamati all'interno della classe stessa.

Non c'è nulla di sbagliato nel chiamare metodi pubblici. L'illustrazione nella tua domanda è l'esempio perfetto di una situazione in cui avere due metodi pubblici è esattamente ciò che dovresti fare.

Tuttavia, è possibile controllare attentamente i seguenti modelli:

  1. Un metodo Hello()chiama in World(string, int, int)questo modo:

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

    Evita questo schema. Utilizzare invece argomenti facoltativi . Gli argomenti opzionali facilitano la rilevabilità: il chiamante che digita Hello(non necessariamente saprà che esiste un metodo che consente di chiamare il metodo con valori predefiniti. Anche gli argomenti opzionali sono auto-documentati. World()non mostra al chiamante quali sono i valori predefiniti effettivi.

  2. Un metodo Hello(ComplexEntity)chiama in World(string, int, int)questo modo:

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

    Utilizzare invece sovraccarichi . Stesso motivo: migliore rilevabilità. Il chiamante può vedere subito tutti i sovraccarichi tramite IntelliSense e scegliere quello giusto.

  3. Un metodo chiama semplicemente altri metodi pubblici senza aggiungere alcun valore sostanziale.

    Pensa due volte. Hai davvero bisogno di questo metodo? O dovresti rimuoverlo e lasciare che il chiamante invochi i diversi metodi? Un suggerimento: se il nome del metodo non sembra giusto o è difficile da trovare, probabilmente dovresti rimuoverlo.

  4. Un metodo convalida l'input e quindi chiama l'altro metodo:

    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);
    }

    Invece, Worlddovrebbe convalidare i suoi parametri stessi o essere privato.


Si applicherebbero gli stessi pensieri se ReverseData fosse effettivamente interno e utilizzato in tutto lo spazio dei nomi contenente istanze di "oggetto"?
Snoop

1
@StevieV: sì, certo.
Arseni Mourzenko,

@MainMa Non capisco qual è la ragione dietro i punti 1 e 2
Kapol

@Kapol: grazie per il tuo feedback. Ho modificato la mia risposta aggiungendo spiegazioni sui primi due punti.
Arseni Mourzenko,

Anche nel caso 1, di solito preferisco sovraccarichi piuttosto che argomenti opzionali. Se le opzioni sono tali che l'utilizzo dei sovraccarichi non è un'opzione o produce un numero particolarmente elevato di sovraccarichi, creerei un tipo personalizzato e lo utilizzerei come argomento (come nel suggerimento 2), oppure rifattorizzerei il codice.
Brian

8

Se qualcosa è pubblico, potrebbe essere chiamato in qualsiasi momento da qualsiasi sistema. Non c'è motivo per cui non si possa essere anche uno di quei sistemi!

In librerie altamente ottimizzate, si desidera copiare il linguaggio proposto java.util.ArrayList#ensureCapacity(int).

ensureCapacity

  • ensureCapacity è pubblico
  • ensureCapacity ha tutti i limiti necessari per il controllo e i valori predefiniti, ecc.
  • ensureCapacity chiamate ensureExplicitCapacity

ensureCapacityInternal

  • ensureCapacityInternal è privato
  • ensureCapacityInternal ha un controllo minimo degli errori perché tutti gli input provengono dall'interno della classe
  • ensureCapacityInternal Chiama ANCHE ensureExplicitCapacity

ensureExplicitCapacity

  • ensureExplicitCapacity è ANCHE privato
  • ensureExplicitCapacitynon ha alcun controllo degli errori
  • ensureExplicitCapacity funziona davvero
  • ensureExplicitCapacitynon viene chiamato da nessuna parte tranne che da ensureCapacityeensureCapacityInternal

In questo modo, il codice di cui ti fidi ottiene un accesso privilegiato (e più veloce!) Perché sai che i suoi input sono buoni. Il codice di cui non ti fidi passa attraverso un certo rigore per verificarne l'integrità e la bomba o fornire valori predefiniti o altrimenti gestire input errati. Entrambi canalizzano verso il luogo che effettivamente funziona.

Tuttavia, questo viene utilizzato in ArrayList, una delle classi più utilizzate in JDK. È molto probabile che il tuo caso non richieda quel livello di complessità e rigore. Per farla breve, se tutte le ensureCapacityInternalchiamate fossero sostituite con ensureCapacitychiamate, le prestazioni sarebbero comunque davvero buone. Questa è una microottimizzazione probabilmente fatta solo dopo un'attenta valutazione.


3

Sono già state fornite diverse buone risposte, e concordo anche con loro sul fatto che sì, un oggetto può chiamare i suoi metodi pubblici dai suoi altri metodi. Tuttavia, c'è un leggero avvertimento di design che dovrai fare attenzione.

I metodi pubblici di solito hanno un contratto di "portare l'oggetto in uno stato coerente, fare qualcosa di sensato, lasciare l'oggetto in uno stato (possibilmente diverso) coerente". Qui, "coerente" può significare, ad esempio, che il valore Lengthdi a List<T>non è maggiore di suo Capacitye che il riferimento agli elementi negli indici da 0a Length-1non verrà lanciato.

Ma all'interno dei metodi dell'oggetto, l'oggetto potrebbe essere in uno stato incoerente, quindi quando chiami uno dei tuoi metodi pubblici, potrebbe fare una cosa molto sbagliata, perché non è stato scritto con una tale possibilità in mente. Quindi, se hai intenzione di chiamare i tuoi metodi pubblici dagli altri tuoi metodi, assicurati che il loro contratto sia "prendi l'oggetto in qualche stato, fai qualcosa di sensato, lascia l'oggetto in un (possibilmente diverso) (forse incoerente) ma solo se l'iniziale stato era incoerente) stato ".


1

Un altro esempio quando si chiama totalmente il metodo pubblico all'interno di un altro metodo pubblico è l'approccio CanExecute / Execute. Lo uso quando ho bisogno sia della validazione che della conservazione invariante .

Ma in generale sono sempre cauto. Se un metodo a()viene chiamato metodo interno b(), significa che il metodo a()è un dettaglio di implementazione di b(). Molto spesso indica che appartengono a diversi livelli di astrazione. E il fatto che entrambi siano pubblici mi fa chiedere se si tratti o meno di una violazione del principio della responsabilità singola .


-5

Ci dispiace ma non dovrò essere d'accordo con la maggior parte delle altre risposte "sì, puoi" e dire che:

Scoraggerei una classe che chiama un metodo pubblico da un altro

Ci sono un paio di potenziali problemi con questa pratica.

1: ciclo infinito nella classe ereditata

Quindi la tua classe base chiama method1 da method2 ma poi tu, o qualcun altro, erediti da essa e nascondi method1 con un nuovo metodo che chiama method2.

2: Eventi, registrazione ecc.

ad es. ho un metodo Add1 che genera un evento "1 aggiunto!" Probabilmente non voglio che il metodo Add10 generi l'evento, scriva su un registro o altro, dieci volte.

3: threading e altri deadlock

Ad esempio InsertComplexData apre una connessione db, avvia una transazione, blocca una tabella, quindi chiama InsertSimpleData, con apre una connessione, avvia una transazione, attende che la tabella venga sbloccata ....

Sono sicuro che ci sono più ragioni, una delle altre ha toccato "modifichi il metodo1 e sei sorpreso che il metodo2 inizi a comportarsi diversamente"

In generale, se hai due metodi pubblici che condividono il codice, è meglio farli chiamare entrambi un metodo privato piuttosto che uno chiama l'altro.

Modificare ----

Consente di espandere il caso specifico nel PO.

non abbiamo molti dettagli, ma sappiamo che ReverseData viene chiamato da un gestore di eventi di qualche tipo e dal metodo ScheduleTransmission.

Suppongo che i dati inversi cambino anche lo stato interno dell'oggetto

Dato questo caso, ritengo che la sicurezza del thread sarebbe importante e quindi si applica la mia terza obiezione alla pratica.

Per rendere sicuro il thread ReverseData puoi aggiungere un lucchetto. Ma se anche ScheduleTransmission deve essere thread-safe, vorrai condividere lo stesso blocco.

Il modo più semplice per farlo è spostare il codice ReverseData in un metodo privato e farli chiamare da entrambi i metodi pubblici. È quindi possibile inserire l'istruzione lock nei metodi pubblici e condividere un oggetto lock.

Ovviamente puoi discutere "che non accadrà mai!" o "Potrei programmare il blocco in un altro modo", ma il punto sulla buona pratica di codifica è di strutturare bene il codice in primo luogo.

In termini accademici direi che ciò viola la L in solido. I metodi pubblici non sono solo accessibili al pubblico. Sono inoltre modificabili dai suoi eredi. Il tuo codice dovrebbe essere chiuso per modifiche, il che significa che devi pensare al lavoro che fai in entrambi i metodi pubblici e protetti.

Eccone un altro: Potresti anche violare il DDD. Se il tuo oggetto è un oggetto di dominio, i suoi metodi pubblici dovrebbero essere termini di dominio che significano qualcosa per l'azienda. In questo caso è molto improbabile che "compri una dozzina di uova" sia uguale a "compri 1 uovo 12 volte" anche se inizia in quel modo.


3
Questi problemi sembrano più un'architettura mal pensata che una condanna generale del principio di progettazione. (Forse chiamare "1 aggiunto" va bene ogni volta. In caso contrario, dovremmo programmare in modo diverso. Forse se due metodi stanno cercando di bloccare in modo esclusivo la stessa risorsa, non dovrebbero chiamarsi a vicenda o li abbiamo scritti male .) Invece di presentare cattive implementazioni, potresti voler concentrarti su argomenti più solidi che affrontano l'accesso ai metodi pubblici come principio universale. Ad esempio, dato un buon codice pulito, perché è cattivo?
doppelgreener,

Esatto, in caso contrario, non hai un posto dove organizzare l'evento. Allo stesso modo con il n. 3 tutto va bene fino a quando uno dei tuoi metodi lungo la catena ha bisogno di una transazione, quindi sei fregato
Ewan

Tutti questi problemi riflettono più che il codice stesso è di scarsa qualità rispetto al difetto del principio generale. Tutti e tre i casi sono solo codice errato. Essi possono essere risolti da una qualche versione di "non farlo, farlo invece" (magari con chiedendo "che cosa ne vuoi esattamente?"), E non rimettono in discussione il principio generale di una classe di chiamare il proprio pubblico metodi. Il vago potenziale per un ciclo infinito è l'unico qui che arriva alla questione come una questione di principio, e anche allora non è affrontato a fondo.
doppelgreener,

l'unico ostacolo del "codice" nelle condivisioni del mio esempio è un metodo pubblico che chiama un altro. Se ritieni che il suo "codice errato" sia buono! Questa è la mia tesi
Ewan,

Il fatto che tu possa usare un martello per romperti la mano non significa che il martello sia cattivo. Significa che non devi fare cose che ti spezzano la mano.
doppelgreener,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.