3-13《元编程》第5章Class Definitions 3-14(5-4Singleton Classes,2小时)3-15(3小时✅)


  • 类宏
  • 环绕别名
  • singleton class
Class就是增强的模块,类定义也就是模块定义。

 


 

5.1 Class Definitions Demystified

 

5.11 Inside Class Definitions 

美  [,dɛfɪ'nɪʃən]

Self关键字:Ruby的每一行代码都会在一个对象中被执行--这个对象就是所谓的当前对象,用self表示。 

class and module 也是 object, so class  also can  be "self" 

 

 

5.12 The Current Class 当前类

Ruby程序的任何位置,总会存在一个当前对象。因此就有一个当前类/模块存在。定义一个方法时,这个方法就会成为当前类的一个instance method.

 

1.在程序的顶层,当前类是Object,这是main对象的类。(所以在顶层定义方法会成为Object的private method。)

p self.class  #=> Object
def my
  "123"
end
p self.private_methods(false)
#=> [:public, :private, :define_method, :include, :using, :DelegateClass, :my]

 

2.在一个方法中,当前类就是当前对象的类。在一个方法中嵌套另一个方法,新方法会定义在self所属的类中。

class C
  def m1
    def m2;end
  end
end
p C.instance_methods(false)  #=>[:m1] 这是因为def定义的代码不会立即执行

 

obj = C.new
obj.m1  #obj是当前对象self。调用m1方法,执行里面的代码(声明了m2方法),m2方法的类是self的类
p C.instance_methods(false)  #=>[:m2, :m1]

 

3.当用class/module关键字时,当前对象self转换为class/module.这个类也称为当前类。

 

class_eval 

如何在不知道类名字的情况下,打开一个类? 使用Module#class_eval方法(module_eval)

例子,下面例子给String增加了一个新的方法m:

p self

p self.class

 
def add_method_to(a_method)
    p self

    p self.class

  a_method.class_eval do
    def m
      "hello"
    end
    p self
    p self.class
  end
end
 
add_method_to(String)
p "ss".m
结果:
 
main
Object
main
Object
String
Class
"hello"
 

class_eval方法同时修改了self和当前类 。

 

class_eval方法和class比较:

 

  • 灵活:可以对任何代表类的变量使用class_eval, 而class关键字只接收常量命名。
  • scope: class_eval flatten the scope可以接收block外部scope的变量. 而class关键字是Scope Gate.

 

module_eval/class_eval 一样,module_exec/class_evec可以接收额外的代码块做参数。class_exec(arg...) {|var...| block } → obj

 

instance_eval 和 module_eval/class_eval 的 选择:

 

  • instance_eval打开非类的对象,可以修改对象的方法和实例变量
  • class_eval打开类,然后用def定义方法。
  • 如果要打开的一个对象也是类/模块,选择可以准确表达你的目的的方法。
 
 

小结:

  • Ruby总是追踪当前类/模块的引用,所有使用def的方法都成为当前类的实例方法。
  • 类定义,当前类就是self--正在定义的类
  • 如果有一个类的引用,可以用class_eval/module_eval打开这个类。(打开类:对已经存在的类进行动态修改) 
 

5.13Class Instance Variables  类实例变量

 Ruby解释权规定所有的实例变量都属于当前对象self。

class Myclass
  p self #=> Myclass
  @my_var = 1
end
 
puts Myclass.instance_variables
# => @my_var

 

类的实例变量,类的对象的实例变量。是两回事。

 class Myclass

  @my_var = 1  #只能被类自身访问,子类都不行。
  def self.read    #类方法,也可以这么写:Myclass.read
    @my_var
  end
  def write
    @my_var = 2
  end
  def read
    @my_var
  end
end
 
obj = Myclass.new
p obj.read  #=>nil
p obj.write #=>2
p obj.read  #=>2
p Myclass.read  #=> 1

 

 

类变量:@@var,可以被子类和类的实例调用,为了避免以外,一般不使用类变量 

 

5.3 Singleton Methods 单件方法 

单件方法可以增强某个对象(类也是对象),是ruby最常见的特性。 

 Object#define_singleton_method:

define_singleton_method(symbol) { block } → symbol

 

str = "just a regular string"
# def str.title?也可以这么写,即用def关键字定义单件方法。
#   self.upcase == self
# end

#或者这么写,用Object#define_singleton_method定义单件方法

str.define_singleton_method(:title?) do
  self.upcase == self
end
 
p str.title?
p str.methods.grep /^title?/
p str.singleton_methods
结果:
false
[:title?]
[:title?]

 

5.32 The truth about Class Methods

 详细(5.4Singleton Classes)

AClass.a_class_method 这是在一个由常量引用的对象(类)上调用方法。 

an_object.a_method 这是在一个由变量引用的对象上调用方法。

语法完全一样。

 

 

类方法的实质是: 它是一个类的单件方法。 

object可以是对象引用,类,self。底层机制一样的。 

def object.method

...

end 

 

Duck Typing:鸭子类型。

Ruby这样的动态语言,对象的类型并不严格与它的类相关,“类型”只是对象能相应的一组方法。这也叫鸭子类型。 116页

 

5.33Class Macro类宏

什么是拟态方法?看起来像关键字或者别的,就是不像方法,但其实是方法。 

puts, private, protect,  attr_accessor, attr_read, attr_write(类宏)


什么是类宏? 

普通的方法,看起来像关键字,其实是在类里定义好了。如attr_accessor, attr_read, attr_write。 类宏依靠类方法实现。

 

本例子:类宏用了类方法实现。

也使用了:

Dynamic method(Module#define_method):用于普获对旧方法的调用,并把调用转发给重命名的新方法。

Dynamic#dispatch(Module#send) 

Kernel#warn:  warn(msg, ...) → nil, 一般用于测试代码。

 

class Book

  def title;end
  def subtitle;end
  def lend_to(user)
    puts "Lending to #{user}"
  end
 
  def self.deprecate(old_method, new_method)
    define_method(old_method) do |*args, &block|
      warn "Warning: #{old_method}() is deprecated. Use #{new_method}()"
      send(new_method, *args, &block)
    end
  end
  # 类宏
  deprecate :GetTitle, :title
  deprecate :title2, :subtitle
  deprecate :lend_to_user, :lend_to
end
 
b = Book.new
b.lend_to_user("bill")
结果:

Warning: lend_to_user() is deprecated. Use lend_to()

Lending to bill

 

 

5.4Singleton Class 

 

5.41 the Mystery of Singleton Methods

 

对象的单件方法不存在对象里,也不存在它的类和超类中。 

单件方法存在哪里?类方法是单件方法的特例,它又存在哪里?

答案:见5.42(绿色框)。

 

5.42 Singleton Classes Revealed

 

 被隐藏起来,需要使用Object#singleton_class或class<<关键字 得到它

str = "just a regular string"
# 定义了2个str的单件方法 house, title?
 
def str.house
  puts "house:我是一个单件方法"
end
def str.title?
  self.upcase == self
end
 

#两种显示对象的单件类的代码

p str.singleton_class #=>#<Class:#<String:0x00007f8f3e03dd68>>
 
puts class<<str      #=>#<Class:#<String:0x00007f8f3e03dd68>>
  self
end
p str.singleton_class.instance_methods(false)  #=> [:house, :title?]

 

由⬆️代码可知: 

一个对象的单件方法(可以有多个单件方法) --存储在--> 一个单件类中;

因此,一个单件类也只有这么一个实例对象。

 

 5.43 单件类在ancesstors中的位置。

 

class C
  def a_method
    "C#a_method()"
  end
end
 
class D < C
end
 
obj = D.new
 
def obj.a_singleton_method
  "obj#a_singleton_method()"
end
p obj.singleton_class.instance_methods(false)    #=>[:a_singleton_method]

p obj.singleton_class.ancestors

#=>[#<Class:#<D:0x00007fa9af03cc98>>, D, C, Object, Kernel, BasicObject] 

由此可知,如果对象有单件方法,那么当查找这个对象的任意方法时,Ruby从它的单件类开始查找,用#obj表示单件类

obj->#obj->D->C->Object

 

Ruby模型对象的7条规则:

 

  1. 只有一种对象,要么是普通对象,要么是模块
  2. 只有一种模块,可以是一个普通模块,一个类或者一个单件类
  3. 只有一种方法,它存在于一个模块--通常是一个类中
  4. 每个对象(包括类)都有自己的“真正的类”--要么是一个普通类,要么是一个单件类
  5. 除了BasicObject类没有超类外,每个类有且只有一个祖先--要么是一个类,要么是一个模块。这意味着任何类只有一条向上的,直到BasicObject的祖先链
  6. 一个对象的单件类的超类是这个对象的类:一个类的单件类的超类是这个类的超类的单件类
  7. 调用一个方法,Ruby先向右迈一步进入接受者真正的类,然后向上进入祖先链。这是Ruby查找方法的方式。

 

类方法的语法:

class Myclass

  def self.another_class_method; end

end 

或者 (这是最好的选择✅,可以在class<<关键字中定义多个类方法,方便阅读代码。)

class Myclass

  class << self

    def yet_another_class_method; end

  end 

end 

不建议:

def Myclass.a_class_method; end

 

 

单件类和instance_eval方法 

 class_eval方法会修改self,同时也就变更了当前类。instance_eval内修改self为调用它的receiver.

  如果在instance_eval的{}中,定义一个def方法,当前类变为receiver的单件类, 这个方法成为receiver的singleton m

 

 

ethod.

 所以,instance_eval也会变更当前类.不过Ruby程序员一般不用这个特点定义方法。

  instance_eval的标准含义: 我想修改当前self. 

s1, s2 = "abc", "def"
p self                #=> main
s1.instance_eval do
  p self              #=> "abc"
  def swoosh
    reverse!
  end
end
p s1.swoosh
p s1.singleton_class.instance_methods(false)   #=> [:swoosh]

 

“类属性”

Ruby对象没有属性的概念。所谓属性只不过是用拟态方法加上的一对儿读写的方法罢了。

这种简写的拟态方法,如attr_*也称为类宏Class Macro,其实就是普通的方法而已。

因此,类属性就是是这个类自己用的方法,类方法,放到这个类的单件类里.

class Myclass

 

  class << self
    attr_accessor :x
  end
end
p Myclass.x = "aa"

 

5.5 Module Trouble 

如何用包含模块的方式来定义一个类方法?

module MyModule
  def my_method
    "sdf"
  end
end
 
class Myclass
  class << self
    include MyModule
  end
end

在模块中定义普通的实例方法,然后把模块放到类的单件类中,成为类方法。这称为Class Extension 类扩展 

 

这种方法同样适用于对象,因为类方法本身就是单件方法的特例。所以可以把Module包含进对象的单件类中。这称为Object Extension.

module MyModule
  def my_method
    p "hello"
  end
end
 
obj = Object.new
 
class << obj
  include MyModule
end
 
obj.my_method  #=> "hello"
p obj.singleton_methods   #=> [:my_method]
 

Object#extend 

类扩展和对象扩展使用普遍,所以打包代码做了一个extend方法。相当于快捷方式。

module Mymodule

 

  def my_method; "hello"; end
end
 
obj = Object.new
obj.extend(Mymodule)
p obj.my_method
 
class Myclass
  extend Mymodule
end
 
p Myclass.my_method

 


5.6Method Wrappers 

方法包装器

  • Around Aliases
  • Refinement Wrapper
  • Prepended Wrapper

用途:有一个不能直接修改(在一个库中)或者修改的地方太多(怕漏掉)的Method, 我们希望为这个方法包装额外的功能。可以使用Method Wrappers.


Module#alias_method :new_name, :old_name 

Ruby提供alias关键字,为顶级作用域Object下提供帮助。

alias :new :old   #⚠️ 没有逗号。 

 

环绕别名 

名字类似指针

 

  1. 给方法a定义一个别名b
  2. 重新定义a,此时我的理解b方法是过去的那个方法,a重新定义了所以是新的方法
  3. 在新的方法a中调用b方法,就叫环绕别名。 

 

绝大部分Ruby操作符合实际上是方法,例如整数的+操作符是Fixnum#+方法的语法糖。

问题:重新定义+方法。让(x+y)的结果返回(x+y+1).?

解答:只能用环绕别名的方法,因为新的+方法依赖于旧的+方法。super方法不能重复用了 

class Fixnum
  alias_method :old_plus, :+
  def +(value)
    self.old_plus(value).old_plus(1)
  end
end
p 2 + 1  #=>warning: constant ::Fixnum is deprecated  4

 

细化封装器 Refinement Wrapper 

使用refine(mod){},配合using方法。作用域是:

 

  1. refine内部
  2. 从using开始到文件结束(如果是在顶层上下文中调用using)
  3. 或者从using开始到模块结束(如果是在模块中调用using) 
注意:细化有很多限制。

 

module StringRefinement

  refine String do
    def length
      super > 5 ? 'long' : "short"
    end
  end
end

 

 
using StringRefinement
 
p "asdfa".length   #=> short

 

module Mymodule
  using StringRefinement
  p "12345678".length#=>long
end

 


 

Prepend Wrapper 下包含包装器(祖先链:当前类的下面)

 

module ExplicitString
  def length
    super > 5 ? 'long' : 'short'
  end
end
 
String.class_eval do
  prepend ExplicitString   
end
p "tttffff".length

 

#把包含的模块插入祖先链下方,而非上方。这意味着prepend方法包含的模块可以覆写该类的同名方法,同时可以通过super调用该类中的原始方法。

 


 5.8小结

 

  • 类定义对self(调用方法时默认的receiver)和当前类(定义方法时默认的所在地)的影响 
  • singleton_method, 单件类,类方法。 从新认识了对象模型(7条规则),和方法查找。
  • 新的法术,类实例变量(类自己用不能继承),类宏(拟态简写),下包含包装器
  • 模块就是类

 

posted @ 2018-03-13 09:57  Mr-chen  阅读(178)  评论(0编辑  收藏  举报