《Rubu基础教程第五版》第八章笔记 类和模块

 

类是什么

生成一个对象,可以用类.new,通过.class来查看他原来属于的类,通过instance_of?来查看实例是否属于这个类。跟Python中的type与.__class__差不多

>> arr = Array.new
=> []
>> p arr
[]
=> []
>> str = 'hello'
=> "hello"
>> arr.class
=> Array
>> str.class
=> String
>> arr.instance_of?(Array)
=> true
>> str.instance_of?(String)
=> true
>> str.instance_of?(Object)
=> false
>> 

 

继承

子类,父类跟Python差不多,Ruby不支持多继承

BasicObject类是Ruby中所有类的父类,它定义了作为Ruby对象的最基本的功能

BasicObject类是最最基础的类,甚至连一般对象需要的功能都没有定义。因此普通对象所需要的类一般都被定义为obejct类。字符串,数组等都是object类的子类。

>> String.is_a?(Object)
=> true
>> str
=> "hello"
>> str.is_a?(String)
=> true
>> str.is_a?(Object)
=> true
>> 

 is_a?这个方法跟Python中的isinstance函数功能差不多,返回的都是boll值

 

创建类

Hello, world. I am Bob.
shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat hello_class.rb 
class HelloWorld
  def initialize(myname = "Ruby")
    @name = myname
  end

  def hello
    puts "Hello, world. I am #{@name}."
  end
end

bob = HelloWorld.new("Bob")
alice = HelloWorld.new("Alice")
ruby = HelloWorld.new

bob.hello  
shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ 

 整个定义跟Python差不多

 

initialize方法等于Python中的__init__,@相当于Python中self

 

实例变量与实例方法

跟Python一样,不解释了。

 

存取器

在ruby中,从对象外部不能直接访问实例变量或对实例变量赋值,需要通过方法来访问对象的内部

 

class HelloWorld
  def initialize(myname = "Ruby")
    @name = myname
  end

  def hello
    puts "Hello, world. I am #{@name}."
  end

  def name=(value)      # 这个是赋值的方法
    @name = value
  end
end

 

这样修改实例变量比较麻烦,可以定义一些相应的存储器

attr_reander :name   只读(定义name方法)

attr_writer :name    只写(定义name=方法)

attr_accessor :name   读写 (定义以上两个方法)

 

特殊变量 self

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat hello_class_self.rb 
class HelloWorld
  attr_accessor :name

  def greet
    puts "Hi, I am #{self.name}"    # 可以添加使用的属性,self可以省略
  end
  
  def test_name
    self.name = "Ruby"     # 添加方法给调用者的属性赋值,不要添加self
  end

end

bob = HelloWorld.new
alice = HelloWorld.new
ruby = HelloWorld.new

bob.name = "new_bob"
bob.test_name
bob.greet 

 

类方法

方法的接收这是类本身的方法称为类方法。

第一种, 用class << 类名的方式来创建类方法

ruby2.6不能实现该方法的定义

 

第二种, 在类中用 class << self ~ end的形式,在class里面定义类方法

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat class_method_1.rb 
class HelloWorld
  class << self    ! 定义类方法的标记
    def hello(name)
      puts "#{name} said hello."
    end
  end
end


HelloWorld.hello "Join"

 还有在方法前面添加类名.或者self.的方式添加类方法

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat class_method_2.rb 
class HelloWorld
  def HelloWorld.hello(name)
    puts "#{name} said hello."
  end
end


HelloWorld.hello "Join"

 

class HelloWorld
  def self.hello(name)    # 通过self定义类方法
    puts "#{name} said hello."
  end
end


HelloWorld.hello "Join"

 从示例来看用self来定义类方法最方便。

 

类常量,可以直接通过::外部访问到

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat class_constant.rb 
class HelloWorld
  Version = "1.0"
end

p HelloWorld::Version    # 通过::读取类的常量的定义

 

类变量

cat: hello_c: No such file or directory
shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat hello_count.rb 
class HelloCount
  @@count = 0

  attr_accessor :name
  def self.count    # 定义类方法
    @@count
  end

  def initialize(myname="Ruby")
    @name = myname
  end

  def hello
    @@count += 1
    puts "Hello, world. Iam #{name}.\n"
  end
end

bob = HelloCount.new("Bob")
alice = HelloCount.new("Alice")
ruby = HelloCount.new

p HelloCount.count
bob.hello
alice.hello
ruby.hello

 类变量,能够实例的方法进行改变

 

限制方法的调用

自己有个转帖有着更加详细的介绍:https://www.cnblogs.com/sidianok/p/12982555.html

书中的代码感觉不是很好,抄写一遍再说

public 默认的默认的方法都是公开的

private 私有的,在指定接受者的情况下不能调用该方法

protected 在同一个类中可将该方法作为实例方法调用的

 

测试代码

class AccTest
  def pub
    puts "pub is a public method."
  end
  
  public :pub   # 把pub方法定义为公开的,默认就是
  
  def priv
    puts "priv is a private method."
  end
  
  private :priv

  def run_priv
    priv
  end

end

acc = AccTest.new
acc.pub
acc.run_priv
acc.priv

 

希望定义多个方法的访问级别,可以使用下面的语法

class AccTest
  public   # 这里下面都是公开的

  def pub
    puts "pub is a public method."
  end
  
  def run_priv
    priv
  end
  
  private    # 这里下面的都是私有的

  def priv
    puts "priv is a private method."
  end

end

acc = AccTest.new
acc.pub
acc.run_priv
acc.priv

 

定义protected的方法,在同一类(及其子类)中可作为实例方法使用,而在除此以外的地方无法使用

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat point.rb 
class Point
  attr_accessor :x, :y  # 定义存储器,可以对该属性进行读取,赋值
  protected :x=, :y=   # 定义属性赋值x=,y=设定为protected

  def initialize(x=0.0,y=0.0)
    @x, @y = x, y
  end

  def swap(other)
    tem_x, tem_y = @x, @y
    @x, @y = other.x, other.y
    other.x, other.y = tem_x, tem_y  # 在同一个类中调用protected方法
    self
  end
end

p0 = Point.new
p1 = Point.new(1.0, 2.0)

p [p0.x, p0.y]
p [p1.x, p1.y]

p0.swap(p1)


p [p0.x, p0.y]
p [p1.x, p1.y]

p0.x = 10.0

 仔细看了以下书中的示例代码,起始写的还是很不错的

 

扩展类

在原有类的基础上添加方法

class String
  def count_word
    ary = self.split(/\s+/) # 用正则,匹配切割空格字符
    ary.size
  end
end


str = "Just Another Ruby Newbie"
p str.count_word

 不需要继承什么的,直接在原来类的方式上面,添加方法就可以

 

继承

class 类名<父类名

  类定义

end

class RingArray < Array
  def [](i)
    idx = i % size
    super(idx)      # 调用父类的方法
  end
end


wday = RingArray["星期日","星期一","星期二","星期三","星期四","星期五","星期六"]
p wday[6]
p wday[11]
p wday[15]
p wday[-1]

 

类对象调用instance_methods方法后,就会以符号的形式返回该类的实例方法列表

>> Object.instance_methods
=> [:instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_get, :instance_variable_set, :instance_variables, :singleton_method, :method, :public_send, :define_singleton_method, :public_method, :extend, :to_enum, :enum_for, :<=>, :===, :=~, :!~, :eql?, :respond_to?, :freeze, :inspect, :object_id, :send, :to_s, :display, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :yield_self, :then, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :equal?, :!, :__id__, :==, :instance_exec, :!=, :instance_eval, :__send__]
>> BasicObject.instance_methods
=> [:equal?, :!, :__id__, :==, :instance_exec, :!=, :instance_eval, :__send__]
>> 

 上面两个不通的类,实例以后返回的方法列表

 

alias与undef

alias 别名 原名

alias :别名 :原名

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat alias_sample.rb 
class C1
  def hello
    "Hello"
  end
end

class C2 < C1
  alias :old_hello :hello   # 这里用old_hello hello这样直接用方法名的方式也可以操作
  
  def hello
    "#{:old_hello}, again"
  end
end

obj = C2.new
p obj.old_hello
p obj.hello

 

undef

在子类中删除父类的方法

可以通过 undef 方法名或者:方法名  的方式删除父类定义的方法

 

单例类

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat single_class.rb 
str1 = "Ruby"
str2 = "Ruby"

class << str1
  def hello
    "Hello. #{self}!"
  end
end

p str1.hello

 这个到可以运行,但没发现有什么用

 

 

 

模块是什么

模块是表示事务的行为部分,动作部分

模块不能被实例,模块不能被继承

 

模块的使用

Minx_in就是将模块混合到类中。在定义时使用include,模块中的方法、常量就都能被类使用。

代码案例

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat mixin_sample.rb 
module MyModule
  # 共同的方法等
end

class MyClass1
  include MyModule
  # MyClass1独有的方法
end

class MyClass2
  include MyModule
  # MyClass2独有的方法
end

 

提供命名空间

所谓命名空间(namespace),就是对方法、常数、类等名称进行区分及管理的单位

不通的模块就是一个不同的命令空间

 

通过Math模块来示例

>> p Math::PI       # 调用模块的参数
3.141592653589793
=> 3.141592653589793
>> p Math::sqrt(2)    # 调用模块的方法
1.4142135623730951
=> 1.4142135623730951
>> p Math.sqrt(2)
1.4142135623730951
=> 1.4142135623730951
>> 
>> include Math    # 命名空间导入当前的命名空间
=> Object
>> PI
=> 3.141592653589793
>> sqrt(5)
=> 2.23606797749979
>> 

::很强大,可以调用模块类的常量,还可以调用模块方法

 

创建模块

module HelloModule
  Version = "1.0"

  def hello(name)
    puts "Hello, #{name}"
  end

  module_function :hello     # 指定hello方法为模块方法

end

p HelloModule::Version
p HelloModule.hello("Ruby")

include HelloModule

p Version
p hello "Python"

 

常量

与类一样,通过::可以取出模块或者类中的常量

 

方法的定义

定义了模块内的方法,只能在模块内部使用,或者包含此模块的语句中使用,不能直接用模块名.方法名的方式使用。

如果想直接通过模块名.方法名的方式调用方法,需要通过module_function :xxx 的方式来定义

 

Mix_in

module M
  def meth
    "meth"
  end

  # module_function :meth # 一旦定义称为了模块方法,不能被实例调用了
end

class C include M
end

c = C.new
# p M.meth
p c.meth

 

如果像知道一个类里面是否包含某个模块可以用类名.include?(模块名)

我们可以用过ancestors[祖先的意思]返回继承关系的列表  类名.ancestors

superclass返回自身的父类    类名.superclass

 

查找方法的规则

1、元类中已经定义了同名的方法时,有限使用该方法

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat find_methods_1.rb 
module M
  def meth
    "M#meth"
  end
end

class C include M

  def meth
    "C#meth"
  end
end

c = C.new
p c.meth

 

2、在同一个类中包含多个模块时,优先使用最后一个包含的模块

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat find_methods_2.rb 
module M1
end

module M2
end

class C
  include M1
  include M2    # 优先继承这个的
end

p C.ancestors

 

3、嵌套include时,查找顺序也是线性的

 

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat find_methods_3.rb 
module M1
end

module M2
end

module M3 include M2
end

class C
  include M1
  include M3    # 优先继承这个的
end

p C.ancestors  #  [C, M3, M2, M1, Object, Kernel, BasicObject]

 

相同的模块被包含两次,第二次以后会被省略

module M1
end

module M2
end

class C
  include M1
  include M2
  include M1   # 这个会被忽略
end

p C.ancestors
shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ 

 

extend方法

extend方法可以使用单例类包含模块,并把模块的功能扩张到对象中

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat test_extend.rb 
module Edition
  def edition(n)
    "#{self} 第#{n}版"
  end
end

str = "Ruby基础教程"

str.extend(Edition)   # 将模块Mix-in进对象

p str.edition 5

 

类与Mix-in

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat test_include_extend.rb 
module ClassMethods
  def cmethod
    "class method"
  end
end

module InstanceMethods
  def imethod
    "instance method"
  end
end

class MyClass
  include InstanceMethods
  extend ClassMethods
end

p MyClass.cmethod
p MyClass.new.imethod

 

这里用到了include和extend继承模块的方法与类方法

 

面向对象的程序设计

面向对象的语言中的"对象"就是指数据(或者说数据的几个)以及操作该数据的方法的组合

 

封装(encapsulation),就是指使对象管理的数据不能直接从外部进行操作,数据的更新、查寻等操作都必须调用对象的方法来完成。

这个做的比Python好太多了,通过学习ruby让我对类的三大特性有了更好的了解。

 

多态就使同名的方法属于多个对象(不同对象的处理结果不一样)这种现象,这就使多态。

 

鸭子类型

鸭子类型的说法来至于"能像鸭子一样走路,能像鸭子一样叫,那一定使鸭子"这句话

这句话的意思,对象的特征并不是由其种类(类极其启程关系决定的),而是由对象具有什么样的行为(拥有什么方法)决定的

用示例代码来演示

shijianzhongdeMacBook-Pro:chapter_8 shijianzhong$ cat fetch_anddowncase.rb 
def fetch_and_downcase(ary, index)
  if str = ary[index]
    str.downcase     # 装换成小写
  end
end


ary = ["Bob", "Foo", "Woo"]
p fetch_and_downcase(ary, 1)

hash = {0 => "Bob", 1 => "Foo", 2 => "Woo"}
p fetch_and_downcase(hash, 1)

 执行的输出结果是一样的

函数只关心arr[index]形式获取元素

获取的元素能执行downcase方法

并不关心传递进来的是什么元素,这个就是鸭子类型,如果报错,可以通过摘取错误的方式,要不然通过instance来处理,可能会错过很多合适的鸭子。

 

感谢ruby让我对鸭子类型有了形象的理解。

 

posted @ 2020-05-29 23:06  就是想学习  阅读(228)  评论(0编辑  收藏  举报