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