很早之前程序员就发现,随着软件的日趋复杂与庞大,维护往往变成了一个很棘手的问题。因为贯穿整个程序的依赖 (dependencies),在一个程序中一个很小的改变就可能引起如同涟漪般扩散的偏差 (errors).
而面向对象编程正是为了解决这个问题而诞生的。
面向对象编程 (Object oriented programming) 是一种基于对象的编程范型。在面向对象编程语言中,一切均是对象。
对象是指一个具体的事物。它有具体的行为 (behaviors),有具体的属性 (states).有时可以对应现实世界的事物,例如小狗聪聪,猫咪毛毛等。
定义:
类相当于一副蓝图,定义了一类事物的抽象特点。例如,"狗"这个类会包含狗的一切基础特征,即所有“狗”都共有的特征或行为,例如它的孕育、毛皮颜色和吠叫的能力。
作用:
这样创建一类具有相同行为与共通的属性的对象时,先创建一个这样的一个类,再用这个类去定义具体的对象,就可以少写很多代码。
限定只有特定的类的对象才能调用特定类的方法,并且隐藏了方法的具体执行步骤。
示例:
/* 一个面向过程的程序会这样写: */
定义莱丝
莱丝.设置音调(5)
莱丝.吸气()
莱丝.吐气()
/* 而当狗的吠叫被封装到类中,任何人都可以简单地使用: */
定义莱丝是狗
莱丝.吠叫()
在某些情况下一个类会有子类,子类比父类更加具体。例如,“狗”这个类可能会有它的子类"牧羊犬"和"吉娃娃".子类会继承父类的属性和行为,并且包含它自己的。假设"狗"这个类有一个方法 (行为) 叫做“吠叫 ()”和一个属性叫做“毛皮颜色”。它的子类(前例中的牧羊犬和吉娃娃犬)会继承这些成员。
例如:
类牧羊犬:继承狗
定义莱丝是牧羊犬
莱丝.吠叫() /* 注意这里调用的是狗这个类的吠叫方法。*/
多态 (Polymorphism) 是一个专业术语,意指"许多形态"("many shapes").更具体来说是同一种操作被用不同方式执行。
例如,狗和鸡都有“叫 ()”这一方法,但是调用狗的“叫 ()”,狗会吠叫;调用鸡的“叫 ()”,鸡则会啼叫。我们将它体现在伪代码上:
类狗
开始
公有成员:
叫()
开始
吠叫()
结束
结束
类鸡
开始
公有成员:
叫()
开始
啼叫()
结束
结束
定义莱丝是狗
定义鲁斯特是鸡
莱丝.叫()
鲁斯特.叫()
这样同样做出叫这个动作,莱丝与鲁斯特的实际行为会大相径庭。
语法:
Classname.new
示例:
--- initialize class called GoodDog ---
class GoodDog
end
--- initialize object ----
sparky=GoodDog.new
--- initialize class called GoodDog ---
class GoodDog
def initialize(name,color)
@name = name
@color = color
end
end
--- initialize object ----
sparky=GoodDog.new("sparky","white")
记录对象的属性 (keep track of states of object)
- 以"@"符号开头
- 后面跟着表示对象属性的单词,并且使用小写字母
示例:
class GoodDog
def initialize(name,color)
@name = name
@color = color
end
end
sparky = GoodDog.new("sparky","white")
背景问题 1:
如何输出@name的值
以上面代码为例,虽然有了 instance variable,但是如果我们想要 输出 sparky 的 name,那么应该怎么办?
sparky.name # => NoMethodError: undefined method `name' for #<GoodDog:0x007f91821239d0
@name="Sparky">
上面的错误提示告诉我们我们没有定义一个叫做 name 的 method,也就是说我们需要定义一个方法来存放@name的值,来方便我们输出。
这似乎是说"object."后面只能跟 method,而不能跟 variable
解决方案:
class GoodDog
def initialize(name,color)
@name = name
@color = color
end
--- add getter_method ---
def get_name
@name
end
end
sparky = GoodDog.new("sparky","white")
这样我们就可以输出 sparky 的名字了:
puts sparky.get_name # => sparky
背景问题 2:
现在已经有了 getter method,
但如何改变 name 的值?
解决方案:
class GoodDog
def initialize(name,color)
@name = name
@color = color
end
--- add getter_method ---
def get_name
@name
end
--- add setter_method ---
def set_name=(name)
@name = name
end
end
sparky = GoodDog.new("sparky","white")
现在运行下面的代码:
sparky.set_name = "spartacus"
puts sparky.get_name #=> spartacus
就会发现 name 的值改变了。
Rubyist 习惯使用相同的名称来命名 instance variable 的 getter method 和 setter method .
统一使用 name 来命名 getter method 和 setter method.
class GoodDog
def initialize(name)
@name = name
end
def name # This was renamed from "get_name"
@name
end
def name=(n) # This was renamed from "set_name="
@name = n
end
end
sparky = GoodDog.new("Sparky")
puts sparky.speak
puts sparky.name # => "sparky"
sparky.name = "Spartacus"
puts sparky.name # => "Spartacus"
上述代码占用的空间太多了,只有一个 instance variable 还好,若是有多个呢?例如 height / weight.这样就太麻烦了。
Ruby 内置了一套方案来解决上述问题。
attr_accessor method 可以自动的帮助我们产生和上面一样的 getter / setter metods.
class GoodDog
attr_accessor :name # using attr_accessor method
def initialize(name)
@name = name
end
end
sparky = GoodDog.new("Sparky")
puts sparky.speak
puts sparky.name # => "Sparky"
sparky.name = "Spartacus"
puts sparky.name # => "Spartacus"
这样写就会简洁/方便很多。
attr_accessor :name, :height, :weight
attr_reader
attr_writer
如果我们有一个 social_security_numbers(用@ssn表示),我们并不想展示所有的数字,只想展示后四位,那么可以这样写:
"****-****-" + @ssn.split("-").last
如果我们需要多次使用到这行代码,与其多次重复写入,不如写一个 method 将这行代码封装起来。
另外原本的 getter mehod 不能让别人直接调用,所以不如直接使用 getter method 来封装这行代码:
def ssn
"****-****-" + @ssn.split("-").last
end
class Account
def initialize(name,ssn)
@name = name
@ssn =ssn
end
def ssn
"****-****-" + @ssn.split("-").last
end
def ssn_hint
"Your ssn is #{ssn}" # using ssn instead of @ssn
end
end
如实在 GoodDog class 中有多个 instance variable,而我们定义一个方法 (change_info) 用来同时改变着多变量的值:
def change_info(n,h,w)
@name = n
@height = h
@weight = w
end
为了与上面的在 class 内部使用 getter method 保持一致 (consistence),从而使用 setter_method 来进行替换。
根据上面的思路我们对 change_info 这个 method 进行了修改。
class GoodDog
attr_accessor :name, :height, :weight
def initialize(n, h, w)
@name = n
@height = h
@weight = w
end
def speak
"#{name} says arf!"
end
def change_info(n, h, w) # try using setter method
name = n
height = h
weight = w
end
def info
"#{name} weighs #{weight} and is #{height} tall."
end
但是当我们进行检测时,却发现变量的值并没有改变。
sparky = GoodDog.new('Sparky', '12 inches', '10 lbs')
puts sparky.info # => Sparky weighs 10 lbs and is 12 inches tall.
sparky.change_info('Spartacus', '24 inches', '45 lbs')
puts sparky.info # => Spartacus weighs 45 lbs and is 24 inches tall.
在上述代码中 Ruby 误以为我们是想创建新的变量,分别叫 name/height/weight 了。
所以就会发现@name等变量并没有改变。
def change_info(n, h, w) # using self method
self.name = n
self.height = h
self.weight = w
end
def info
"#{self.name} weighs #{self.weight} and is #{self.height} tall."
end
- class 内,用到 instance variable 的地方使用 getter method ;
- class 内,除 setter method 以外,需要改变 instance variable 值的地方统一使用 setter method;
- 在 class 内使用到 getter / setter method 的地方都使用 self method
不知道怎么描述,所以通过下面的代码进行感受。
- 使用"@@"作为开头;
- 紧跟着是能够描述想要记录属性的小写的英文单词。
class GoodDog
@@number_of_dogs = 0
def initialize
@@number_of_dogs += 1
end
end
- 使用 self method;
- 用小写单词命名。
class GoodDog
@@number_of_dogs = 0
def initialize
@@number_of_dogs += 1
end
def self.total_number_of_dogs
@@number_of_dogs
end
end
puts GoodDog.total_number_of_dogs # => 0
dog1 = GoodDog.new
dog2 = GoodDog.new
puts GoodDog.total_number_of_dogs # => 2
其实和 class 并没有什么关系。
有时候我们无论如何都不想要定义的量的值改变,这时就使用常量。
- 使用首字母大写的英语单词;
- Rubyist 习惯上将常量的所有字母都大写。
class GoodDog
DOG_YEARS = 7 # constants
attr_accessor :name, :age
def initialize(n, a)
self.name = n
self.age = a * DOG_YEARS
end
end
sparky = GoodDog.new("Sparky", 4)
puts sparky.age
这部分其实和 class 也没有什么关系。
示例:
---这里输出的结果是:前面是sparky这个对象对应的class(GoodDog),紧跟的一串数字/字母是这个是这个对象的ID编码---
puts sparky # => #<GoodDog:0x007fe542323320>
puts sparky 等价于 puts sparky.to_s
字符串插值 (string interpolation) 自动调用 to_s method:
示例:
irb :001 > arr = [1, 2, 3]
=> [1, 2, 3]
irb :002 > x = 5
=> 5
irb :003 > "The #{arr} array doesn't include #{x}."
=> The [1, 2, 3] array doesn't include 5.
在这里字符串插值自动的调用 to_s method,然后将 arr 和 x 两个变量转换成了对应的值,然后与字符串结合在了一起。
虽然当前感觉了解 to_s method 是无关紧要的,但这却会在日后帮助我们更好的读写 oo code.
上面有两处使用了 self method:
- 告诉 Ruby 我们想要调用的是 setter method,而不是创建新的变量;
- 使用 self method 帮助定义 class method.
那么 self 究竟是什么?
代码示例
class GoodDog
# ... rest of code omitted for brevity
def what_is_self
self
end end
sparky = GoodDog.new('Sparky', '12 inches', '10 lbs')
p sparky.what_is_self
# => #<GoodDog:0x007f83ac062b38 @name="Sparky", @height="12 inches",
@weight="10 lbs">
通过上述代码我们可以发现 self 指代的是 object(sparky)
代码示例:
class GoodDog
# ... rest of code omitted for brevity
puts self
end
irb :001 > GoodDog
=> GoodDog
通过上面我们得出 self 指代的是 GoodDog 这个 class.
- self,insideofaninstancemethod,referencestheinstance(object)thatcalledthemethod - the calling object. Therefore, self.weight= is the same as sparky.weight= ,in our example.
- self,outsideofaninstancemethod,referencestheclassandcanbeusedtodefineclass methods. Therefore, def self.name=(n) is the same as def GoodDog.name=(n) ,in our example.
继承的东西是 superclass 中的 behavior.
通过继承能够实现代码复用,从而达到以下好处:
使用
<
符号去标记继承方向。
示例:
class Animal
def speak
"Hello!"
end
end
class GoodDog < Animal # 标记GoodDog从Animal继承behavior
end
class Cat < Animal
end
sparky = GoodDog.new
paws = Cat.new
puts sparky.speak # => Hello!
puts paws.speak # => Hello!
当 subclass 中有与 superclass 中想相同的 method 时,subclass 的 object 调用的就是 subclass 中的 method.
示例:
class Animal
def speak
"Hello!"
end
end
class GoodDog < Animal
attr_accessor :name
def initialize(n)
self.name = n
end
def speak
"#{self.name} says arf!"
end
end
class Cat < Animal
end
sparky = GoodDog.new("Sparky")
paws = Cat.new
puts sparky.speak # => Sparky says arf!
puts paws.speak # => Hello!
在 subclass 中调用 superclass 中与 instance method 同名 method.
示例:
class Animal
def speak
"Hello!"
end
end
class GoodDog < Animal
def speak
super + " from GoodDog class"
end
end
sparky = GoodDog.new
sparky.speak # => "Hello! from GoodDog class"
class Animal
def speak
"Hello!"
end
end
class GoodDog < Animal
def speak
super + " from GoodDog class"
end
end
sparky = GoodDog.new
sparky.speak # => "Hello! from GoodDog class"
class Animal
attr_accessor :name
def initialize(name)
@name = name
end
end
class GoodDog < Animal
def initialize(color)
super
@color = color
end
end
---这里因为使用了super 所以初始化object的时候调用了Animal中的initialze method,从而导致bruno多了一个属性--name ---
bruno = GoodDog.new("brown") # => #<GoodDog:0x007fb40b1e6718,@color="brown", @name="brown">
class BadDog < Animal
def initialize(age, name)
super(name)
@age = age
end
end
BadDog.new(2, "bear") # => #<BadDog:0x007fb40b2beb68 @age=2,@name="bear">
[图片上传失败...(image-614c12-1510277893483)]
已上图为例,如实 Cat 与 Dog 有相同的 behavior,这个时候我们就可以将它提取出来,放在 Mammal 中,通过继承来使用。
那么问题来了,Dog 与 Fish 也有相同的 behavior—swim,这个时候显然不能将对应的 method 提取出来放到 Mammal 或者是 Animal 中 (因为 Cat 不可以 swim).解决方案就是使用 Mixing in module .
就是将两个或者多个无法通过继承来 dry up 的 code 封装到一个容器中,然后让这些 class 调用这个容器中的内容。
具体来说是这样。将 Fish 与 Dog 都有的 behavior(swim) 用 Swimmable 这个 module 进行封装,然后让 Fish 与 Dog 进行调用。
示例:
module Swimmable
def swim
"I'm swim."
end
end
解释:
- 使用首字母大写的单词;
- rubyist 习惯上将单词以 able 作为后缀。
module Swimmable
def swim
"I'm swimming!"
end
end
class Animal; end
class Fish < Animal
include Swimmable # mixing in Swimmable module
end
class Mammal < Animal
end
class Cat < Mammal
end
class Dog < Mammal
include Swimmable # mixing in Swimmable module
end
语法:
include modulename
object 在调用方法 (method) 时,是按照一定顺序进行的,这个顺序就叫 method loopup path.
当我们研究一个较大的项目时,可能会困惑于那些方法是哪来的。但是若是知道了 method lookup path,便能够较好的理解那些方法是在哪儿,以及是如何组织的。
以下述代码为例,如何才能知道 Animal 的 object 的 method loopup path 呢?
module Walkable
def walk
"I'm walking."
end
end
module Swimmable
def swim
"I'm swimming."
end
end
module Climbable
def climb
"I'm climbing."
end
end
class Animal
include Walkable
def speak
"I'm an animal, and I speak!"
end
end
使用 ancestors method :
puts "---Animal method lookup---"
puts Animal.ancestors
结果:
--- Animal method lookup ---
Animal
Walkable
Object
Kernel
BasicObject
顺序:
- 在初始化对象的类中查找
- 在该对象的 module 中查找 (按照 mixing 进的 module 的顺序,进行倒序查找)
- 在该类的父类中查找
示例:
module Walkable
def walk
"I'm walking."
end
end
module Swimmable
def swim
"I'm swimming."
end
end
module Climbable
def climb
"I'm climbing."
end
end
class Animal
include Walkable
def speak
"I'm an animal, and I speak!"
end
end
class GoodDog < Animal
include Swimmable
include Climbable
end
puts "---GoodDog method lookup---"
puts GoodDog.ancestors
输出结果:
---GoodDog method lookup path ---
GoodDog
Climbable
Swimmable
Animal
Walkable
Object
Kernel
BasicObject
1.用于 namespacing;
2.作为 container of methods
作用:
定义方法:
module Mammal
class Dog
def speak(sound)
p "#{sound}"
end
end
class Cat
def say_name(name)
p "#{name}"
end
end
end
如何使用 module 中的 class 初始化对象:
buddy = Mammal::Dog.new
kitty = Mammal::Cat.new
buddy.speak('Arf!') # => "Arf!"
kitty.say_name('kitty') # => "kitty"
定义方法:
module Mammal
...
def self.some_out_of_place_method(num)
num ** 2
end
end
调用方法:
value = Mammal.some_out_of_place_method(4)
value = Mammal::some_out_of_place_method(4)
作用:
有时候我们只想让某个方法在 class 内部发生作用,而不再外部被调用,这个时候就使用 private method.
定义方法:
使用保留字:private
class GoodDog
DOG_YEARS = 7
attr_accessor :name, :age
def initialize(n, a)
self.name = n
self.age = a
end
private # method following "private is pirvate method
def human_years
age * DOG_YEARS
end
end
sparky = GoodDog.new("Sparky", 4)
sparky.human_years
说明:
私有方法 (private method) 只能在 instance method 中被调用,且是直接调用不能用 self method.
即使是 objectname.privatemethod 的形式也不行。
作用:
有时候我们想要在 instance method 中使用 self.privatemethod 或者 objectname.privatemethod,的形式,但是有不想外界调用,这是就使用 protected method.
语法:
使用保留字:protected
class Animal
def a_public_method
"Will this work? " + self.a_protected_method
end
protected
def a_protected_method
"Yes, I'm protected!"
end
end
class Student
def initialize(name, grade)
@name = name
@grade = grade
end
def better_grade_than?(other_student)
grade > other_student.grade
end
protected
def grade
@grade
end
end
joe = Student.new("Joe", 90)
bob = Student.new("Bob", 84)
puts "Well done!" if joe.better_grade_than?(bob)
因为所有自己创建的类都继承自 class Object,这就导致当有些 method 与 Object 中的 method 同名时,发生 method overring.
示例:
例如:Object 的 send method 可以调用某个方法,但是若被重写了,就会出问题。
class Child
def say_hi
p "Hi from Child."
end
def send
p "send from Child..."
end
end
lad = Child.new
lad.send :say_hi
报错:
ArgumentError: wrong number of arguments (1 for 0)
from (pry):12:in `send'
要熟知一些常见的 Object methods,避免发生重写 (overrding),否则会对应用造成灾难性的后果。