几乎所有这些模式都有Scala替代方案,涵盖了一些但不是全部用例。当然,这是我个人的看法:
创建型模式
建造者模式
Scala可以通过泛型类型更优雅地实现此功能,而Java则无法做到,但总体思路相同。在Scala中,该模式最简单的实现方式如下:
trait Status
trait Done extends Status
trait Need extends Status
case class Built(a: Int, b: String) {}
class Builder[A <: Status, B <: Status] private () {
private var built = Built(0,"")
def setA(a0: Int) = { built = built.copy(a = a0); this.asInstanceOf[Builder[Done,B]] }
def setB(b0: String) = { built = built.copy(b = b0); this.asInstanceOf[Builder[A,Done]] }
def result(implicit ev: Builder[A,B] <:< Builder[Done,Done]) = built
}
object Builder {
def apply() = new Builder[Need, Need]
}
如果您在REPL中尝试此操作,请确保Builder类和对象在同一块中定义,即使用:paste
。通过使用<:<
检查类型、泛型类型参数和case类的复制方法,可以形成非常强大的组合。
工厂方法(和抽象工厂方法)
工厂方法的主要用途是使您的类型保持清晰;否则,您可能会使用构造函数。由于Scala强大的类型系统,您不需要帮助来保持类型清晰,因此您可以使用构造函数或类伴生对象中的apply
方法来创建事物。特别是在伴生对象的情况下,保持接口一致的难度与保持工厂对象中的接口一致的难度相同。因此,大多数工厂对象的动机已经消失。
同样,许多抽象工厂方法的情况可以通过使类伴生对象继承一个适当的trait来替换。
原型
当然,重写方法等在Scala中也有它们的用处。但是
Design Patterns网站上用于原型模式的示例在Scala(或Java IMO)中不太可取。但是,如果你希望超类根据其子类选择操作而不是让它们自己决定,那么你应该使用
match
而不是笨拙的
instanceof
测试。
单例
Scala使用
object
来实现单例——使用和享受吧!
结构型模式
适配器
Scala的
trait
在这里提供了更强大的功能——例如,你可以创建一个只实现接口的
部分的特质,留下其他部分由你来定义。例如,
java.awt.event.MouseMotionListener
要求你填写两个方法:
def mouseDragged(me: java.awt.event.MouseEvent)
def mouseMoved(me: java.awt.event.MouseEvent)
也许您想忽略拖动。那么您可以编写一个特质
trait
:
trait MouseMoveListener extends java.awt.event.MouseMotionListener {
def mouseDragged(me: java.awt.event.MouseEvent) {}
}
现在您可以从中继承并仅实现
mouseMoved
。因此:使用类似的模式,但使用Scala更加强大。
桥接器
您可以在Scala中编写桥接器。虽然不像Java那样糟糕,但这需要大量的样板文件。我不建议经常使用它作为抽象的一种方法;首先请仔细考虑您的接口。请记住,随着特质的增强功能,您通常可以在否则可能会编写桥接器的地方使用它们来简化更复杂的接口。
在某些情况下,您可能希望编写接口转换器而不是Java桥接器模式。例如,也许您想要使用相同的接口处理鼠标拖动和移动,只有一个布尔标志区分它们。然后您可以
trait MouseMotioner extends java.awt.event.MouseMotionListener {
def mouseMotion(me: java.awt.event.MouseEvent, drag: Boolean): Unit
def mouseMoved(me: java.awt.event.MouseEvent) { mouseMotion(me, false) }
def mouseDragged(me: java.awt.event.MouseEvent) { mouseMotion(me, true) }
}
这让您跳过大部分桥接模式样板代码,同时实现高度独立性,并仍然让您的类遵守原始接口(因此您不必保持包装和解包装)。
组合模式
使用 case 类可以轻松实现组合模式,但更新可能相当费力。在 Scala 和 Java 中同样有价值。
装饰器模式
装饰器模式很棘手。通常情况下,您不想在继承不是您想要的情况下在不同类上使用相同的方法;您真正想要的是在同一类上有一个不同的方法,该方法执行您想要的而不是默认的操作。
enrich-my-library pattern 往往是一个更好的替代品。
外观模式
外观模式在 Scala 中比 Java 更好,因为您可以使特质携带部分实现,因此在组合它们时不必全部自己完成。
享元模式
尽管在Scala中与Java一样,使用轻量级的概念,但是你可以使用更多工具来实现它:
lazy val
,只有在需要时才创建变量(并且此后重复使用),以及
by-name parameters
,仅在函数实际使用该值时才执行创建函数参数所需的工作。尽管如此,在某些情况下,Java模式保持不变。
代理模式
在Scala中与Java相同。
行为模式
责任链模式
在那些可以按顺序列出负责方的情况下,你可以
xs.find(_.handleMessage(m))
假设每个人都有一个
handleMessage
方法,如果消息已处理,则返回
true
。如果您想在消息传递过程中对其进行更改,请使用fold。
由于很容易将责任方放入某种缓冲区中,因此Java解决方案中使用的复杂框架在Scala中很少有用处。
命令
这种模式几乎完全被函数取代。例如,可以使用以下方式代替所有内容:
public interface ChangeListener extends EventListener {
void stateChanged(ChangeEvent e)
}
...
void addChangeListener(ChangeListener listener) { ... }
你只需要
def onChange(f: ChangeEvent => Unit)
解释器
Scala提供了解析器组合器,比简单的解释器设计模式要强大得多。
迭代器
Scala在其标准库中内置了Iterator
。几乎可以轻松地使自己的类扩展Iterator
或Iterable
;后者通常更好,因为它使重用变得简单。这绝对是一个好主意,但如此直截了当,我几乎不会称之为一种设计模式。
中介者
这在Scala中可以很好地工作,但通常适用于可变数据,即使中介者也可能因使用不当而遇到竞态条件等问题。相反,尽可能将所有相关数据存储在一个不可变集合、case class或其他类型中,并在需要协调更改的更新时同时更改所有内容。这样做不能帮助您与javax.swing
进行接口交互,但在其他情况下广泛适用:
case class Entry(s: String, d: Double, notes: Option[String]) {}
def parse(s0: String, old: Entry) = {
try { old.copy(s = s0, d = s0.toDouble) }
catch { case e: Exception => old }
}
当您需要处理多个不同关系(每个关系一个中介者)或具有可变数据时,请使用中介者模式。
备忘录
lazy val
几乎是备忘录模式的许多最简单应用的理想选择,例如:
class OneRandom {
lazy val value = scala.util.Random.nextInt
}
val r = new OneRandom
r.value
r.value
你可以创建一个专门用于惰性求值的小类:
class Lazily[A](a: => A) {
lazy val value = a
}
val r = Lazily(scala.util.Random.nextInt)
观察者模式
这是一个相当脆弱的模式。在可能的情况下,最好使用不可变状态(参见中介者模式),或者使用演员模式,其中一个演员向所有其他演员发送有关状态更改的消息,但每个演员都可以处理过时的状态。
状态模式
这在Scala中同样有用,并且实际上是创建没有方法的特征枚举的首选方式:
sealed trait DayOfWeek
final trait Sunday extends DayOfWeek
...
final trait Saturday extends DayOfWeek
通常你希望工作日做些事情来证明这些样板代码的数量是有必要的。
策略
这几乎完全被使用方法来接受实现策略的函数所取代,并提供可供选择的函数。
def printElapsedTime(t: Long, rounding: Double => Long = math.round) {
println(rounding(t*0.001))
}
printElapsedTime(1700, math.floor)
模板方法
特质在这里提供了更多的可能性,最好将它们视为另一种模式。您可以根据您的抽象级别从尽可能多的信息中填充代码。我不想真的称它为同样的东西。
访问者
在 结构类型 和 隐式转换 之间,Scala 拥有比Java典型的访问者模式更多的能力。使用原始模式没有意义;你只会分心于正确的方法。许多例子实际上只是希望在被访问的对象上定义一个函数,而Scala可以轻松地为您完成这个操作(即将任意方法转换为函数)。