R语言 面向对象的编程
在这篇文章中,我们将讨论R编程语言中的面向对象编程(OOPs)。我们将讨论S3和S4类,这些类的继承性,以及这些类提供的方法。
在R编程中,OOPs提供类和对象作为其关键工具,以减少和管理程序的复杂性。R是一种使用OOPs概念的功能语言。我们可以把一个类看作是一辆汽车的草图。它包含了所有关于车型名称、车型编号、发动机等的细节。基于这些描述,我们选择一辆汽车。汽车是一个对象。每个汽车对象都有自己的特点和特征。一个对象也被称为一个类的实例,创建这个对象的过程被称为实例化。在R语言中,S3和S4类是面向对象编程的两个最重要的类。但在讨论这些类之前,让我们先看看关于类和对象的简介。
类和对象
类是一个蓝图或原型,通过封装数据成员和函数,对象是由它构成的。一个对象是一个数据结构,它包含一些作用于其属性的方法。
S3类
S3类没有预定义的定义,能够进行调度。在这个类中,通用函数对方法进行了调用。轻松实现S3是可能的,因为它不同于传统的编程语言Java、C++和C#,后者实现了面向对象的消息传递。
创建S3类
为了创建这个类的一个对象,我们将创建一个包含所有类成员的列表。然后将这个列表作为参数传递给class()方法。
语法
variable_name <- list(attribute1, attribute2, attribute3....attributeN)
例子
在下面的代码中,定义了一个学生类。给出了一个适当的类名,其属性是学生的姓名和学号。然后,学生类的对象被创建和调用。
# List creation with its attributes name
# and roll no.
a < - list(name="Adam", Roll_No=15)
# Defining a class "Student"
class(a) < - "Student"
# Creation of object
a
输出
$name
[1] "Adam"
$Roll_No
[1] 15
attr(, "class")
[1] "Student"
泛型函数
通用函数是多态性的一个好例子。为了理解通用函数的概念,请考虑print()函数。print()函数是各种打印函数的集合,这些函数是为R编程语言中不同的数据类型和数据结构创建的。它根据作为参数传递的对象的类型,调用适当的函数。我们可以通过methods()函数看到打印函数的各种实现。
例子: 看到不同类型的打印函数
methods(print)
输出
现在让我们创建一个属于我们自己的通用函数。我们将为我们的类创建打印函数,它将以我们指定的格式打印所有的成员。但在创建打印函数之前,让我们先创建打印函数对我们类的作用。
例子
# List creation with its attributes name
# and roll no.
a = list(name="Adam", Roll_No=15)
# Defining a class "Student"
class(a) = "Student"
# Creation of object
print(a)
输出
$name
[1] "Adam"
$Roll_No
[1] 15
attr(,"class")
[1] "Student"
现在让我们以我们指定的格式打印所有的成员。考虑一下下面的例子 –
例子
print.Student <- function(obj){
cat("name: " ,objname, "\n")
cat("Roll No: ", objRoll_No, "\n")
}
print(a)
输出
name: Adam
Roll No: 15
属性
对象的属性不影响对象的值,但它们是用于处理对象的额外信息。函数attributes()可以用来查看一个对象的属性。
例子: 一个S3对象被创建,其属性被显示。
attributes(a)
输出
$names
'name''Roll_No'
$class
'Student'
另外,你可以通过使用attr来给一个对象添加属性。
attr(a, "age")<-c(18)
attributes(a)
输出
$names
'name''Roll_No'
$class
'Student'
$age
18
S3类中的继承性
继承是OOP(面向对象编程)的一个重要概念,它允许一个类派生出另一个类的特征和功能。这个特点有利于代码的重复使用。
R编程语言中的S3类没有正式和固定的定义。在一个S3对象中,一个带有其类属性的列表被设置为一个类名。S3类对象只继承其基类的方法。
例子
在下面的代码中,使用S3类进行了继承,首先创建了学生类的对象。
# student function with argument
# name(n) and roll_no(r)
student < - function(n, r) {
value < - list(name=n, Roll=r)
attr(value, "class") < - "student"
value
}
然后,该方法被定义为打印学生的详细信息。
# 'print.student' method created
print.student < - function(obj) {
# 'cat' function is used to concatenate
# strings
cat("Name:", objname, "\n")
cat("Roll", objroll, "\n")}
现在,在创建另一个类时,通过做**class(obj) <- c(child, parent) **来完成继承。
s < - list(name="Kesha", Roll=21, country="India")
# child class 'Student' inherits
# parent class 'student'
class(s) < - c("Student", "student")
s
输出
Name: Kesha
Roll: 21
下面的代码覆盖了类student的方法。
# 'Student' class object is passed
# in the function of class 'student'
print.student < - function(obj) {
cat(objname, "is from", objcountry, "\n")
}
s
输出
Kesha is from India
S4类
S4类有一个预定义的定义。它包含了定义方法和泛型的功能。它使多次派发变得容易。这个类包含用于定义方法和泛型的辅助函数。
创建S4类和对象
setClass() 命令被用来创建S4类。以下是setclass命令的语法,它用包含名称和rollno的槽来表示我的类。
语法
setClass("myclass", slots=list(name="character", Roll_No="numeric")
new() 函数用于创建一个S4类的对象。在这个函数中,我们将传递类的名称以及槽的值。
例子
# Function setClass() command used
# to create S4 class containing list of slots.
setClass("Student", slots=list(name="character",
Roll_No="numeric"))
# 'new' keyword used to create object of
# class 'Student'
a <- new("Student", name="Adam", Roll_No=20)
# Calling object
a
输出
Slot "name":
[1] "Adam"
Slot "Roll_No":
[1] 20
从生成器函数创建S4对象
setClass()返回一个生成器函数,该函数有助于创建对象,它就像一个构造函数。
例子
stud <- setClass("Student", slots=list(name="character",
Roll_No="numeric"))
# Calling object
stud
输出
new(“classGeneratorFunction”, .Data = function (…)
new(“Student”, …), className = structure(“Student”, package = “.GlobalEnv”),
package = “.GlobalEnv”)
现在,上述创建的stud函数将作为学生类的构造函数。它将表现为new()函数。
例子
stud(name="Adam", Roll_No=15)
输出
An object of class "Student"
Slot "name":
[1] "Adam"
Slot "Roll_No":
[1] 15
S4类中的继承性
R编程中的S4类有适当的定义,派生类将能够继承其基类的属性和方法。为此,我们将首先创建一个带有适当槽的基类,并为该类创建一个通用函数。然后我们将创建一个派生类,它将使用包含参数来继承。派生类将继承基类中的成员和函数。
例子
# Define S4 class
setClass("student",
slots=list(name="character",
age="numeric", rno="numeric")
)
# Defining a function to display object details
setMethod("show", "student",
function(obj){
cat(obj@name, "\n")
cat(obj@age, "\n")
cat(obj@rno, "\n")
}
)
# Inherit from student
setClass("InternationalStudent",
slots=list(country="character"),
contains="student"
)
# Rest of the attributes will be inherited from student
s < - new("InternationalStudent", name="Adam",
age=22, rno=15, country="India")
show(s)
输出
Adam
22
15
同时定义S3和S4类的原因 如下。
- 如果直接调用S3泛型函数,将不会单独看到S4类。例如,如果某个函数从一个包中调用 unique() ,而这个包并没有让该函数成为S4泛型,就会出现这种情况。
- 然而,原始函数和运算符是例外。内部C代码将寻找S4方法,如果且仅当对象是一个S4对象。S4方法调度将用于调度任何二进制运算符的调用,其中任何一个操作数是S4对象。
- 如果有任何符合条件的非默认的S4方法,S3类将不会被单独调用。
因此,如果一个包为一个S4类定义了一个唯一的S3方法,但另一个包为该类的超类定义了一个S4方法,那么超类的方法将被选择,可能不是原意。