ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Programming Languages PartC Week1学习笔记——Ruby与面向对象编程

2022-09-14 21:05:16  阅读:566  来源: 互联网

标签:PartC den get 方法 self Programming send 面向对象编程 Ruby


@

目录

Introduction to Ruby

终于来到了我们比较熟悉的领域,OOP

image-20220607105302547

我们关注的Ruby的特性,最重要的是“纯面向对象语言”,基于类Class-based,动态类型。

image-20220607105837127

我们不关注的部分:

image-20220607110031886

特性对比:

image-20220607110137224

image-20220607110304554

例子:

class Hello
    def my_first_method
        puts "Hello, World!"
    end
end
x = Hello.new
x.my_first_method

在CMD中运行Ruby程序使用ruby命令,通过irb命令使用Ruby的REPL:

image-20220607110708796

Classes and Objects

基于类的OOP规则

image-20220607161842813

image-20220607162121309

例子:

image-20220607162258442

image-20220607162433838

创建和使用对象,这部分语法都很熟悉的。

image-20220607162656198

变量以任意字母作为变量名的首字母,需要注意变量是可修改的mutable(修改内容还是修改引用?)

image-20220607162748331

关于self,跟python的self一样。语法糖也类似,在相同对象中调用方法可以省略self

image-20220607163331891

Ruby中self的常见用法,直接使用方法返回self本身,例如:

image-20220607163643848

image-20220607163631036

最后,在Ruby中,每条语句换行很重要,如果多条语句放在同一行,那就需要使用分号“ ; ”分割。另外,跟python不一样,Ruby中缩进(indentation)对语义(semantics)不重要,只是良好的代码风格而已。

Object State

对象拥有自己的状态。状态组成实例变量(fields),只能被对象方法访问,state变量以@开头。如果使用了某个没有的state变量,会生成nil对象

image-20220607164510038

对象变量赋值创建了一个别名(引用),这时他们具有同样的state。

但如果new一个新的对象的引用,创建的对象就有不同的state

image-20220607165252447

例子:

image-20220607170919573

image-20220607170952331

image-20220607171140572

例子2:initialize特殊方法,用于创建对象实例时初始化,相当于构造函数。

image-20220607171241186

Ruby中因为不要求所有变量都预先声明并且初始化,相同类的不同对象实例可以有不同的实例变量。

image-20220607172125116

通过@@关键词声明类变量,也就是类的静态变量

image-20220607172310283

类常量和类方法,就是类中的静态常量和静态方法

类常量用在类中以大写字母开头,不能被修改。在外部需要使用类名修饰访问,例如C::Foo

类方法定义需要使用self.method_name来定义(说实话有点奇怪),它属于类本身,所以需要使用类名来调用

image-20220607172424494

Visibility

image-20220609164553258

Ruby中,对象的状态state总是private的,同一个类的不同对象也无法访问。通常定义getter和setter来让对象state可见(跟JAVA的POJO类似)

image-20220609164634690

Ruby的语法糖。用state变量的名称来直接定义一个getter,用变量名加=来定义一个setter(并且以等号结尾地方法在调用时可以在等号前面加空格)。这样相当于将state封装成了一个普通的成员变量。

同时,getter/setter还有简记的定义方式(类似java的注解)

image-20220609165144560

为何需要private的对象state:

image-20220609165613740

Ruby的三种visibility,public是默认值:

public在任何类中都能调用

protected在相同类或者子类中能够调用

private只有在相同实例中能调用

image-20220610123445498

三种visibility访问权限的使用方法类似C++

image-20220610123625970

Ruby中比较特别的一个细节,对于private的成员或方法,必须通过简化的方式,即m或m(args)来调用(唯一方式),不能通过self来访问。

image-20220610124209960

A Longer Example

本节以有理数类为例,综合应用前几节的内容:

image-20220610161950604

该例子中展现了很多Ruby的语法特性:

# Section 7: A Longer Example

class MyRational

  def initialize(num,den=1) # second argument has a default
    if den == 0
      raise "MyRational received an inappropriate argument"
    elsif den < 0 # notice non-english word elsif
      @num = - num # fields created when you assign to them
      @den = - den
    else
      @num = num # semicolons optional to separate expressions on different lines
      @den = den
    end
    reduce # i.e., self.reduce() but private so must write reduce or reduce()
  end

  def to_s 
    ans = @num.to_s
    if @den != 1 # everything true except false _and_ nil objects
      ans += "/"
      ans += @den.to_s 
    end
    ans
  end

  def to_s2 # using some unimportant syntax and a slightly different algorithm
    dens = ""
    dens = "/" + @den.to_s if @den != 1
    @num.to_s + dens
  end

  def to_s3 # using things like Racket's quasiquote and unquote
    "#{@num}#{if @den==1 then "" else "/" + @den.to_s end}"
  end

  def add! r # mutate self in-place
    a = r.num # only works b/c of protected methods below
    b = r.den # only works b/c of protected methods below
    c = @num
    d = @den
    @num = (a * d) + (b * c)
    @den = b * d
    reduce
    self # convenient for stringing calls
  end

  # a functional addition, so we can write r1.+ r2 to make a new rational
  # and built-in syntactic sugar will work: can write r1 + r2
  def + r
    ans = MyRational.new(@num,@den)
    ans.add! r
    ans  # 因为add!返回ans运算后的self,因此这一句其实不需要
  end
    
protected  
  # there is very common sugar for this (attr_reader)
  # the better way:
  # attr_reader :num, :den
  # protected :num, :den
  # we do not want these methods public, but we cannot make them private
  # because of the add! method above
  def num
    @num
  end
  def den
    @den
  end

private
  def gcd(x,y) # recursive method calls work as expected
    if x == y
      x
    elsif x < y
      gcd(x,y-x)
    else
      gcd(y,x)
    end
  end

  def reduce
    if @num == 0
      @den = 1
    else
      d = gcd(@num.abs, @den) # notice method call on number
      @num = @num / d
      @den = @den / d
    end
  end
end

# can have a top-level method (just part of Object class) for testing, etc.
def use_rationals
  r1 = MyRational.new(3,4)
  r2 = r1 + r1 + MyRational.new(-5,2) # (r1.+(r1)).+ (...)
  puts r2.to_s
  (r2.add! r1).add! (MyRational.new(1,-4))
  puts r2.to_s
  puts r2.to_s2
  puts r2.to_s3
end

Everything is an Object

纯粹的面向对象语言

可以在任何东西(都是对象)上调用方法,如果方法不存在则抛出“undefined method”异常,不需要判断某个东西是不是可以调用方法(都能调用,但方法不一定都存在)。

几乎所有东西都是方法调用,包括基本运算等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ssJwFzDF-1663159644531)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611155402688.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dM12k3JW-1663159644532)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611160920913.png)]

关于nil:在Ruby中用来描述某些没有包含任何数据的对象。类似于ML的unit,或者java的null。重点是nil也是一个对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1xqKF3Be-1663159644532)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611161246206.png)]

另外,nil counts as false,nil在逻辑判断时相当于false。因此Ruby中,false代表fasle,nil也可以代表false

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fBUncJ1c-1663159644533)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611161456293.png)]

所以,Ruby中任何结果都是对象,任何操作都是对象方法的调用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JHmwlaAa-1663159644533)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611161706403.png)]

反射,了解java应该对这个词不陌生,指程序能在运行时获取一个(类)对象,查询“对象能做的事”并随之响应的能力:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v8jld8UG-1663159644533)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611161902045.png)]

ruby中的用法之一,查询可调用的方法(甚至可以在两个对象的methods结果之间做运算,下图是在3的类方法中而不在nil类方法中的方法):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p8pZyU8g-1663159644534)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611163037777.png)]

Class Definitions are Dynamic

Ruby能够在运行时操作对象,添加改变替换方法,某些时候会有用,但会破坏抽象和封装。在大型语言中是有争议的。

image-20220613151725084

image-20220614121901761

甚至能够向内部类添加新方法

image-20220614121956960

由于所有的东西都是对象,即使是顶层函数,也是Object的对象,因此直接用def定义顶层函数,相当于添加新Object的方法。然后由于所有对象都直接或间接继承自Object,因此顶层函数可以作为每个对象的方法使用。例如:

image-20220614122759257

直接定义在Object中的方法与顶层函数相同,重复名称的方法会覆盖:

image-20220614122930274

但是,对Ruby预定义的运算符覆盖时一定要当心,例如:

class Fixnum
    def + x
        13
    end
end

这段代码覆盖了原有+运算符的定义,此时Ruby中一切的Fixnum都会受到影响,因此irb(REPL)会崩溃,因为irb本身也是Ruby编写的,会被这个覆盖影响。

(可见,Ruby给运行时程序的权力过大还是挺危险的)

image-20220614123516632

Duck Typing

面向对象中经典的鸭子类型:

在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的,实际上是一种不严格的多态实现。

image-20220614163229800

例子:

image-20220614165652765

得益于动态类型语言的鸭子类型某些时候可能是有用的,但也在一定程度上属于poor style(如果使用者调用了duck typing,就难以更改函数的内部实现了,比如double那个例子,如果有字符串参数调用过这个方法,就不能再改成x * 2的实现方式了)。因此,这种不严格的多态方式也可能带来问题。

image-20220614165940065

Arrays

Array类提供了Ruby的数组类型

image-20220614195914906

Ruby数组具有与python的list类似的语法,但注意Ruby数组在超出引用时会返回nil而不是抛出异常。

image-20220615135729852

同样,由于是动态类型语言,array允许不同数据类型;并且允许使用重载的运算符+作为作为concat方法。

image-20220615135816575

image-20220615140146768

Array的初始化方法(具体的语法需要去翻阅文档)

image-20220615140338253

然后是push和pop,从右侧入栈出栈方法

image-20220615140456938

利用shift和unshift,从左侧入栈出栈方法

image-20220615140828227

Array对象变量仍然是一个引用,因此

image-20220616102220863

值得注意的是,多维数组是引用的引用(类似C++指针的指针),因此a+[]只能改变b的外层引用,但内层地址指向的一维数组没变。

image-20220616102621609

对数组的切片操作,例如f[2,4]表示从idx为2的元素开始,取4个元素。同时,切片的替换并不需要对应数量的数组(这一点很灵活)。

image-20220616104607061

Array可以调用一些迭代方法,例如each

image-20220616104915900

Blocks

代码块,又一个与闭包相关的重要概念。代码块基本上就是一种闭包。

image-20220616105227691

image-20220616105549578

一些奇怪的事情(block的语法)。{}可以用do end来代替。

image-20220616105609854

标准库函数通过代码块定义了大量有用的高等函数,代码块充当了其他函数中一等函数闭包的作用。

image-20220616105937251

代码块在Ruby中非常强大,甚至在初始化Array时都能使用

image-20220616110640707

对于any?和all?方法,如果没有参数,就默认去判断每个元素是否为真(注意Ruby中,只有false和nil是假)

image-20220616110937069

常用高等方法:

方法名 常见作用
map / collect 与Racket的map类似,对每个元素使用f方法
inject 与Racket的reduce类似,对每个函数用f方法进行累计
select 类似filter

image-20220616113531809

代码块嵌套的使用,其中(0..i)是序列(range),与python的range类似

image-20220616115506833

Using Blocks

定义使用Block的函数时,通过yield关键词代替Block(类似匿名函数)。

Snipaste_2022-06-16_16-12-53

例子,这个例子用到了递归,并且递归传递了Block

image-20220616162245314

Procs

Block实际上是第二级,能通过yield来调用,但不能在一个对象中被传递、返回和存储。它不是真正的闭包,但可以转换成真正的闭包。(Block不是对象!)

闭包是Proc类的实例,Proc才是真正的一等表达式。

Obeject的lambda表达式可以通过一个Block来生成Proc实例闭包。

image-20220616170142003

例子:

例如Block难以实现类似Partial Application的应用,但如果使用Proc就能做到

# 由于Block不能传递,不能用Block作为存储对象
c = a.map {|x| {|y| x >= y }}
# 但Proc实例可以传递
c = a.map {|x| lambda {|y| x >= y} }

并且,Proc实例通过call方法调用其中的代码块。

image-20220616171344832

这让我们更加理解“First Class”

image-20220616172015457

Hashes and Ranges

(1)Hash

相当于Racket的python的字典或动态的ML的Record

语法类似python的字典

image-20220616172314534

能够根据键在hash表中添加值

image-20220616172810548

也能用键值对初始化hash表,使用=>连接键值。

由于和Array是不同的数据结构,hash的each方法需要两个参数(key和value)

image-20220616172859145

(2)Range

类似于用来动态存储一系列连续数的数组,但不是数组而是单独的数据结构,类似python的range,可以用于迭代。同时,Range相比之下更高效。

image-20220616173518300

两个优秀的风格:

  • 尽可能使用Range

  • 非数字索引时使用Hash

Hash和Range拥有一些与Array相同的方法(迭代器方法),这也是对duck typing有利。

image-20220616173857724

Subclassing

回到面向对象的部分,介绍Ruby的继承

image-20220622152540770

子类定义:

子类会继承超类所有的方法,而不会有继承权限的问题(例如C++和Java)。子类也不会被类型检测,能访问所有方法和成员。

image-20220622152617905

image-20220622220641263

注意反射机制:

image-20220622220924615

另外,与所有OOP语言类似,子类都(is)超类,所以子类实例(子类)是属于的超类

image-20220622221002459

但需要区分的是,子类实例本身并不是超类的实例,类有继承关系,但子类对象实例不是超类对象实例。

image-20220622221355682

注意,is_a? 或者 instance_of?之类的方法并不通常是OOP style,因为我们假设了使用的对象是 某个假定的类或类对象,它拒绝了duck typing(我们不能使用一个具有类似x,y,color等 成员和方法的其他类对象)。

image-20220622221419406

Why Use Subclassing?

image-20220623212323926

image-20220623212422873

image-20220623212632495

image-20220623212750729

Overriding and Dynamic Dispatch

例子

image-20220623225532227

image-20220623225549016

对象与闭包在很多特性上类似,但最大的不同是覆写可以让一个方法定义在超类中但在子类中调用。

image-20220623225717580

image-20220623230417661

image-20220623230527983

image-20220623230711107

image-20220623231010363

Method-Lookup Rules, Precisely

动态分派(dynamic dispatch),比较熟知的名字是后期绑定或者虚函数,用于在运行期选择调用方法的实现的流程。被认为是面向对象语言(Object-Oriented programming:OOP)的基本特性。

定义方法查找语法是需要的。

image-20220624205053885

回顾各种语言中的变量查找过程。

image-20220624205348869

Ruby中使用self来查找各种类或实例的成员或方法。

image-20220624205539680

Ruby的方法查找:

image-20220624205801610

image-20220624210107516

动态分派的过程比闭包更复杂,必须将self特殊对待。

image-20220624210157295

在Java等语言中,方法查找规则是类似的,但是更复杂(因为方法在类中能具有相同名字(重载Overload)),只有在方法名与参数数量、顺序、类型等都完全相同时,才会发生子类方法的覆写Override。这与Java等语言的静态类型有关,能够通过参数的静态类型来判断选择最符合条件的方法。这种信赖根本上基于type-checking的规则

但在Ruby中,名字相同的方法就总是会被覆写。因为不存在type checking。

这一点对于Java或者C++的使用者来说是比较容易理解的。

总的来书就是,Java和Ruby等都有动态分派(dynamic dispatch)的特性(区别于静态分派),但只有具有静态类型(static type or type chcking) 和 静态重载(static overloading)的语言才能实现上述复杂的覆写机制(即方法名与参数数量、顺序、类型等都完全相同时,才会发生子类方法的覆写)。因此Ruby的覆写机制没有那么复杂,名字相同的方法就总是会被覆写。

image-20220624210540077

Dynamic Dispatch Versus Closures

动态分派与闭包的比较:

ML的闭包示例:闭包不会被后续的shadowing影响,这在某些时候是有用的但某些时候是糟糕的(这就是闭包的代价)

image-20220624220933885

image-20220624222102557

Ruby的动态分派示例:覆写将会改变超类已定义的方法,它的代价恰好与闭包相反。可能最终会覆写其他方法所依赖的方法,甚至不知道它是重要的。例如下面的B类中的odd没有任何问题甚至更快,但是C类由于错误的覆写导致问题产生(这恰好是闭包能防止的事情)。

image-20220624221629652

image-20220624221641732

image-20220624221703213

image-20220624222044973

面向对象的代价:

某些方法的可覆写性会带来方法行为改变的问题(甚至不被覆写时也可能),这让我们难以推断“正在关注的代码”是哪一段。因此我们需要去避免一些覆写,通过类似private继承或者final 方法(类似C++或Java中的做法)的方式。

但面向对象的优势是,它使得子类更容易影响超类方法的行为(在不复制代码的情况下)。

image-20220624222141100

Optional: Dynamic Dispatch Manually in Racket

如何在Racket这样的语言中,手动实现类似动态分派的特性?

这一节的做法有点类似于之前实现解释器的方式。

尽管Racket已经有内置的类和对象,但这一节为了示范一种语言的某种语法(特性)可以在(通过)另一种语言的习惯用法(来构建)。并且为了更好的理解动态分派。

image-20220624223141713

借助struct。这里的示例中,self只是lambda匿名函数的一个用来记录对象本身的额外参数(这个额外参数的做法有点类似python,用来保证对象方法中的对象self调用),但在这里,self不是一个特殊关键词(不会被特殊对待,它可以不叫self,可以叫this、it等等)

image-20220624223643984

image-20220624223842409

本节的代码:其中make-polar-point子类的实现方式是重点。

; Section 7: Optional: Dynamic Dispatch Manually in Racket

#lang racket

;; We can "use" dynamic dispatch in a language without it manually

;; Our "objects" will have:
;;  * an immutable list of mutable "fields" (symbols and contents)
;;  * an immutable list of immutable "methods" (symbols and functions taking self)
(struct obj (fields methods))

; like assoc but for an immutable list of mutable pairs
(define (assoc-m v xs)
  (cond [(null? xs) #f]
        [(equal? v (mcar (car xs))) (car xs)]
        [#t (assoc-m v (cdr xs))]))

(define (get obj fld)
  (let ([pr (assoc-m fld (obj-fields obj))])
    (if pr
        (mcdr pr)
        (error "field not found"))))

(define (set obj fld v)
  (let ([pr (assoc-m fld (obj-fields obj))])
    (if pr
        (set-mcdr! pr v)
        (error "field not found"))))
 
(define (send obj msg . args) ; convenience: multi-argument functions (2+ arguments)
  (let ([pr (assoc msg (obj-methods obj))])
    (if pr
        ((cdr pr) obj args) ; do the call
        (error "method not found" msg))))

(define (make-point _x _y)
  (obj
   (list (mcons 'x _x)
         (mcons 'y _y))
   (list (cons 'get-x (lambda (self args) (get self 'x)))
         (cons 'get-y (lambda (self args) (get self 'y)))
         (cons 'set-x (lambda (self args) (set self 'x (car args))))
         (cons 'set-y (lambda (self args) (set self 'y (car args))))
         (cons 'distToOrigin 
               (lambda (self args)
                 (let ([a (send self 'get-x)]
                       [b (send self 'get-y)])
                   (sqrt (+ (* a a) (* b b)))))))))

(define (make-color-point _x _y _c)
  (let ([pt (make-point _x _y)])
    (obj
     (cons (mcons 'color _c) 
           (obj-fields pt))
     (append (list
              (cons 'get-color (lambda (self args) (get self 'color)))
              (cons 'set-color (lambda (self args) (set self 'color (car args)))))
           (obj-methods pt)))))
      
(define (make-polar-point _r _th)
  (let ([pt (make-point #f #f)])
    (obj
     (append (list (mcons 'r _r)
                   (mcons 'theta _th))
             (obj-fields pt)) ; Java-style field extension
     (append ; overriding by being earlier in the list (see send function)
      (list 
       (cons 'set-r-theta 
             (lambda (self args)
               (begin 
                 (set self 'r (car args))
                 (set self 'theta (cadr args)))))
       (cons 'get-x (lambda (self args)
                      (let ([r (get self 'r)]
                            [theta (get self 'theta)])
                        (* r (cos theta)))))
       (cons 'get-y (lambda (self args)
                      (let ([r (get self 'r)]
                            [theta (get self 'theta)])
                        (* r (sin theta)))))
       (cons 'set-x (lambda (self args) 
                      (let* ([a     (car args)]
                             [b     (send self 'get-y)]
                             [theta (atan b a)]
                             [r     (sqrt (+ (* a a) (* b b)))])
                        (send self 'set-r-theta r theta))))
       (cons 'set-y (lambda (self args) 
                      (let* ([b     (car args)]
                             [a     (send self 'get-x)]
                             [theta (atan b a)]
                             [r     (sqrt (+ (* a a) (* b b)))])
                        (send self 'set-r-theta r theta)))))
      (obj-methods pt)))))

(define p1 (make-point -4 0))
p1
(send p1 'get-x)
(send p1 'get-y)
(send p1 'distToOrigin)
(send p1 'set-y 3)
(send p1 'distToOrigin)

(define p2 (make-color-point -4 0 "red"))
p2
(send p2 'get-x)
(send p2 'get-y)
(send p2 'distToOrigin)
(send p2 'set-y 3)
(send p2 'distToOrigin)

(define p3 (make-polar-point 4 3.1415926535))
p3
(send p3 'get-x)
(send p3 'get-y)
(send p3 'distToOrigin)
(send p3 'set-y 3)
(send p3 'distToOrigin)

image-20220624225236774

但某些语言仍然难以实现类似特性,例如ML,他的type system不能实现类似的子类型判断。

但注意,老师强调了一下,难以实现并不是不能实现,我们仍然可以用之前学到的方法,在ML中用datatype构造一个足够大的“Obeject”,然后让所有的“类”都是这个datatype的值,这样,这些所谓“类”之间的“继承关系”就可以通过ML的类型系统来判断了(还是借助pattern match之类的特性)。

这其实也算是一种代价交换,ML虽然难以实现动态分派的特性,但却很容易实现闭包,相反Java是面向对象的,很容易动态分派,但闭包的实现就更难一些(例如PartA中week4时我们在Java中实现闭包的方式)。

image-20220624230456469

标签:PartC,den,get,方法,self,Programming,send,面向对象编程,Ruby
来源: https://www.cnblogs.com/ssjxx98/p/16694472.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有