【问题标题】:which GOF Design pattern(s) has entirely different implementation (java vs Scala)哪个 GOF 设计模式具有完全不同的实现(java vs Scala)
【发布时间】:2012-06-20 16:28:35
【问题描述】:

最近我读到以下 SO 问题:

在 Scala 中是否有使用访问者模式的用例? 我应该每次使用 Scala 中的模式匹配 Java 中的访问者模式?

带有标题的问题的链接: Visitor Pattern in Scala。接受的答案以

开头

是的,您可能应该从模式匹配开始,而不是 访客模式。看到这个 http://www.artima.com/scalazine/articles/pattern_matching.html

我的问题(受上述问题的启发)是哪种 GOF 设计模式在 Scala 中有完全不同的实现?如果我在 Scala 中编程,我应该注意哪些方面而不遵循基于 Java 的设计模式(四人组)编程模型?

创作模式

  • 抽象工厂
  • 建设者
  • 工厂方法
  • 原型
  • 单例:直接创建一个对象(scala)

结构模式

  • 适配器
  • 复合
  • 装饰器
  • 立面
  • 蝇量级
  • 代理

行为模式

  • 责任链
  • 命令
  • 口译员
  • 迭代器
  • 调解员
  • 纪念品
  • 观察者
  • 状态
  • 战略
  • 模板方法
  • 访问者:模式匹配(scala)

【问题讨论】:

标签: scala design-patterns


【解决方案1】:

对于几乎所有这些,有 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。)检查类型与 &lt;:&lt;、泛型类型参数和案例类的复制方法是一个非常强大的组合。

工厂方法(和抽象工厂方法)

工厂方法的主要用途是使您的类型保持直截了当;否则你也可以使用构造函数。使用 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。让自己的类扩展IteratorIterable 几乎是微不足道的;后者通常更好,因为它使重用变得微不足道。绝对是个好主意,但如此简单,我几乎不会称其为模式。

调解员

这在 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 typingimplicit conversion 之间,Scala 的功能比Java 的典型访问者模式要多得多。使用原始模式没有意义;你只会从正确的方法上分心。许多示例实际上只是希望在被访问的事物上定义一个函数,Scala 可以为您做些微不足道的事情(即将任意方法转换为函数)。

【讨论】:

  • 比较你的答案很有趣,更专注于 Scala,而我的,更专注于函数式编程。我什至没有提到特质,因为那确实是一个面向对象的概念。
  • 命令模式与监听器和事件无关。这些将是观察者模式的一部分。
【解决方案2】:

好的,让我们来看看这些模式。我纯粹从函数式编程的角度来看待所有这些模式,而忽略了许多 Scala 从 OO 角度可以改进的东西。 Rex Kerr 的答案为我自己的答案提供了一个有趣的反驳点(我只是在写完自己的答案后才阅读他的答案)。

考虑到这一点,我想说研究持久性数据结构(功能纯数据结构)和 monad 很重要。如果你想深入,我认为范畴论基础很重要——范畴论可以正式描述所有程序结构,包括命令式结构。

创作模式

构造函数只不过是一个函数。例如,类型 T 的无参数构造函数只不过是一个函数() =&gt; T。事实上,Scala 的函数语法糖在案例类中得到了利用:

case class T(x: Int)

相当于:

class T(val x: Int) { /* bunch of methods */ }
object T {
  def apply(x: Int) = new T(x)
  /* other stuff */
}

这样您就可以使用T(n) 而不是new T(n) 来实例化T。你甚至可以这样写:

object T extends Int => T {
  def apply(x: Int) = new T(x)
  /* other stuff */
}

这会将T 转换为正式函数,而无需更改任何代码。

这是在考虑创建模式时要牢记的重要一点。那么让我们来看看它们:

抽象工厂

这个不太可能发生太大变化。一个类可以被认为是一组密切相关的函数,因此一组密切相关的函数很容易通过一个类来实现,这就是这种模式对构造函数的作用。

建造者

构建器模式可以被柯里化函数或部分函数应用程序替换。

def makeCar: Size => Engine => Luxuries => Car = ???
def makeLargeCars = makeCar(Size.Large) _

def makeCar: (Size, Engine, Luxuries) => Car = ???
def makeLargeCars = makeCar(Size.Large, _: Engine, _: Luxuries)

工厂方法

如果你放弃子类化,就会过时。

原型

不会改变——事实上,这是在函数式数据结构中创建数据的常用方法。请参阅案例类copy 方法,或返回集合的集合上的所有非可变方法。

单例

当您的数据不可变时,单例并不是特别有用,但 Scala object 实现这种模式是一种安全的方式。

结构模式

这主要与数据结构有关,而函数式编程的重点是数据结构通常是不可变的。您最好查看持久数据结构、monad 和相关概念,而不是尝试翻译这些模式。

并不是说这里的某些模式不相关。我只是说,作为一般规则,您应该研究上述内容,而不是试图将结构模式转换为功能等价物。

适配器

这种模式与类(名义类型)有关,所以只要你有它,它仍然很重要,如果你没有,它就无关紧要了。

与OO架构有关,同上。

复合

很多镜头和拉链。

装饰器

装饰器只是功能组合。如果你正在装饰整个班级,那可能不适用。但是,如果您将功能作为函数提供,那么在保持其类型的同时组合函数就是装饰器。

立面

与 Bridge 的评论相同。

享元

如果您将构造函数视为函数,请将享元视为函数记忆。此外,享元与持久数据结构的构建方式内在相关,并且从不变性中受益匪浅。

代理

与适配器的注释相同。

行为模式

到处都是。其中一些是完全无用的,而另一些则与功能设置一样相关。

责任链

和装饰器一样,这是函数组合。

命令

这是一个函数。如果您的数据是不可变的,则不需要撤消部分。否则,只需保留一对函数及其相反。另请参阅镜头。

口译员

这是一个单子。

迭代器

只需将函数传递给集合即可使其过时。实际上,这就是Traversableforeach 所做的事情。另请参阅 Iteratee。

调解员

仍然相关。

纪念品

对不可变对象无用。此外,它的重点是保持封装,这不是 FP 中的主要问题。

请注意,这种模式不是序列化,它仍然是相关的。

观察者

相关,但请参阅函数响应式编程。

状态

这是一个单子。

策略

策略是一种功能。

模板方法

这是一种 OO 设计模式,因此与 OO 设计相关。

访客

访问者只是一个接收函数的方法。事实上,Traversableforeach 就是这样做的。

在 Scala 中,它也可以替换为提取器。

【讨论】:

  • 不错!我希望通过链接或其他指针对其进行扩展。这个答案只对那些了解“Scala 中的 FP”一书并且对上述每种模式有实践经验的人有帮助。仍然很棒,因为给出了正确的指针。
【解决方案3】:

我想,Command 模式在函数式语言中根本不需要。不用将命令函数封装在对象内部,然后选择合适的对象,只需使用合适的函数本身即可。

Flyweight 只是缓存,在大多数函数式语言中都有默认实现(memoize in clojure)

即使Template methodStrategyState 也可以通过在方法中传递适当的函数来实现。

所以,我建议你在尝试函数式风格时不要深入设计模式,而是阅读一些关于函数式概念(高阶函数、惰性、柯里化等)的书籍

【讨论】:

  • 你能推荐一些我可以从中学习纯函数概念的流行书名吗?
  • 我不知道 Scala 特定的书籍,但 clojure 特定的是 Joy of ClojureClojure in Action,您可以尝试 Lisp 和普通书籍:On LispLet Over LambdaSICP
  • @Optimight 这个问题与stackoverflow.com/questions/9342930/… 相关,并查看joshua suereth's bookrunar's 以了解fp 的scala 方面
  • @om-nom-nom 是的,先生。 SO 纪律在这里暗示了什么?我应该关闭我的问题并从这里指向您建议的问题的链接吗?或者我应该怎么做?
  • 声称不需要函数式语言中的模式似乎很时髦,但大多数时候这似乎没有抓住重点。您如何通过“仅使用适当的功能”来实现撤消(命令模式的一部分)?并且“在方法中传递适当的函数”对我来说是使用策略模式(例如)只是使用不同的实现(参见en.wikipedia.org/wiki/Strategy_pattern,它指出这可以用一流的函数实现)
猜你喜欢
  • 1970-01-01
  • 2020-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-18
  • 2012-01-15
  • 1970-01-01
  • 2013-06-01
相关资源
最近更新 更多