i metodi disponibili cambiano con lo stato dell'oggetto

2

Sto programmando un oggetto grafico che può essere in uno stato mutabile o immutabile. In entrambi gli stati, ha un metodo get , ma solo nello stato mutabile è consentito il metodo set . La transizione avviene quando si richiama un metodo compress che ottimizza la memorizzazione del grafico.

Come faccio a considerare il fatto che alcuni metodi sono legali in alcuni stati ma non in altri?

Posso pensare a due approcci. Il modo schietto è usare il modello di stato. Se invochi set quando il grafico è mutabile, viene eseguito normalmente; ma, se invochi set quando il grafico è immutabile, viene generato un errore. Mi sembra brutto perché un oggetto immutabile ha ancora un metodo set .

In alternativa, posso creare un tipo graph con un sottotipo mutable_graph ; solo il secondo ha un metodo set . Il cambiamento di stato viene effettuato cambiando il tipo del grafico. Tuttavia, anche questo mi sembra brutto; per ogni implementazione di graph (di cui ce ne sono diversi), ora devo fare due tipi invece di uno solo.

EDIT: sto usando lo schema di stato e lanciando un'eccezione; se nessuna delle due soluzioni è particolarmente preferibile, questa implica la scrittura del codice meno significativo. Grazie per il consiglio!

    
posta korrok 28.01.2014 - 00:14
fonte

4 risposte

2

Assunzione del linguaggio compilato dalla famiglia Java / C # / C ++.

Sai se il grafico è mutabile o meno al momento della compilazione? Se sì, introdurre un sottotipo (anche il C ++ ha "const" per gli oggetti). Altrimenti, genera un'eccezione e il modello di stato.

    
risposta data 28.01.2014 - 01:09
fonte
3

Se davvero non vuoi scoprire dettagli sulle operazioni "privilegiate", la risposta più pulita nei linguaggi OO popolari come Java e .NET è l'ereditarietà dell'interfaccia.

Uno che ho usato semi-frequentemente fa qualcosa del tipo:

  • IFooRepository - contiene metodi GetFoo , DeleteFoo , SaveFoo
  • IReadOnlyFooRepository - contiene solo il metodo GetFoo

Nell'esempio sopra, IFooRepository potrebbe sottoclasse IReadOnlyFooRepository . Inoltre, se esiste un modo ben definito di transizione tra lo stato di sola lettura e quello scrivibile, è possibile inserire un metodo Edit in IReadOnlyFooRepository che restituisce un'istanza di IFooRepository e / o un AsReadOnly metodo su IFooRepository che restituisce un'istanza di IReadOnlyFooRepository .

Questo non è senza precedenti, almeno in .NET. Una delle interfacce più comuni, IList<T> , ha un metodo AsReadOnly che restituisce un ReadOnlyCollection<T> , che come puoi immaginare, non espone alcun tipo di operazioni di scrittura.

Non è difficile da implementare - in genere hai bisogno di una sola classe per implementare entrambe le interfacce, e i metodi Edit / AsReadOnly possono solo restituire this (o self o qualunque cosa sia nella lingua scelta) . Naturalmente, se lo fai, non pensare a te stesso che è una forma di sicurezza , dato che un chiamante potrebbe potenzialmente lanciare solo tra i tipi - ma assumendo che tu voglia solo proteggere da errori stupidi piuttosto e non l'abuso intenzionale, è abbastanza buono.

Puoi ottenere una maggiore quantità di dettagli se vuoi, ad esempio, non hai necessariamente bisogno di un'interfaccia per derivare dall'altra, potresti avere IFooReader e IFooWriter senza metodi in comune. Oppure potresti definire un IFooComponent (o qualcosa del genere) nella radice se vuoi condividere solo alcuni metodi, ma la maggior parte di essi è univoca. Sky è il limite, ma l'idea di base sta in piedi: usa le interfacce per ritagliare specifici blocchi di funzionalità specifici per uno stato particolare.

Questo richiede molto più lavoro di progettazione e aggiunge molti altri tipi al codice se i tuoi stati e le transizioni sono complessi, quindi assicurati che ne valga davvero la pena. È perfettamente funzionante e in effetti comune in molte architetture (come la maggior parte dei framework MVVM) per generare un'eccezione solo se un'operazione non è valida per lo stato corrente e fornisce anche metodi di "guardia" convenienti come CanWrite , per evitare spargendo cerchi try-catch dappertutto. Va bene se hai 2 o 3 stati discreti; se hai 10 o 20, potresti pensare a uno schema di stato oa un progetto come quello sopra che rende le transizioni più esplicite.

    
risposta data 28.01.2014 - 04:06
fonte
1

Hai considerato un modello di Builder? L'utente inizia con un FooGraphBuilder, con getter e setter. Chiamare compress() restituisce un FooGraph immutabile, rendendo la transizione abbastanza chiara.

    
risposta data 28.01.2014 - 07:06
fonte
1

Preferirei generare un'eccezione , perché è:

  • facile da capire
  • facile da implementare (e leggere)
  • facile da testare
  • Forse non sembra perfetto, ma il miglior design è ancora "Il più semplice possibile, complesso quanto necessario" ! E uno degli anti-design più implementati è "sovradimensionato e troppo complicato senza un valore aziendale" !

es. in java l'interfaccia List descrive più metodi che potrebbero generare eccezioni (perché non devono essere implementate un'implementazione) - ma in caso contrario (con interfacce multiple per ogni caso) le interfacce di List sarebbero sovraelaborate e sarebbero sicuramente molto complicate ..

    
risposta data 30.01.2014 - 11:34
fonte

Leggi altre domande sui tag