基于Scala Trait的设计模式

开发 开发工具
之前,我重点介绍了那些已经融入Scala语法的设计模式。今天要介绍的两个设计模式,则主要与Scala的trait有关。

[[184365]]

《作为Scala语法糖的设计模式》中,我重点介绍了那些已经融入Scala语法的设计模式。今天要介绍的两个设计模式,则主要与Scala的trait有关。

Decorator Pattern

在GoF 23种设计模式中,Decorator Pattern算是一个比较特殊的模式。它充分利用了继承和组合(或者委派)各自的优势,将它们混合起来,不仅让优势扩大,还让各自的缺点得到了抵消。Decorator模式的核心思想其实是“职责分离”,即将要装饰的职责与装饰的职责分离,从而使得它们可以在各自的继承体系下独立演化,然后通过传递对象(组合)的形式完成对被装饰职责的重用。

从某种角度来讲,装饰职责与被装饰职责之间的分离与各自抽象,不妨可以看做是Bridge模式的变种。但不同之处在于Decorator模式又额外地引入了继承,但不是为了重用,而是为了多态,使得装饰者因为继承自被装饰者,从而拥有了被装饰的能力。所以说,继承的引入真真算得上是点睛之笔了。

理解Decorator模式,一定要理解继承与组合各自扮演的角色。简而言之,就是:

  • 继承:装饰者的多态
  • 组合:被装饰者的重用

正因为此,在Java代码中实现Decorator模式,要注意装饰器类在重写被装饰器的业务行为时,一定要通过传入的对象来调用被装饰者的行为。假设传入的被装饰者对象为decoratee,则调用时就一定是decoratee,而不是super(由于继承的关系,装饰类是可以访问super的)。

例如BufferedOutputStream类作为装饰类,要装饰OutputStream的write行为,就必须这样实现:

  1. public interface OutputStream { 
  2.     void write(byte b); 
  3.     void write(byte[] b); 
  4. public class FileOutputStream implements OutputStream { /* ... */ } 
  5. public class BufferedOutputStream extends OutputStream { 
  6.     //这里是组合的被装饰者     
  7.     protected final OutputStream decoratee; 
  8.     public BufferedOutputStream(OutputStream decoratee) { 
  9.         this.decoratee = decoratee; 
  10.     } 
  11.  
  12.     public void write(byte b) { 
  13.         //这里应该是调用decoratee, 而非super,虽然你可以访问super     
  14.         decoratee.write(buffer) 
  15.     } 

然而,在Scala中实现Decorator模式,情况却有些不同了。Scala的trait既体现了Java Interface的语义,却又可以提供实现逻辑(相当于Java 8的default interface),并在编译时采用mixin方式完成代码的重用。换言之,trait已经***地融合了继承与组合的各自优势。因此,在Scala中若要实现Decorator模式,只需要定义trait去实现装饰者的功能即可:

  1. trait OutputStream { 
  2.   def write(b: Byte) 
  3.   def write(b: Array[Byte]) 
  4. class FileOutputStream(path: String) extends OutputStream { /* ... */ } 
  5. trait Buffering extends OutputStream { 
  6.   abstract override def write(b: Byte) { 
  7.     // ... 
  8.     super.write(buffer) 
  9.   } 

在Buffering的定义中,根本看不到组合的影子,且在对write方法进行重写时,调用的是super,这与我前面讲到的内容背道而驰啊!

区别在于组合(delegation)的时机。在Java(原谅我,因为使用Scala的缘故,我对Java 8的default interface没有研究,不知道是否与scala的trait完全相同)语言中,组合是通过传递对象方式完成的职责委派与重用,也就是说,组合是在运行时发生的。Scala的实现则不然,在trait中利用abstract override关键字来完成一种stackable modifications,这种方式被称之为Stackable Trait Pattern。这种语法仅能用于trait,它表示trait会将某个具体类针对该方法提供的实现混入(mixin)到trait中。装饰的客户端代码如下:

  1. new FileOutputStream("foo.txt") with Buffering 

FileOutputStream的write方法实现在编译时就被混入到Buffering中。所以可以称这种组合为静态组合。

Dependency Injection

Dependency Injection(依赖注入或者称为IoC,即控制反转)其实应该与依赖倒置原则结合起来理解,首先应该保证不依赖于实现细节,而是依赖于抽象(接口),然后,再考虑将具体依赖从类的内部转移到外面,并在运行时将依赖注入到类的内部。这也是Dependency Injection的得名由来。

在Java世界,多数情况下我们会引入框架如Spring、Guice来完成依赖注入(这并不是说依赖注入一定需要框架,严格意义上,只要将依赖转移到外面,然后通过set或者构造器注入依赖,都可以认为是实现了依赖注入),无论是基于xml配置,还是annotation,或者Groovy,核心思想都是将对象之间的依赖设置(装配)转交给框架来完成。Scala也有类似的IoC框架。但是,多数情况下,Scala程序员会充分利用trait与self type来实现所谓的依赖注入。这种设计模式在Scala中常常被昵称为Cake Pattern。

一个典型的案例就是将一个Repository的实现注入到Service中。在Scala中,就应该将Repository的抽象定义为trait,然后在具体的Service实现中,通过Self Type引入Repository:

  1. trait Repository { 
  2.   def save(user: User) 
  3. trait DatabaseRepository extends Repository { /* ... */ } 
  4. trait UserService {  
  5.   self: Repository =>  
  6.   def create(user: User) { 
  7.     //这里调用的是Repository的save方法 
  8.     //调用Self Type的方法就像调用自己的方法一般 
  9.     save(user) 
  10.   } 
  11.  
  12. //这里的with完成了对DatabaseRepository依赖的注入 
  13. new UserService with DatabaseRepository 

Cake Pattern遵循了Dependency Inject的要求,只是它没有像Spring或者Guice那样彻底将注入依赖的职责转移给外部框架,而是将注入的权利交到了调用者手里。这样会导致调用端代码并没有完全与具体依赖解耦,但在大多数情况下,这种轻量级的依赖注入方式,反而更讨人喜欢。

在Scala开发中,我们常常会使用Cake Pattern。在我的一篇文章《一次设计演进之旅》中,就引入了Cake Pattern来完成将ReportMetadata依赖的注入。

【本文为51CTO专栏作者“张逸”原创稿件,转载请联系原作者】

戳这里,看该作者更多好文

责任编辑:赵宁宁 来源: 51CTO专栏
相关推荐

2009-09-09 14:09:35

Scala Trait

2015-09-28 13:48:55

云计算设计模式负载均衡

2009-09-09 11:37:08

Scala的模式匹配

2009-07-08 16:42:57

Scala语言设计

2010-08-02 16:19:00

ibmdw面向对象

2013-12-04 14:19:40

JavaScript代码重用

2021-04-19 21:25:48

设计模式到元

2021-02-01 10:01:58

设计模式 Java单例模式

2009-07-08 12:43:59

Scala ServlScala语言

2012-08-30 09:07:33

设计模式

2023-11-02 21:11:11

JavaScript设计模式

2010-09-14 15:34:41

Scala

2012-07-10 01:59:12

设计模式

2020-10-31 17:33:18

Scala语言函数

2021-12-29 10:38:35

运维框架KubeNest

2011-12-15 01:12:59

ibmdw

2012-10-29 11:16:13

2022-01-19 08:21:12

设计装饰器模式

2009-07-09 17:30:59

Singleton模式C++ SingletJava Single

2023-05-26 08:41:23

模式Go设计模式
点赞
收藏

51CTO技术栈公众号