第三章 类与对象

第三章              类与对象

Ruby是一种真正的面向对象程序设计语言,面向对象指以对象为中心的理论体系。

l  封装(Encapsulation

将内部结构和算法隐藏起来,以确保只有特定的过程(也叫方法)才能直接操作数据,其结果是不能从外部直接使用数据构造,同时一旦内部构造发生变化也不会对外界造成不良影响。这种隔离方法就叫做封装。

l  继承

l  多态(Polymorphism

根据对象的不同选择合适的操作。在Ruby中的实现方法是,根据被调的对象的不同来选择不同的方法。

 

虽然有很多语言都宣称自己是面向对象的,但是他们往往对面向对象的解释都一样,大多是以自己特有的方式来解释什么是面向对象,而在实际情况中,这些面向对象语言又采用了很多非面向对象的做法。

Java 为例:如果你想取一个数字取绝对值,java 的做法是:

int num = Math.abs(-99);

也就是将一个数值传递给 Math 类的一个静态函数 abs 处理。为什么这么做?因为在 java 中,数值是基本类型不是类。

而在 Ruby 中,任何事物都是对象,也就是说,数字–99就是对象,取绝对值这样的操作应该属于数字本身,所以Ruby的做法就是:

c = -99.abs

Ruby中,你所操作的一切都是对象,操作的结果也是对象。

§3.1  类的定义

类是对具有同样属性和同样行为的对象的抽象,Ruby中类的声明使用class关键字。定义类的语法如下,

       class ClassName

           def method_name(variables)

              #some code

           end

       end

类的定义要在class…end之间,在上面的格式中,ClassName是类名,类名必须以大写字母开始,也就是说类名要是个常量。

 

看下面的例子:

class Person

def initialize(name, gender, age)

  @name = name

  @gender = gender

@age = age

end

end

 

若某个类已经被定义过,此时又用相同的类名进行类定义的话,就意味着对原有的类的定义进行追加。

 

class Test

        def meth1

           puts "This is meth1"

        end

end

 

class Test

        def meth2

           puts "This is meth2"

        end

end

 

Test类中,原有meth1方法,我们又追加了meth2方法,这时候,对于Test类的对象,meth1meth2同样可用。

§3.2  对象,属性和方法

类在实例化后生成对象,在强调对象归属于某类时,有时候我们也使用实例对象一词。

方法(Method)是对对象进行的操作。操作对象(被调)self来表示。在Ruby中,除去内部类的对象以外,通常对象的构造都是动态确定的。某对象的性质由其内部定义的方法所决定。

看下面的例子,我们使用new方法构造一个新的对象,

 

class Person

def initialize(name, gender, age)

  @name = name

  @gender = gender

@age = age

end

end

    people = Person.new('Tom', 'male', 15)

 

我们可以使用Person.new方法来创建一个Person类的实例对象。以@打头的变量是实例变量,他们从属于某一实例对象,Ruby中实例变量的命名规则是变量名以@开始,您只能在方法内部使用它。

initialize方法使对象变为“就绪”状态,initialize方法是一个特殊的方法,这个方法在构造实例对象时会被自动调用。

对实例进行初始化操作时,需要重定义initialize方法。类方法new的默认的行为就是对新生成的实例执行initialize方法,传给new方法的参数会被原封不动地传给initialize方法。另外,若带块调用时,该块会被传给initialize方法。因此,不必对new方法进行重定义。

Ruby中,只有方法可以操作实例变量,因此可以说Ruby中的封装是强制性的。在对象外部不可以直接访问,只能通过接口方法访问。

 

class Person

        def name

           @name

        end

 

        def gender

           @gender

        end

 

        def age

           @age

        end

end

 

people = Person.new('Tom', 'male', 15)

puts people.name

puts people.gender

puts people.age

 

输出结果为:

Tom

male

15

 

Ruby中,一个对象的内部属性都是私有的。上面的代码中,我们定义了方法namegenderage三个方法用来访问Person类实例对象的实例变量。注意namegenderage访问只能读取相应实例变量,而不能改变它们的值。

 

我们也可以用成员变量只读控制符attr_reader来达到同样的效果。

class Person

        attr_reader :name, :gender, :age

end

 

类似地,我们可以定义方法去改变成员变量的值。

class Person

        def name=(name)

           @name=name

        end

 

        def gender=(gender)

           @gender=gender

        end

 

        def age=(age)

           @age=age

        end

end

people = Person.new('Tom', 'male', 15)

people.name   = "Henry"

people.gender = "male"

people.age    = 25

 

也可以用成员变量写控制符attr_writer来达到同样的效果。

class Person

       attr_writer :name, :gender, :age

end

 

我们也可以使用attr_accessor来说明成员变量既可以读,也可以写。

class Person

       attr_accessor :name, :gender, :age

end

 

也可以使用attr控制符来控制变量是否可读写。attr 只能带一个符号参数,第二个参数是一个 bool 参数,用于指示是否为符号参数产生写方法。它的默认值是 false,只产生读方法,不产生写方法。

class Person

        attr :name, true     #读写

        attr :gender, true   #读写

        attr :age, true      #读写

        attr :id, false      #只读

end

 

注意attr_readerattr_writerattr_accessorattr不是语言的关键字,而是Module模块的方法。

 

class Test

        attr_accessor :value

end

puts Test.instance_methods - Test.superclass.public_methods

 

执行结果为:

value

value=

 

上面代码中,我们使用Test.instance_methods得到Test类所有的实例方法,使用Test.superclass.public_methods得到Test父类所有的实例方法,然后相减就得到Test类不包含父类的所有的实例方法。

由于instance_methods方法返回值为一个Array,所以我们作差值运算,Array的具体操作后面章节会讲到。

 

也可以重定义方法,重定义一个方法时,新的定义会覆盖原有的定义。

 

下面的例子重定义类中的方法meth1

class Test

        def meth1

           puts "This is meth1"

        end

end

 

a = Test.new

a.meth1

 

class Test

        def meth1

           puts "This is new meth1"

        end

end

 

a.  meth1

 

执行结果为:

This is meth1

This is new meth1

 

重定义同一个类时,意味着对原有定义进行补充,不会覆盖原来的定义。而重定义方法时,则会覆盖原有定义。

 

我们可以使用self标识本身,selfJava中的this有些类似,代表当前对象。

class Person

def initialize(name, gender, age)

  @name = name

  @gender = gender

@age = age

end

 

def <=>(other)

  self.age <=> other.age

end

end

 

<=> 方法通常意思为比较,返回值为-101分别表示小于,等于和大于。

§3.3  继承

Ruby继承的语法很简单,使用 < 即可。

class Student < Person

  def initialize(name, gender, age, school)

     @name = name

     @gender = gender

     @age = age

     @school = school

  end

end

 

Ruby语言只支持单继承,每一个类都只能有一个直接父类。这样避免了多继承的复杂度。但同时,Ruby提供了mixin的机制可以用来实现多继承

 

可以使用super关键字调用对象父类的方法,当super省略参数时,将使用当前方法的参数来进行调用。

class Base

       def meth(info)

           puts "This is Base #{info}"

       end

end

 

class Derived < Base

       def meth(info)

           puts "This is derived #{info}"

           super

       end

end

 

obj1 = Derived.new

obj1.meth("test")

 

执行结果为:

This is derived test

This is Base test

 

如果传入的参数被修改再调用super的话,那么将会使用使用修改后的值。

 

class Base

       def meth(info)

           puts "This is Base #{info}"

       end

end

 

class Derived < Base

       def meth(info)

           puts "This is derived #{info}"

           info = "over"

           super

       end

end

 

obj1 = Derived.new

obj1.meth("test")

 

执行结果为:

This is derived test

This is Base over

§3.4  特殊方法与特殊类

特殊方法是指某实例所特有的方法。一个对象有哪些行为由对向所属的类决定,但是有时候,一些特殊的对象有何其他对象不一样的行为,在多数程序设计语言中,例如C++Java,我们必须定义一个新类,但在Ruby中,我们可以定义只从属于某个特定对象的方法,这种方法我们成为特殊方法(Singleton Method)

 

class SingletonTest

        def info

           puts "This is This is SingletonTest method"

        end

end

 

obj1 = SingletonTest.new

obj2 = SingletonTest.new

 

def obj2.info

        puts "This is obj2"

end

 

obj1.info

obj2.info

 

       执行结果为:

This is This is SingletonTest method

This is obj2

 

有时候,我们需要给一个对象定义一系列的特殊方法,如果按照前面的方法,那么只能一个一个定义:

 

def obj2.singleton_method1

end

 

def obj2.singleton_method2

end

 

def obj2.singleton_method3

end

……

def obj2.singleton_methodn

end

 

这样做非常繁复麻烦,而且无法给出一个统一的概念模型,因此Ruby提供了另外一种方法,

class << obj

……

end

 

obj是一个具体的对象实例,class << 代表它的特殊类。

 

class SingletonTest

       def meth1

           puts "This is meth1"

       end

 

       def meth2

           puts "This is meth2"

       end

end

 

obj1 = SingletonTest.new

obj2 = SingletonTest.new

 

class << obj2

        def meth1

            puts "This is obj2's meth1"

        end

   

        def meth2

            puts "This is obj2's meth2"

        end

end

 

obj1.meth1

obj1.meth2

obj2.meth1

obj2.meth2

 

执行结果为:

This is meth1

This is meth2

This is obj2's meth1

This is obj2's meth2

 

 

§3.5  类变量与类方法

类变量被一个类的所有实例对象共享,也可以被类方法访问到。类变量名以‘@@’,开始,例如‘@@number’。和全局变量,实例变量不同,类变量在使用前必须初始化

class Person

       @@number = 0 #使用前必须有初值

def initialize(name, gender, age)

  @name = name

  @gender = gender

@age = age

@@number += 1

end

end

 

类变量是私有的,在类外无法直接访问,你只能通过实例方法和类方法去访问它。

 

同样,类方法是属于一个类的方法,定义类方法时需要在方法前加上类名

class Person

       @@number = 0

 

def initialize(name, gender, age)

  @name = name

  @gender = gender

@age = age

@@number += 1

end

 

def Person.getNumber #类方法

  return @@number

end

end

 

除了Person.getNumber这种方式定义类方法外,还可以使用其它方式定义类方法,在后续章节可以陆续见到。

 

§3.4  存取控制

当你设计一个类时,你需要决定哪些属性和方法可以在类外被访问到,哪些属性和方法在类外被隐藏。如果一个类有过多的属性和方法在类外可以被访问到,那么势必破坏这个类的封装性。幸运的是在Ruby中,只能通过方法去改变一个类的属性,这样我们只需要考虑方法的存取控制。

方法的存取控制有三种:

l  公有方法(Public Method)

n  方法在任何地方都可以被调用,这是方法的默认存取控制。除了initialize和initialize_cpoy方法,他们永远是私有方法。

l  保护方法(Protected Method)

n  方法只能被定义这个方法的类自己的对象和这个类的子类的对象所访问。

l  私有方法(private Method)

n  方法只能被定义这个方法的类的对象自己访问,即使是这个类的其他对象也不能访问。

 

Ruby中的保护方法和私有方法与一般面向对象程序设计语言的概念有所区别,保护方法的意思是方法只能方法只能被定义这个方法的类自己的对象和子类的对象访问,私有方法只能被对象自己访问。

 

class Test

  def method1    #默认为公有方法

 

  end

 

  protected     #保护方法

  def method2

  …

  end

 

  private       #私有方法

  def method3

  end

 

  public

  def test_protected(arg) #arg是Test类的对象

     arg.method2  #正确,可以访问同类其他对象的保护方法

  end

 

  def test_private(arg)    #arg是Test类的对象

     arg.method3  #错误,不能访问同类其他对象的私有方法

  end

end

 

obj1 = Test.new

obj2 = Test.new

      

obj1.test_protected(obj2)

obj1.test_private(obj2) 

 

可以看到,和C++/Java相比,Ruby提供了更好的封装性。

 

也可以使用以下更简单的形式:

class Test

    def method1

    ...

    end

 

    def method2

    ...

    end

   

    def method3

    ...

    end

   

    def methdo4

    ...

    end

   

    public     :method1

    protected  :method2

    private    :method3, :method4

end

 

RubyC++/Java的一个显著不同是存取控制是程序运行时决定的而不是静态绑定的。所以只有在访问一个受限制的方法时才会产生运行时错误。

 

§3.6  元类

在Ruby中一切都是对象。类和实例对象都是对象。这句话听起来有点拗口,让我们来看一个例子:

    class Person

        def initialize(name, gender, age)

           @name = name

           @gender = gender

            @age = age

        end

end

a = Person.new('Tom', 'male', 15)

 

puts a.object_id         =>  22429840

puts Person.object_id       =>  22429960

 

没错,类也是对象,这是Ruby和C++/Java的一个显著不同,在C++/Java中,类仅仅是一个数据抽象,并没有类也是对象这样的概念。而在Ruby中存在着元类的概念,类也是对象,所有类都是元类的实例对象。和C++/Java相比,Ruby的面向对象程度更高。

可以看到,类对象和实例对象一样有自己的ojbect_id,你可以象调用一个实例对象的方法一样去用它去调用类方法。所有类对象的类是Class类,Oject类是所有类的基类。

irb(main):003:0> Object.class

=> Class

irb(main):004:0> Object.superclass

=> nil

 

这样,我们可以从另一个角度去理解类变量与类方法,类变量就是一个类对象的实例变量,类方法就是指一个类对象类的特殊方法。

类方法具体可分为两种:第一种是在所有的类的父类Class中定义的,且被所有的类所共享的方法;第二种是各个类所特有的特殊方法。

类方法中的self指的是类本身,这点需要牢记,这样我们可以使用多种方式定义类方法。

 

class Test

       #定义类方法方式1

        def Test.meth1

           # ...

        end

 

       #定义类方法方式2

        def self.meth2

            # ...

        end

   

       #定义类方法方式3

        class << Test

            def meth3

               # ...

           end

        end

 

       #定义类方法方式4

        class << self

            def meth4

               # ...

           end

        end

end

 

§3.7  Ruby的动态性

可以重新定义同一个方法,

class RedefTest

        def meth

           puts "This is meth"

        end

end

 

obj1 = RedefTest.new

obj1.meth

 

class RedefTest

        def meth

           puts "This is new meth"

        end

end

 

obj1.meth

 

执行结果为:

This is meth

This is new meth

 

可以使用undef_method取消一个方法的定义,

 

class UndefTest

        def meth

           puts "This is meth"

        end

end

 

obj1 = UndefTest.new

obj1.meth

 

class UndefTest

        undef_method(:meth)

end

 

obj1.meth

 

执行结果为:

This is meth

test.rb:14: undefined method `meth' for #<UndefTest:0x2ac8240> (NoMethodError)

 

§3.8  变量

变量名长度只受内存大小的限制。可以通过区分Ruby变量名的首字符来区分它是局部变量、实例变量、类变量、全局变量还是常量。通常情况下,变量名的第二位字符以后是数字、字母或下划线,但有的内部变量名比较特殊,如“$?”。

 

§3.8.1  局部变量

局部变量以小写字母或下划线开始。

 

num = 1

foo

 

局部变量的作用域起始于声明处,结束于该声明所在的块、方法定义、类/模块定义的结尾。

 

2.times {

  p defined?(num)

  num = 10

  p num

}

 

输出为:

nil

10

nil

10

 

即使声明部分未被解释器执行仍有效,因为已经经过解释器的处理。

 

v = 1 if false

p defined?(v)

p v

 

输出为:

"local-variable"

nil

 

但若块已经变成过程对象的话,则局部变量将一直持续到该过程对象终结为止。若多个过程对象引用同一个作用域的话,局部变量将被这些对象所共享。

              (to-do例子)

 

§3.8.2  实例变量

@开始的变量是实例变量,实例变量属于特定的对象。

class Person

def initialize(name, gender, age)

  @name = name

  @gender = gender

@age = age

end

end

 

上面的例子中,@name @gender@age都是实例变量。可以在类或子类的方法中引用实例变量,但不能直接被实例对象引用,只能通过调用类方法。若引用尚未被初始化的实例变量的话,其值为nil

 

§3.8.3  类变量

以@@开始的变量是类变量。类变量在类的定义中定义,可以在类的特殊方法、实例方法等处对类变量进行赋值和引用。类变量被类,类的子类和他们的实例对象共享。

class Person

       @@number = 0 #使用前必须有初值

def initialize(name, gender, age)

  @name = name

  @gender = gender

@age = age

@@number += 1

end

end

 

类变量是私有的,在类外无法直接访问,你只能通过实例方法和类方法去访问它可以把类变量看作一种被类、子类以及它们的实例所共享的全局变量。

模块中定义的类变量(模块变量)被所有包含该模块的类所共享。

module TestModule

@@foo = 10

end

class Klass

  include Foo

  p @@foo += 1          # => 11

end

class Base

  include Foo

  p @@foo += 2          # => 12

end

 

 

 

§3.8.4  全局变量

$开始的变量是全局变量,全局变量可以在程序的任何地方加以引用。全局变量无需变量声明。引用尚未初始化的全局变量时,其值为nil

Ruby运行时环境预定义了一系列的全局变量,有关预定义的全局变量的信息,请参见附表。

 

§3.8.5  常量

常量以大写字母开始,常数的定义和初始化由赋值过程完成。

PI = 3.14

E = 2.71

若对已定义的常数进行赋值的话,会出现警告信息。若引用未定义的常数会引发NameError异常。

PI = 3.14

obj1 = 2 * PI * 10

PI = 3.1415       #warning: already initialized constant PI

obj2 = Foo        #uninitialized constant Foo (NameError)

 

常量可以定义在类和模块中,不能定义在方法中。

 

class Meth

        PI = 3.14     #OK

end

 

def circle_area(arg)

    PI = 3.14         #ERROR

    PI * arg * arg

end

 

若想在外部访问类或模块中的常数时,要使用“::”操作符。

 

class Meth

    PI = 3.14

end

 

def circle_area(arg)

    Math::PI * arg * arg

end

 

在类定义表达式生成类对象的同时,还会将类对象赋值给一个与该类同名的常数,引用类名也就是引用该常数。

class Test

end

 

p Test.class         #Class

p Test               #test

 

若想访问Object类中的常数(顶层的常数)时,也需要也使用"::"操作符,但操作符左边为空。

 

§3.8  与定义有关的操作

§3.8.1  alias

Alias关键字给方法或全局变量添加别名可以给方法名指定一个标识符或Symbol作为别名。给方法添加别名时,别名方法将和此刻的原始方法绑定,此后即使重新定义了原始方法,别名方法仍然保持着重定义前的老方法的特性。若改变了某方法的内容后,又想使用修改前的方法时,别名会很有用。也可以使用Module#alias_method给方法添加别名。

# 定义meth方法

def meth

  puts "This is meth"

end

 

#设定别名

alias :orig_meth :meth

 

#重定义foo

def meth

  puts "This is new meth"

end

 

p meth

 

执行结果为:

This is new meth

nil

 

给全局变量设定别名意味两个名称指向同一个全局变量。当你向一个赋值时,另一个也会被改变。

 

$abc = 1

alias $xyz $abc

$xyz = 2

p [$abc, $xyz]   # => [2, 2]

 

但是不能给正则表达式中的变量$1,$2等添加别名,另外,有些全局变量对于解释器来说是举足轻重的,若重新定义它们的话,有时会影响解释器的正常工作。

 

§3.8.2  undef

undef用来取消一个方法的定义,也可以使用Module#undef_method方法取消方法的定义。undef会取消方法名和方法定义之间的关系,即使超类中有同名方法,调用时也会引发异常。

 

class Base

       def meth

           puts "This is Base#meth"

       end

end

 

class Derived < Base

       def meth

           puts "This is Derived#meth"

       end

end

 

class Test1 < Derived

        def meth

           puts "This is Test1#meth"

        end

   

        undef_method(:meth)

end

 

obj1 = Test1.new

obj1.meth

 

执行结果为:

Tes1.rb:22: undefined method `meth' for #<Test1:0x2ac7c88> (NoMethodError)

 

Module#remove_method方法只负责取消当前类中方法名和方法定义之间的关系,父类的同名方法仍可调用,这点差别非常重要。

class Base

       def meth

           puts "This is Base#meth"

       end

end

 

class Derived < Base

       def meth

           puts "This is Derived#meth"

       end

end

 

class Test2 < Derived

    def meth

       puts "This is Test2#meth"

    end

   

    remove_method(:meth)

end

 

obj2 = Test2.new

obj2.meth

 

执行结果为:

This is Derived#meth

 

用alias添加别名或用undef取消定义时,会修改类的接口,而不受父类的限制。继承和Mix-in的功能都是在类中添加方法,而undef则可以取消方法。但是,如果取消了类所必需的方法(被其他方法所调用的方法)的话,其后果不堪设想。

 

§3.8.3  defined?

Defined?用来判断表达式是否定义。若表达式尚未定义,则返回nil,若已经定义,则返回一个字符串描述该表达式的种类。

 

defined? Val         #=> nil

defined? true         #=> “true”

defined? $*           #=> "global-variable"

defined? Array        #=> "constant"

defined? Math::PI #=> "constant"

defined? num = 0     #=> "assignment"

defined? 100         #=> "expression"

defined? 100.times   #=> "method"

 

虽然defined?看起来像一个方法,实际上是Ruby语法中的操作符,因此不会对参数进行计算。因此下面的表达式并不会输出“abc”。

defined? print("abc\n")

 

如果是方法未定义,或方法使用undef或Module#remove_method取消了原有定义,defined?都将返回nil。

注意如果一个方法以大写字母开头,使用defined? 判断时需要在方法名后添加"()"时,否则方法名会被当做常数处理。

def Foo(arg)

end

 

p defined? Foo       # => nil

p defined? Foo()     # => "method"

 

Foo = 1

p defined? Foo       # => "constant"

 

 

还可以使用下列特殊用法:

 

l  判断yield是否可用

defined? yield

yield调用可用,则返回真,具体返回值为字符串"yield"。它的作用同block_given?一样,可以判断能否以带块方式来调用某方法。

class Base

        def foo

           puts defined? yield

        end

end

 

a = Base.new

a.foo

a.foo {}

 

执行结果为:

nil

yield

 

l  判断super是否可用

defined? super

super可被调用,则返回真,具体返回值为字符串"super"

 

class Base

        def foo

        end

end

 

class Derived < Base

        def foo

           puts defined? super

        end

   

        def fun

           puts defined? super

        end

end

 

obj = Derived.new

obj.foo

obj.fun

 

执行结果为:

super

nil

 

l  返回没有赋值但已经定义的局部变量.

 

defined? a = 1 #=> assignment

p a # => nil

 

l  在正则表达式中使用

/(.)/ =~ "foo"

p defined? $&  # => "$&"

p defined? $1  # => "$1"

p defined? $2  # => nil

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值