layout: post title: “面向对象编程” date: 2021-06-11 description: “介绍面向对象编程的一些概念,例子”
tag: 对象 类 Class
对象、类、实例、接口、共有、私有、方法、属性
背景
类
定义: 是现实世界的抽象与简化,决定可使用数据对象的操作或方法
-
首先,必须考虑如何表示股票。如stock类(股票),可以将一股作为基本单位,定义一个表示一股股票的类,但是这意味着需要100个对象才能表示100股,这不现实。可以将某人持有的某种股票作为一个基本单元,数据表示中包含他持有的股票数量。首次定义类考虑很多因素有些困难,因此可以对其简化,如可以将可执行操作限制为:
获取股票;增持;卖出股票;更新股票价格;显示关于所持有股票的信息
-
可以根据上述清单定义stock类的共有接口,还可以定义其他特性。为支持该接口,需要存储一些信息。再次进行简化,我们将存储下面的信息:
公司名称;所持股票的数量;每股的价格;股票总值
-
接下来定义类,一般包括
类的声明:以数据成员方式描述数据部分;以成员函数(方法)描述的方式描述共有接口
类方法定义:描述如何实现类成员函数
-
要编写类,必须创建其公共接口,来操作(私有)的数据。共有函数是程序和对象的私有成员之间的桥梁,提供了对象和程序之间的接口。数据通常放在私有部分,以隐藏数据。
R6对象
-
设置公有变量部分(内含可见的参数、初始化函数等)
-
设置私有变量(内可以包含安全级别高的一些变量、参数、函数)
-
创建对象
hellobi <- R6Class( "hellobi", public = list( i = NA, fullinfo = NA, headers = NA, #初始化函数 initialize = function(i,fullinfo,headers) { #以下主要是进行参数检查并进行分配初始化参数 if (!missing(i)) self$i <- i if (!missing(fullinfo)) self$fullinfo <- fullinfo if (!missing(headers)) self$headers <- headers }, #方法调用(这里我将爬虫程序定义在私有域内,然后在公有域内进行引用) GetData = function() { private$Crawler() } ), #定义私有域(这里私有域主要定义爬虫程序) private = list( Crawler = function(){ d <- debugGatherer() handle <- getCurlHandle(debugfunction=d$update,followlocation=TRUE,cookiefile="",verbose = TRUE) while (self$i < 10){ self$i <<- self$i + 1 url <- sprintf("https://www.hellobi.com/jobs/search?page=%d",self$i) tryCatch({ content <- getURL(url,.opts=list(httpheader=self$headers),.encoding="utf-8",curl=handle) %>% htmlParse() job_item <- content %>% xpathSApply(.,"//div[@class='job_item_middle pull-left']/h4/a",xmlValue) job_links <- content %>% xpathSApply(.,"//div[@class='job_item_middle pull-left']/h4/a",xmlGetAttr,"href") job_info <- content %>% xpathSApply(.,"//div[@class='job_item_middle pull-left']/h5",xmlValue,trim = TRUE) job_salary <- content %>% xpathSApply(.,"//div[@class='job_item-right pull-right']/h4",xmlValue,trim = TRUE) job_origin <- content %>% xpathSApply(.,"//div[@class='job_item-right pull-right']/h5",xmlValue,trim = TRUE) myreslut <- data.frame(job_item,job_links,job_info,job_salary,job_origin,stringsAsFactors = FALSE) self$fullinfo <<- rbind(self$fullinfo,myreslut) cat(sprintf("第【%d】页已抓取完毕!",self$i),sep = "\n") },error = function(e){ cat(sprintf("第【%d】页抓取失败!",self$i),sep = "\n") }) Sys.sleep(runif(1)) } cat("all page is OK!!!") return (self$fullinfo) } ) )
-
创建类实例
mydata <- hellobi$new( i =0, fullinfo = data.frame(), headers = c( Referer = "https://www.hellobi.com/jobs/search", `User-Agent` = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36" ) )
-
调用类中的方法执行爬虫程序:
mydatainfo2 <- mydata$GetData()
R6类
如何创建R6类
R6Class()来创建类,该函数定义,
R6Class
function(classname=NULL,public=list(),private=NULL,activate=NULL,inherit=NULL,lock=TRUE,class=TRUE,portable=TRUE,parent_env=parent.frame())
参数列表:
- classname 定义类名
- public 定义共有成员,包括公有方法和属性
- private 定义私有成员,包括私有方法和属性
- active 主动绑定的函数列表
- inherit 定义父类,继承关系
- lock 是否上锁,如果上锁则用于变量存储的环境空间被锁定,不能修改
- class 是否把属性封装成对象,默认是封装,如果选择不封装,类中属性存在一个环境空间中
- portable 是否可移植类型,默认是可移植型类,类中成员访问需要调用self和private对象
- parent_env 定义对象的父环境空间
- self对象,就像实例化的对象本身
公有成员
Person <- R6Class("Person",
public = list(
name = NULL,
hair = NULL,
initialize = function(name = NA, hair = NA) {
self$name <- name
self$hair <- hair
self$greet()
},
set_hair = function(val) {
self$hair <- val
},
greet = function() {
cat(paste0("Hello, my name is ", self$name, ".\n"))
}
)
)
ann <- Person$new("Ann", "black")
ann$hair
ann$greet()
#> Hello, my name is Ann.
ann$set_hair("red")
ann$hair
#> [1] "red"
私有成员
Queue <- R6Class("Queue",
public = list(
initialize = function(...) {
for (item in list(...)) {
self$add(item)
}
},
add = function(x) {
private$queue <- c(private$queue, list(x))
invisible(self)
},
remove = function() {
if (private$length() == 0) return(NULL)
# Can use private$queue for explicit access
head <- private$queue[[1]]
private$queue <- private$queue[-1]
out <- list('value'= head,
#'len' = length(private$queue)
'len' = private$length())
return(out)
}
),
private = list(
queue = list(),
length = function() return(base::length(private$queue))
)
)
q <- Queue$new(5, 6, "foo")
q <- Queue$new(5, 6, "foo")
q$add("something")
q$add("another thing")
q$add(17)
q$remove()
主动绑定
通过主动绑定,可以把函数的行为转换成属性的行为,让类中额函数操作更加灵活。
Person <- R6Class("Person",
public = list(
num=100
),
active = list( # 主动绑定
active= function(value){
if(missing(value))
return (self$num+10)
else self$num <- value/2},
rand = function() rnorm(1)
)
)
conan <- Person$new()
conan$num # 查看公有属性
## [1] 100
conan$active #调用主动绑定的active()函数,结果为num +10 = 100+10
## [1] 110
# 给主动绑定额active函数传参书,用赋值符号"<-",而不是方法调用"()"
conan$active <- 20
conan$num
## [1] 10
conan$active
## [1] 20
R6继承关系
继承是函数面向对象的基本特征,R6的面向对象系统也是支持继承的。当创建一个类时,可以继承另一个类作为父类存在。先创建一个父类Person,包括共有成员和私有成员
子类会继承父类的方法和属性;如果在子类中定义父类同名方法,调用时会忽略父类方法(覆盖)。
如果在子类中像调用父类的方法,有一个办法是使用super对象,通过super$xx()的语法进行调用。
Person <- R6Class("Person",
public = list(
name=NA,
initialize = function(name,gender){
self$name <- name
private$gender <- gender
},
hello = function(){
print(paste("hello",self$name))
private$myGender()
}
),
private=list(
gender = NA,
myGender = function(){
print(paste(self$name,"is",private$gender))
}
))
Worker <- R6Class("Worker",
inherit = Person, #继承,指向父类
public = list(
bye = function(){
print(paste("bye",self$name))
}
)
)
u2 <- Worker$new("Conan","Male") # 实例化子类
u2$hello()
u2$bye()
## [1] "bye Conan"
Worker <- R6Class("Worker",
inherit = Person,
public = list(
bye = function(){
print(paste("bye",self$name))
}
),
private = list(
gender = NA,
myGender = function(){
super$myGender()# 调用父类的方法
print(paste("worker",self$name,"is",private$gender))
}
))
R6类的对象的静态属性
用面向对象的方法进行编程,那么所有变量其实都是对象,我们可以把一个实例化的对象定义成另一个类的属性,这样就形成了对象的引用关系链。
需要注意的是,当属性赋值给另一个R6的对象时,属性的值保存了对象的引用,而非对象实例本身。利用这个规则就可以实现对象的静态属性,也就是可以在多种不同的实例中是共享对象属性
从输出结果上来看,a对象实现了在多个b实例的共享,当b2实例修改a对象x值得时候,b实例的a对象的x值也发生了变化。
这里有一种写法,我们是应该避免的,就是通过initialize()方法赋值
A <- R6Class("A",
public=list(
x = NULL
))
B <- R6Class("B",
public=list(
a = A$new()
))
b <- B$new() # 实例化B对象
b$a$x <- 1 # 给x变量赋值
b$a$x
## [1] 1
b2 <- B$new()
b2$a$x <-2
b2$a$x
## [1] 2
b$a$x
## [1] 2
R6类的可移植类型
在R6类的定义中,portable参数可以设置R6类的类型为可移植类型和不可移植类型。可移植类型和不可移植类型主要有两个明显的特征。
- 可移植类型支持跨R包的继承;不可移植类型,在跨R包的继承的时候,兼容性不太好
- 可移植类型必须用self和private对象来访问类中的成员,如self$x.private$y。不可移植类型,可以直接使用变量x,y,并通过“«-”(超赋值)实现赋值。
A <- R6Class("A",
public = list(
x = 1,
getx = function() x
))
A$set("public","getx2",function() self$x*2) # 动态增加getx2()方法
s <- A$new()
# 动态修改方法
A$set("public","getx2",function() self$x*2) # 动态增加getx2()方法
# 动态修改属性
A$set("public","x",10,overwrite=TRUE) # 动态改变x属性
实例
Book <- R6Class("Book",
private = list(
title = NA,
price= NA,
category = NA
),
public = list(
initialize = function(title,price,category){
private$title <- title
private$price <- price
private$category <- category
},
getPrice = function(){
private$price
}
))
R <- R6Class("R",inherit=Book)
Java <- R6Class("Java",inherit=Book)
Php <- R6Class("Php",inherit=Book)
r1 <- R$new("R的极客思想",59,"R")
r1$getPrice()
## [1] 59
j1 <- Java$new("Java编程思想",108,"Java")
j1$getPrice()
## [1] 108
p1 <- Php$new("head First PHP & MySQL",98,"PHP")
p1$getPrice()
## [1] 98
Book$set("public","getPrice2",function() private$price*0.9) # 动态增加getx2()方法
R$set("public","getPrice2",function() private$price*0.7*0.9) # 动态增加getx2()方法
Java$set("public","getPrice2",function() private$price*0.7) # 动态增加getx2()方法
任务2:双11图书打折
- 所有图书9折
- Java图书7折,不支持重复打折
- R打7折,支持重复打折
- PHP图书无特别优惠