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()
}
}
}
运行结果如下: