目的
以某种方式对在数据结构上操作进行封装,进而在不修改原有数据结构的前提下为该数据结构添加新的操作。
概述
对于一个长期运行的程序而言,一个焦点是如何对数据结构进行扩展。而这个扩展有两个维度:新操作与新实现。
一般来说,我们希望在不重新编译源代码,甚至不访问源代码的情况下完成对数据结构的扩展。在Java中,Collection作为一个通用的集合接口,定义了很多方法和操作,同时也有很多实现类。在面向对象的语言中,我们可以实现接口为Collection添加新的实现。如果要为Collection添加新的操作,并要求在所有实现的Collecton对象中生效,那么将变得非常棘手。
访问者模式是用以解决为向对象添加新操作的一个方案,如下图是该模式的主要组成部分。
元素的accept方法接受一个Visitor,accept方法体一般是Visitor.visit(this),用以访问数据结构。现在,如果想添加新的操作,只需要创建新的访问者,并编写代码来指定如何访问既有的数据结构。
- 但是访问者模式给数据结构添加新的实现也带来了麻烦。一旦我们要实现新的数据结构,需要修改既有的所有访问者的实现,以便它们知道如何访问新的数据结构。修改这些访问者,一般来说是不太可能。
Scala实现访问者模式
1.隐式转化系统
利用一个Person数据类型,如下:
trait Person {
def fullName: String
def firstName: String
def lastName: String
def houseNum: String
def street: String
}
class SimplePerson extends Person {
override def fullName: String = firstName+lastName
override def firstName: String = "P"
override def lastName: String = "cc"
override def houseNum: String = "111"
override def street: String = "street"
}
下面我们将添加一个fullAddress操作,来打印出Simple对象的完整地址:
object Example extends App {
/**
* 隐式转化类
* @param person
*/
implicit class ExtendedPerson(person: Person) {
def fullAddress = person.houseNum + " " + person.street
}
/**
* 当编译器发现SimplePerson没有fullAddress方法时,它将开始查找所有来自Person的隐式转化类型,
* 直到找到ExtendedPerson类,编译器将把Person传入ExtendedPerson主构造器来构造一个实例,调用fullAddress方法
*/
val fullAddr = new SimplePerson().fullAddress
//fullAddr : 111 street
}
上面利用了Scala的隐式转化功能,非常强大。
2.混入继承(mix-in inheritance)与特质(trait)
混入继承(mix-in inheritance):mix-in模式并不是复制一个完整的对象,而是从多个对象中复制出任意的成员并将这些成员组合成一个新的对象。
用特质来实现混入技术,那么我们就可以不过的组合与扩展特质中的数据,从而实现访问者模式所带来的功能。
下面是一个形状接口与实现类:
trait PerimeterShapes {
trait Shape {
def perimeter: Double
}
class Circle(radius: Double) extends Shape {
override def perimeter: Double = 2 * Math.PI * radius
}
class Rectangle(width: Double, height: Double) extends Shape {
override def perimeter: Double = 2 * width + 2 * height
}
}
我们可以继承这个顶层接口,使用里面的实现类:
object Example2 extends App with PerimeterShapes {
val rectangle = new Rectangle(12,12)
println (rectangle.perimeter)
//48
}
下面我们将使用Scala里的Trait来扩展形状类的操作,创建一个AreaShapes来实现计算面积的操作:
trait AreaShapes extends PerimeterShapes {
trait Shape extends super.Shape {
def area: Double
}
class Circle(radius: Double) extends super.Circle(radius) with Shape {
override def area: Double = Math.PI * radius * radius
}
class Rectangle(width: Double, height: Double) extends super.Rectangle(width, height) with Shape {
override def area: Double = width * height
}
}
Scala中的特质可以继承,所以我们可以修改PerimeterShapes里的内容,下面继承新特质来调用新的操作:
object Example1 extends App with AreaShapes {
val rectangle = new Rectangle(12,12)
println (rectangle.area)
println (rectangle.perimeter)
//144
//48
}
以上就完成了对数据结构添加新操作的功能,下面将添加一个新的形状,并实现以上两个操作:
trait MorePerimeterShapes extends PerimeterShapes {
class Square(side: Double) extends Shape {
override def perimeter: Double = 4 * side
}
}
trait MoreAreaShapes extends AreaShapes with MorePerimeterShapes {
class Square(side: Double) extends super.Square(side) with Shape {
override def area: Double = side * side
}
}
如上,我们继续利用特质来扩展形状。现在就完成了对类型与操作的扩展,且是一种类型安全的方式。