ruby对象是严格封装的:只能通过定义的方法访问其内部状态。方法使用的成员变量在对象外部不能直接访问,不过可以通过getter、setter等访问器方法(accessor),使他们看起来好像是直接访问的。
与对象状态的封装性相反,ruby中的类非常开放。每个ruby程序都可以为现有类添加方法,而且也可以为单个对象添加“单键方法(singleton method)”。
创建类
Classes are created in Ruby with the class keyword:
class Point
end
Like most Ruby constructs, a class definition is delimited with an end. In addition to
defining a new class, the class keyword creates a new constant to refer to the class. The
class name and the constant name are the same, so all class names must begin with a
capital letter.
Within the body of a class, but outside of any instance methods defined by the class,
the self keyword refer
class关键字还创建了一个常量用于引用这个类,这个常量名与类名相同,因此所有类名必须以大写字母开头。(因为常量名必须以大写字母开头)。
写成class a会报错:
class/module name must be CONSTANT
跟绝大多数ruby语句一样,class是一个表达式,class表达式得到值等于类定义体中最后一个表达式的值。一般而言,类的最后一个表达式都是一个用于定义方法的def语句,def语句的值总是等于nil。
实例化一个Point对象
p = Point.new
The constant Point holds a class object that represents our new class. All class objects
have a method named new that creates a new instance.
常量Point代表了类对象,所有的类对象都有一个new方法。
We can’t do anything very interesting with the newly created Point object we’ve stored
in the local variable p, because we haven’t yet defined any methods for the class. We
can, however, ask the new object what kind of object it is:
p.class # => Point
p.is_a? Point # => true
初始化
通过initialize方法实现
class Point def initialize(x,y) @x,@y=x,y end end
这里定义了一个实例方法,当一个实例方法被调用时,self的的值就是代表实例。
类对象的new方法在创建一个实例化,自动调用该实例的initialize方法,传给new方法的所有 参数被传递给initialize方法。
除了被Point.new自动调用外,initialize方法会自动成为类的私有方法。对象自身可以调用initialize方法,不过不能显式对p调用initialize来重新初始化其状态。
定义to_s方法
任何自定义的类都应该定义一个to_s方法,这在调试时非常有用。下面显示如恶化为Point定义这个方法。
class Point def initialize(x,y) @x,@y=x,y end def to_s "(#@x,#@y)" end end
我们就可以调用
puts p
Accessors and Attributes 访问器和属性
定义getter:
def x @x def y @y
如果想要Point类成为一个可变类mutable,可以增加setter方法
class Point def initialize(x,y) @x,@y=x,y end def to_s "(#@x,#@y)" end def x;@x;end def y;@y;end def x=(value) @x=value end def y=(value) @y=value end end
可以这么调用:
p=Point.new(1,2)
p.x=0
p.y=0
一旦定义了像x=这样的写者方法,你可能会试图在实例方法中使用它们。也就是说,你想用x=2来隐士调用x=(2),而不再用@x=2.不过这是不行的,x=2只会创建一个新的局部变量。
这对新手是一个常见的错误。只有当对一个对象使用使用赋值表达式式,才会调用它的写着方法。如果你希望在定义写者方法的类中使用这个写者方法,则要通过self显式调用它,比如:self.x=2。
This combination of instance variable with trivial getter and setter methods is so common
that Ruby provides a way to automate it. The attr_reader and attr_accessor
methods are defined by the Module class. All classes are modules, (the Class class is a
subclass of Module) so you can invoke these method inside any class definition. Both
methods take any number of symbols naming attributes. attr_reader creates trivial
getter methods for the instance variables with the same name. attr_accessor creates
getter and setter methods. Thus, if we were defining a mutable Point class, we could
write:
Module有attr_reade和attr_accessor方法。因为所有的类都是模块(Class类是Module的子类),所以可以在任何类的实例中调用这些方法。每个方法接受任意数目的符号(用作属性名)作为参数,atrtr_reader为给定名字的实例变量创建同名的读者方法,
定义可变的类:
class Point
attr_accessor :x, :y # Define accessor methods for our instance variables
end
定义不可变的类:
And if we were defining an immutable version of the class, we’d write:
class Point
attr_reader :x, :y # Define reader methods for our instance variables
end
Each of these methods can accept an attribute name or names as a string rather than
as a symbol. The accepted style is to use symbols, but we can also write code like this:
attr_reader "x", "y"
attr_reader和attr_accessor方法可以为我们创建实例方法。这是元编程的一个例子。他展示了ruby的一个强大的特性。注意attr这些方法在类的定义内被调用,这使得他们仅在类定义时执行一次。在这里没有效率方面的问题:这样创建的读者和写者和硬编码出来的方法是一样快的。
Defining Operators定义操作符
We’d like the + operator to perform vector addition of two Point objects, the * operator
to multiply a Point by a scalar, and the unary – operator to do the equivalent of multiplying
by –1. Method-based operators such as + are simply methods with punctuation
for names. Because there are unary and binary forms of the – operator, Ruby uses the
method name –@ for unary minus.减号有一元的和二元的。[email protected]代表一元的。 Here is a version of the Point class with mathematical
operators defined:
class Point attr_reader :x, :y # Define accessor methods for our instance variables def initialize(x,y) @x,@y=x,y end def +(other) # Define + to do vector addition Point.new(@x + other.x, @y + other.y) end def [email protected] # Define unary minus to negate both coordinates Point.new([email protected], -@y) end def *(scalar) # Define * to perform scalar multiplication Point.new(@x*scalar, @y*scalar) end end
请注意,我们定义的*方法需要一个数值参数,而非Point对象,如果p是一个点,p*2是允许的;不过由于我们的实现方式,2*p是不能正常工作的。如果想要2*p与p*2返回同样的结果,可以定义一个coerce方法:
# If we try passing a Point to the * method of an Integer, it will call
# this method on the Point and then will try to multiply the elements of
# the array. Instead of doing type conversion, we switch the order of
# the operands, so that we invoke the * method defined above.
def coerce(other)
[self, other]
end
Array and Hash Access with [ ]
Ruby uses square brackets for array and hash access, and allows any class to define a
[] method and use these brackets itself. Let’s define a [] method for our class to allow
Point objects to be treated as read-only arrays of length 2, or as read-only hashes with
keys :x and :y:
def [](index) case index when 0, -2 then @x when 1,-1 then @y when :x,"x" then @x when :y,"y" then @y else nil end end
Enumerating Coordinates枚举坐标
If a Point object can behave like an array with two elements, then perhaps we ought tobe able to iterate through those elements as we can with a true array. Here is a definition
of the each iterator for our Point class. Because a Point always has exactly two elements,
our iterator doesn’t have to loop; it can simply call yield twice:因为Point仅仅只有2个元素,没必要loop,仅仅调用yield两次即可。
# This iterator passes the X coordinate to the associated block, and then
# passes the Y coordinate, and then returns. It allows us to enumerate
# a point as if it were an array with two elements. This each method is
# required by the Enumerable module.
def each
yield @x
yield @y
end
With this iterator defined, we can write code like this:
p = Point.new(1,2)
p.each {|x| print x } # Prints "12"
更重要的是,一旦定义了each迭代器,我们就可以混入Enumerable模块的一些方法,这些方法都是基于each定义的,这样,通过加入下面的一行代码,他就会获得超过20个迭代器:
include Enumerable
如果加入了这行代码,我们就可以写出如下有趣的代码:
#is the point p at the origin
p.all? {|x| x==0} #true if the block is true for all elems
ruby面向对象class