[翻译]Ruby教程12——面向对象

Ruby的面向对象编程

在这部分的Ruby教程我们将讨论面向对象编程。


编程语言有过程式编程、函数式编程和面向对象编程范式。Ruby中面向对象语言并包含了一些函数式和过程式。


面向对象(OOP)是一种使用对象及其接口来设计应用程序和计算机程序的编程范式。


面向对象的基本概念如下:



  • 抽象(Abstraction)

  • 多态(Polymorphism)

  • 封装(Encapsulation)

  • 继承(Inheritance)


抽象是对于现实中复杂的问题通过适当的建模将其简化。多态是使用相同的操作符或者函数对不同的输入数据进行不同处理。封装是将一个类的具体实现对其他对象进行隐藏。继承是一种使用已经定义的类来创建新的类的方式。


对象


对象是Ruby面向对象程序的基本组成。一个对象包含了数据和方法。对象之间通过方法进行交流。每个对象可以接收消息、发送消息和处理数据。


创建一个对象需要两步。首先定义一个类。类是对象的模板。它是一张蓝图,用来描述这个类的所有对象的状态和行为。一个类可以创建多个对象。运行时创建的对象称为这个类的实例。


#!/usr/bin/ruby

class Being

end

b = Being.new
puts b

第一个例子我们创建了一个简单的对象。


class Being

end

定义一个简单的类,内容为空。表示它没有任何数据和方法。


b = Being.new

创建一个Being类的新实例。这里我们使用new方法,新创建的对象保存在变量b中。


puts b

在终端上打印对象的基本描述。当我们打印一个对象时,实际上是调用的to_s方法。但是我们没有任何的定义,因为每个创建的对象都是继承自Object。它有一些基本的函数,to_s是其中一个。


$ ./simple.rb
#<Being:0x9f3c290>

我们得到这个对象的类名。


构造函数


构造函数是一个特殊的方法。它在对象创建时自动执行。它没有返回值。构造函数的目的是初始化对象的状态。在Ruby中构造函数名为intialize。


构造函数不能被继承。父对象的构造函数是通过super方法来调用。它们的调用顺序与继承顺序一致。


#!/usr/bin/ruby

class Being

def initialize
puts “Being is created”
end

end

Being.new

定义一个Being类。


class Being

def initialize
puts “Being is created”
end

end

类Being定义了一个构造函数名为initialize。它在终端上打印一条信息。Ruby中方法定义位置def和end关键字之间。


Being.new

创建一个Being类的实例对象。在对象初创时构造函数将被调用。


$ ./constructor.rb

Being is created


程序的输出。




对象的属性是绑定在对象里的数据项。这些数据项也称为实例变量(instance variables)或者成员字段(member fields)。实例变量在类中定义但是各个对象都有单独的复本。


下面的例子我们初始化类的成员数据。变量初始化是构造函数的典型工作。


#!/usr/bin/ruby

class Person

def initialize name
@name = name
end

def get_name
@name
end

end

p1 = Person.new “Jane”
p2 = Person.new “Beky”

puts p1.get_name
puts p2.get_name

上面的例子定义了一个Person类,并且有一个实例变量。


class Person

def initialize name
@name = name
end

Person的构造函数设置了一个实例变量name。构造函数的name参数是在创建时传递的。构造函数是在实例对象创建时调用。@name是一个实例变量。在Ruby中实例变量以@字符开头。


def get_name
@name
end

get_name方法返回成员字段。在Ruby蝇成员字段只能通过方法来访问。


p1 = Person.new “Jane”
p2 = Person.new “Beky”

我们创建了Person类的两个对象。每个对象的构造函数都传递了一个字符串参数。


puts p1.get_name
puts p2.get_name

通过调用每个对象的get_name方法来打印成员字段。


$ ./person.rb

Jane

Beky


从程序的输出看到每个实例都有自己的name成员字段。




我们可以创建一个对象而不调用构造函数。Ruby有一个特殊的allocate方法。allocate方法为新的对象分配空间而不调用initialize。


#!/usr/bin/ruby

class Being

def initialize
puts “Being created”
end
end


b1 = Being.new
b2 = Being.allocate
puts b2

这个例子我们创建了两个对象。第一介对象使用new方法,第二个对象使用allocate方法。


b1 = Being.new

这里我们通过new关键字创建一个实例对象。构造函数initialize将会调用,并且在终端上打印消息。


b2 = Being.allocate
puts b2

这里使用allocate方法,没有调用构造函数。使用puts关键字调用对象的to_s方法将其显示。


$ ./allocate.rb
Being created
#<Being:0x8ea0044>

程序的输出。


构造函数重载


重载构造函数可以使用类有多种类型的构造函数。这样我们可以使用不同数量或者不同类型的参数来创建对象。


Ruby没有我们所知的其他语言那样的构造函数重载。在Ruby中这种行为可以通过一些有默认值的扩展参数来模拟。


#!/usr/bin/ruby


class Person

def initialize name=”unknown”, age=0
@name = name
@age = age
end

def to_s
“Name: #{@name}, Age: #{@age}”
end

end

p1 = Person.new
p2 = Person.new “unknown”, 17
p3 = Person.new “Becky”, 19
p4 = Person.new “Robert”

p p1, p2, p3, p4

这个例子展示了模拟构造函数的重载。当name参数没有指定时使用“unknow”代替,对于age使用0。


def initialize name=”unknown”, age=0
@name = name
@age = age
end

这个构造传入两个参数。它们都有默认值。当我们创建对象没有指定值时就使用默认值。注意参数顺序必须一致。第一个是name,第二介是age。


p1 = Person.new
p2 = Person.new “unknown”, 17
p3 = Person.new “Becky”, 19
p4 = Person.new “Robert”

p p1, p2, p3, p4

我们创建了四个对象。构造函数传入了不同个数的参数。


$ ./consover.rb

Name: unknown, Age: 0

Name: unknown, Age: 17

Name: Becky, Age: 19

Name: Robert, Age: 0


例子的输出结果。


方法


方法是定义在类里面的函数。它们用于对对象的属性执行一些操作。方法在面向对象范式的封装性中必不可少。例如我们AccessDatabase类中有一个connect方法,我们不需要关心这个方法到底是如何连接数据库的。我们仅需要知道使用这个方法连接数据库。这对程序功能的划分必不可少,尤其是大的应用程序。


在Ruby中数据仅能够通过方法访问。


#!/usr/bin/ruby

class Person

def initialize name
@name = name
end

def get_name
@name
end

end

per = Person.new “Jane”

puts per.get_name
puts per.send :get_name

这个例子展示了调用方法的两个基本方式。


puts per.get_name

通常的方式是在对象后面使用点操作符。


puts per.send :get_name

另种方式是使用内建的send方法。它将方法名符号作为参数传入。




方法通常对对象的数据进行一些操作。


#!/usr/bin/ruby

class Circle

@@PI = 3.141592

def initialize
@radius = 0
end

def set_radius radius
@radius = radius
end

def area
@radius @radius @@PI
end

end


c = Circle.new
c.set_radius 5
puts c.area

这个例子的代码我们定义了一个Circle类两个方法。


@@PI = 3.141592

我们在Circle类中定义了一个@@PI变量。类变量以@@开头。类变量是属于类的,每个对象都可以访问它们的类变量。我们@@PI来计算圆的面积。


def initialize
@radius = 0
end


定义了一个成员字段。它是圆的半径。如果我们想在外部修改这个变量,我们必须使用公开的set_radius方法。这个数据是受保护的。


def set_radius radius
@radius = radius
end

这是set_radius方法。它为@radius实例变量设置一个新的值。


def area
@radius @radius @@PI
end

area方法返回圆的面积。


c = Circle.new
c.set_radius 5
puts c.area

我们创建一个Circle类的实例对象,并且通过set_radius方法设置它的半径。


$ ./circle.rb

78.5398


例子的输出结果。


访问修饰符


访问修饰符设置成员和方法的可见性。Ruby有三种访问修饰符:public、protected和private。在Ruby中所有的数据都是私有的。访问修饰符可以仅对方法使用。Ruby中的方法是公开的,除非使用了其他修饰符。


公开的方法在类的内部和外部都可以访问。保护和私有的方法略微不同。都不能在类外部访问,仅能在这个类和它的子类或者父类内部访问。


注意与其他面向对象编程语言不同,继承不会充当访问修饰符。仅有两件事很重要。第一,我们是否可以在类的内部或者外部访问方法。第二,是否我们要使用或者不使用self关键字。


访问修饰符保护数据避免受到意外的修改。使用程序更健壮。实现一些主要用于修改数据的方法。这些方法最好是私有的。只有真正需要修改才将接口公开给用户。多年来用户习惯使用特殊方法并对打破向后兼容普遍不满。


#!/usr/bin/ruby

class Some

def method1
puts “public method1 called”
end

public

def method2
puts “public method2 called”
end

def method3
puts “public method3 called”
method1
self.method1
end
end

s = Some.new
s.method1
s.method2
s.method3

这个例子解释了Ruby公有方法的用法。


def method1
puts “public method1 called”
end

method1是公有的,尽管我们没有使用public修饰符。因为方法默认都是公有的,除非指明为其他。


public

def method2
puts “public method2 called”
end

…

public关键字之后的方法是公有的。


def method3
puts “public method3 called”
method1
self.method1
end

在公有方法method3中我们通过使用和没有使用self关键字调用了另一个公有方法。


s = Some.new
s.method1
s.method2
s.method3

公有方法是仅能够在类外部调用的方法。


$ ./public_methods.rb
public method1 called
public method2 called
public method3 called
public method1 called
public method1 called


例子运行结果。




下一个例子看私有方法。


#!/usr/bin/ruby


class Some

def initialize
method1
# self.method1
end

private

def method1
puts “private method1 called”
end

end


s = Some.new
# s.method1

私有方法是Ruby中严厉的方法。它们只能够在类内部调用并且不能使用self关键字。


def initialize
method1
# self.method1
end

在构造函数方法中我们调用了私有方法method1。使用self调用的被注释了。私有方法不能指定接收者。


private

def method1
puts “private method1 called”
end

private关键字之后的是私有方法。


s = Some.new
# s.method1

创建了一个Some类的实例对象。在外部调用这个方法是禁止的,如果将这行取消注释Ruby解释器会报错。


$ ./private_methods.rb

private method called


输出结果。




最后我们使用保护方法。保护方法和私有方法的区别很小。保护方法与私有方法相似,不过它们可以通过self关键字调用。


#!/usr/bin/ruby

class Some

def initialize
method1
self.method1
end

protected

def method1
puts “protected method1 called”
end

end


s = Some.new
# s.method1

上面的例子展示了保护方法的用法。


def initialize
method1
self.method1
end

保护方法可以使用和不使用self关键字。


protected

def method1
puts “protected method1 called”
end

保护方法以protected关键字开头。


s = Some.new
# s.method1

保护方法不能在类外部调用。取消注释会报错。


继承


继承是使用已经定义的类来构造新的类的方式。新构建的类称为派生类。派生自的类称为基类。继承的好处是代码利用,减少程序的复杂性。派生类(后代)覆盖或者扩展基类(祖先)的函数。


#!/usr/bin/ruby

class Being

def initialize
puts “Being class created”
end
end

class Human < Being

def initialize
super
puts “Human class created”
end
end

Being.new
Human.new

这个程序我们定义了两个类:一个基类Being和一个派生类Human。


class Human < Being

Ruby中使用<操作符创建继承关系。Human类继承自Being类。


def initialize
super
puts “Human class created”
end

super方法调用父类的构造函数。


Being.new
Human.new

实例化了Being类和Human类。


$ ./inheritance.rb

Being class created

Being class created

Human class created


首先创建Being类。基类Human同样也调用了父类的构造函数。




一个对象的关系可能很复杂。一个对象可以有多个祖先。Ruby有ancestors方法获取一个类的祖先列表。


每个Ruby对象都是Object、BaseObject和Kernel的后代。它们内建于Ruby语言的内核中。


#!/usr/bin/ruby


class Being
end

class Living < Being
end

class Mammal < Living
end

class Human < Mammal
end


p Human.ancestors

这个例子中定义了四个类。Human、Mammal、Living和Being。


p Human.ancestors

打印Human类的祖先。


$ ./ancestors.rb

[Human, Mammal, Living, Being, Object, Kernel, BasicObject]


Human类有三个自定义的和三个内建的祖先。




一个更复杂的例子。


#!/usr/bin/ruby

class Being

@@count = 0

def initialize
@@count += 1
puts “Being class created”
end

def show_count
“There are #{@@count} beings”
end

end

class Human < Being

def initialize
super
puts “Human is created”
end
end

class Animal < Being

def initialize
super
puts “Animal is created”
end
end

class Dog < Animal

def initialize
super
puts “Dog is created”
end
end

Human.new
d = Dog.new
puts d.show_count

我们定义了四个类。继承的层级有点复杂。Human和Animal继承自Being。Dog继承自Animal。我们还使用了类变量来统计beings的创建个数。


@@count = 0

我们定义一个类变量。它用于统计beings的创建个数。


def initialize
@@count += 1
puts “Being class created”
end

每次Being类实例化时我们将@@count变量加1。这使用我们可以跟踪实例创建的个数。


class Animal < Being
…

class Dog < Animal
…

Animal继承自Being,Dog继承自Animal。进一步的Dog也继承自Being。


Human.new
d = Dog.new
puts d.show_count

我们通过Human和Dog创建实例。然后调用Dog对象的show_count方法。Dog类没有该方法,将调用Being类的。


$ ./inheritance2.rb
Being class created

Human is created

Being class created

Animal is created

Dog is created

There are 2 beings


Human对象调用了两个构造函数。Dog对象调用了三个构造函数。创建了两个Being实例。




方法和数据成员可见性在继承中不起作用。这与其他通常的面向对象编程语言是显著的不同。


在C#或者Java中公有的和保护的数据成员和方法可以被继承,私有的不能。与这相比,在Ruby中私有的数据成员和方法也可以被继承。数据成员和方法的可见性不会受继承的影响。


#!/usr/bin/ruby

class Base

def initialize
@name = “Base”
end

private

def private_method
puts “private method called”
end

protected

def protected_method
puts “protected_method called”
end

public

def get_name
return @name
end
end


class Derived < Base

def public_method
private_method
protected_method
end
end

d = Derived.new
d.public_method
puts d.get_name

这个例子中有两个类。Derived继承乍Base。它继承了三个方法和一个数据字段。


def public_method
private_method
protected_method
end

Derived类的public_method调用了一个私有方法和一个保护方法。它们定义在父类中。


d = Derived.new
d.public_method
puts d.get_name

创建一个Derived类的实例。调用public_method方法和get_name方法,它返回私有的实例变量@name。记住Ruby中所有的实例变量都是私有的。get_name方法返回这个变量不管@name是私有的还是在父类中定义的。


$ ./inheritance3.rb

private method called

protected_method called

Base


输出结果证实了在Ruby中公有的、保护的、私有的方法和私有的成员字段都能被继承。


super方法


super方法调用父类的同名方法。如果没有传递参数它将自动的把当前的所有参数传入。如果写为super()则没有参数传入。


#!/usr/bin/ruby

class Base

def show x=0, y=0
p “Base class, x: #{x}, y: #{y}”
end
end

class Derived < Base

def show x, y
super
super x
super x, y
super()
end
end


d = Derived.new
d.show 3, 3

这个例子有两个类一个继承。它们都定义了show方法。这个方法在Derived类中使用super调用了父类的方法。


def show x, y
super
super x
super x, y
super()
end

super不带参数则会传递将当前传入的参数,这里是x=3、y=3。super()方法不传递参数。


$ ./super.rb

“Base class, x: 3, y: 3”

“Base class, x: 3, y: 0”

“Base class, x: 3, y: 3”

“Base class, x: 0, y: 0”


输出结果。


这是Ruby的面向对象的第一部分。




原文地址: http://zetcode.com/lang/rubytutorial/oop/

翻译:龙昌 admin@longchangjin.cn

完整教程:https://github.com/wusuopu/Ruby-tutorial