Opt*_*ght 31 design-patterns scala
最近我读了以下SO问题:
在Scala中使用访问者模式是否有任何用例?每次我在Java中使用访问者模式时,我是否应该在Scala中使用模式匹配?
带标题问题的链接: Scala中的访客模式.接受的答案始于
是的,您应该从模式匹配开始,而不是访问者模式.请参阅 http://www.artima.com/scalazine/articles/pattern_matching.html
我的问题(受上面提到的问题的启发)是哪个GOF设计模式在Scala中有完全不同的实现?如果我在Scala中编程,我应该在哪里注意并且不要遵循设计模式(Gang of Four)的基于Java的编程模型?
创作模式
结构模式
行为模式
Rex*_*err 44
对于几乎所有这些,Scala备选方案涵盖了这些模式的一些但不是全部用例.当然,所有这些都是IMO,但是:
与Java相比,Scala可以使用泛型类型更优雅地完成此任务,但总体思路是相同的.在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]
}
Run Code Online (Sandbox Code Playgroud)
(如果在REPL中尝试这个,请确保类和对象Builder在同一个块中定义,即使用:paste.)检查类型与<:<泛型类型参数和case类的复制方法的组合使得功能非常强大组合.
工厂方法的主要用途是保持你的类型笔直; 否则你也可以使用构造函数.使用Scala强大的类型系统,你不需要帮助保持你的类型,所以你也可以使用apply同伴对象中的构造函数或方法来创建类,并以这种方式创建.特别是在伴随对象的情况下,保持该接口的一致性并不比保持工厂对象中的接口一致.因此,工厂对象的大部分动机都消失了.
类似地,许多抽象工厂方法的情况可以通过让伴随对象从适当的特征继承来代替.
当然,被覆盖的方法等在Scala中占有一席之地.但是,在Scala(或Java IMO)中,Design Patterns网站上用于Prototype模式的示例是不可取的.但是,如果您希望有一个超类选择基于其子类的操作而不是让他们自己决定,那么您应该使用match而不是使用笨重的instanceof测试.
Scala拥抱这些object.他们是单身人士 - 使用和享受!
Scala trait在这里提供了更多的功能 - 而不是创建一个实现接口的类,例如,你可以创建一个只实现部分接口的特性,剩下的就是你定义的.例如, java.awt.event.MouseMotionListener要求您填写两种方法:
def mouseDragged(me: java.awt.event.MouseEvent)
def mouseMoved(me: java.awt.event.MouseEvent)
Run Code Online (Sandbox Code Playgroud)
也许你想忽略拖动.然后你写一个trait:
trait MouseMoveListener extends java.awt.event.MouseMotionListener {
def mouseDragged(me: java.awt.event.MouseEvent) {}
}
Run Code Online (Sandbox Code Playgroud)
现在,只有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) }
}
Run Code Online (Sandbox Code Playgroud)
这使您可以跳过大部分桥接模式样板,同时实现高度的实现独立性,并且仍然让您的类遵循原始接口(因此您不必保持包装和展开它们).
虽然更新是相当艰巨的,但使用案例类特别容易实现复合模式.它在Scala和Java中同样有价值.
装饰者很尴尬.在继承不是您想要的情况下,您通常不希望在不同的类上使用相同的方法; 你真正想要的是在同一个类上使用不同的方法,而不是默认的东西.该充实,我的图书馆模式往往是一个卓越的替代品.
Facade在Scala中比在Java中更好用,因为你可以让traits进行部分实现,这样你就不需要在组合它们时自己完成所有工作.
虽然flyweight的想法在Scala中和Java一样有效,但是你可以使用更多的工具来实现它:lazy val除非实际需要变量(之后重新使用),否则不会创建变量,并且by-name parameters只有如果函数实际使用该值,则需要创建函数参数.也就是说,在某些情况下,Java模式保持不变.
在Scala中以与Java相同的方式工作.
在那些您可以按顺序列出责任方的情况下,您可以
xs.find(_.handleMessage(m))
Run Code Online (Sandbox Code Playgroud)
假设每个人都有一个handleMessage方法,true如果处理了消息则返回.如果要在消息发生时改变消息,请改用折叠.
由于很容易将责任方放入Buffer某种类型,因此Java解决方案中使用的复杂框架很少在Scala中占有一席之地.
这种模式几乎完全被功能所取代.例如,而不是全部
public interface ChangeListener extends EventListener {
void stateChanged(ChangeEvent e)
}
...
void addChangeListener(ChangeListener listener) { ... }
Run Code Online (Sandbox Code Playgroud)
你干脆
def onChange(f: ChangeEvent => Unit)
Run Code Online (Sandbox Code Playgroud)
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 }
}
Run Code Online (Sandbox Code Playgroud)
保存介体模式,以便在需要处理多个不同的关系(每个关系一个中介)时,或者当您有可变数据时.
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
Run Code Online (Sandbox Code Playgroud)
您可能希望专门为懒惰评估创建一个小类:
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
Run Code Online (Sandbox Code Playgroud)
这充其量只是一种脆弱的模式.尽可能地支持保持不可变状态(参见Mediator),或者使用actor,其中一个actor向所有其他人发送关于状态变化的消息,但是每个actor都可以应对过时.
这在Scala中同样有用,实际上是应用于无方法特征时创建枚举的首选方法:
sealed trait DayOfWeek
final trait Sunday extends DayOfWeek
...
final trait Saturday extends DayOfWeek
Run Code Online (Sandbox Code Playgroud)
(通常你会希望工作日做一些事情来证明这一数量的样板).
这几乎完全取代了让方法采用实现策略的功能,并提供可供选择的功能.
def printElapsedTime(t: Long, rounding: Double => Long = math.round) {
println(rounding(t*0.001))
}
printElapsedTime(1700, math.floor) // Change strategy
Run Code Online (Sandbox Code Playgroud)
特质在这里提供了更多的可能性,最好只考虑它们的另一种模式.您可以从您在抽象级别获得的尽可能多的信息中填写尽可能多的代码.我真的不想把它称为同一件事.
在结构类型和隐式转换之间,Scala具有比Java典型访问者模式更强大的功能.使用原始模式没有意义; 你会因为正确的方式而分心.许多示例实际上只是希望在被访问的东西上定义一个函数,Scala可以为您轻松地做(即将任意方法转换为函数).
Dan*_*ral 12
好的,让我们简要介绍一下这些模式.我纯粹从功能编程的角度来看待所有这些模式,并且从OO的角度来看,Scala可以改进很多东西.Rex Kerr的答案为我自己的答案提供了一个有趣的反对意见(我只是在写完自己的答案后才读到他的答案).
考虑到这一点,我想说研究持久性数据结构(功能纯数据结构)和monad非常重要.如果你想深入,我认为范畴论基础是重要的 - 范畴理论可以正式描述所有程序结构,包括命令性结构.
构造函数只不过是一个函数.例如,类型T的无参数构造函数只不过是一个函数() => T.实际上,Scala的函数语法糖在案例类中占据优势:
case class T(x: Int)
Run Code Online (Sandbox Code Playgroud)
这相当于:
class T(val x: Int) { /* bunch of methods */ }
object T {
def apply(x: Int) = new T(x)
/* other stuff */
}
Run Code Online (Sandbox Code Playgroud)
这样你就可以T用T(n)而不是实例化new T(n).你甚至可以这样写:
object T extends Int => T {
def apply(x: Int) = new T(x)
/* other stuff */
}
Run Code Online (Sandbox Code Playgroud)
在T没有更改任何代码的情况下,它变成了正式的函数.
在考虑创作模式时,这是要记住的重点.那么让我们来看看它们:
这个不太可能改变太多.类可以被认为是一组密切相关的函数,因此一组紧密相关的函数可以通过类轻松实现,这就是这种模式对构造函数的作用.
构建器模式可以由curried函数或部分函数应用程序替换.
def makeCar: Size => Engine => Luxuries => Car = ???
def makeLargeCars = makeCar(Size.Large) _
def makeCar: (Size, Engine, Luxuries) => Car = ???
def makeLargeCars = makeCar(Size.Large, _: Engine, _: Luxuries)
Run Code Online (Sandbox Code Playgroud)
如果丢弃子类,则变得过时.
不会改变 - 实际上,这是在功能数据结构中创建数据的常用方法.请参阅case类copy方法,或返回集合的集合上的所有非可变方法.
当您的数据不可变时,单例并不是特别有用,但Scala object实现此模式是一种安全的方式.
这主要与数据结构有关,函数式编程的重点是数据结构通常是不可变的.除了尝试翻译这些模式之外,您最好还是查看持久性数据结构,monad和相关概念.
并不是说这里的某些模式不相关.我只是说,作为一般规则,你应该研究上面的内容,而不是试图将结构模式转换为功能等价物.
这种模式与类(名义输入)有关,所以只要你有这种模式,它就很重要,而当你不这样做时则无关紧要.
与OO架构有关,所以与上面相同.
很多镜头和拉链.
装饰器只是功能组合.如果你正在装修整个班级,那可能不适用.但是,如果您将功能作为函数提供,那么在保持其类型的同时编写函数就是装饰器.
与Bridge相同的评论.
如果您将构造函数视为函数,请将flyweight视为函数memoization.此外,Flyweight与持久性数据结构的构建方式有着内在的联系,并且从不变性中获益很多.
与适配器相同的评论.
这到处都是.其中一些是完全没用的,而另一些则在功能设置中一如既往地相关.
像装饰师一样,这是功能组合.
这是一个功能.如果您的数据是不可变的,则无需撤消部分.否则,只需保留一对功能及其反向.另见镜片.
这是一个单子.
只需将函数传递给集合即可使其过时.这就是Traversable用呢foreach,其实.另外,请参阅Iteratee.
仍然相关.
对不可变对象无用.此外,其重点是保持封装,这不是FP的主要问题.
请注意,此模式不是序列化,这仍然是相关的.
相关,但请参阅功能反应式编程.
这是一个单子.
策略是一种功能.
这是OO设计模式,因此它与OO设计相关.
访客只是一种接收功能的方法.事实上,这就是它Traversable的foreach作用.
在Scala中,它也可以用提取器替换.