多重分派
S4 泛型函数更加灵活,因为它也支持多重分派,也就是说,S4 泛型函数可以根据多个
参数执行方法分派。
这里,我们定义另外一个 S4 类族:具有数值height 类表示的 Object 类。Cylinder
和 Cone 都继承 Object。然后,我们使用多重分派计算底面具有特定形状的特定几何物
体的体积:
setClass("Object", representation(height = "numeric"))
setClass("Cylinder", contains = "Object")
setClass("Cone", contains = "Object")
现在,我们定义一个新的名为 volume 的泛型函数。顾名思义,这个函数用来计算对
象的体积,其中这个对象由底面形状和对象的几何体形状描述:
setGeneric("volume",
function(shape, object) standardGeneric("volume"))
## [1] "volume"
接下来的代码,实现了两种几何体体积的计算方法:一种是长方体(底面为矩形的柱
体),另一种是四棱锥(底面是矩形的锥体):
setMethod("volume", signature("Rectangle", "Cylinder"),
function(shape, object) {
shape@a * shape@b * object@height
})
## [1] "volume"
setMethod("volume", signature("Rectangle", "Cone"),
function(shape, object) {
shape@a * shape@b * object@height /3
})
## [1] "volume"
注意到,所有计算体积的方法都需要两个参数(底面形状和几何体形状)。因此,方法
分派根据两个参数进行,也就是说,它需要匹配两个输入对象的类来选择正确的方法。接
下来,我们分别用 Rectagle 类和 Cylinder 类的对象实例来检验泛型函数 volume( ):
rectangle <- new("Rectangle", a =2, b =3)
cylinder <- new("Cylinder", height =3)
volume(rectangle, cylinder)
## [1] 18
同底等高的柱体和锥体的体积有一个关系:柱体体积(底面积×高)是锥体体积(
1/3x底面积 × 高)的 3 倍。为了简化 volume 方法的实现过程,我们直接将 Shape 放进函数的
signature 参数中,并调用 area( ) 泛型函数,然后在计算中直接使用底面面积:
setMethod("volume", signature("Shape", "Cylinder"),
function(shape, object) {
area(shape) * object@height
})
## [1] "volume"
setMethod("volume", signature("Shape", "Cone"),
function(shape, object) {
area(shape) * object@height /3
})
## [1] "volume"
现在,volume 方法自动适用于 Circle 类:
circle <- new("Circle", r =2)
cone <- new("Cone", height =3)
volume(circle, cone)
## [1] 12.56637
为了使 volume 方法更简单易用,我们可以另外定义一种直接利用 Shape 类的对象实
例和一个代表 Object 类几何体(默认为柱体)的高的数值:
setMethod("volume", signature("Shape", "numeric"),
function(shape, object) {
area(shape) * object
})
## [1] "volume"
这样就可以直接在计算中使用数值。例如计算给定底面和高的柱体的体积:
volume(rectangle, 3)
## [1] 18
更进一步,我们通过实现方法*来简化符号:
setMethod("*", signature("Shape", "Object"),
function(e1, e2) {
volume(e1, e2)
})
## [1] "*"
现在,我们通过底面形状和几何体的简单相乘就可以计算体积了:
rectangle * cone
## [1] 6
注意到, S4 对象不是列表或环境,它具有复制—修改语义。从这个意义上说,当 S4 对
象的一个字段值在函数中通过 < - 被修改时,它的行为更像一个列表。也就是说, S4 对象在
函数中被复制了(即复制后修改副本),而原始对象并没有被修改。
举个例子,在下面这段代码中,我们定义了一个函数,它通过将 Object 的高修改为原
来的高乘以一个数值因子(倍数)来“拉长” Object :
lengthen <- function(object, factor) {
object@height <- object@height * factor
object
}
当我们将这个函数应用在 cylinder(前面创建的 Cylinder 类的对象实例)时,它
的高根本不会变。相反,它在函数内部被复制(然后修改)了:
cylinder
## An object of class "Cylinder"
## Slot "height":
## [1] 3
lengthen(cylinder, 2)
## An object of class "Cylinder"
## Slot "height":
## [1] 6
cylinder
## An object of class "Cylinder"
## Slot "height":
## [1] 3