在前面两篇中,主要讲了函数式编程语言的一些基础概念。这篇是 Coursera Programming Languages, Part C 的总结,通过 Ruby 介绍面向对象编程里的一些概念。了解这些概念能让你在上手任何一门新的面向对象语言时,都更加得心应手。
虽然用的是 Ruby,但是不会涉及很深的 Ruby,即使不懂 Ruby,读下来应该没问题。对于已经了解面向对象编程的朋友,可以考虑直接跳到子类和继承那部分,或许你会有一些新的启发。
面向对象编程 & Ruby
面向对象编程(Object Oriented Programming)简称 OOP,像 Java,C++ 等语言的主要编程模式都是面向对象的。OOP 主要是通过 对象(Object) 来抽象表示,比如说平面上的一个点,可以抽象表示成 Point(x, y)
,x,y 表示这个点的横纵坐标。在对象中可以有一些方法(methods) 来具体表示这个对象能做些什么,比如对于一个点,可定义一个 method distFromOrigin
,去求这个点的到原点的距离。
Everything is object in Ruby
Ruby 是动态类型的面向对象语言。Ruby 中所有的表达式都是一个对象,比如数字 1, 2, 3, 4 等是对象,+ 加法是对象的一个方法。Ruby 最出名的是在网页应用的开发,但是因为 Everything is object in Ruby
,被选做为这个课程的教学语言。
Class & Methods
在 OOP 中,最基本的就是怎么定义和使用对象以及对象中的方法。
定义 Class 和 Methods
Ruby 中,对象的定义通过 Class 实现。
class Foo
def m1
...
end
def m2 (x,y)
...
end
end
这里定义了一个对象 Foo,以及两个方法 m1 和 m2。
调用方法
foo = Foo.new
foo.m1
foo.m2(x, y)
上例是最简单的方法调用。
e0.m(e1, ..., en)
这是调用方法的抽象表达,可以有另一种理解: 发送消息,e0 是消息的接受者,可以说将 e1 的结果作为参数放在消息 m 里,发送给 e0。
在 Ruby 中,所有表达式都是对象,e1 + e2
实际上是 e1.+ e2
的简写法,e1 调用了 方法 +,e2 是这个方法调用的参数。
定义 实例变量、类变量、类常量、类方法
class Foo
Const = "constant"
def initialize
@foo = 1
@@bar = 2
def m1
...
end
def m2 (x,y)
...
end
def self.classMethod
...
end
end
上例中,@f
为实例变量 (instance variable) ,@@f
为类变量 (class variable) ,Const
是类常量 (Class constant),classMethod
是类方法。
怎么使用类常量和类方法?
Foo::Const
Foo.classMethod
别名 Aliasing
foo = Foo.new
bar = foo
在这个例子中,bar 是 foo 的别名,他们对应是同一个 object,如果 bar 更改了,foo 也会相对应的更改。在 OOP 中,不像函数式编程里数据不可更改,需要特别注意什么时候用别名,什么时候要新建一个 object。
可见性 Visibility
对象内的变量和方法根据不同的定义方式,对象外不一定可见,可以理解为知不知道它的存在。
class Foo
def initialize
@foo = 1
public
def m1
...
end
protected
def m2
...
end
private
def m3
...
end
end
foo = Foo.new
foo.@foo # 报错
foo.m1 # 可见
foo.m2 # 报错
foo.m3 # 报错
实例变量 @foo
在 class Foo 外是不知道它的存在的,foo.@foo
会报错,实例变量只有对象内的方法 m1, m2, m3 可以使用。
这样的设计其实很符合面向对象的概念,Foo 是一个对象,要跟这个对象交流,只能通过发送消息,也就是调用方法的方式。
对象内的方法也可以定义可见性。
- public,表示对外可见,Ruby 对象默认的模式,在类之外可以调用。
- protected 和 private 只能在 类和子类都能调用。
Getter & Setter
对象的实例变量对外不可见,但是很多情况下可能会需要实例变量,这个时候可以通过定义 Getter 和 Setter 两个方法来实现实例变量的读写。
# getter
def foo @foo
end
# setter
def foo= x
@foo = x
end
# simpler way to define getters
attr_reader :y, :z # defines getters
# simpler way to define getters and setters
attr_accessor :x # defines getters and setters
反射 Reflections
反射指的是在程序运行的过程中,能访问、检测和修改它本身的这个对象。比如说可以在运行的过程中去访问这个对象里都有哪些方法,然后动态的去调用这些方法。
鸭子类型 Duck Typing
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
def mirror_update point
point.x = point.x * -1
end
这个方法本意是将一个点 (point) 的 x 值变为 -x,但是所有"像点一样有 x 这个方法"的对象都可以作为参数传入这个函数,这就是所谓的鸭子类型。通常只有动态类型的语言才会支持鸭子类型,但是 Golang 也是有办法实现,这里不展开了。
优缺点
鸭子类型的最大的优点在于代码的重用,同样一段代码,传进的参数只要有那些方法,就能够复用这段代码。但是也带来了缺点,重用使得代码表意不清,我们在用这个方法时,考虑的不是这个方法怎么调用,而是要清楚的了解这个方法的具体实现。举个例子:
def double x
x + x
end
看到 double 这个函数名,可能最直观的理解就是把数字翻倍,具体的实现 x + x
,也可以把两个字符串连在一起。但假如 Ruby 的字符串没有 *
这个方法,具体实现是 2*x
的话,这个函数就不适用于字符串。所以知道一个函数名不够,还得看具体的实现,也挺麻烦的。
Blocks and Using Blocks
这是 Ruby 特有的一个