基于Scala Trait的设计模式

简介: 基于Scala Trait的设计模式

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


Decorator Pattern


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

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

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

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

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

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

public interface OutputStream {
    void write(byte b);
    void write(byte[] b);
}public class FileOutputStream implements OutputStream { /* ... */ }public class BufferedOutputStream extends OutputStream {
    //这里是组合的被装饰者    
    protected final OutputStream decoratee;
    public BufferedOutputStream(OutputStream decoratee) {
        this.decoratee = decoratee;
    }
    public void write(byte b) {
        //这里应该是调用decoratee, 而非super,虽然你可以访问super    
        decoratee.write(buffer)
    }
}

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

trait OutputStream {
  def write(b: Byte)
  def write(b: Array[Byte])
}class FileOutputStream(path: String) extends OutputStream { /* ... */ }trait Buffering extends OutputStream {
  abstract override def write(b: Byte) {
    // ...
    super.write(buffer)
  }
}

在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中。装饰的客户端代码如下:

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:

trait Repository {
  def save(user: User)
}trait DatabaseRepository extends Repository { /* ... */ }trait UserService {
  self: Repository =>
  def create(user: User) {
    //这里调用的是Repository的save方法
    //调用Self Type的方法就像调用自己的方法一般
    save(user)
  }
}
//这里的with完成了对DatabaseRepository依赖的注入new UserService with DatabaseRepository

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

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


相关文章
|
20天前
|
Java Scala
Scala Trait(特征)
Scala Trait(特征)
26 2
|
7月前
|
分布式计算 Java Scala
Scala:面向对象、Object、抽象类、内部类、特质Trait(二)
Scala:面向对象、Object、抽象类、内部类、特质Trait(二)
98 0
|
大数据 Scala 容器
【建议收藏】|3分钟让你学会Scala Trait 使用
Scala 是一种强大的静态类型编程语言,其中的 Trait 是一种重要的特性。Trait 可以被看作是一种包含方法和字段定义的模板,可以被其他类或 Trait 继承或混入。在本文中,我们将介绍 Scala Trait 的边界(Boundary)的概念,并展示如何使用它来限制 Trait 的使用范围。
262 11
|
Java Scala
scala面向对象编程之trait特质
特质就像是java的implement,是scala中代码复用的基础单元,它可以将方法和字段定义封装起来,然后添加到类中与类继承不一样的是,类继承要求每个类都只能继承一个超类,而一个类可以添加任意数量的特质。特质的定义和抽象类的定义很像,但它是使用trait关键字
122 0
scala面向对象编程之trait特质
|
大数据 编译器 Scala
大数据开发基础的编程语言的Scala的Trait
Scala是一种支持面向对象编程和函数式编程的编程语言,它提供了强大的Trait功能。本文将介绍Scala中Trait的概念和用法,帮助开发者更好地理解和应用这门语言。
99 0
|
Scala
Scala入门到精通——第十一节 Trait进阶
本节主要内容 trait构造顺序 trait与类的比较 提前定义与懒加载 trait扩展类 self type 1 trait构造顺序 在前一讲当中我们提到,对于不存在具体实现及字段的trait,它最终生成的字节码文件反编译后是等同于java中的接口,而对于存在具体实现及字段的trait,其字节码文件反编译后得到的java中的抽象类,它有着scala语言自己的实现方式
3555 0
|
Scala 网络架构 存储
|
Scala 索引 开发工具
|
Java Scala
scala 学习笔记(05) OOP(中)灵活的trait
trait -- 不仅仅只是接口! 接上回继续,scala是一个非常有想法的语言,从接口的设计上就可以发现它的与众不同。scala中与java的接口最接近的概念是trait,见下面的代码: package yjmyzz object App { def main(args...
949 0