`
simohayha
  • 浏览: 1386590 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

ruby way之OOP之二

    博客分类:
  • ruby
阅读更多
1 理解allocate

在一些特殊的环境中,你可能需要不调用它的构造器(也就是initialize)来创建一个对象。也就是说你想要创建一个空的对象.例如,假设你有一个对象,它的状态完全的由它的存取方法所确定,因此如果你不是真的想要一个另外的状态的话,调用new方法是没必要的.此时我们能allocate方法.

class Person
  attr_accessor :name, :age, :phone

  def initialize(n,a,p)
    @name, @age, @phone = n, a, p
  end
  def test
    p "aaa"
  end
end

p1 = Person.new("John Smith",29,"555-1234")

p2 = Person.allocate

p p1.age    # 29
p p2.age    # nil
p2.test


2 Modules

在ruby中使用Modules有两个原因.第一个原因是namespace 的管理,当我们在module中存储常量和方法时,我们将会有更少的命名冲突.调用module中的方法和类方法很类似,就是模块名+方法名.因此我们看到File.ctime 和FileTest.exist?, 时,我们从表面无法知道 File和FileTest是模块还是类.

第二个原因更有趣,我们能使用模块进行mixin.关于什么是mixin,就不说了。

一个模块也能有实例方法,当某个类include了它之后,这些实例方法就变成那个类的了。

module MyMod

  def meth1
    puts "This is method 1"
  end

end


class MyClass

  include MyMod

  # ...
end


x = MyClass.new
x.meth1                # This is method 1


但是如果有一个模块方法时,要怎么办。你可能认为他们被作为类方法来调用。但是ruby没有这种行为。module methods 没有被mixed in。

可是我们如果想要这样使用,我们有一个小技巧.我们可以覆盖一个叫做append_features的方法来实现模块方法的mix.看下面的例子:

module MyMod

  def MyMod.append_features(someClass)
    def someClass.modmeth
      puts "Module (class) method"
    end
    super   # This call is necessary!
  end

  def meth1
    puts "Method 1"
  end

end


class MyClass

  include MyMod

  def MyClass.classmeth
    puts "Class method"
  end

  def meth2
    puts "Method 2"
  end

end


x = MyClass.new

                      # Output:
MyClass.classmeth     #   Class method
x.meth1               #   Method 1
MyClass.modmeth       #   Module (class) method
x.meth2               #   Method 2


这是一个非常有价值的例子.首先我们应当理解append_features 不是一个当include发生时就被调用的hook。它实际上做的是include 操作的工作.这就是为什么super方法在这里是必须的了。没有super。模块剩下的(这里是meth1   )将不会被include.

这里还要注意的是append_features里面定义了一个方法,在ruby中方法内部定义的方法只能是singleton method (也就是说要么是类方法,要么是模块方法).如果你尝试着定义一个实例方法,就会抛出一个Nested method error.

有时一个模块可能想要决定一个mixin的发起类。append_features 也可以做这个,因为它的参数就是那个类.

我们其实也可以mix一个实例方法,作为类方法:

module MyMod

  def meth3
    puts "Module instance method meth3"
    puts "can become a class method."
  end

end


class MyClass

  class << self    # Here, self is MyClass
    include MyMod
  end

end


MyClass.meth3

# Output:
#   Module instance method meth3
#   can become a class method.


如果我们使用extend,代码就变成这样了:

class MyClass
    extend MyMod
end


我们这里谈论的都是方法,那么实例变量呢,它能被mix吗。尽管模块拥有自己的实例数据是可能的,可是经常都不需要这样做.

mix一个模块到一个对象,也是可以操作的。后面我们会介绍.

我们可以mix Comparable模块 和定义<=> 方法。这样的话我们就能使用<, >, <=, 这样的操作.

3 转换一个对象

这张其实前面几张都有了。比如to_s和to_str的区别,比如coerce的使用。因此这边就简要的介绍下了.

看下面的例子

class String

  def coerce(n)
    if self['.']
      [n, Float(self)]
    else
      [n, Integer(self)]
    end
  end
end

x = 1 + "23"        # 24
y = 23 * "1.23"     # 29.29


也就是说调用*或者+的时候会将字符串转换,而数字将不会做任何动作.

4 创建一个只有数据的类(Structs)

可能你想这样做:

class Address

  attr_accessor :street, :city, :state

  def initialize(street1, city, state)
    @street, @city, @state = street, city, state
  end

end

books = Address.new("411 Elm St", "Dallas", "TX")


虽然这样也可以做,可是他太丑陋了。在ruby中,我们有Struct 来做这个事.我们能够这样使用Struct:

Address = Struct.new("Address", :street, :city, :state)
books = Address.new("411 Elm St", "Dallas", "TX")


那么new方法的第一个参数的意思是什么呢?当我们调用Struct.new创建一个新的structure template 时,一个新的类就通过Struct 类他自己被创建。这个类的名字就是第一个参数的内容。因此我们还能这样做:

Struct.new("Address", :street, :city, :state)
books = Struct::Address.new("411 Elm St", "Dallas", "TX")


当你创建一个structure 的实例的时候不一定要把所有的变量都写上,当你调用new时,忽略的变量,它会默认为nil。

这里的话,不要创建一个名叫Tms的Struct,因为Struct内置了一个Tms的类。

5 冻结对象

我们如果想要保护一个对象不被改变,这时我们能使用freeze 方法。

str = "This is a test. "
str.freeze

begin
  str << " Don't be alarmed."   # Attempting to modify
rescue => err
  puts "#{err.class} #{err}"
end

arr = [1, 2, 3]
arr.freeze

begin
  arr << 4                      # Attempting to modify
rescue => err
  puts "#{err.class} #{err}"
end

# Output:
#   TypeError: can't modify frozen string
#   TypeError: can't modify frozen array


但是这里注意的是freeze 操作的是对象的引用,而不是那个变量。请看下面的例子:

str = "counter-"
str.freeze
str += "intuitive"       # "counter-intuitive"

arr = [8, 6, 7]
arr.freeze
arr += [5, 3, 0, 9]      # [8, 6, 7, 5, 3, 0, 9]


这里可以看到str使用+=之后创建了一个新的对象,这时str指向了新的对象,因此新的对象并没有被冻结,而老的对象依旧被冻结了。

frozen? 方法返回一个对象是否被冻结.
hash = { 1 => 1, 2 => 4, 3 => 9 }
hash.freeze
arr = hash.to_a
puts hash.frozen?                   # true
puts arr.frozen?                    # false
hash2 = hash
puts hash2.frozen?                  # true









2
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics