定义 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++ 基类构造函数带参数的继承方式及派生类的初始化 定义拷贝构造函数 [注意]对派生类进行拷贝构造时,如果想让基类的成 ...
随机推荐
- GOOGLE和百度的长域名
GOOGLE的变态域名:www.mamashuojiusuannizhucedeyumingzaichanggoogledounengsousuochulai.cn/中文拼音:“妈妈说就算你注册的域名 ...
- shell脚本备份日志
#!/bin/sh # back tomcat catalina.out cd /home/log_bak #the file DATE=`date '+%Y%m%d-%H%M'` ARCHIVE=$ ...
- wamp环境解决局域网不能访问的问题!
安装好wamp后,想用手机通过局域访问电脑上wamp下的网页,结果出现如下提示403错误: 第一步:找到 conf 这个文件: 找到下图中红色方框中的onlineoffline tag - don’t ...
- spriing boot 启动报错:Cannot determine embedded database driver class for database type NONE
最近在学习使用spring boot.使用maven创建好工程,只引用需要用到的spring boot相关的jar包,除此之外没有任何的配置. 写了一个最简单的例子,如下所示: package com ...
- Spring Boot - Building RESTful Web Services
Spring Boot Building RESTful Web Services https://www.tutorialspoint.com/spring_boot/spring_boot_bui ...
- nginx:服务器集群
一.Nginx的事件处理机制 对于一个基本的web服务器来说,事件通常有三种类型,网络事件.信号.定时器. 首先看一个请求的基本过程:建立连接---接收数据---发送数据 . 再次看系统底层的操作 : ...
- 【Python】自动化测试框架-共通方法汇总
1.滚动滚动条(有的时候页面元素element取得对但是并没有回显正确的数据,可能是因为页面第一次加载很慢,所以页面可能做了滚动到哪里就加载到哪里的效果,此刻我们就需要用到滚动条自动滚动这段代码让页面 ...
- ThinkPhp3.2.3 多项目 后台 APP接口设计 框架设计
↓↓↓项目文件组成部分↓↓↓ APP文件是后台,index.php是入口文件 Interface文件是接口,注意这里不要用api命名!可能会有问题!interface.php是入口文件 注:两个入口文 ...
- Mac Atom的PHP插件
首先,需要在 ~/.atom目录下创建 .atom文件,写入如下内容: strict-ssl = false http_proxy = socks5://127.0.0.1:16888 https_p ...
- Jenkins时区设置
系统管理->脚本命令行 System.setProperty('org.apache.commons.jelly.tags.fmt.timeZone', 'Asia/Shanghai')