项目github地址:bitcarmanlee easy-algorithm-interview-and-practice
欢迎大家star,留言,一起学习进步

Scala算是一门博采众家之长的语言,兼具OO与FP的特性,若使用恰当,可以更好地将OO与FP的各自优势发挥到极致;然而问题也随之而来,倘若过分地夸大OO特性,Scala就变成了一门精简版的Java,写出的是没有Scala Style的拙劣代码;倘若过分追求FP的不变性等特性,因为Scala在类型系统以及Monad实现的繁琐性,又可能导致代码变得复杂,不易阅读,反而得不偿失。

看来,赋予程序员选择的自由,有时候未必是好事!

在OO世界里,设计模式曾经风靡全世界,你不懂设计模式,都不好意思说自己是程序员。现在呢?说你懂设计模式,倒显得你逼格低了,心里鄙视:“这年头谁还用设计模式,早过时了!”程序员心中的鄙视链开始加成,直接失血二十格。

其实什么事情都得辩证来看!设计模式对OO设计的推进作用不容忽视,更不容轻视。我只是反对那种为了“模式”而“模式”的僵化思想,如果没有明白设计模式的本质思想,了解根本的设计原理,设计模式无非就是花拳绣腿罢了。当然,在FP世界里,设计模式开始变味开始走形,但诸多模式的本质,例如封装、抽象,仍然贯穿其中,不过是表达形式迥然而已罢了。

在混合了OO与FP的Scala语言中,我们来观察设计模式的实现,会非常有趣。Pavel Fatin有篇博客 Design Pattern in Scala将Java设计模式与Scala进行了对比,值得一读。我这里想借用他的案例,然后从另一个角度来俯瞰设计模式。

在Pavel Fatin比较的设计模式中,部分模式在Scala中不过是一种语法糖(Syntax Sugar),包括:

Factory Method
Lazy Initialization
Singleton
Adapter
Value Object

1.Factory Method

文中给出的Factory Method模式,准确地说其实是静态工厂模式,它并不在GOF 23种模式之列,但作为对复杂创建逻辑的一种封装,常常被开发人员使用。站在OCP(开放封闭原则)的角度讲,该模式对扩展不是开放的,但对于修改而言,却是封闭的。如果创建逻辑发生了变化,可以保证仅修改该静态工厂方法一处。同时,该模式还可以极大地简化对象创建的API。

在Scala中,通过引入伴生对象(Companion Object)来简化静态工厂方法,语法更加干净,体现了Scala精简的设计哲学。即使不是要使用静态工厂,我们也常常建议为Scala类定义伴生对象,尤其是在DSL上下文中,更是如此,因为这样可以减少new关键字对代码的干扰。

2.Lazy Initialization

lazy修饰符在Scala中有更深远的涵义,例如牵涉到所谓严格(Strictness)函数与非严格(Non-strictness)函数。在Scala中,若未明确声明,所有函数都是严格求值的,即函数会立即对它的参数进行求值。而如果对val变量添加lazy修饰符,则Scala会延迟对该变量求值,直到它第一次被引用时。如果要定义非严格函数,可以将函数设置为by name参数。

scala的lazy修饰符常常被用作定义一些消耗资源的变量。这些资源在初始化时并不需要,只有在调用某些方法时,才需要准备好这些资源。例如在Spark SQL的QeuryExecution类中,包括optimizedPlan、sparkPlan、executedPlan以及toRdd等,都被定义为lazy val:

class QueryExecution(val sparkSession: SparkSession, val logical: LogicalPlan) {lazy val analyzed: LogicalPlan = {SparkSession.setActiveSession(sparkSession)sparkSession.sessionState.analyzer.execute(logical)}lazy val withCachedData: LogicalPlan = {assertAnalyzed()assertSupported()sparkSession.sharedState.cacheManager.useCachedData(analyzed)}lazy val optimizedPlan: LogicalPlan = sparkSession.sessionState.optimizer.execute(withCachedData)lazy val sparkPlan: SparkPlan = {SparkSession.setActiveSession(sparkSession)planner.plan(ReturnAnswer(optimizedPlan)).next()}lazy val executedPlan: SparkPlan = prepareForExecution(sparkPlan)lazy val toRdd: RDD[InternalRow] = executedPlan.execute()
}

这样设计有一个好处是,当程序在执行到这些步骤时,并不会被马上执行,从而使得初始化QueryExecution变得更快。只有在需要时,这些变量对应的代码才会执行。这也是延迟加载的涵义。

3.Singleton Pattern

C#提供了静态类的概念,但Java没有,而Scala则通过引入Object弥补了Java的这一缺失,而且从语义上讲,似乎比静态类(Static Class)更容易让人理解。

Object可以派生自多个trait。例如派生自App trait,就可直接享有main函数的福利。

trait App extends DelayedInit {def main(args: Array[String]) = {this._args = argsfor (proc <- initCode) proc()if (util.Properties.propIsSet("scala.time")) {val total = currentTime - executionStartConsole.println("[total " + total + "ms]")}}
}object Main extends App

继承多个trait的好处是代码复用。我们可以将许多小粒度方法的实现定义在多个trait中。这些方法如果被类继承,则成为实例方法,如果被Object继承,则变成了线程安全的静态方法(因为继承trait的实现就是一个mixin)。多么奇妙!所以很多时候,我们会尽量保证Obejct的短小精悍,然后将许多逻辑放到trait中。当你看到如下代码时,其实不必惊讶:

object Main extends App with InitHookwith ShutdownHookwith ActorSystemProviderwith ScheduledTaskSupport

这种小粒度的trait既可以保证代码的复用,也有助于职责分离,还有利于测试。真是再好不过了!

4.Adapter Pattern

隐式转换当然可以用作Adapter。在Scala中,之所以可以更好地调用Java库,隐式转换功不可没。从语法上看,隐式转换比C#提供的扩展方法更强大,适用范围更广。

Pavel Fatin给出了日志转换的Adapter案例:

trait Log {def warning(message: String)def error(message: String)
}final class Logger {def log(level: Level, message: String) { /* ... */ }
}implicit class LoggerToLogAdapter(logger: Logger) extends Log {def warning(message: String) { logger.log(WARNING, message) }def error(message: String) { logger.log(ERROR, message) }
}val log: Log = new Logger()

这里的隐式类LoggerToLogAdapter可以将Logger适配为Log。与Java实现Adapter模式不同的是,我们不需要去创建LoggerToLogAdapter的实例。如上代码中,创建的是Logger实例。Logger自身与Log无关,但在创建该对象的上下文中,由于我们定义了隐式类,当Scala编译器遇到该隐式类时,就会为Logger添加通过隐式类定义的代码,包括隐式类中定义的对Log的继承,以及额外增加的warning与error方法。

在大多数场景,Adapter关注的是接口之间的适配。但是,当要适配的接口只有一个函数时,在支持高阶函数(甚至只要支持Lambda)的语言中,此时的Adapter模式就味如鸡肋了。假设Log与Logger接口只有一个log函数(不管它的函数名是什么),接收的参数为(Level, String),那么从抽象的角度来看,它们其实属于相同的一个抽象:

f: (Level, String) => Unit

任何一个符合该定义的函数,都是完全适配的,没有类型与函数名的约束。

如果再加上泛型,抽象会更加彻底。例如典型的Load Pattern实现:

def using[A](r : Resource)(f : Resource => A) : A =try {f(r)} finally {r.dispose()}

泛型A可以是任何类型,包括Unit类型。这里的f扩大了抽象范围,只要满足从Resource转换到A的语义,都可以传递给using函数。更而甚者可以完全抛开对Resource类型的依赖,只需要定义了close()方法,都可以作为参数传入:

def using[A <: def close():Unit, B][resource: A](f: A => B): B =try {f(resource)} finally {resource.close()}using(io.Source.fromFile("example.txt")) { source => {for (line <- source.getLines) {println(line)}}
}

因为FileResource定义了close()函数,所以可以作为参数传给using()函数。

5.Value Object

Value Object来自DDD中的概念,通常指的是没有唯一标识的不变对象。Java没有Value Object的语法,然而因其在多数业务领域中被频繁使用,Scala为其提供了快捷语法Case Class。在几乎所有的Scala项目中,都可以看到Case Class的身影。除了在业务中表现Value Object之外,还可以用于消息传递(例如AKKA在Actor之间传递的消息)、序列化等场景。此外,Case Class又可以很好地支持模式匹配,或者作为典型的代数数据类型(ADT)。例如Scala中的List,可以被定义为:

sealed trait List[+T]
case object Nil extends List[Nothing]
case class Cons[+T](h: T, t: List[T]) extends List[T]

这里,case object是一个单例的值对象。而Nil与Cons又都同时继承自一个sealed trait。在消息定义时,我们常常采用这样的ADT定义。例如List定义中,Nil与Cons就是List ADT的sum或者union,而Cons构造器则被称之为是参数h(代表List的head)与t(代表List的tail)的product。这也是ADT(algebraic data type)之所以得名。注意它与OO中的ADT(抽象数据类型)是风马牛不相及的两个概念。

原文链接:http://zhangyi.farbox.com/post/designthinking/design-patterns-with-scala-syntax-sugar

作为Scala语法糖的设计模式相关推荐

  1. scala java抽象理解_Scala学习笔记(五) 抽象类以及类中的一些语法糖

    1. 抽象类 Scala 的抽象类跟Java的一样,不能被实例化. 1.1抽象字段 抽象类中,变量不使用就无需初始化,可以等到子类继承时再进行初始化. scala> abstract class ...

  2. java for 迭代器_Java基础-迭代器Iterator与语法糖for-each

    迭代器Iterator与语法糖for-each 一.为什么需要迭代器 设计模式迭代器 迭代器作用于集合,是用来遍历集合元素的对象.迭代器不是Java独有的,大部分高级语言都提供了迭代器来遍历集合.实际 ...

  3. Android学习-Kotlin语言入门-变量、函数、语法糖、when、for-in、主构造函数、单例类、函数式API、集合遍历、隐式Intent、Activity生命周期、四种启动模式、标准函数

    探究java语言的运行机制 变量.函数.语法糖 when条件语句 for循环 主构造函数.次构造函数 数据类和单例类 集合的创建与遍历 集合的函数式API 创建菜单 隐式使用Intent Activi ...

  4. Kotlin 特性 语法糖 优势 扩展 高阶 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  5. Scala语法从入门到高级运用

    目录 1.scala语言特点 2.函数式编程 3.scala的安装 4.scala语言的简介 5.scala编程语言的规范 6.scala的类型体系(重点!) 7.变量的用法: 8.数值类型 9.类型 ...

  6. python语法糖是什么_Python语法糖Syntactic Sugar

    语法糖(Syntactic sugar): 计算机语言中特殊的某种语法 这种语法对语言的功能并没有影响 对于程序员有更好的易用性 能够增加程序的可读性 简而言之,语法糖就是程序语言中提供[奇技淫巧]的 ...

  7. javascript语法糖_语法糖和JavaScript糖尿病

    javascript语法糖 by Ryan Yurkanin 瑞安·尤卡宁(Ryan Yurkanin) 语法糖和JavaScript糖尿病 (Syntactic Sugar and JavaScri ...

  8. Java 中的语法糖,真甜。

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 我们在日常开发中经常会使用到诸如泛型.自动拆箱和装箱.内部 ...

  9. 装饰器的定义、语法糖用法及示例代码

    1. 装饰器的定义 就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数. 装饰器的功能特点: 不修改已有函数的源代码 不修改已有函数的调用方式 给已有函数增加额外的功能 2. 装饰器的示例代码 ...

  10. Jvm 系列(十一)Java 语法糖背后的真相

    语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语.指的是,在计算机语言中添加某种语法,这些语法糖虽然不会对语言 ...

最新文章

  1. 科普 | 12个关键词,告诉你到底什么是机器学习
  2. ado数据处理超时限制
  3. Matlab在坐标点上按顺序标序号
  4. LeetCode 1009. 十进制整数的反码(位运算)
  5. 用2468这四个数字c语言,C语言作业及参考答案.doc
  6. list 相加_Python 基础 list类、运算符
  7. hibernate框架搭建与使用
  8. Hibernate【2】——封装工具、HibernateUtil类以及DAO层的BaseDAO类
  9. 74. PHP 计数器
  10. 5个超实用抠图方法,哪个适用用哪个
  11. gradle入门教程
  12. c语言 ctype 下一字母,LC_CTYPE - [ C语言中文开发手册 ] - 在线原生手册 - php中文网...
  13. 百度竞价推广之关键词的选择策略
  14. 怎么屏蔽计算机集成声卡,win10系统主板集成声卡关闭的设置方案
  15. 数据驱动测试(DDT)入门
  16. 用Go构建Teamwork项目的9条教训
  17. Python数据处理035:结构化数据分析工具Pandas之Pandas概览
  18. linux命令详解--pmap
  19. 操作系统原理——内存的分段、分页和平坦模型:区别与发展
  20. 太原linux运维学校,山西太原linux运维培训班价格思诺培训价格表

热门文章

  1. mysql的随机查询
  2. Jmeter之简单控制器
  3. 第二章 算法 (大话数据结构)
  4. python 之selectors 实现文件上传下载
  5. Maven 用Eclipse创建web项目后报错的解决方式
  6. postman传各种类型的数组格式
  7. 解决Spark集群无法停止
  8. redis3.x集群搭建
  9. 2017 计蒜之道 初赛 第五场 A. UCloud 机房的网络搭建
  10. APT入门知识:抗击APT和针对性攻击