ruby way之OOP之一

1 使用多个构造方法

在ruby中没有像c++或者ruby中的构造器,可是我们依然能够创建出类似的多个构造器:

class ColoredRectangle

def initialize(r, g, b, s1, s2)
@r, @g, @b, @s1, @s2 = r, g, b, s1, s2
end

def ColoredRectangle.white_rect(s1, s2)
new(0xff, 0xff, 0xff, s1, s2)
end

def ColoredRectangle.gray_rect(s1, s2)
new(0x88, 0x88, 0x88, s1, s2)
end

def ColoredRectangle.colored_square(r, g, b, s)
new(r, g, b, s, s)
end

def ColoredRectangle.red_square(s)
new(0xff, 0, 0, s, s)
end

def inspect
"#@r #@g #@b #@s1 #@s2"
end
end
a = ColoredRectangle.new(0x88, 0xaa, 0xff, 20, 30)
b = ColoredRectangle.white_rect(15,25)
c = ColoredRectangle.red_square(40)


可是如果属性很多的话,那不是要写的很郁闷,于是我们这里还有一个更漂亮的方法:

class PersonalComputer
attr_accessor :manufacturer,
:model, :processor, :clock,
:ram, :disk, :monitor,
:colors, :vres, :hres, :net

def initialize(&block)
instance_eval &block
end

# Other methods...
end

desktop = PersonalComputer.new do
self.manufacturer = "Acme"
self.model = "THX-1138"
self.processor = "986"
self.clock = 9.6 # GHz
self.ram = 16 # Gb
self.disk = 20 # Tb
self.monitor = 25 # inches
self.colors = 16777216
self.vres = 1280
self.hres = 1600
self.net = "T3"
end

p desktop


这边主要是使用了一个instance_eval方法,它和eval很像,只不过他是在一个给定的对象的上下文中对一个字符串,或者一个block进行求值.这里还要注意的是block中的self
是必须的.如果你想要将一个属性的存取方法去除掉,你可以在block的结尾加上undef(后面是你的方法名):

desktop = PersonalComputer.new do
self.manufacturer = "Acme"
self.model = "THX-1138"
undef model
end
p desktop.model #报错


2 创建一个实例属性

这个很简单,有好几种方法:

class Person

def name
@name
end
def name=(x)
@name = x
end
def age
@age
end

end


class Person
attr :name, true # Create @name, name, name=
attr :age # Create @age, age
end


class SomeClass
attr_reader :a1, :a2 # Creates @a1, a1, @a2, a2
attr_writer :b1, :b2 # Creates @b1, b1=, @b2, b2=
attr_accessor :c1, :c2 # Creates @c1, c1, c1=, @c2, c2, c2=
# ...
end


最常用第三种.

3创建一个类级别的属性和方法.

一个方法或者属性不只能关联到一个实例,而且还能关联到类它自己:

创建一个类方法:
class SoundPlayer

MAX_SAMPLE = 192

def SoundPlayer.detect_hardware
# ...
end

def play
# ...
end

end


这边还有另外一种创建方法:

class SoundPlayer

MAX_SAMPLE = 192

def play
# ...
end

end

def SoundPlayer.detect_hardware
# ...
end


两种的区别是,当类方法被定义在类的外面时,他的常量不在它的作用域中,也就是说在第二种方法里面要得到常量MAX_SAMPLE,必须这样,SoundPlayer::MAX_SAMPLE .

类变量的话是以@@开头,看下面的例子:

class Metal

@@current_temp = 70

attr_accessor :atomic_number

def Metal.current_temp=(x)
@@current_temp = x
end

def Metal.current_temp
@@current_temp
end

def liquid?
@@current_temp >= @melting
end

def initialize(atnum, melt)
@atomic_number = atnum
@melting = melt
end

end

aluminum = Metal.new(13, 1236)
copper = Metal.new(29, 1982)
gold = Metal.new(79, 1948)

Metal.current_temp = 1600

puts aluminum.liquid? # true
puts copper.liquid? # false
puts gold.liquid? # false

Metal.current_temp = 2100

puts aluminum.liquid? # true
puts copper.liquid? # true
puts gold.liquid? # true


这里的类变量在类方法被调用之前就被实例化了。要注意的是,我们能够从一个实例方法里面存取一个类变量,但是我们不能从一个类方法存取一个实例变量.

class Test
def Test.test
puts @a
end
def tests
puts @a
end
def a=(x)
@a=x
end
end
temp=Test.new
temp.a="2"
Test.test #nil
temp.tests #2

当我们想要从一个类方法中打印出一个实例变量会出现什么呢,这时就会打印出nil.为什么是这样呢?原因是这时我们打印出的已经不是这个类的实例变量了,而是Class类的一个实例变量,因此会打印出nil.

还有一种叫做是类实例变量,这边只是简要的介绍,后面我们会详细的介绍:

class MyClass

SOME_CONST = "alpha" # A class-level constant

@@var = "beta" # A class variable
@var = "gamma" # A class instance variable

def initialize
@var = "delta" # An instance variable
end

def mymethod
puts SOME_CONST # (the class constant)
puts @@var # (the class variable)
puts @var # (the instance variable)
end

def MyClass.classmeth1
puts SOME_CONST # (the class constant)
puts @@var # (the class variable)
puts @var # (the class instance variable)
end

end

def MyClass.classmeth2
puts MyClass::SOME_CONST # (the class constant)
# puts @@var # error out of scope
puts @var # (the class instance variable)
end


myobj = MyClass.new
MyClass.classmeth1 # alpha, beta, gamma
MyClass.classmeth2 # alpha, gamma
myobj.mymethod # alpha, beta, delta


可见这边是建立了两个变量(虽然名字什么都是一样),一个是类实例变量,一个是实例变量,当在类方法中使用的是类实例变量,而在实例方法中调用的是实例变量.


4 测试一个对象所属的类

经常我们需要知道这个对象是属于那个类的,这里有很多方法:

class 方法将会返回一个对象的类,和它同义的方法type 已经被废弃了:

s = "Hello"
n = 237
sc = s.class # String
nc = n.class # Fixnum


不要被class方法所返回的东西所迷惑,其实它返回的是一个Class类的实例,因此我们能够调用返回类型的类方法,看起来就好像是Class的一个实例方法:

s2 = "some string"
var = s2.class # String
my_str = var.new("Hi...") # A new string


我们能够比较一个变量和一个类名来看他们是否相等,甚至我们能够使用一个变量作为一个超类,从而定义一个子类.这里只要记住在ruby中,Class是一个对象,Object是一个类.

我们如果想要知道一个变量是否属于某一个类,我们能这样做:

puts (5.instance_of? Fixnum)        # true
puts ("XYZZY".instance_of? Fixnum) # false
puts ("PLUGH".instance_of? String) # true


如果我们想要测试一个有继承关系的对象时,我们可以使用kind_of?或者is_a?方法:

n = 9876543210
flag1 = n.instance_of? Bignum # true
flag2 = n.kind_of? Bignum # true
flag3 = n.is_a? Bignum # true
flag3 = n.is_a? Integer # true
flag4 = n.is_a? Numeric # true
flag5 = n.is_a? Object # true
flag6 = n.is_a? String # false
flag7 = n.is_a? Array # false


这里还有一种就是mix,当一个类mix了一个模块的时候,我们使用is_a?测试时会出现什么呢:

x = [1, 2, 3]
flag8 = x.kind_of? Enumerable # true
flag9 = x.is_a? Enumerable # true


可以看到由于array mix了 Enumerable ,因此一个数组对象也就是(is_a?)一个Enumerable.

我们还可以使用一些其他的操作符:

flag1 = Integer < Numeric         # true
flag2 = Integer < Object # true
flag3 = Object == Array # false
flag4 = IO >= File # true
flag5 = Float < Integer # nil


这些比较的都是他们的继承关系.

每一个类都有一个===方法,如果是class === instance ,当这个实例属于这个类时,就会返回true.

这里我们要注意respond_to方法。它可以测试是否对象含有某个方法,它的第二个参数是来决定是否搜索private方法:

# Search public methods
if wumpus.respond_to?(:bite)
puts "It's got teeth!"
else
puts "Go ahead and taunt it."
end

# 会搜索private方法

if woozle.respond_to?(:bite,true)
puts "Woozles bite!"
else
puts "Ah, the non-biting woozle."
end


有时我们想要立即得到一个类或者对象的父类:

array_parent = Array.superclass    # Object
fn_parent = 237.class.superclass # Integer
obj_parent = Object.superclass # nil


5 测试对象的相等

ruby的Object实现了5个不同的方法来测试对象是否相等,你的类可能实现了他们中的一些,让我们来一个个看.

equal?方法,如果接受者和参数有相同的object id,那么就返回true,这个方法是object的基本的语义,因此你不能覆盖它。

==方法,它测试它的接受者和参数的值,相等的话就返回true.

eql?方法,它是Object的一部分(eql?是在Kernel 模块被实现的,而Object mix了Kernel).就像是==方法,但是eq?方法更严格。例如不同的numeric 对象使用==比较时将会转换为一个更通用类型,而使用eq?就不会进行这种转换:

flag1 = (1 == 1.0)     # true
flag2 = (1.eql?(1.0)) # false


eq?方法存在还有一个原因:那被用来比较hash keys的值。当你使用你的对象作为一个hash时,你如果想要覆盖ruby默认的行为,你就需要同时覆盖eql?和hash方法.

还剩下的两个方法,所有对象都实现了他们,=== 被用来在case语句中:

case an_object
when String
puts "It's a string."
when Numeric
puts "It's a number."
else
puts "It's something else entirely."
end


这边比较an_object和下面的条件就是用===方法.

还有一个正则表达式的匹配方法=~,它的相反的方法是!~.

6 控制方法的存取

定义一个private方法:

class Bank
def open_safe
# ...
end

def close_safe
# ...
end

private :open_safe, :close_safe

def make_withdrawal(amount)
if access_allowed
open_safe
get_cash(amount)
close_safe
end
end

# make the rest private

private

def get_cash
# ...
end

def access_allowed
# ...
end
end


Private 方法不能被一个明确的接受者所调用,可是他们能被隐式的接受者self所调用。这就意味着你不能在另外的对象里面调用一个Private方法.private方法对子类是可用的,但是只能在相同的对象.
class Bank
private
def get_cash
puts "aaa"
end
end

class BankChild <Bank
def test
get_cash
end
end

BankChild.new.test


protected 有更少的限制,Protected方法只能够被类和他的子类的实例所存取.请看下面的例子,age方法被设置为protected的,因此它可以被多个对象调用,可是在Person外面,age方法是不可用的:

class Person
def initialize(name, age)
@name, @age = name, age
end

def <=>(other)
age <=> other.age
end

attr_reader :name, :age
protected :age
end

p1 = Person.new("fred", 31)
p2 = Person.new("agnes", 43)
compare = (p1 <=> p2) # -1
x = p1.age # Error!


如果把age方法变为private的话,直接<=>方法就会出错,那是因为age被多个对象调用了.而不仅仅是被self.

一般的在一个类或者模块的外面的定义的方法,默认都是private的。那是因为他们都是在Object类中定义的,他们都是全局变量,但是他们不能被一个接收者所调用.


7拷贝一个对象

ruby内置的Object#clone 和 #dup方法能够复制它们的接收者.他们的区别是clone方法能够把singleton method 也复制了:

s1 = "cat"

def s1.upcase
"CaT"
end

s1_dup = s1.dup
s1_clone = s1.clone
s1 #=> "cat"
s1_dup.upcase #=> "CAT" (singleton method not copied)
s1_clone.upcase #=> "CaT" (uses singleton method)


dup 和clone都只是浅拷贝,也就是说如果有嵌套对象的话,他们将不会复制:

arr1 = [ 1, "flipper", 3 ]
arr2 = arr1.dup

arr2[2] = 99
arr2[1][2] = 'a'

arr1 # [1, "flapper", 3]
arr2 # [1, "flapper", 99]


你可以选择自己实现一个deep copy,或者使用前面讲的Marshal 模块.
arr1 = [ 1, "flipper", 3 ]
arr2 = Marshal.load(Marshal.dump(arr1))

arr2[2] = 99
arr2[1][2] = 'a'

arr1 # [1, "flipper", 3]
arr2 # [1, "flapper", 99]


可以看到arr2的改变并没有影响arr1

9 使用initialize_copy

当你使用dup或者clone时,构造器被绕过了。所有的状态信息被保存.

如果你不想绕过构造器呢?看下面的代码:

class Document
attr_accessor :title, :text
attr_reader :timestamp

def initialize(title, text)
@title, @text = title, text
@timestamp = Time.now
end
end

doc1 = Document.new("Random Stuff",File.read("somefile"))
sleep 300 # Wait awhile...
doc2 = doc1.clone

doc1.timestamp == doc2.timestamp # true
#两个timestamp是一样的!


我们想要得到拷贝发生时的时间的话,这时我们能够使用initialize_copy方法:

class Document     # 重新打开Document类
def initialize_copy(other)
@timestamp = Time.now
end
end

doc3 = Document.new("More Stuff",File.read("somefile"))
sleep 3 # Wait awhile...
doc4 = doc3.clone

p doc3.timestamp == doc4.timestamp # 这时变成了false


这里要注意initialize_copy是在状态信息被拷贝之后调用的.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值