面向对象编程

目录

layout: post title: “面向对象编程” date: 2021-06-11 description: “介绍面向对象编程的一些概念,例子”

tag: 对象 类 Class

对象、类、实例、接口、共有、私有、方法、属性

背景

定义: 是现实世界的抽象与简化,决定可使用数据对象的操作或方法

  1. 首先,必须考虑如何表示股票。如stock类(股票),可以将一股作为基本单位,定义一个表示一股股票的类,但是这意味着需要100个对象才能表示100股,这不现实。可以将某人持有的某种股票作为一个基本单元,数据表示中包含他持有的股票数量。首次定义类考虑很多因素有些困难,因此可以对其简化,如可以将可执行操作限制为:

    获取股票;增持;卖出股票;更新股票价格;显示关于所持有股票的信息

  2. 可以根据上述清单定义stock类的共有接口,还可以定义其他特性。为支持该接口,需要存储一些信息。再次进行简化,我们将存储下面的信息:

    公司名称;所持股票的数量;每股的价格;股票总值

  3. 接下来定义类,一般包括

    ​ 类的声明:以数据成员方式描述数据部分;以成员函数(方法)描述的方式描述共有接口

    ​ 类方法定义:描述如何实现类成员函数

  4. 要编写类,必须创建其公共接口,来操作(私有)的数据。共有函数是程序和对象的私有成员之间的桥梁,提供了对象和程序之间的接口。数据通常放在私有部分,以隐藏数据。

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图书无特别优惠