Scala設(shè)計(jì)模式

本文展示了一些經(jīng)典的軟件設(shè)計(jì)模式在Scala中的實(shí)現(xiàn)。

所謂設(shè)計(jì)模式强霎,就是針對(duì)在軟件設(shè)計(jì)過程中出現(xiàn)的一些共性問題夏跷,從而產(chǎn)生的一種可重用的解決方案。設(shè)計(jì)模式不是已完成的代碼窃这,而更像是一個(gè)可以在不同場(chǎng)景下解決問題的通用模板瞳别。

模式是由一些設(shè)計(jì)的最佳實(shí)踐組成的,可以幫助我們避免一些問題杭攻,并且能增加代碼的可讀性祟敛,及加快開發(fā)進(jìn)度。

經(jīng)典的設(shè)計(jì)模式(一般指GoF)都是基于面向?qū)ο蟮恼捉狻K麄冋故玖祟惻c對(duì)象間的關(guān)系和行為馆铁。這些模式并不能很好的應(yīng)用到純函數(shù)式編程語言上,但是既然Scala是一種結(jié)合了面向?qū)ο缶幊毯秃瘮?shù)式編程的語言锅睛,那Scala還是能夠采用這些模式的埠巨,甚至是在函數(shù)式風(fēng)格的Scala代碼中。

很多時(shí)候設(shè)計(jì)模式被認(rèn)為是某種語言缺乏一些特性的信號(hào)衣撬。在此種情況下乖订,當(dāng)一種語言提供了相關(guān)特性以后,這些模式可以被簡(jiǎn)化或者索性消除具练。得益于Scala富有表現(xiàn)力的語法乍构,很多經(jīng)典設(shè)計(jì)模式都可以直接實(shí)現(xiàn)。

盡管Scala還有一些基于語言特性的設(shè)計(jì)模式扛点,單本文還是著重于介紹大家所周知的經(jīng)典設(shè)計(jì)模式哥遮,因?yàn)檫@些設(shè)計(jì)模式被認(rèn)為是開發(fā)者之間交流的工具。

創(chuàng)建型設(shè)計(jì)模式
        1陵究、工廠方法模式
        2眠饮、延遲加載模式
        3、單例模式

結(jié)構(gòu)型模式
        1铜邮、適配器模式
        2仪召、裝飾模式

行為型
        1寨蹋、值對(duì)象模式
        2、空值模式
        3扔茅、策略模式
        4已旧、命令模式
        5、責(zé)任鏈模
        6召娜、依賴注入模式

一运褪、工廠方法模式
工廠方法模式將對(duì)實(shí)際類的初始化封裝在一個(gè)方法中,讓子類來決定初始化哪個(gè)類玖瘸。
工廠方法允許:
1秸讹、組合復(fù)雜的對(duì)象創(chuàng)建代碼
2、選擇需要初始化的類
3雅倒、緩存對(duì)象
4璃诀、協(xié)調(diào)對(duì)共享資源的訪問
我們考慮靜態(tài)工廠模式,這和經(jīng)典的工廠模式略有不同屯断,靜態(tài)工廠方法避免了子類來覆蓋此方法文虏。

在Java中,我們使用new關(guān)鍵字殖演,通過調(diào)用類的構(gòu)造器來初始化對(duì)象氧秘。為了實(shí)現(xiàn)這個(gè)模式,我們需要依靠普通方法趴久,此外我們無法在接口中定義靜態(tài)方法丸相,所以我們只能使用一個(gè)額外的工廠類。

public interface Animal {}  
  
private class Dog implements Animal {}  
  
private class Cat implements Animal {}  
  
public class AnimalFactory {  
    public static Animal createAnimal(String kind) {  
        if ("cat".equals(kind)) return new Cat();  
        if ("dog".equals(kind)) return new Dog();  
        throw new IllegalArgumentException();  
    }  
}  
  
AnimalFactory.createAnimal("dog"); 

除了構(gòu)造器之外彼棍,Scala提供了一種類似于構(gòu)造器調(diào)用的特殊的語法灭忠,其實(shí)這就是一種簡(jiǎn)便的工廠模式。

trait Animal  
private class Dog extends Animal  
private class Cat extends Animal  
  
object Animal {  
  def apply(kind: String) = kind match {  
    case "dog" => new Dog()  
    case "cat" => new Cat()  
  }  
}  
  
Animal("dog") 

以上代碼中座硕,工廠方法被定義為伴生對(duì)象弛作,它是一種特殊的單例對(duì)象,和之前定義的類或特質(zhì)具有相同的名字华匾,并且需要定義在同一個(gè)原文件中映琳。這種語法僅限于工廠模式中的靜態(tài)工廠模式,因?yàn)槲覀儾荒軐?chuàng)建對(duì)象的動(dòng)作代理給子類來完成蜘拉。

優(yōu)勢(shì):

  • 重用基類名字
  • 標(biāo)準(zhǔn)并且簡(jiǎn)潔
  • 類似于構(gòu)造器調(diào)用

劣勢(shì):

  • 僅限于靜態(tài)工廠方法

二 萨西、延遲初始化模式
延遲初始化是延遲加載的一個(gè)特例。它指僅當(dāng)?shù)谝淮卧L問一個(gè)值或者對(duì)象的時(shí)候旭旭,才去初始化他們谎脯。
延遲初始化可以延遲或者避免一些比較復(fù)雜的運(yùn)算。
在Java中持寄,一般用null來代表未初始化狀態(tài)源梭,但假如null是一個(gè)合法的final值的時(shí)候娱俺,我們就需要一個(gè)獨(dú)立的標(biāo)記來指示初始化過程已經(jīng)進(jìn)行。

在多線程環(huán)境下废麻,對(duì)以上提到的標(biāo)記的訪問必須要進(jìn)行同步矢否,并且會(huì)采用雙重檢測(cè)技術(shù)(double-check)來保證正確性,當(dāng)然這也進(jìn)一步增加了代碼的復(fù)雜性脑溢。

private volatile Component component;  
  
public Component getComponent() {  
    Component result = component;  
    if (result == null) {  
        synchronized(this) {  
            result = component;  
            if (result == null) {  
                component = result = new Component();  
            }  
        }  
    }  
    return result;  
}  

Scala提供了一個(gè)內(nèi)置的語法來定義延遲變量.

lazy val x = {  
  print("(computing x) ")  
  42  
}  
  
print("x = ")   
println(x)   
  
// x = (computing x) 42 

在Scala中,延遲變量能夠持有null值赖欣,并且是線程安全的屑彻。

優(yōu)勢(shì)

  • 語法簡(jiǎn)潔
  • 延遲變量能夠持有null值
  • 延遲變量的訪問是線程安全的

劣勢(shì)

  • 對(duì)初始化行為缺乏控制

三、單例模式
單例模式限制了一個(gè)類只能初始化一個(gè)對(duì)象顶吮,并且會(huì)提供一個(gè)全局引用指向它社牲。
在Java中,單例模式或許是最為被人熟知的一個(gè)模式了悴了。這是java缺少某種語言特性的明顯信號(hào)搏恤。

在java中有static關(guān)鍵字,靜態(tài)方法不能被任何對(duì)象訪問湃交,并且靜態(tài)成員類不能實(shí)現(xiàn)任何接口熟空。所以靜態(tài)方法和Java提出的一切皆對(duì)象背離了。靜態(tài)成員也只是個(gè)花哨的名字搞莺,本質(zhì)上只不過是傳統(tǒng)意義上的子程序息罗。

public class Cat implements Runnable {  
    private static final Cat instance = new Cat();  
   
    private Cat() {}  
   
    public void run() {  
        // do nothing  
    }  
  
    public static Cat getInstance() {  
        return instance;  
    }  
}  
  
Cat.getInstance().run() 

在Scala中完成單例簡(jiǎn)直巨簡(jiǎn)單無比

object Cat extends Runnable {  
  def run() {  
    // do nothing  
  }  
}  
  
Cat.run() 

優(yōu)勢(shì):

  • 含義明確
  • 語法簡(jiǎn)潔
  • 按需初始化
  • 線程安全

劣勢(shì):

  • 對(duì)初始化行為缺乏控制

四、適配器模式
適配器模式能將不兼容的接口放在一起協(xié)同工作才沧,適配器對(duì)集成已經(jīng)存在的各個(gè)組件很有用迈喉。
在Java實(shí)現(xiàn)中,需要?jiǎng)?chuàng)建一個(gè)封裝類温圆,如下所示:

public interface Log {  
    void warning(String message);  
    void error(String message);  
}  
  
public final class Logger {  
    void log(Level level, String message) { /* ... */ }  
}  
  
public class LoggerToLogAdapter implements Log {  
    private final Logger logger;  
  
    public LoggerToLogAdapter(Logger logger) { this.logger = logger; }  
  
    public void warning(String message) {  
        logger.log(WARNING, message);  
    }  
      
    public void error(String message) {  
        logger.log(ERROR, message);  
    }  
}  
  
Log log = new LoggerToLogAdapter(new Logger()); 

在Scala中挨摸,我們可以用隱式類輕松搞定。(注意:2.10后加的特性)

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() 

最后的表達(dá)式期望的得到一個(gè)Log實(shí)例岁歉,而卻使用了Logger得运,這個(gè)時(shí)候Scala編譯器會(huì)自動(dòng)把log實(shí)例封裝到適配器類中。

優(yōu)勢(shì):

  • 含義清晰
  • 語法簡(jiǎn)潔

劣勢(shì):

  • 在沒有IDE的支持下會(huì)顯得晦澀

五刨裆、裝飾模式
裝飾模式被用來在不影響一個(gè)類其它實(shí)例的基礎(chǔ)上擴(kuò)展一些對(duì)象的功能澈圈。裝飾者是對(duì)繼承的一個(gè)靈活替代。
當(dāng)需要有很多獨(dú)立的方式來擴(kuò)展功能時(shí)帆啃,裝飾者模式是很有用的瞬女,這些擴(kuò)展可以隨意組合。
在Java中努潘,需要新建一個(gè)裝飾類诽偷,實(shí)現(xiàn)原來的接口坤学,封裝原來實(shí)現(xiàn)接口的類,不同的裝飾者可以組合起來使用报慕。一個(gè)處于中間層的裝飾者一般會(huì)用來代理原接口中很多的方法深浮。

public interface OutputStream {  
    void write(byte b);  
    void write(byte[] b);  
}  
  
public class FileOutputStream implements OutputStream { /* ... */ }  
  
public abstract class OutputStreamDecorator implements OutputStream {  
    protected final OutputStream delegate;  
  
    protected OutputStreamDecorator(OutputStream delegate) {  
        this.delegate = delegate;  
    }  
  
    public void write(byte b) { delegate.write(b); }  
    public void write(byte[] b) { delegate.write(b); }  
}  
  
public class BufferedOutputStream extends OutputStreamDecorator {  
    public BufferedOutputStream(OutputStream delegate) {  
        super(delegate);  
    }  
  
    public void write(byte b) {  
        // ...  
        delegate.write(buffer)  
    }  
}  
  
new BufferedOutputStream(new FileOutputStream("foo.txt")) 

Scala提供了一種更直接的方式來重寫接口中的方法,并且不用綁定到具體實(shí)現(xiàn)眠冈。下面看下如何來使用abstract override標(biāo)識(shí)符飞苇。

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)  
  }  
}  
  
new FileOutputStream("foo.txt") with Buffering // with Filtering, ...  

這種代理是在編譯時(shí)期靜態(tài)建立的,不過通常來說只要我們能在創(chuàng)建對(duì)象時(shí)任何組合裝飾器蜗顽,就已經(jīng)夠用了布卡。

與基于組合(指需要特定的裝飾類來把原類封裝進(jìn)去)的實(shí)現(xiàn)方式不一樣,Scala保持了對(duì)象的一致性雇盖,所以可以在裝飾對(duì)象上放心使用equals忿等。

優(yōu)勢(shì):

  • 含義清晰
  • 語法簡(jiǎn)潔
  • 保持了對(duì)象一致性
  • 無需顯式的代理
  • 無需中間層的裝飾類

劣勢(shì):

  • 靜態(tài)綁定
  • 沒有構(gòu)造器參數(shù)

六、值對(duì)象模式
值對(duì)象是一個(gè)很小的不可變對(duì)象崔挖,他們的相等性不基于identity贸街,而是基于不同對(duì)象包含的字段是否相等。
值對(duì)象被廣泛應(yīng)用于表示數(shù)字狸相、時(shí)間薛匪、顏色等等。在企業(yè)級(jí)應(yīng)用中脓鹃,它們經(jīng)常被用作DTO(可以用來做進(jìn)程間通信)蛋辈,由于不變性,值對(duì)象在多線程環(huán)境下使用起來非常方便将谊。

在Java中冷溶,并沒有特殊語法來支持值對(duì)象。所以我們必須顯式定義一個(gè)構(gòu)造器尊浓,getter方法及相關(guān)輔助方法逞频。

public class Point {  
    private final int x, y;  
  
    public Point(int x, int y) { this.x = x; this.y = y; }  
  
    public int getX() { return x; }  
  
    public int getY() { return y; }  
  
    public boolean equals(Object o) {  
        // ...  
        return x == that.x && y == that.y;  
    }  
  
    public int hashCode() {  
        return 31 * x + y;  
    }  
  
    public String toString() {  
        return String.format("Point(%d, %d)", x, y);  
    }  
}  
  
Point point = new Point(1, 2) 

在Scala中,我們使用元組或者樣例類來申明值對(duì)象栋齿。當(dāng)不需要使用特定的類的時(shí)候苗胀,元組就足夠了.

val point = (1, 2) // new Tuple2(1, 2) 

元組是一個(gè)預(yù)先定義好的不變集合,它能夠持有若干個(gè)不同類型的元素瓦堵。元組提供構(gòu)造器基协,getter方法以及所有輔助方法。
我們也可以為Point類定義一個(gè)類型別名

type Point = (Int, Int) // Tuple2[Int, Int]  
  
val point: Point = (1, 2)  

當(dāng)需要一個(gè)特定的類或者需要對(duì)數(shù)據(jù)元素名稱有更明確的描述的時(shí)候菇用,可以使用樣例類;

case class Point(x: Int, y: Int)  
  
val point = Point(1, 2)  

樣例類將構(gòu)造器參數(shù)默認(rèn)為屬性澜驮。樣例類是不可變的,與元組一樣惋鸥,它提供了所有所需的方法杂穷。因?yàn)闃永愂呛戏ǖ念惡凡运部梢允褂美^承及定義成員。

值對(duì)象模式是函數(shù)式編程中一個(gè)非常常用的工具耐量,Scala在語言級(jí)別對(duì)其提供了直接支持飞蚓。

優(yōu)勢(shì):

  • 語法簡(jiǎn)潔
  • 預(yù)定義元組類
  • 內(nèi)置輔助方法

劣勢(shì):

七、空值模式
空值模式定義了一個(gè)“啥都不干”的行為廊蜒,這個(gè)模式比起空引用有一個(gè)優(yōu)勢(shì)趴拧,它不需要在使用前檢查引用的合法性。
在java中山叮,我們需要定義一個(gè)帶空方法的子類來實(shí)現(xiàn)此模式八堡。

public interface Sound {  
    void play();  
}  
  
public class Music implements Sound {  
    public void play() { /* ... */ }  
}  
  
public class NullSound implements Sound {  
    public void play() {}  
}  
  
public class SoundSource {  
    public static Sound getSound() {  
        return available ? music : new NullSound();  
    }  
}  
  
SoundSource.getSound().play(); 

所以,由getSound獲得Sound實(shí)例再調(diào)用play方法聘芜,不需要檢查Sound實(shí)例是否為空。更進(jìn)一步缝龄,我們可以使用單例模式來限制只生成唯一的空對(duì)象汰现。Scala也采用了類似的方法,但是它提供了一個(gè)Option類型叔壤,可以用來表示可有可無的值瞎饲。

trait Sound {  
  def play()  
}   
    
class Music extends Sound {  
    def play() { /* ... */ }  
}  
  
object SoundSource {  
  def getSound: Option[Sound] =   
    if (available) Some(music) else None  
}  
    
for (sound <- SoundSource.getSound) {  
  sound.play()  
} 

在此場(chǎng)景下,我們使用for推導(dǎo)來處理Option類型(高階函數(shù)和模式匹配也能輕松搞定此事)炼绘。
優(yōu)勢(shì):

  • 預(yù)定義類型
  • 明確的可選擇性
  • 內(nèi)置結(jié)構(gòu)支持

劣勢(shì):

  • 比較冗長(zhǎng)的用法

八嗅战、策略模式
策略模式定義了一組封裝好的算法,讓算法變化獨(dú)立于用戶調(diào)用俺亮。需要在運(yùn)行時(shí)選擇算法時(shí)驮捍,策略模式非常有用。
在java中脚曾,一般先要定義一個(gè)接口东且,然后新建幾個(gè)類分別去實(shí)現(xiàn)這個(gè)接口。

public interface Strategy {  
    int compute(int a, int b);  
}  
  
public class Add implements Strategy {  
    public int compute(int a, int b) { return a + b; }  
}  
  
public class Multiply implements Strategy {  
    public int compute(int a, int b) { return a * b; }  
}  
  
public class Context  {  
    private final Strategy strategy;  
  
    public Context(Strategy strategy) { this.strategy = strategy; }  
  
    public void use(int a, int b) { strategy.compute(a, b); }  
}  
  
new Context(new Multiply()).use(2, 3); 

在Scala中本讥,函數(shù)是頭等公民珊泳,可以直接實(shí)現(xiàn)如下(不得不說實(shí)現(xiàn)起來很爽)。

type Strategy = (Int, Int) => Int   
  
class Context(computer: Strategy) {  
  def use(a: Int, b: Int)  { computer(a, b) }  
}  
  
val add: Strategy = _ + _  
val multiply: Strategy = _ * _  
  
new Context(multiply).use(2, 3)  

假如策略包含很多方法的話拷沸,我們可以使用元組或者樣例類把所有方法封裝在一起色查。
優(yōu)勢(shì):

  • 語法簡(jiǎn)潔

劣勢(shì):

  • 通用類型

九、命令模式
命令模式封裝了需要在稍后調(diào)用方法的所有信息撞芍,這些信息包括擁有這些方法的對(duì)象和這些方法的參數(shù)值秧了。
命令模式適用于延時(shí)方法調(diào)用,順序化方法調(diào)用及方法調(diào)用時(shí)記錄日志序无。(當(dāng)然還有其它很多場(chǎng)景)
在Java中示惊,需要把方法調(diào)用封裝在對(duì)象中好港。

public class PrintCommand implements Runnable {  
    private final String s;  
  
    PrintCommand(String s) { this.s = s; }  
  
    public void run() {  
        System.out.println(s);  
    }  
}  
  
public class Invoker {  
    private final List<Runnable> history = new ArrayList<>();  
  
    void invoke(Runnable command) {  
        command.run();  
        history.add(command);  
    }  
}  
  
Invoker invoker = new Invoker();  
invoker.invoke(new PrintCommand("foo"));  
invoker.invoke(new PrintCommand("bar"));  

在Scala中,我們使用換名調(diào)用來實(shí)現(xiàn)延遲調(diào)用

object Invoker {  
  private var history: Seq[() => Unit] = Seq.empty  
  
  def invoke(command: => Unit) { // by-name parameter  
    command  
    history :+= command _  
  }  
}  
  
Invoker.invoke(println("foo"))  
    
Invoker.invoke {  
  println("bar 1")  
  println("bar 2")  
}  

這就是我們?cè)鯓影讶我獾谋磉_(dá)式或者代碼塊轉(zhuǎn)換為一個(gè)函數(shù)對(duì)象米罚。當(dāng)調(diào)用invoke方法的時(shí)候才會(huì)調(diào)用println方法钧汹,然后以函數(shù)形式存在歷史序列中。我們也可以直接定義函數(shù)录择,而不采用換名調(diào)用拔莱,但是那種方式太冗長(zhǎng)了。

優(yōu)勢(shì):

  • 語法簡(jiǎn)潔

劣勢(shì):

  • 通用類型

十隘竭、責(zé)任鏈模式
責(zé)任鏈模式解耦了發(fā)送方與接收方塘秦,使得有更多的對(duì)象有機(jī)會(huì)去處理這個(gè)請(qǐng)求,這個(gè)請(qǐng)求一直在這個(gè)鏈中流動(dòng)直到有個(gè)對(duì)象處理了它
責(zé)任鏈模式的一個(gè)典型實(shí)現(xiàn)是責(zé)任鏈中的所有的對(duì)象都會(huì)繼承一個(gè)基類动看,并且可能會(huì)包含一個(gè)指向鏈中下一個(gè)處理對(duì)象的引用尊剔。每一個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求(或者中斷請(qǐng)求),或者將請(qǐng)求推給下一個(gè)處理對(duì)象菱皆。責(zé)任鏈的順序邏輯可以要么代理給對(duì)象處理须误,要么就封裝在一個(gè)基類中。

public abstract class EventHandler {  
    private EventHandler next;  
  
    void setNext(EventHandler handler) { next = handler; }  
  
    public void handle(Event event) {  
        if (canHandle(event)) doHandle(event);  
        else if (next != null) next.handle(event);  
    }  
  
    abstract protected boolean canHandle(Event event);  
    abstract protected void doHandle(Event event);  
}  
  
public class KeyboardHandler extends EventHandler { // MouseHandler...  
    protected boolean canHandle(Event event) {  
        return "keyboard".equals(event.getSource());  
    }  
  
    protected void doHandle(Event event) { /* ... */ }  
}  
  
KeyboardHandler handler = new KeyboardHandler();  
handler.setNext(new MouseHandler()); 

由于以上的實(shí)現(xiàn)有點(diǎn)類似于裝飾者模式仇轻,所以我們?cè)赟cala中可以使用abstract override來解決這個(gè)問題京痢。不過Scala提供了一種更加直接的方式,即基于偏函數(shù)篷店。
偏函數(shù)簡(jiǎn)單來說就是某個(gè)函數(shù)只會(huì)針對(duì)它參數(shù)的可能值的自己進(jìn)行處理祭椰。可以直接使用偏函數(shù)的isDefinedAt和apply方法來實(shí)現(xiàn)順序邏輯疲陕,更好的方法是使用內(nèi)置的orElse方法來實(shí)現(xiàn)請(qǐng)求的傳遞方淤。

case class Event(source: String)  
  
type EventHandler = PartialFunction[Event, Unit]  
  
val defaultHandler: EventHandler = PartialFunction(_ => ())  
  
val keyboardHandler: EventHandler = {  
  case Event("keyboard") => /* ... */  
}  
  
def mouseHandler(delay: Int): EventHandler = {  
  case Event("mouse") => /* ... */  
}  
  
keyboardHandler.orElse(mouseHandler(100)).orElse(defaultHandler) 

注意我們必須使用defaultHandler來避免出現(xiàn)“undefined”事件的錯(cuò)誤。

優(yōu)勢(shì):

  • 語法簡(jiǎn)潔
  • 內(nèi)置邏輯

劣質(zhì):

  • 通用類型

十一蹄殃、依賴注入模式
依賴注入可以讓我們避免硬編碼依賴關(guān)系臣淤,并且允許在編譯期或者運(yùn)行時(shí)替換依賴關(guān)系。此模式是控制反轉(zhuǎn)的一個(gè)特例(用過Spring的同學(xué)都對(duì)這個(gè)模式熟爛了吧)窃爷。
依賴注入是在某個(gè)組件的眾多實(shí)現(xiàn)中選擇邑蒋,或者為了單元測(cè)試而去模擬組件。
除了使用IoC容器按厘,在Java中最簡(jiǎn)單的實(shí)現(xiàn)就是像構(gòu)造器參數(shù)需要的依賴医吊。所以我們可以利用組合來表達(dá)依賴需求。

public interface Repository {  
    void save(User user);  
}  
  
public class DatabaseRepository implements Repository { /* ... */ }  
  
public class UserService {  
    private final Repository repository;  
  
    UserService(Repository repository) {  
        this.repository = repository;  
    }  
  
    void create(User user) {  
        // ...  
        repository.save(user);  
    }  
}  
  
new UserService(new DatabaseRepository()); 

除了組合(“HAS-A”)與繼承(“HAS-A”)的關(guān)系外逮京,Scala還增加一種新的關(guān)系:需要(“REQUIRES -A”), 通過自身類型注解來實(shí)現(xiàn)卿堂。(建議大家去熟悉一下自身類型的定義與使用)

Scala中可以混合使用自身類型與特質(zhì)來進(jìn)行依賴注入。

trait Repository {  
  def save(user: User)  
}  
  
trait DatabaseRepository extends Repository { /* ... */ }  
  
trait UserService { self: Repository => // requires Repository  
  def create(user: User) {  
    // ...  
    save(user)  
  }  
}  
  
new UserService with DatabaseRepository 

不同于構(gòu)造器注入,以上方式有個(gè)要求:配置中的每一種依賴都需要一個(gè)單獨(dú)的引用草描,這種技術(shù)的完整實(shí)踐就叫蛋糕模式览绿。(當(dāng)然,在Scala中穗慕,還有很多方式來實(shí)現(xiàn)依賴注入)饿敲。

在Scala中,既然特質(zhì)的混入是靜態(tài)的逛绵,所以此方法也僅限于編譯時(shí)依賴注入怀各。事實(shí)上,運(yùn)行時(shí)的依賴注入幾乎用不著术浪,而對(duì)配置的靜態(tài)檢查相對(duì)于運(yùn)行時(shí)檢查有很大的優(yōu)勢(shì)瓢对。

優(yōu)勢(shì):

  • 含義明確
  • 語法簡(jiǎn)潔
  • 靜態(tài)檢查

劣勢(shì):

  • 編譯期配置
  • 形式上可能有點(diǎn)冗長(zhǎng)

原文地址: https://pavelfatin.com/design-patterns-in-scala/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市胰苏,隨后出現(xiàn)的幾起案子硕蛹,更是在濱河造成了極大的恐慌,老刑警劉巖硕并,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件法焰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鲤孵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門辰如,熙熙樓的掌柜王于貴愁眉苦臉地迎上來普监,“玉大人,你說我怎么就攤上這事琉兜】” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵豌蟋,是天一觀的道長(zhǎng)廊散。 經(jīng)常有香客問我,道長(zhǎng)梧疲,這世上最難降的妖魔是什么允睹? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮幌氮,結(jié)果婚禮上缭受,老公的妹妹穿的比我還像新娘。我一直安慰自己该互,他們只是感情好米者,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般蔓搞。 火紅的嫁衣襯著肌膚如雪胰丁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天喂分,我揣著相機(jī)與錄音锦庸,去河邊找鬼。 笑死妻顶,一個(gè)胖子當(dāng)著我的面吹牛酸员,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播讳嘱,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼幔嗦,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了沥潭?” 一聲冷哼從身側(cè)響起邀泉,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钝鸽,沒想到半個(gè)月后汇恤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拔恰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年因谎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颜懊。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡财岔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出河爹,到底是詐尸還是另有隱情匠璧,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布咸这,位于F島的核電站夷恍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏媳维。R本人自食惡果不足惜酿雪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望侄刽。 院中可真熱鬧执虹,春花似錦、人聲如沸唠梨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至茬故,卻和暖如春盖灸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背磺芭。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工赁炎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钾腺。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓徙垫,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親放棒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子姻报,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容