1. 程式人生 > >Scala設計模式UML圖例和程式碼實現實戰 結構模式--裝飾器模式

Scala設計模式UML圖例和程式碼實現實戰 結構模式--裝飾器模式

Scala設計模式UML圖例和程式碼實現實戰

結構模式中的 裝飾器設計模式

在某些情況下,我們可能希望為應用程式中的類新增一些額外的功能。這可以通過繼承來完成;但是,我們可能不想這樣做,或者它可能會影響我們應用程式中的所有其他類。這是裝飾器設計模式有用的地方。

裝飾器設計模式的目的是向物件新增功能而不擴充套件它們,並且不會影響同一類中其他物件的行為。

裝飾器設計模式通過包裝裝飾物件來工作,並且可以在執行時應用。裝飾器在可以有多個類的擴充套件並且可以以各種方式組合的情況下非常有用。

可以建立裝飾器,而不是編寫所有可能的組合,它們可以將修改堆疊在一起。

接下來的幾個小節將展示如何以及何時在現實世界中使用裝飾器。

示例類圖正如我們之前看到的介面卡設計模式,其目的是將介面更改為不同的介面。另一方面,裝飾器通過向方法新增額外功能來幫助我們增強介面。對於類圖,我們將使用資料流的示例。想象一下,我們有一個基本的流。我們可能希望能夠對其進行加密,壓縮,替換其字元等。

下面是類圖:在上圖中,AdvancedInputReader提供了InputReader的基本實現。它包裝了一個標準的BufferedReader。

然後,我們有一個抽象的InputReaderDecorator類,它擴充套件了InputReader幷包含它的一個例項。通過擴充套件基礎裝飾器,我們提供了對流進行大寫,壓縮或Base64編碼所獲得的輸入的可能性。我們可能希望在我們的應用程式中使用不同的流,並且它們能夠以不同的順序執行上述一個或多個操作。如果我們嘗試提供所有可能性,特別是當可能的運算元量更多時,我們的程式碼將很快變得難以維護和混亂。有了裝飾器,它很乾淨,我們將在下一節中看到。

從前面的螢幕截圖中可以看出,在壓縮裝飾器程式碼中,我們以位元組為單位記錄行的大小。輸出是gzip壓縮的,這就是文字顯示為不可讀字元的原因。您可以試驗並更改裝飾器應用程式的順序或新增新裝置以檢視事物的不同之處。

對裝飾器有利的是為我們的應用程式增加了很多靈活性。它們不會更改原始類,因此它們不會在舊程式碼中引入錯誤,並且可以節省大量程式碼編寫和維護。

此外,它們可以防止我們忘記或不預測我們建立的類的一些用例。

在前面的示例中,我們展示了一些靜態行為修改。但是,也可以在執行時動態裝飾例項。

什麼不是那麼好我們已經涵蓋了使用裝飾器的積極方面;但是,我們應該指出過度使用裝飾器也可能導致問題。我們最終可能會有大量的小類,他們可能會使我們的庫更難以使用,並且在需要更多領域知識方面要求更高。它們還使例項化過程複雜化,這需要其他(建立)設計模式,例如工廠或構建器。

Scala方式的裝飾器設計模式與其他設計模式一樣,這個模型有一個實現,它利用了Scala的豐富性。

Scala中的裝飾器設計模式也稱為可堆疊特徵。 讓我們看看它的樣子以及如何使用它。 InputReader和AdvancedInputReader程式碼將完全按照上一節中的說明進行操作。 我們實際上是在兩個例子中重複使用它。

接下來,我們將在新特徵中定義不同的讀者修改,而不是定義抽象裝飾器類,如下所示:

trait CapitalizedInputReaderTrait extends InputReader {

abstract override def readLines(): Stream[String] =

super.readLines().map(_.toUpperCase)

}

Then, we define the compressing input reader:

trait CompressingInputReaderTrait extends InputReader with LazyLogging

{

abstract override def readLines(): Stream[String] =

super.readLines().map {

case line =>

val text = line.getBytes(Charset.forName("UTF-8"))

logger.info("Length before compression: {}",

text.length.toString)

val output = new ByteArrayOutputStream()

val compressor = new GZIPOutputStream(output)

try {

compressor.write(text, 0, text.length)

val outputByteArray = output.toByteArray

logger.info("Length after compression: {}",

outputByteArray.length.toString)

new String(outputByteArray, Charset.forName("UTF-8"))

} finally {

compressor.close()

output.close()

}

}

}

最後,Base64編碼器閱讀器如下:

trait Base64EncoderInputReaderTrait extends InputReader {

abstract override def readLines(): Stream[String] =

super.readLines().map {

case line =>

Base64.getEncoder.encodeToString(line.getBytes(Charset.forName("UTF-

8")))

}

}

如您所見,這裡的實現沒有太大的不同。

在這裡,我們使用traits而不是類,擴充套件了基本的InputReader特性,並使用了抽象覆蓋。

抽象覆蓋允許我們為一個宣告為abstract的特徵中的方法呼叫super。

只要特徵在另一個特徵或實現上述方法的類之後混合,這對於特徵是允許的。 抽象覆蓋告訴編譯器我們是故意這樣做的,並且它不會使我們的編譯失敗 - 它將在我們使用特徵後檢查是否滿足使用它的要求。

以前,我們提出了兩個例子。 我們現在將向您展示它們具有可堆疊特徵的外觀。 只有資本化的第一個看起來如下:

object StackableTraitsExample {

def main(args: Array[String]): Unit = {

val stream = new BufferedReader(

new InputStreamReader(

new BufferedInputStream(this.getClass.getResourceAsStream("data.txt"))

)

)

try {

val reader = new AdvancedInputReader(stream) with

CapitalizedInputReaderTrait

reader.readLines().foreach(println)

} finally {

stream.close()

}

}

}

大寫的第二個示例,Base64編碼和壓縮流將如下所示:

object StackableTraitsBigExample {

def main(args: Array[String]): Unit = {

val stream = new BufferedReader(

new InputStreamReader(

new

BufferedInputStream(this.getClass.getResourceAsStream("data.txt"))

)

)

try {

val reader = new AdvancedInputReader(stream) with

CapitalizedInputReaderTrait

with Base64EncoderInputReaderTrait

with CompressingInputReaderTrait

reader.readLines().foreach(println)

} finally {

stream.close()

}

}

}

兩個示例的輸出將與原始示例中的輸出完全相同。 然而,在這裡,我們使用mixin組合,事情看起來更清潔。 我們也少了一個類,因為我們不需要抽象裝飾器類。 瞭解如何應用修改也很容易 - 我們只需遵循可堆疊特徵的混合順序。

可堆疊特徵遵循線性化規則在我們當前示例中從左到右應用修改的事實是欺騙。 發生這種情況的原因是因為我們將堆疊上的呼叫推送到readLines的基本實現,然後以相反的順序應用修改。 

package com.ivan.nikolov.structural.decorator.common

import java.io.BufferedReader

import scala.collection.JavaConverters._

trait InputReader {

def readLines(): Stream[String]

}

class AdvancedInputReader(reader: BufferedReader) extends InputReader {

override def readLines(): Stream[String] = reader.lines().iterator().asScala.toStream

}

package com.ivan.nikolov.structural.decorator

import java.io.{BufferedInputStream, InputStreamReader, BufferedReader, ByteArrayOutputStream}

import java.nio.charset.Charset

import java.util.Base64

import java.util.zip.GZIPOutputStream

import com.ivan.nikolov.structural.decorator.common.{AdvancedInputReader, InputReader}

import com.typesafe.scalalogging.LazyLogging

trait CapitalizedInputReaderTrait extends InputReader {

abstract override def readLines(): Stream[String] = super.readLines().map(_.toUpperCase)

}

trait CompressingInputReaderTrait extends InputReader with LazyLogging {

abstract override def readLines(): Stream[String] = super.readLines().map {

case line =>

val text = line.getBytes(Charset.forName("UTF-8"))

logger.info("Length before compression: {}", text.length.toString)

val output = new ByteArrayOutputStream()

val compressor = new GZIPOutputStream(output)

try {

compressor.write(text, 0, text.length)

val outputByteArray = output.toByteArray

logger.info("Length after compression: {}", outputByteArray.length.toString)

new String(outputByteArray, Charset.forName("UTF-8"))

} finally {

compressor.close()

output.close()

}

}

}

trait Base64EncoderInputReaderTrait extends InputReader {

abstract override def readLines(): Stream[String] = super.readLines().map {

case line => Base64.getEncoder.encodeToString(line.getBytes(Charset.forName("UTF-8")))

}

}

object StackableTraitsExample {

def main(args: Array[String]): Unit = {

val stream = new BufferedReader(

new InputStreamReader(

new BufferedInputStream(this.getClass.getResourceAsStream("data.txt"))

)

)

try {

val reader = new AdvancedInputReader(stream) with CapitalizedInputReaderTrait

reader.readLines().foreach(println)

} finally {

stream.close()

}

}

}

object StackableTraitsBigExample {

def main(args: Array[String]): Unit = {

val stream = new BufferedReader(

new InputStreamReader(

new BufferedInputStream(this.getClass.getResourceAsStream("data.txt"))

)

)

try {

val reader = new AdvancedInputReader(stream) with CapitalizedInputReaderTrait with Base64EncoderInputReaderTrait with CompressingInputReaderTrait

reader.readLines().foreach(println)

} finally {

stream.close()

}

}

}

package com.ivan.nikolov.structural.decorator

import java.io.{InputStreamReader, BufferedInputStream, ByteArrayOutputStream, BufferedReader}

import java.nio.charset.Charset

import java.util.Base64

import java.util.zip.GZIPOutputStream

import com.ivan.nikolov.structural.decorator.common.{AdvancedInputReader, InputReader}

import com.typesafe.scalalogging.LazyLogging

abstract class InputReaderDecorator(inputReader: InputReader) extends InputReader {

override def readLines(): Stream[String] = inputReader.readLines()

}

class CapitalizedInputReader(inputReader: InputReader) extends InputReaderDecorator(inputReader) {

override def readLines(): Stream[String] = super.readLines().map(_.toUpperCase)

}

class CompressingInputReader(inputReader: InputReader) extends InputReaderDecorator(inputReader) with LazyLogging {

override def readLines(): Stream[String] = super.readLines().map {

case line =>

val text = line.getBytes(Charset.forName("UTF-8"))

logger.info("Length before compression: {}", text.length.toString)

val output = new ByteArrayOutputStream()

val compressor = new GZIPOutputStream(output)

try {

compressor.write(text, 0, text.length)

val outputByteArray = output.toByteArray

logger.info("Length after compression: {}", outputByteArray.length.toString)

new String(outputByteArray, Charset.forName("UTF-8"))

} finally {

compressor.close()

output.close()

}

}

}

class Base64EncoderInputReader(inputReader: InputReader) extends InputReaderDecorator(inputReader) {

override def readLines(): Stream[String] = super.readLines().map {

case line => Base64.getEncoder.encodeToString(line.getBytes(Charset.forName("UTF-8")))

}

}

object DecoratorExample {

def main(args: Array[String]): Unit = {

val stream = new BufferedReader(

new InputStreamReader(

new BufferedInputStream(this.getClass.getResourceAsStream("data.txt"))

)

)

try {

val reader = new CapitalizedInputReader(new AdvancedInputReader(stream))

reader.readLines().foreach(println)

} finally {

stream.close()

}

}

}

object DecoratorExampleBig {

def main(args: Array[String]): Unit = {

val stream = new BufferedReader(

new InputStreamReader(

new BufferedInputStream(this.getClass.getResourceAsStream("data.txt"))

)

)

try {

val reader = new CompressingInputReader(

new Base64EncoderInputReader(

new CapitalizedInputReader(

new AdvancedInputReader(stream)

)

)

)

reader.readLines().foreach(println)

} finally {

stream.close()

}

}

}

執行結果如下: