模板方法设计模式 示例类图
模板方法设计模式适用于实现框架。这里典型的是算法通常执行相同的步骤集,然后这些步骤由不同的客户端以不同的方式实现。您可以提出各种可能的用例。
对于我们的示例,让我们假设我们要编写一个应用程序,它将从数据源中读取一些数据,解析它,并查找是否存在满足某些条件的对象并将其返回。如果我们考虑它,我们有以下主要操作:读取数据解析数据搜索满足条件的项目如果需要,清理所有资源下图显示了我们代码的类图:我们使用了一个示例,我们'已经显示了从文件中读取有关人员的数据。然而,在这里,我们使用它来查找满足过滤功能的人的数据。使用模板方法设计模式,我们可以从服务器,数据库或任何想到的东西中读取具有不同格式的文件的人员列表。使用多态,我们的应用程序确保调用正确的方法,一切正常。代码示例让我们通过代表上图的代码,看看它的作用。
case class Person(name: String, age: Int, address: String)
import java.io.{InputStreamReader, ByteArrayInputStream}
import com.github.tototoshi.csv.CSVReader
import com.ivan.nikolov.behavioral.template.model.Person
import org.json4s.{StringInput, DefaultFormats}
import org.json4s.jackson.JsonMethods
abstract class DataFinder[T, Y] {
def find(f: T => Option[Y]): Option[Y] =
try {
val data = readData()
val parsed = parse(data)
f(parsed)
} finally {
cleanup()
}
def readData(): Array[Byte]
def parse(data: Array[Byte]): T
def cleanup()
}
class JsonDataFinder extends DataFinder[List[Person], Person] {
implicit val formats = DefaultFormats
override def readData(): Array[Byte] = {
val stream = this.getClass.getResourceAsStream("people.json")
Stream.continually(stream.read).takeWhile(_ != -1).map(_.toByte).toArray
}
override def cleanup(): Unit = {
System.out.println("Reading json: nothing to do.")
}
override def parse(data: Array[Byte]): List[Person] =
JsonMethods.parse(StringInput(new String(data, "UTF-8"))).extract[List[Person]]
}
class CSVDataFinder extends DataFinder[List[Person], Person] {
override def readData(): Array[Byte] = {
val stream = this.getClass.getResourceAsStream("people.csv")
Stream.continually(stream.read).takeWhile(_ != -1).map(_.toByte).toArray
}
override def cleanup(): Unit = {
System.out.println("Reading csv: nothing to do.")
}
override def parse(data: Array[Byte]): List[Person] =
CSVReader.open(new InputStreamReader(new ByteArrayInputStream(data))).all().map {
case List(name, age, address) =>
Person(name, age.toInt, address)
}
}
object DataFinderExample {
def main(args: Array[String]): Unit = {
val jsonDataFinder: DataFinder[List[Person], Person] = new JsonDataFinder
val csvDataFinder: DataFinder[List[Person], Person] = new CSVDataFinder
System.out.println(s"Find a person with name Ivan in the json: ${jsonDataFinder.find(_.find(_.name == "Ivan"))}")
System.out.println(s"Find a person with name James in the json: ${jsonDataFinder.find(_.find(_.name == "James"))}")
System.out.println(s"Find a person with name Maria in the csv: ${csvDataFinder.find(_.find(_.name == "Maria"))}")
System.out.println(s"Find a person with name Alice in the csv: ${csvDataFinder.find(_.find(_.name == "Alice"))}")
}
}
运行结果如下:
首先,我们的模型Person类:case类Person(name:String,age:Int,address:String)没有什么特别之处。现在,让我们转到有趣的部分 - DataFinder类:我们使用泛型来使这个类可用于各种类型。正如您在前面的代码中看到的那样,DataFinder类的三个方法没有实现,但它们仍然在find方法中引用。后者是实际的模板方法,抽象方法将在扩展DataFinder的不同类中实现。
对于我们的示例,我们提供了两种不同的实现,一种用于JSON,另一种用于CSV文件。 JSON查找器看起来如下:每当我们使用它时,根据我们具有的具体实例,find方法将通过多态调用正确的实现。可以通过扩展DataFinder类来添加新格式和数据源。
使用我们的数据查找器现在很简单:我们示例中的代码使用抽象类。这使得它在某种意义上略有限制,即我们只能扩展一个类。
但是,将抽象类更改为特征然后将其混合到类中会很简单。
它有什么好处正如您所看到的,每当我们有一个算法结构相同且我们提供不同实现的用例时,我们就可以使用模板方法设计模式。这非常适合创建框架。
它不是那么好用每当我们使用模板方法设计模式实现的框架变大时,简单地扩展一个庞大的类并实现它的一些方法就更难了。在这些情况下,将接口传递给构造函数并在框架中使用它可能是一个更好的想法(策略设计模式)。