Oh!Coder

Coding Life

读《Ruby编程语言》笔记(四)

| Comments

继续阅读。

这部分摘录了书中第七章《类和模块》的部分内容。老样子,东西不想一次记录太多,所以把这一章内容单独算作一篇。就这样,内容如下:

一、访问Ruby对象(P.214)

  • Ruby对象是严格封装的:只能通过定义的方法访问其内部状态。

二、class是一个表达式(P.215)

  • 像绝大多数Ruby语句一样,class是一个表达式。class表达式的值等于类定义体中最后一个表达式的值。一般而言,类的最后一个表达式都是一个用于定义方法的def语句,def语句的值总是等于nil

三、不想让eql?等价于==的两个的原因(P.223~P.224)

  • 首先,有些类定义eql?方法进行更严格的相等性测试。比如,在Numeric类及其子类中,==允许类型转换,而eql?不允许。
  • 用不同于==的方式实现eql?方法的第二个原因是,你可能想在该对象作为哈希主键时有特殊行为。

#### 四、Enumerable模块定义若干方法注意事项(P.225)
* 值得注意的是Enumberable模块定义了若干方法,比如sortminmax,它们只有在被枚举的对象定义了\<=\>操作符后才能运作。

五、用Struct类创建新类(P.226~P.228)

  • Struct是一个Ruby内核类,可以用于生成其他类。在这些生成的类中,你指定的字段名自动具有访问器方法。
  • Struct.new来创建一个新类可以有两种方式:
  • Struct.new("Point", :x, :y) # Creates new class Struct::Point Point = Struct.new(:x, :y) # Creates new class, assigns to Point
  • 不过,只要做一点工作,我们就可以把基于Struct的类变为不变类:
  • Point = Struct.new(:x, :y) # Define mutable class class Point # Open the class undef x=, y=, []= # Undefine mutator methods end

六、类实例变量(P.231)

  • 在类定义体内而在实例方法定义体外使用的实例变量被称为类实例变量。跟类变量相似,类实例变量与类本身相关联而非与类的某个特定实例关联。
  • 类实例变量的一个不足在于它们不能像类变量那样在实例方法中被使用,另一个不足之处则在于它们容易与普通的实例变量混淆。

七、方法可见性(P.232~p.234)

  • 如果没有明确声明为privateprotected,方法默认的可见性是public的。
  • 一个例外是initialize方法,它总是默认为private
  • 另外一个例外是所有在类外定义的“全局”方法——这些方法被定义为object对象的私有实例方法。
  • 方法可见性是通过三个分别名为publicprivateprotected的方法来定义的。这三个方法都是Module类的实例方法。
  • 所有的类都是模块,在类的定义体内(但在方法定义体外),self指向被定义的类。因此,publicprivateprotected方法就可以像语言的关键字一样被使用,但实际上,它们不过是在self上被调用的方法而已。
  • 在Ruby中,publicprivateprotected仅仅应用于方法。
  • 被封装的实例变量和类变量在效果上是私有的,而常量则在效果上是公开的。

八、私有方法(P.232)

  • 私有(private)方法是实现一个类时使用的内部方法,它只能被这个类(或它的子类)的实例方法所调用。
  • 私有方法只能隐式地被self对象调用,并且不能通过一个对象进行显式调用。

九、被保护方法(P.232~P.234)

  • 被保护(protect)方法与私有方法的相似之处在于它也只能在该类或子类的内部被调用。
  • 被保护方法与私有方法的不同之处在于它可以被该类的实例显式调用,而不仅仅只能被self所隐式调用。
  • 例如:一个被类C所定义的被保护方法,只有当对象op的类都是C的子类(或c本身)时,.p中的方法才可以调用对象o上的该方法。

十、覆盖私有方法(P.237~P.238)

  • 私有方法不能在所定义的类外被调用,但是它们会被子类继承,这意味着子类可以调用它们,也可以覆盖它们。
  • 在子类化一个别人写的类时需要特别小心:私有方法一般作为内部的辅助方法被使用,它们不是公开API的一部分,不被外界可见。
  • 其结果是,在Ruby中,应该只对你了解的超类进行子类化。如果希望使用某个类的公开API而非它的具体实现,应该通过封装(其实例)和代理来扩展它的功能,而不是继承它。

十一、类方法的调用(P.239)

  • 在明确指明接收者的情况下调用类方法时,要尽量避免依赖继承——应该总是通过定义时所在的类来调用类方法。
  • 注意类方法也可以像实例方法一样使用super,用来调用超类中的同名方法。

十二、Ruby实例变量不会被继承(P.239~P.240)

  • 所有Ruby对象都包含一组实例变量,这些变量不是在相应的类中被定义的,它们只是在被赋值时才被创建出来。因为实例变量并非在类中被定义,所以它们与继承和子类化机制无关。
  • Ruby的实例变量不会被继承,它们与继承机制毫无关系。有时它们看起来好像被继承的原因是,方法在第一次赋值时创建它们,而这些方法经常是继承而来的并进行了链式调用。

十三、常量和方法的重要区别(P.241)

  • 常量和方法的重要区别在于,常量首先在其句法空间中进行查找,然后才在继承体系中查找,这意味着如果Point3D中继承的方法使用了ORIGIN常量,那么即使Point3D定义了自己的ORIGIN常量,这些方法的行为也不会改变。

十四、clonedup属于浅拷贝(P.243~P.244)

  • clonedup方法把实例变量从原始对象拷贝到拷贝对象中时,它们拷贝的是引用而非实际值。换句话说,它们用的是浅拷贝(shallow copy),这也是为什么很多类需要定制initialize\_copy方法的一个原因。

十五、序列化与反序列化(P.245~P.246)

  • 创建对象的第三种方式是调用Marshal.load方法来重新生成早先用Marshal.dump序列化的对象。Marshal.dump方法保存一个对象的类,并递归序列化其中每个实例变量的值。
  • 如果自定义了一个marshal\_dump方法,必须同时定义一个对应的marshal\_load方法。
  • marshal\_load被一个新分配的(用allocate方法)但是尚未初始化的对象所调用,它需要一个由marshal\_dump返回的可再生的对象拷贝作为参数,然后根据这个参数对象的状态初始化接收者对象。
  • \_dump是一个实例方法,它必须返回一个字符串表示对象的状态。相应的\_load方法是一个类方法,它接受\_dump返回的字符串作为参数,并返回一个对象。\_load方法可以创建一个新对象,也可以仅仅返回一个现有对象的引用。

十六、混入模块(P.251)

  • 混入一个模块的普通方式是使用Module.include方法,另外一种方法则是使用Object.extend方法,这个方法使指定模块的实例方法成为接收对象的单键方法。
  • 如果接收者对象是一个Class实例,那么这些方法就成为那个类的类方法。

十七、requireload的区别(P.253)

  • 除了加载源代码,require还可以加载Ruby的二进制扩展。当然,二进制扩展是与实现相关的,不过在基于C的实现中,这些共享库的文件名一般都以.sodll做后缀名。
  • load方法的参数要求是包括扩展名的完整文件名,而require命令根据库名寻找一个合适的源文件或本地扩展文件。如果一个目录下对同样的库名既有.rb结尾的源文件,又有二进制扩展的文件,require命令会加载那个源文件而非二进制文件。
  • load把一个文件加载多次,而require则试图避免对同一文件的多次加载。require把加载过的文件名放入全局数组$"(也被称为$LOADED\_FEATURES)中,从而可以追踪加载的库。load则不这么做。
  • load在当前$SAFE级别上加载指定文件,而require则使用$SAFE=0对指定库进行加载,即使调用require的代码对此变量有大雨0的值。(注意如果$SAFE的值大于0,require拒绝加载任何文件名被修改的或任何来自公开可写目录的文件,因此,在理论上,用降低的$SAFE级别来调用require是安全的。)

十八、加载路径(P.253)

  • Ruby的加载路径是一个数组,可以通过全局变量$LOAD_PATH$:(可以这样来记住这个全局变量名:冒号在类Unix操作系统中,用做路径的分隔符)进行访问。
  • 数组的每个元素都是一个目录名,Ruby在这些目录中查找加载文件,前面的目录比后面的目录优先被查找。

十九、自动加载模块(P.256)

  • 全局autoload函数允许你注册一个未定义的常量(通常是类或模块名)及一个定义了该常量的包名,当这个常量第一次被引用时,那个注册的包就使用require进行加载。
  • 例如: # Require 'socket' if and when the TCPSocket is first used autoload :TCPSocket, "socket"

Comments