对于几乎所有这些,有 Scala 替代方案涵盖了这些模式的部分但不是全部用例。当然,所有这些都是 IMO,但是:
创作模式
建造者
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。)检查类型与 <:<、泛型类型参数和案例类的复制方法是一个非常强大的组合。
工厂方法(和抽象工厂方法)
工厂方法的主要用途是使您的类型保持直截了当;否则你也可以使用构造函数。使用 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) }
}
这使您可以跳过大部分桥接模式样板,同时实现高度的实现独立性,并且仍然让您的类遵守原始接口(因此您不必继续包装和解包它们)。
复合
复合模式使用案例类特别容易实现,尽管进行更新相当费力。它在 Scala 和 Java 中同样有价值。
装饰器
装饰师很尴尬。如果继承不是您想要的,您通常不想在不同的类上使用相同的方法;你真正想要的是同一个类上的不同方法,它可以做你想做的事情,而不是默认的事情。 enrich-my-library pattern 通常是更好的替代品。
立面
Facade 在 Scala 中比在 Java 中效果更好,因为您可以让特征携带部分实现,因此当您组合它们时,您不必所有自己完成工作。
享元
尽管享元的想法在 Scala 中与 Java 一样有效,但您可以使用更多工具来实现它:lazy val,除非实际需要,否则不会创建变量(然后被重用),并且by-name parameters,如果函数实际使用该值,您只需执行创建函数参数所需的工作。也就是说,在某些情况下,Java 模式保持不变。
代理
在 Scala 中的工作方式与 Java 相同。
行为模式
责任链
在您可以按顺序列出责任方的情况下,您可以
xs.find(_.handleMessage(m))
假设每个人都有一个handleMessage 方法,如果消息被处理,则返回true。如果您想在消息发生时对其进行变异,请改用折叠。
由于很容易将责任方放到某种Buffer 中,Java 解决方案中使用的复杂框架很少在 Scala 中占有一席之地。
命令
这种模式几乎完全被函数所取代。例如,而不是全部
public interface ChangeListener extends EventListener {
void stateChanged(ChangeEvent e)
}
...
void addChangeListener(ChangeListener listener) { ... }
你只是
def onChange(f: ChangeEvent => Unit)
口译员
Scala 提供的parser combinators 比作为设计模式建议的简单解释器要强大得多。
迭代器
Scala 在其标准库中内置了Iterator。让自己的类扩展Iterator 或Iterable 几乎是微不足道的;后者通常更好,因为它使重用变得微不足道。绝对是个好主意,但如此简单,我几乎不会称其为模式。
调解员
这在 Scala 中运行良好,但通常对可变数据有用,如果不小心使用,即使中介也可能与竞争条件发生冲突。相反,尽可能尝试将您的相关数据全部存储在一个不可变集合、案例类或其他任何东西中,并且在进行需要协调更改的更新时,同时更改所有内容。这不会帮助您与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 // Evaluated here
r.value // Same value returned again
您可能希望创建一个专门用于惰性求值的小类:
class Lazily[A](a: => A) {
lazy val value = a
}
val r = Lazily(scala.util.Random.nextInt)
// not actually called until/unless we ask for r.value
观察者
这充其量只是一个脆弱的模式。尽可能支持保持不可变状态(请参阅 Mediator),或者使用参与者,其中一个参与者向所有其他参与者发送有关状态更改的消息,但每个参与者都可以应对过时的情况。
状态
这在 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) // Change strategy
模板方法
特征在这里提供了更多可能性,因此最好将它们视为另一种模式。您可以从抽象级别的尽可能多的信息中填写尽可能多的代码。我真的不想把它叫做同样的东西。
访客
在structural typing 和implicit conversion 之间,Scala 的功能比Java 的典型访问者模式要多得多。使用原始模式没有意义;你只会从正确的方法上分心。许多示例实际上只是希望在被访问的事物上定义一个函数,Scala 可以为您做些微不足道的事情(即将任意方法转换为函数)。