Comportamento della classe della catena

6

Ho una classe di uccelli. Un uccello può imparare e un uccello può volare (metodi). Un uccello può volare solo dopo averlo imparato.

Considerando entrambi i metodi sono pubblici. Come puoi chiarire questo a un altro sviluppatore. Di solito dovrebbero dare un'occhiata ai nomi dei metodi pubblici e capire cosa fanno. Ma come può capire senza leggere i commenti o capire il codice incapsulato che metodo2 può essere chiamato solo dopo il metodo1?

    
posta danidacar 08.05.2013 - 22:09
fonte

7 risposte

14

Il modo più pulito (ma non sempre il più pratico) è di avere learn restituire un EducatedBird che può quindi fly .

Non sono sicuro che ci sia un modello formale chiamato per questo, ma questo tipo di schema funziona molto bene per altri tipi di transizioni di stato come l'apertura delle connessioni.

    
risposta data 08.05.2013 - 22:12
fonte
6

La risposta di Telastyn è il suggerimento da manuale e non negativo. Questo è chiamato modello di stato e può essere implementato nella maggior parte dei linguaggi di programmazione, ma lo trovo molto goffo e quando inizia a entrare nell'intervallo 10-20, vorresti aver implementato il pattern come una serie di decoratori e quando hai finito avrai completamente dimenticato OOP.

Lo schema di stato è utile solo se si dispone di un numero chiaro e distinto di stati e transizioni di stato. Se vuoi avere un numero arbitrario di stati, o avere stati multidimensionali, dovresti probabilmente usare il schema di decorazione . Ciò, tuttavia, ha i suoi avvertimenti. A seconda del tuo linguaggio, anche questo può essere clunky (se hai un metodo che si aspetta un FlyingBird ma passi un YellowBird che avvolge un FlyingBird ...). Tendi a perdere alcuni dei vantaggi della tua lingua implementando in questo modo lo schema del decoratore.

Il modo migliore , secondo me, sarebbe quello di implementare il pattern di stato implicitamente usando uno stato interno per la gestione di oggetti ed eccezioni, o per comporre l'oggetto (se la tua lingua lo supporta) in fase di esecuzione.

    
risposta data 08.05.2013 - 22:34
fonte
4

In generale, se devo imporre in fase di compilazione che un metodo debba essere eseguito per primo e renderlo ovvio al programmatore, generalmente uso una monade che è un'idea simile al modello di stato di Telastyn, o ne impongo l'uso di un token.

Have Learn () restituisce un oggetto FlightKnowledge. Questo oggetto dovrebbe avere una visibilità del costruttore che impedisce al consumatore medio di crearne uno (privato, protetto, interno, ecc.). Quindi esponi Fly (FlightKnowledge) come unico sovraccarico del metodo Fly. Ciò richiede che il codificatore passi in un oggetto FlightKnowledge valido (a seconda della tua lingua puoi rendere FlightKnowledge un tipo che non può essere nullo, o applicare per decorazione che il parametro non può essere nullo), e se l'unico modo in cui può ottenere uno è chiamando Learn (), quindi hai applicato il tuo ordine di operazioni.

Non trasformarlo in un anti-pattern di "King's Quest" (per volare (), devi avere FlightKnowledge, che ottieni chiamando Learn (), che prende un oggetto Tuition prodotto da MamaBird di chiamando AskForTuition () che prende un oggetto SweetTalk che è ...). Se il tuo ordine delle operazioni è così profondo, i token di per sé sono il modello sbagliato.

    
risposta data 09.05.2013 - 00:15
fonte
1

È malvagio, ma puoi usare il metodo di rinforzo negativo:

 class Bird
   fn learn :  _learned = true
   fn fly : assert(_learned) ...do flying...

   bool _learned = false

Il chiaro svantaggio è la mancanza di informazioni sul tipo in fase di compilazione o di tipo statico per guidare lo sviluppatore, ma è stato usato prima, in particolare in C.

    
risposta data 08.05.2013 - 23:30
fonte
1

Non puoi close un file a meno che non sia open . Come dovrebbe reagire il metodo close quando viene eseguito nello stato sbagliato? Dovrebbe restituire un errore o generare un'eccezione. Ciò dipende dalla lingua e dallo stile di programmazione che stai seguendo.

Meteo o non Bird può volare o no è una rappresentazione del suo stato interno. Lo stesso di una risorsa file e come tale seguiresti gli stessi schemi di progettazione. Trattando l'uccello come se avesse uno stato interno che lo sviluppatore deve controllare.

class Bird {
    boolean canFly();
    void teach();
    void fly();
}

try
{
  if(!b.canFly()) { b.teach(); }
  b.fly();
} catch (e) { ... }

Avere la prospettiva, che un metodo dipende dall'ordine di un altro metodo, non segue la mia comprensione del design OOP. L'oggetto ha uno stato interno. L'oggetto gestisce il proprio stato interno e il codice sorgente esterno non dovrebbe essere responsabile della gestione di tale stato (se possibile). Se devi dire agli sviluppatori di eseguire i metodi in un dato ordine, allora hai progettato l'oggetto sbagliato.

Invece di insegnare e volare. Dovresti inizializzare l'uccello e rendere la gestione dello stato interno dell'uccello una questione interna dell'oggetto. In questo modo, non spezzerai il codice sorgente di altre persone quando dovrai rivedere il design di Bird.

class Bird {
   private boolean taught = false;
   private boolean flying = false;
   private void teach() {...}

   protected void init() {
      if(!this->taught) { this->teach(); }
   }

   public void startFlying() { this->init(); this->flying = true; }
   public void stopFlying() { this->init(); this->flying = false; }
}
    
risposta data 08.05.2013 - 23:40
fonte
0

La mia soluzione si piega di UML attorno ai suoi angoli, ma penso che valga la pena sparare. se è solo per fare in modo che un collega programmatore sappia come chiamare i metodi, allora questo può essere sufficiente

Quando modellate il design della classe Bird , aggiungete una dipendenza tra metodi. Attualmente, UML consente solo la dipendenza tra gli oggetti, ma puoi scegliere di definire uno standard che ti permetta di specificare le dipendenze tra gli attributi. In questo modo, la dipendenza ...

fly --------------> learn

... vorrebbe dire che fly dipende da learn

Nell'implementazione, potresti avere una bandiera che indica che l'uccello ha imparato a volare. se la quantità di cose da imparare diventa troppa, potresti voler astrarre la capacità di apprendimento dell'uccello in una classe diversa; ricadendo così nella parte migliore delle risposte pre-dichiarate

    
risposta data 09.05.2013 - 03:26
fonte
-1

La risposta accettata non è conforme agli standard OO perché ho capito questa soluzione, perché implicherebbe l'oggetto cambiando classe. Per prima cosa abbiamo trattato un'istanza di Bird , quindi con un'istanza di EducatedBird .

Questo è un anti-pattern che richiede di copiare lo stato del vecchio oggetto nel nuovo e di controllare che tutti i riferimenti al "vecchio" oggetto puntino ora al "nuovo" oggetto.

Un'implementazione dello schema di stato è migliore: a Bird ha un'associazione con Education . Questa è una classe astratta. L'istanza attuale a Bird ha una relazione con un'istanza di una sottoclasse concreta. L'istanza Bird rimane la stessa per tutta la sua durata.

Quando nasce Bird , il suo Education è in realtà un'istanza di una sottoclasse di Education chiamata, ad esempio, "Nessuno".

In realtà potresti delegare la responsabilità di "imparare" a quella classe, rendendo così Bird più semplice (tutto ciò che riguarda l'istruzione è delegato fuori dalla classe). In modo che Bird diventi presto "Novizio" e poi passa da "Grad" a "Master" e infine "Guru", quindi sarà in grado di pubblicare su Stack Exchange; -)

    
risposta data 16.05.2013 - 13:53
fonte

Leggi altre domande sui tag