Passando da uno sfondo procedurale a "FP in small, OO in the large", sono alle prese col seguente problema. Supponiamo che ci siano moduli, ciascuno contenente solo funzioni matematiche numeriche senza effetti collaterali. Alcune funzioni richiedono i risultati delle funzioni in un altro modulo. Ecco un esempio di giocattolo in pseudo-Scala:
//Maths Domain:
case class A(v: Decimal), B(..), ..., FF(..) //basic numeric outputs
case class EE(b: B, c: C, d: FF) //compound results
object Primary{
def alpha(v: A): B = ... // maths
def beta(v: A): C = ...
... }
object Secondary{
import Primary.{alpha, beta}
def one(a: A, d: DD): EE = EE(alpha(a), beta(a), two(d))
def two(d: DD): FF = ...
}
//Core Domain:
case class Foo(..)
case class Baz(foo: Foo, e: EE)
object Service {
import Secondary.one
def makeFoo(): Baz = {
val e = one(A(..), DD(..))
Baz(Foo(..), e) }
}
Le dipendenze hardcoded sembrano come una Big Ball of Mud in divenire. Quindi, come dovrebbero essere sistemate tali dipendenze in modo pulito? (Ci sono state domande pertinenti su SE, ad esempio, Esistono alternative all'iniezione delle dipendenze per le classi stateless? ; Iniezione delle dipendenze vs Metodi statici ; Functional Programming è una valida alternativa ai pattern di iniezione di dipendenza? ; Functional Programming è una valida alternativa ai pattern di iniezione di dipendenza? e È statico universalmente" malvagio "per i test unitari e se quindi perché consigliare di nuovo il programma? . Tuttavia, sembrano affrontare altri aspetti del problema, principalmente per le lingue diverse da Scala.)
In particolare, sono interessato
- Se il problema di dipendenza emerge nel modulo
Secondary
o, sePrimary
funziona sono indipendenti e non cambieranno, solo nelService
modulo - L'uso di
import
è un odore di design (e molto diverso, ad es. dall'importazione dijava.math
)?
Alcuni di questi potrebbero funzionare in applicazioni di grandi dimensioni o essere eccessivi?
- Crea
Service
chiama i metodi inPrimary
e fornisce i risultati aSecondary
. QuindiSecondary
è indipendente, maService
è esposto a dettagli di livello inferiore - Currying e fornitura di funzioni come argomenti. Ciò aumenta il numero di parametri del metodo e probabilmente espone i dettagli di implementazione
- Trasforma entrambi i moduli matematici in tratti e definisci
object MathService extends Primary with Secondary
. Questo viene iniettato inmakeFoo()
o inService
, che diventerebbe una classe. Tuttavia, questo potrebbe esporreService
a metodi non necessari, violando il "Principio di segregazione dell'interfaccia" - Composizione completa:
object Program extends MathService with Service
. Questo è in linea con l'approccio algebrico in "Modellazione di dominio funzionale e reattiva" di D. Ghosh. La mia prenotazione riguarda la coesione della composizione di moduli attraverso contesti limitati - Modelli di torta
- Reader Monad
- Contenitori DI standard, ma per moduli funzionali
Apprezzerei la guida su queste o su un'altra soluzione, sia generica che specifica per Scala.