zoukankan      html  css  js  c++  java
  • 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条规则),和方法查找。
    • 新的法术,类实例变量(类自己用不能继承),类宏(拟态简写),下包含包装器
    • 模块就是类
  • 相关阅读:
    bootstrap_table自定义排序方法
    react 给组件设置默认的props值
    Echarts Binning on map 根据真实经纬度渲染数据
    Echarts 如何使用 bmap 的 API
    工作中遇到的前后台联调的一些规则
    redux 调试工具
    react 开发过程中的总结/归纳
    mac svn cornerstone 破解版资源以及使用方法(仅供学习,非商业使用)
    typescript 学习笔记
    iconfont 不居中的问题
  • 原文地址:https://www.cnblogs.com/chentianwei/p/8553942.html
Copyright © 2011-2022 走看看