定义 S4 类
S3 类仅用一个字符向量表示,与之不同的是,S4 类要求对类和方法有正式定义。为了
定义一个 S4 类,我们需要调用 setClass( ),并提供一种类成员的表示,这种表示被称
为字段(slots)。通过名称和每个字段的类来指定这种表示。本节中,我们使用 S4 类重新
定义 product 对象:
setClass("Product",
representation(name ="character",
price = "numeric",
inventory = "integer"))
一旦类被定义了,就可以使用 getSlots( ) 从类定义中获取字段:
getSlots("Product")
## name price inventory
## "character" "numeric" "integer"
S4 比 S3 更严谨,不仅因为 S4 要求类定义,还因为 R 能够确保新创建的对象实例中成
员的类与原来的类表示是一致的。现在,我们使用 new( ) 创建一个新的 S4 类对象实例,
并且指定字段的取值:
laptop <- new("Product", name = "Laptop-A", price = 299, inventory = 100)
## Error in validObject(.Object): invalid class "Product" object: invalid
object for slot "inventory" in class "Product": got class "numeric", should be
or extend class "integer"
上述代码产生了错误,这可能会让你觉得惊讶。如果仔细查看一下类表示,就会发现
inventory 必须是整数。换句话说,100 是个数值,它的类不是 integer。相反,应该
使用 100L:
laptop <- new("Product", name = "Laptop-A", price = 299, inventory = 100L)
laptop
## An object of class "Product"
## Slot "name":
## [1] "Laptop-A"
##
## Slot "price":
## [1] 299
##
## Slot "inventory":
## [1] 100
现在,一个 Product 类的新对象实例 laptop 已经创建好了,并且作为 Product 类
的一个对象被打印出来,所有字段的值也被自动打印出来。
对于一个 S4 对象,我们仍然可以使用 typeof( )和 class( )来获取类型信息:
typeof(laptop)
## [1] "S4"
class(laptop)
## [1] "Product"
## attr(,"package")
## [1] ".GlobalEnv"
这次,对象的类型是 S4,而非列表或其他数据类型,而且它的类是 S4 类中的名字。
S4 对象也是 R 中的“一等公民”,因为它有对应的检查函数:
isS4(laptop)
## [1] TRUE
与使用 $ 访问一个列表或环境不同,我们需要使用 @ 来访问一个 S4 对象的字段:
laptop@price *laptop@inventory
## [1] 29900
此外,我们还可以调用 slot( ),以字符形式提供字段名来访问一个字段。这与使用
双层方括号([[ ]])访问列表或环境的元素是等价的:
slot(laptop, "price")
## [1] 299
也可以用修改列表的方式修改一个 S4 对象:
laptop@price <- 289
但是,不能提供给字段与类表示不一致的内容:
laptop@inventory <- 200
## Error in (function (cl, name, valueClass) : assignment of an object of
class "numeric" is not valid for @'inventory' in an object of class "Product";
is(value, "integer") is not TRUE
也不能像给列表添加成分那样创建一个新的字段,因为 S4 对象的结构是根据它的类表
示固定下来的:
laptop@value <- laptop@price *laptop@inventory
## Error in (function (cl, name, valueClass) : 'value' is not a slot in class
"Product"
现在,我们创建另一个对象实例,但是只提供部分字段值:
toy <- new("Product", name = "Toys", price = 10)
toy
## An object of class "Product"
## Slot "name":
## [1] "Toys"
##
## Slot "price":
## [1] 10
##
## Slot "inventory":
## integer(0)
上述代码没有指定字段 inventory,所以结果对象 toy 选取了一个空的整数向量作
为 inventory。如果你认为这并不是一个合意的默认值,我们可以指定类的原型,这样
将会以它作为模板创建每个对象实例:
setClass("Product",
representation(name = "character",
price = "numeric",
inventory = "integer"),
prototype(name = "Unnamed", price = NA_real_, inventory = 0L))
在上面这个原型中,我们将 price 的默认值设定为数值型缺失值,将 inventory 的
默认值设定为整数。注意到 NA 是一个逻辑值,与类表示不一致,所以不能用在这里。
然后,我们就可以使用相同的代码重新创建 toy:
toy <- new("Product", name = "Toys", price = 10)
toy
## An object of class "Product"
## Slot "name":
## [1] "Toys"
##
## Slot "price":
## [1] 10
##
## Slot "inventory":
## [1] 0
这次,inventory 取原型中的默认值 0L。然而,如果我们需要对输入参数施加更多
约束呢?尽管参数的类会被检查,但是仍然可以提供对 Product 类的对象实例无意义的
值。举个例子,我们可以创建一个有负库存的 bottle 对象:
bottle <- new("Product", name = "Bottle", price = 1.5, inventory = -2L)
bottle
## An object of class "Product"
## Slot "name":
## [1] "Bottle"
##
## Slot "price":
## [1] 1.5
##
## Slot "inventory":
## [1] -2
接下来的代码创建了一个验证函数,用于确保一个 Product 类的对象的字段是有意
义的。这个验证函数有些特殊,因为当输入对象没有错误时,函数返回 TRUE;当输入对象
有错误时,函数返回一个字符向量来描述错误。因此,当字段无效时,最好不要使用 stop( )
或者 warning( )。
这里,我们通过检查每个字段的长度和它们是不是缺失值来验证对象的有效性。而且,
price 必须是正数,inventory 必须是非负数:
validate_product <- function(object) {
errors <- c(
if (length(object@name) != 1)
"Length of name should be 1"
else if (is.na(object@name))
"name should not be missing value",
if (length(object@price) != 1)
"Length of price should be 1"
else if (is.na(object@price))
"price should not be missing value"
else if (object@price <= 0)
"price must be positive",
if (length(object@inventory) != 1)
"Length of inventory should be 1"
else if (is.na(object@inventory))
"inventory should not be missing value"
else if (object@inventory < 0)
"inventory must be non-negative")
if (length(errors) == 0) TRUE else errors
}
我们编写了这个很长的函数,考虑所有可能出现的错误值,并明确标注每种情况的错
误信息。这个函数是可以运行的,因为表达式 if (FALSE) expr 返回 NULL,而
c(x, NULL)返回 x。最后如果没有产生错误信息,函数返回 TRUE,否则返回错误信息。
定义了这个函数,我们就可以直接使用它对 bottle 进行验证:
validate_ _product(bottle)
## [1] "inventory must be non-negative"
验证函数返回了预料之中的错误信息。现在,我们可以进一步改进类定义函数,使其
每次创建一个新的对象实例时,都会执行验证过程。当使用 setClass( )定义 Product
类时,只需指定 validity 参数:
setClass("Product",
representation(name = "character",
price = "numeric",
inventory = "integer"),
prototype(name = "Unnamed",
price = NA_real_, inventory = 0L),
validity = validate_product)
这样每次创建 Product 类的对象实例时,我们提供的值都会被自动检查。甚至原型
也会被检查。下面是两种没有通过验证的情况。
第 1 种情况:
bottle <- new("Product", name = "Bottle")
## Error in validObject(.Object): invalid class "Product" object: price
should not be missing value
上述代码无效,因为原型中 price 的默认值是 NA_real_。而在验证函数中,价格不
能是缺失值。
第 2 种情况:
bottle <- new("Product", name = "Bottle", price = 3, inventory = -2L)
## Error in validObject(.Object): invalid class "Product" object: inventory
must be non-negative
这次失效的原因是 inventory 必须是非负整数。
注意到,只有在创建一个新的 S4 类对象实例时,才会对其进行验证。一旦对象被创建
出来,就再也不会进行验证了。换句话说,除非我们再次明确地对其进行验证,否则仍然
可以设定一个糟糕的字段值。
定义 S4 类的更多相关文章
- 定义 S4 泛型函数
在前面的例子中,我们可以看出 S4 比 S3 更正式,因为 S4 类有类的正式定义.同样, S4 的泛型函数也更加正式.在一个关于形状的例子中,我们定义了一系列具有继承关系的 S4 类,只是继承关系的 ...
- 类的继承和多态性-编写Java应用程序,定义Animal类,此类中有动物的属性:名称 name,腿的数量legs,统计动物的数量 count;方法:设置动物腿数量的方法 void setLegs(),获得腿数量的方法 getLegs(),设置动物名称的方法 setKind(),获得动物名称的方法 getKind(),获得动物数量的方法 getCount()。定义Fish类,是Animal类的子类,
编写Java应用程序,定义Animal类,此类中有动物的属性:名称 name,腿的数量legs,统计动物的数量 count;方法:设置动物腿数量的方法 void setLegs(),获得腿数量的方法 ...
- KVC在定义Model类中的妙用
@我们应用程序使用MVC架构的话,对于处理数据类,我们会单独的定义Model类,在里面为要展示的属性进行初始化赋值,一般採用的方法是通过定义相应的属性,挨个赋值.如今我要介绍的就是通过KVC,key- ...
- 定义Java类的数组的问题
定义了一个类: class Student{ private int Id; public int getId() { return Id; } public void setId(int id) { ...
- Java TreeSet集合排序 && 定义一个类实现Comparator接口,覆盖compare方法 && 按照字符串长度排序
package TreeSetTest; import java.util.Iterator; import java.util.TreeSet; import javax.management.Ru ...
- JavaScript数据结构与算法(八) 集合(ECMAScript 6中定义的类似的Set类)
TypeScript方式实现源码 // 特性: // 1. 集合是由一组无序且唯一(即不能重复)的项组成的.这个数据结构使用了与有限集合相同的数学概念,但应用在计算机科学的数据结构中. // 2. 也 ...
- JAVA 类的定义(定义一个类,来模拟“学生”)
package Code413;/*定义一个类,来模拟“学生”属性 (是什么) 姓名 年龄行为(能做什么) 吃饭 睡觉 学习对应到Java的类当中 成员变量(属性) String nanme; //姓 ...
- 【C++ Primer 第15章】定义派生类析构函数
学习资料 • 基类和派生类析构函数执行顺序 定义派生类析构函数 [注意]定义一个对象时先调用基类的构造函数.然后调用派生类的构造函数:析构的时候恰好相反:先调用派生类的析构函数.然后调用基类的析构函数 ...
- 【C++ Primer 第15章】定义派生类拷贝构造函数、赋值运算符
学习资料 • 派生类的赋值运算符/赋值构造函数也必须处理它的基类成员的赋值 • C++ 基类构造函数带参数的继承方式及派生类的初始化 定义拷贝构造函数 [注意]对派生类进行拷贝构造时,如果想让基类的成 ...
随机推荐
- [ASP.NET]从Request.Url获取根网址的最简单方法
在拼接绝对路径的网址时,经常需要从Request.Url中获取根网址(比如http://www.cnblogs.com),然后与相对路径一起拼接为绝对路径. 以前的做法如下: var uri = Re ...
- India and China Origins---hdu5652(二分 + bfs)或者(并查集)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5652 题意: 很久以前,中国和印度之间并没有喜马拉雅山相隔,两国的文化交流很频繁.随着喜马拉雅山海拔逐 ...
- Python开发【模块】:Urllib(二)
Urllib实战 1.爬取糗事百科中段子和用户名: 代码实例: # 爬取网站页面内容 import re import urllib.request url = 'https://www.qiushi ...
- JavaScript原型链的理解
JavaScript中的每一个对象都有prototype属性,我们称之为原型,而原型的值也是一个对象,因此它有自己的原型,这样就串联起来形成了一条原型链.原型链的链头是object,它的prototy ...
- Alpine Linux配置使用技巧【一个只有5M的操作系统(转)】
Alpine Linux是一个面向安全应用的轻量级Linux发行版.它采用了musl libc和busybox以减小系统的体积和运行时资源消耗,同时还提供了自己的包管理工具apk. Alpine Li ...
- tar 压缩解压命令详解
tar -c: 建立压缩档案-x:解压-t:查看内容-r:向压缩归档文件末尾追加文件-u:更新原压缩包中的文件 这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个.下面的 ...
- Django的FBV和CB
Django的FBV和CBV FBV FBV(function base views) 就是在视图里使用函数处理请求. 在之前django的学习中,我们一直使用的是这种方式,所以不再赘述. CBV C ...
- wordpress防止网站被镜像四个方法
第一种:拆分域名链接与镜像站比对,然后用img标签src空值触发onerror来执行js比对,比对失败则跳转回源站.代码如下:(复制粘贴到主题的functions.php最后一个?>之前,代码出 ...
- C语言头文件#include<stdlib.h>的作用
stdlib 头文件即standard library标准库头文件 stdlib 头文件里包含了C.C++语言的最常用的系统函数 该文件包含了的C语言标准库函数的定义 stdlib.h里面定义了五 ...
- SQL面试题及答案
我觉得里面有些答案是不正确的,请只作参考 Student(S#,Sname,Sage,Ssex) 学生表 S#:学号:Sname:学生姓名:Sage:学生年龄:Ssex:学生性别 Cour ...