定义 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++ 基类构造函数带参数的继承方式及派生类的初始化 定义拷贝构造函数 [注意]对派生类进行拷贝构造时,如果想让基类的成 ...
随机推荐
- 问答项目---登陆账号密码登陆做AJAX异步校验
异步验证管理员帐号方法: /* 异步验证管理员帐号 */ public function checkAccount(){ if(!IS_AJAX){echo "页面不存在";die ...
- JavaMVC框架之SpringMVC
欢迎查看Java开发之上帝之眼系列教程,如果您正在为Java后端庞大的体系所困扰,如果您正在为各种繁出不穷的技术和各种框架所迷茫,那么本系列文章将带您窥探Java庞大的体系.本系列教程希望您能站在上帝 ...
- MVC学习之简单的CRUD
1.一点知识的总结 (1)MVC将展示页面和后台处理逻辑分离,不像ASPX中展示页面继承自后台的cs页面,MVC展示页面继承自ViewPage<dynamic>,最终继承自Page(使用A ...
- explain 分析 聚合统计语句的性能
EXPLAIN SELECT COUNT(1) FROM question; id select_type table partitions type possible_keys key key_le ...
- Django - rest - framework - 上
一.快速实例 http://www.django-rest-framework.org/tutorial/quickstart/#quickstart http://www.cnblogs.com/y ...
- 个人理解---KMP与Next数组详解
Kmp就是求子串在母串中的位置等相关问题:当然KMP最重要的是Next数组,也称失败数组,Next[i]代表的意思是子串 sub 从sub[0] 到 sub[i-1]的前缀和后缀的最大匹配.模拟KMP ...
- 使用linuxbridge + vlan网络模式
#openstack pike 使用 linuxbridge + vlan openstack pike 集群高可用 安装部署 汇总 http://www.cnblogs.com/elvi/p/76 ...
- 针对Redis队列的理解,实例操作(转)
原文:本文出自 “峰云,就她了.” http://rfyiamcool.blog.51cto.com/1030776/1131271 为什么要使用消息队列 用我的话来说, 队列特点是先进先出,在任务 ...
- glassfish3新建domain
下载路径:http://download.oracle.com/glassfish/3.1.2.2/release/index.html .zip (解压缩)cd /glassfish3/glassf ...
- log4j2介绍及配置
一.log4j2概述 在日常的开发,测试和生产环境中,日志记录了应用,服务运行过程中的关键信息,以及出现异常时的堆栈,这些信息常常作为查询,定位,解决问题的关键,因此在任何系统中,对日志的使用得当,将 ...