对于几乎所有这些,有 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
类的伴随对象中的构造函数或方法并以这种方式创建事物。特别是在伴随对象的情况下,保持接口一致并不比保持工厂对象中的接口一致更难。因此,工厂对象的大部分动机都消失了。
类似地,抽象工厂方法的许多情况可以通过从适当的特征继承的伴随对象来替换。
原型
当然,被覆盖的方法等在 Scala 中占有一席之地。但是,在设计模式网站上用于原型模式的示例在 Scala(或 Java IMO)中是相当不可取的。但是,如果您希望超类根据其子类选择操作而不是让它们自己决定,那么您应该使用match
而不是笨拙的instanceof
测试。
辛格尔顿
Scala 使用object
. 他们是单身人士——使用和享受!
结构模式
适配器
Scalatrait
在这里提供了更多的功能——而不是创建一个实现接口的类,例如,您可以创建一个仅实现部分接口的 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 中同样有价值。
装饰器
装饰师很尴尬。如果继承不是您想要的,您通常不想在不同的类上使用相同的方法;你真正想要的是同一个类上的不同方法,它可以做你想做的事情,而不是默认的事情。“丰富我的图书馆”模式通常是更好的替代品。
正面
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 提供的解析器组合器比作为设计模式建议的简单解释器要强大得多。
迭代器
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
模板法
特征在这里提供了更多可能性,因此最好将它们视为另一种模式。您可以从抽象级别的尽可能多的信息中填写尽可能多的代码。我真的不想把它叫做同样的东西。
游客
在结构类型和隐式转换之间,Scala 比 Java 的典型访问者模式具有惊人的更多功能。使用原始模式没有意义;你只会从正确的方法上分心。许多示例实际上只是希望在被访问的事物上定义一个函数,Scala 可以为您做些微不足道的事情(即将任意方法转换为函数)。