@ La risposta di JorgWMittag fornisce un'ottima base teorica su un concetto teorico molto importante: un oggetto può essere simulato utilizzando una chiusura che seleziona il comportamento in base a un parametro. Sfortunatamente, questo approccio ha alcuni problemi:
- Devi scrivere un codice per interpretare i singoli messaggi ricevuti da un oggetto, che è qualcosa che di solito vorresti che facesse la tua lingua per te
- Nei linguaggi tipizzati staticamente avrai problemi con diversi metodi che gestiscono diversi tipi di argomenti.
Una possibile soluzione per i due problemi di cui sopra è quella di codificare il protocollo usando un tipo di dati algebrico. Ad esempio, in Haskell, il codice di Jorg potrebbe essere simile a questo:
data MyObjectProtocol = MyObjectProtocol_Add Int |
MyObjectProtocol_Sub Int |
MyObjectProtocol_ToString
type MyObject = MyObjectProtocol -> Either MyObject String
makeNewObject :: Int -> MyObject
makeNewObject state = handleRequests
where
handleRequests (MyObjectProtocol_Add amount) = Left $ makeNewObject $ state + amount
handleRequests (MyObjectProtocol_Sub amount) = Left $ makeNewObject $ state - amount
handleRequests (MyObjectProtocol_ToString) = Right $ "I am an object with value: " ++ show state
Questo lascia ancora un paio di problemi:
- I vari tipi di restituzione delle funzioni devono essere codificati in un altro tipo, al quale dovrai associare il pattern quando usi l'oggetto, che è molto lontano dal comodo.
- Il modo in cui l'ho fatto non può gestire un metodo che altera entrambi l'oggetto e restituisce un valore in un modo semplice. Probabilmente il modo migliore per risolverlo è trasformare l'oggetto in una monade, ma ciò rende l'implementazione piuttosto complicata.
Sarebbe meglio, quindi, utilizzare più funzioni per codificare i metodi dell'oggetto e implementare qualcosa di simile a un dispatch virtuale usato dalla maggior parte dei linguaggi OO oggi, ovvero memorizzando una tabella di puntatori del metodo virtuale (cioè funzioni ) nell'oggetto. È quindi possibile avere una serie di funzioni standard che recuperano il metodo virtuale dall'oggetto ed eseguirlo. Questo potrebbe apparire in questo modo:
data MyObject = MyObject (Int -> MyObject)
(Int -> MyObject)
(String)
myObject_add :: MyObject -> Int -> MyObject
myObject_add (MyObject f _ _) amount = f amount
myObject_sub :: MyObject -> Int -> MyObject
myObject_sub (MyObject _ f _) amount = f amount
myObject_toString :: MyObject -> String
myObject_toString (MyObject _ _ f) = f
makeNewObject :: Int -> MyObject
makeNewObject state = MyObject add sub toString where
add amount = makeNewObject $ state + amount
sub amount = makeNewObject $ state - amount
toString = "I am an object with value: " ++ show state
Il risultato è un po 'più piatto nella definizione del tipo di oggetto, ma lo rende molto più facile per usare l'oggetto. E se hai diverse implementazioni del tipo di oggetto, la quantità di boilerplate per ogni implementazione aggiuntiva è inferiore, quindi questo è sicuramente un modo migliore di lavorare per interfacce che potrebbero avere molte implementazioni.