2015-02-14 5 views
6

Ho sviluppato molto codice in Java e mi sono occupato di Groovy e Haskell che ora mi ha portato a Scala.Codice collaudabile di Scala con ereditarietà e mixin

Mi sento relativamente a mio agio con il lato funzionale di Scala, ma mi trovo un po 'traballante sulla progettazione orientata agli oggetti in Scala, perché si sente un po' diverso da Java, in particolare a causa di tratti/mix-in.

mi propongo di scrivere il codice che è come verificabile possibile, che nel mio sviluppo Java è sempre tradotto in focus sul

  • Immutabilità ove possibile
  • Preferisco iniezione di stato dai costruttori
  • sempre andare per la composizione, invece di eredità (fortemente influenzato da, e probabilmente una reazione eccessiva a this post on SO)

Ora sto cercando di atterrare sulla I miei piedi in questo nuovo territorio di Scala, e sto facendo fatica a capire quale approccio dovrei andare qui, in particolare se dovrei iniziare ad usare l'ereditarietà per alcuni scopi.

programmazione Scala (Wampler e Payne, O'Reilly, 2nd Edition) ha una sezione con considerazioni ("Good Object-Oriented Design: una digressione"), e ho letto una serie di messaggi su SO, ma non ho visto menzioni esplicite della considerazione progettuale della testabilità. Il libro offre questo consiglio sull'utilizzo di eredità:

  1. una classe base astratta o un tratto è una sottoclasse di un livello da classi concrete, tra cui classi case.
  2. classi di cemento non sono mai sottoclassi, ad eccezione di due casi:
    • Le classi che miscela in altri comportamenti definiti tratti (...)
    • Test-solo le versioni per promuovere unità automatizzata Teting.
  3. Quando la sottoclasse sembra l'approccio giusto, considera i comportamenti di partizionamento in tratti e mescola invece questi tratti.
  4. Non suddividere mai lo stato logico tra i limiti del tipo padre-figlio.

Alcuni scavo su SO inoltre suggerisce che sometimes mix-ins are preferable to composition.

Quindi, in sostanza ho due domande:

  1. sono lì casi comuni in cui sarebbe meglio, anche considerando testabilità, utilizzare l'ereditarietà?

  2. I mix-in offrono buoni modi per migliorare la testabilità del mio codice?

risposta

8

L'utilizzo del tratto nella Q/A a cui si fa riferimento si è effettivamente occupato della flessibilità fornita dal mixaggio dei tratti.

Ad esempio, quando si estrae una caratteristica in modo esplicito, il compilatore blocca i tipi della classe e della super-classe in fase di compilazione. In questo esempio è un MyService LockingFlavorA

trait Locking { // ... } 

class LockingFlavorA extends Locking { //... } 

class MyService extends LockingFlavorA { 

} 

Quando si è utilizzato un riferimento sé digitato (come mostrato in Q/A si indicò):

class MyService { 
    this: Locking => 
} 

.. il Locking può fare riferimento alla stessa Locking o qualsiasi sottoclasse valida di Locking. L'autore mescola poi nella realizzazione di blocco presso il sito di chiamata, senza creare esplicitamente una nuova classe a tal fine:

val myService: MyService = new MyService with JDK15Locking 

Credo che quando dicono che si può facilitare il test, sono davvero dicendo di usare questa funzionalità per emulare ciò che noi sviluppatori Java faremmo normalmente con la composizione e gli oggetti mock. È sufficiente creare un'implementazione fittizia Locking e mescolare quella in corso durante il test e realizzare un'implementazione reale per il runtime.

Alla tua domanda: è meglio o peggio che usare una libreria di derisione e un'iniezione di dipendenza? Sarebbe difficile da dire, ma penso che alla fine molto si ridurrà a quanto bene una tecnica o l'altra giochi con il resto del codice base.

Se stai già utilizzando la composizione e l'iniezione di dipendenza con buoni risultati, direi che continuare con quel modello potrebbe essere una buona idea.

Se sei appena agli inizi e non hai ancora veramente bisogno di tutta quell'artiglieria, o non hai filosoficamente deciso che l'iniezione di dipendenza è giusta per te, puoi ottenere un sacco di chilometraggio dai mixaggi per un costo molto piccolo in termini di complessità di runtime.

Penso che la vera risposta si dimostrerà altamente situazionale.

TL; DR sotto

Domanda 1) Penso che sia una valida alternativa alla situazione alla composizione/dep-inj, ma io non credo che fornisce ogni grande guadagno tranne forse la semplicità.

Domanda 2) Sì, può migliorare la testabilità, in gran parte emulando oggetti simulati tramite implementazioni di tratto.

+0

Grazie. Apprezzo molto questa risposta in quanto fornisce un buon commento sui miei riferimenti e risposte dirette alle domande. –

+1

Nessun problema, Scala è un linguaggio interessante ed è bello capire come alcune di queste caratteristiche linguistiche più esoteriche si adattino a ciò che le persone hanno già fatto là fuori nel mondo. E 'stata una buona domanda. –

+0

hmm ... a giudicare dal suo grosso punteggio zero, suppongo che avrebbe potuto essere migliore. Ma ho avuto la mia risposta, questa è la parte importante. –

3

Ho potuto sperimentare utilizzando una combinazione di mix-in e composizione.

quindi, ad esempio, utilizzare il componente per miscelare il comportamento in un tratto specifico. L'esempio seguente mostra una struttura che utilizza più tratti del layer dao in una classe.

trait ServiceXXX { 
    def findAllByXXX(): Future[SomeClass] 
} 

trait ServiceYYY { 
    def findAllByYYY(): Future[AnotherClass] 
} 

trait SomeTraitsComponent { 
    val serviceXXX: ServiceXXX 
    val serviceYYY: ServiceYYY 
} 

trait SomeTraitsUsingMixing { 
    self: SomeTraitsComponent => 

    def getXXX() = Action.async { 
    serviceXXX.findAllByXXX() map { results => 
     Ok(Json.toJson(results)) 
    } 
    } 

    def getYYY() = Actiona.async { 
    serviceYYY.findAllByYYY() map {results => 
     Ok(Json.toJson(results)) 
    } 
    } 
} 

Dopo di che è possibile dichiarare un manufatto in calcestruzzo e associarlo con l'esempio all'oggetto compagna:

trait ConreteTraitsComponent extends SomeTraitsComponent { 
    val serviceXXX = new ConcreteServiceXXX 
    val serviceYYY = new ConcreteServiceYYY 
} 

object SomeTraitsUsingMixing extends ConreteTraitsComponent 

Usando questo modello yo potrebbe facilmente creare un componente di prova e l'utilizzo di finto per testare il comportamento concreto del vostro Tait/classe:

trait SomeTraitsComponentMock { 
    val serviceXXX = mock[ServiceXXX] 
    val serviceYYY = mock[ServiceYYY] 
} 

object SomeTraitsUsingMixingMock extends SomeTraitsComponentMock 

E in voi spec si potrebbe dichiarare controllare i risultati dei servizi utilizzando ScalaMock http://scalamock.org/

+0

Grazie per la risposta. Puoi approfondire un po 'il motivo per cui hai deciso di rendere 'ConcreteTraitsComponent' un tratto piuttosto che una classe? –

+0

Preferisco dichiarare un'unità di funzioni come tratto il più a lungo possibile perché in scala è possibile solo [estendere da una classe] (http://stackoverflow.com/questions/2005681/difference-between-abstract-class- and-trait) ma da più tratti. – toggm