POSTS
第十二章、ruby中的面向对象编程
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中构造函数名为initialize。
构造函数不能被继承。父对象的构造函数是通过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的面向对象的第一部分。zetcode原文地址