Wie elegant Umsetzung des pipeline-Muster mit Scala
Ich bin auf der Suche nach Aufbau einer pipeline-Muster mit Scala. Ich Wünsche, nachdem ich das schreiben der pipeline-Objekte, Sie könnten miteinander verbunden werden, wie dies:
Pipeline1 :: Pipeline2 :: Pipeline3 ...
Habe ich experimentierte mit ein paar Ideen so weit. Einige arbeiten und einige nicht. Aber keiner von Ihnen scheint, um vollständig loszuwerden boilerplate-code. Im folgenden ist der nächste, den ich bekommen habe.
Definieren Sie zunächst die Rohrleitung und die Source-abstrakte Klasse:
//I is the input type and O is the output type of the pipeline
abstract class Pipeline[I, +O](p: Pipeline[_, _ <: I]) {
val source = p
val name: String
def produce(): O
def stats():String
}
abstract class Source[+T] extends Pipeline[AnyRef, T](null)
Weiter, ich habe zwei pipelines und versuchen, Sie zu verknüpfen
//this creates a random integer
class RandomInteger extends Source[Int] {
override val name = "randInt"
def produce() = {
scala.Math.round(scala.Math.random.asInstanceOf[Float] * 10)
}
def stats()="this pipeline is stateless"
}
//multiply it by ten
class TimesTen(p: Pipeline[_, Int]) extends Pipeline[Int, Int](p) {
private var count = 0 //this is a simple state of the pipeline
override val name = "Times"
def produce = {
val i = source.produce()
count += 1 //updating the state
i * 10
}
def stats() = "this pipeline has been called for " + count + " times"
}
object TimesTen {
//this code achieves the desired connection using ::
//but this has to be repeated in each pipeline subclass.
//how to remove or abstract away this boilerplate code?
def ::(that: Pipeline[_, Int]) = new TimesTen(that)
}
Dies ist die main-Klasse, wo die beiden Leitungen miteinander verbunden sind.
object Pipeline {
def main(args: Array[String]) {
val p = new RandomInteger() :: TimesTen
println(p.source)
for (i <- 0 to 10)
println(p.produce())
println(p.stats())
}
}
Damit dieser code funktioniert. Aber ich würde wiederholen Sie den code in die TimesTen companion Objekts in jeder pipeline-Klasse, die ich Schreibe. Dies ist sicherlich nicht wünschenswert. Gibt es eine bessere Möglichkeit, dies zu tun? Reflexion funktionieren könnte, aber ich habe gehört, schlechte Dinge über Sie, wie alles was mit Reflexion ist schlechtes design. Ich bin auch unsicher, Scala support für die Reflexion.
Vielen Dank für Ihre Zeit.
Update: ich habe dieses Spielzeug-problem, um es einfach zu verstehen. Als Allgemeine Lösung, da meine Anwendung benötigt jede pipeline-Objekt, hat einen Zustand, der idealerweise gekapselt innerhalb des Objekts selbst eher ausgesetzt als jede andere pipeline. Ich veränderte den code oben, um diese zu reflektieren. Ich wünschte, es könnte sein, ein Objekt-basierte Lösung. Ich bin noch am Experimentieren und werde Sie wissen lassen, wenn ich eins finden.
Update 2: Nach einigen Gedanken, die ich denke, die Idee der pipeline ist wirklich nur eine verallgemeinerte Funktion, die enthält einige interne Zustände sowie die Fähigkeit zum verfassen einer Function0
Funktion mit einem Function1
Funktion. In Scala, die Function0
Klasse nicht die compose()
oder andThen()
Methode.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Es sei denn, ich bin fehlt etwas, Ihre pipeline-Objekte sind nur Funktionen, und Ihr :: - operator ist nur "Komponieren",
Ihre "erzeugen ()" - Methode ist einfach nur "apply()", aber es ist üblich, es ist eine Abkürzung von "()". Eine kleine Menge der Bibliothek Zuhälterei würde ermöglichen es Ihnen, verwenden Sie einen operator für die Komposition. Dies ist einer jener Fälle, in denen Objekt-orientierte boilerplate wirklich auf dem Weg des einfachen, funktionalen Konzepte. Zum Glück, Scala können Sie vermeiden, die Textvorschlag für eine Menge von Anwendungsfällen, wie diese.
randomInteger compose timesTen
ist der falsche Weg um. Sie können nicht nehmen SieUnit
, multiplizieren Sie mit zehn, und dann füttern, dassrandomInteger
. Stattdessen wollen SierandomInteger andThen timesTen
odertimesTen compose randomInteger
. Die Nutzung vonFunction1
ist definitiv der bequemste Weg, dies zu tun machen Sie diese "pipeline" - Idee arbeiten.(func1::func2:func3::func4::Nil).reduce{_ andThen _}
reduce
die standard-Bibliothek hatFunction.chain[A](s: Seq[(A) => A]): (A) => A
error: type mismatch; found : () => Int required: ? => Int
Dies ist, weilcompose
dauertFunction1
argument.randomInteger
ist der TypFunction0
.Function.chain
auch nicht funktionieren, da die Funktionen verkettet haben die gleichen input-und output-Typ.Hier ist die Lösung mit Objekten mit
andThen
. Der Grundgedanke ist der, die Schaffung vonFunction1
Objekte mithilfe der input -Unit
. Die Verbindung von zwei Rohrleitungen erstellt eine neue Pipeline mit den zwei Funktionen zusammen. Diese Lösung ermöglicht es, die Rohrleitungen zu innerer Zustände.Eine weitere Vereinfachung wäre die Verwendung
apply()
stattproduce()
. Dies bleibt als übung für den Leser.alle credits für StephaneLD "|> - operator, wie in F#"
http://www.scala-lang.org/node/8747
value |> is not a member of List[Int]
was vermisse ich hier?Du meinst, wie ein Datenfluss oder Funktionale Reaktive Programmierung? Versuchen diese Frage. Die reaktive Bibliothek wird aktiv weiterentwickelt-ich weiß nicht über den rest.