ESS多版本 【TA】【ESS】07 类的多态性

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
7: More classes and polymorphism 类的多态性

Classes aren’t just stand-alone classes, but they’re actually kind of like a family tree in that they have ancestors. A String, for example, has the Object class as ancestor (yes, Object is also a class). To illustrate which ancestors a class has, we’ll write it down like so:

类不仅仅只是单独的一个类,不同的类之间是有一定的关系的。
就好像是人类跟狗类也是存在一定的关系的一样。
更加具体的,某些类之间是有父子关系的,比如说String类(字符串类),我们来看下面的这个例子:

Ruby:
Kernel -> Object -> Comparable -> String

This is the ancestor line for the String class. The first class that comes before String, Comparable, is the superclass of the String class.

从上面的这个例子中,我们可以清楚的看到String的父子类关系链。
可以看到,String类的父类(Superclass)是Comparable类(可比较类);String类是Comparable类的子类(Subclass)。

The ancestor line of the Fixnum class is this:

再来看一个Fixnum类(整数类)的父子类关系链:

Ruby:
Kernel -> Object -> Comparable -> Numeric -> Precision -> Integer -> Fixnum

Now let’s say we’re doing 12.odd? #=> false. This method returns whether or not the number is odd (13.odd? would be true).
When you call a method on an object (an instance method), it will look at the highest class there is, which is Fixnum in this case. If that Fixnum class contains an instance method called odd?, it will call that method.

我们写下12.odd?这段代码的时候,对于这段代码来说,我们对12这个整数类的实例调用了odd?方法。
当整数类存在一个叫做odd?的方法时,那么就会直接调用这个odd?方法。

If that Fixnum class does not contain an instance method called odd?, it will look at the class below it, which is Integer. If that Integer class contains an instance method called odd?, it’ll call that. If not, it’ll check Precision, Numeric, Comparable, Object, and then Kernel. If it still hasn’t found it after Kernel, it will raise a NoMethodError. This is the concept of inheritance/polymorphism; a class will inherit all methods from its ancestors.

而如果整数类中不存在一个叫做odd?的方法的话,程序会向上从整数类的父类中继续查找是否有一个叫做odd?的方法,如果有的话就运行这个方法,如果还没有的话,就继续向上查找,有就运行,没有就向上查找,直到Kernel(核心)为止,如果Kernel依然没有,那么就会报错,因为你调用了一个根本不存在的方法。

也就是说,子类会继承父类的全部方法。

Comparable has an instance method called .is_a?, which Strings, Arrays, Floats, Integers and what not all use. But Kernel also has an instance method called .is_a?, which is used by classes that don’t have Comparable (such as NilClass). NilClass’s ancestors are:

我们来看一个is_a?这个方法的例子,这个方法存在于Comparable类中,虽然NilClass并不是Comparable的子类,但是我们依然可以对nil对象使用这个方法,这就是因为在Kernel中也存在着一个叫做is_a?的方法。
我们来看一下NilClass的父子类关系链:

Ruby:
Kernel -> Object -> NilClass

So when you call 14.is_a?(Fixnum), you’re calling it on Comparable.
When you call nil.is_a?(Fixnum), you’re calling it on Kernel.

所以,当你写14.is_a?(Fixnum)的时候,程序调用的其实是Comparable里面的is_a?。
而当你写nil.is_a?(Fixnum)的时候,程序调用的其实是Kernel里面的is_a?。

Speaking of .is_a?, if you give it a class that is a part of the class’s inheritance tree, it’ll also return true.

另外,我们也可以用is_a?来检查一个对象的父类是什么,请看下面的例子:

Ruby:
14.is_a?(Numeric) #=> true
14.is_a?(Object) #=> true
14.is_a?(String) #=> false
# 14同时属于Numeric(数字类)类和对象类。

Every single object in Ruby inherits both Kernel and Object, which means var.is_a?(Object) is always going to return true no matter what (as long as var exists). The same applies to var.is_a?(Kernel).
 
最后编辑:

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
Applying knowledge on inheritance trees 利用类的父子关系链

This might sound useless, but it can actually come in handy.
Where normally, to determine if a number is a Float or a Fixnum, we’d use var.is_a?(Float) || var.is_a?(Fixnum), their inheritance trees reveal this:

Fixnum:

这是整数类的父子关系链:

Ruby:
Kernel -> Object -> Comparable -> Numeric -> Integer -> Fixnum

Float:

这是浮点数类的父子关系链:

Ruby:
Kernel -> Object -> Comparable -> Numeric -> Float

What we can gather from this, is that var.is_a?(Numeric) will be true for both Floats and Fixnums. You just have to know if any other classes use Numeric, but that isn’t the case, so it’s safe to use. If we did Comparable, for instance, we’d also get String in there, which we don’t want.

我们可以利用哪些类是共有的,而哪些类是独有的,等等的关系,来检查对象。
 

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
Writing your own classes 新建一个类

You’re not limited to just the existing, internal classes like String, Array and the like. You can also create your own. A class has a name, and this name is a constant - this means that it starts with an uppercase letter and cannot contain any special characters or operators like !@#$%^&*() (and a lot more, probably).

除了本来就有的内置的类外,我们也可以新建类。
一个类需要有一个名字,类的名字是一个常量,也就是说,类的名字以大写字母开头,并且,类的名字不能包含有特殊字符,同时,类是唯一的,不存在两个名字相同的类。

To create or define a class, you use the class keyword, followed by the name. Just like for, def, and if, class has to be closed with end. In the example below we’ll create a class named CustomClass with nothing in it:

你可以使用class关键词新创建一个类,就跟def定义新方法一样,定义新类你也需要在结尾加上end。
我们来看下面这个例子:

Ruby:
class CustomClass
end
# 新定义了一个叫做CustomClass(自定义类)的类。

Now, if you type CustomClass in your code, you’re referring to this newly created class. If you do this before the class is actually defined, it won’t exist yet, and using it will cause errors. But now you can also use this in methods like something.is_a?(CustomClass), or something.class == CustomClass.

现在,你可以使用is_a?(CustomClass)来检查一个对象是否是CustomClass类了。

We now have a class, but we don’t have any instances of it yet. To initialize an instance of CustomClass, you write CustomClass.new. This returns an object, which is an instance of CustomClass. You should store this in a variable somewhere so you can use it again later.

现在,我们有了一个类,但是我们还没有这个类的任何实例。
要创建一个这个类的实例,我们需要这样写CustomClass.new,也就是说,我们对这个类调用new(新的)方法,这样就会创建一个这个类的实例。
创建了实例之后,我们应该把它和变量关联起来,这样我们就能随时使用这个实例了。

When you do [1,2,3].size #=> 3, you’re calling the instance method called size on an instance of an array (hence it’s called instance method, duh). You can very easily add similar instance methods to your class by simply defining a method like you normally would.

目前,我们的新类中还并没有任何的实例方法,那我们就根据我们之前学过的内容,新建一个实例方法吧,请看下面的例子:

Ruby:
class CustomClass
  def say_hello
    print "Hello!"
  end
end
# 我们新创建了一个叫做say_hello(说你好)的方法。

To test this out, we create an instance of the class with CustomClass.new, and then call .say_hello on it:

我们来看下面的这个例子:

Ruby:
var = CustomClass.new
var.say_hello
# 首先,通过CustomClass.new创建了一个CustomClass的实例,
# 接着,将这个实例与var变量关联起来,
# 最后,对var调用say_hello这个实例方法。

This might be a bit confusing when you’re used to the special 1..6, “hello”, [1,2,3] formats and the like to create an instance of a class. These are actually just very special ways to create an instance of those classes - normally, for any class, you would use <ClassName>.new. (And no, there’s no way to make one of these kind of formats yourself, unfortunately. You’re stuck with .new).
 

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
Class methods 类方法

All methods we’ve seen so far were instance methods, but there’s also a thing called class methods. As the name suggests, these are methods on the class rather than the instance.

到目前为止我们说到的所有方法,其实都是实例方法,其实还有另一种类型的方法,叫做类方法。
顾名思义,实例方法,就是可以对实例调用的方法。
而类方法,就是可以对类本身调用的方法。

Where an instance is a creation or version of the class, methods on the class are only accessible on that class and will always be the same. They’re not bound to any instances. An instance would be 10, “hello”, [1,2], 1..5, false, or else; these are versions or instances of the classes, respectively Fixnum, String, Array, Range, and FalseClass, of which you can have as many as you want. As there are no simple examples of class methods by default, we’ll create our own first. You do everything the way you normally would when creating an instance method in a class, but instead of def <MethodName>, you write def self.<MethodName> (or def <ClassName>.<MethodName>, even).

那么如何创建类方法呢?
很简单,创建类方法和实例方法差不多。
首先是位置,你需要在类内部创建类方法,这个和创建实例方法是一样的;
接着,创建类方法和实例方法不同的是,你需要在类方法的名字的前面加一个self.,我们来看下面的这个例子:

Ruby:
class CustomClass
  def self.say_hello
    print "Hello!"
  end
end
# 这里我们创建了一个叫做say_hello的类方法。
# 除了可以使用常规的“.”来调用类方法外,对于类方法来说,
# 你还可以使用“::”(两个冒号),来调用类方法。

Another way to call class methods (which works only with class methods), is ::say_hello rather than .say_hello on the class.

We would use this on CustomClass itself, and not on its instances:

我们来看下面的例子:

Ruby:
CustomClass.say_hello #=> "Hello!"
# 这里我们直接对CustomClass这个类调用了say_hello方法。

var = CustomClass.new
var.say_hello #=> Undefined method 'say_hello' for #<CustomClass>
# 对CustomClass的实例调用say_hello方法时会直接报错,
# 因为没有一个叫做say_hello的实例方法。

#<CustomClass> means it’s an instance of said class (it usually has a 0x0000 (hex) number after it - ignore this)
 
最后编辑:

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
The constructor 构造器

Looking at this, you should think “But isn’t CustomClass.new also a class method?”, and you’d be right. This might not seem very interesting, but Class::new (just like Class#method refers to an instance method, Class::method refers to a class method in speech) is a very special kind of class method. It creates an instance of itself and allocates the memory needed, and then immediately after creation, it calls an instance method called initialize if it exists - this is called the constructor of the class, which just means it’s called upon creation. We can implement this and add code to it, if we want:

到这里你可能会想,我们在创建类的新的实例的时候,是对类调用了new方法,比如CustomClass.new,那是不是就是说new其实也是一个类方法?
对的!
new是一个非常特殊的类方法,它实际上存在于Class(类类)类中,任何类其实都是Class类的子类,所以你就算没在你的类里定义new方法,你依然可以调用父类中的new方法。

在实例被创建出来之后,如果类中有一个叫做initialize(初始化)的实例方法的话,会立即调用这个方法,这个方法被称为这个类的构造器,或者你也可以直接说初始化方法,另外需要注意的是,这个方法的名字是固定的,不可修改。
初始化方法,顾名思义,就是对你新创建的实例的各项属性进行一个初始的设置,或者说默认的设置。

还是可以拿人来举例,人在出生时,相当于创建了一个人类的实例,而人出生时都是小孩子,这个就是初始化,默认设置出生的是小孩,而不是大人。

我们可以随意修改initialize方法来设置一个实例的初始状态以符合我们的需求,我们来看下面的这个例子:

Ruby:
class CustomClass
  def initialize
    p "This object was just created!"
    # 打印 “这个对象刚被创建!”
  end

  def say_hello
    print "Hello!"
  end
end

var = CustomClass.new # Before var is assigned, it prints "This object was just created!"
# 这里会打印出“这个对象刚被创建!” ,因为你用new创建了一个新的实例,
# 而类中存在初始化方法,所以初始化方法会在实例创建之后立刻被调用。

var.say_hello # Prints "Hello!"

We can even pass arguments via Class::new (the constructor). You just need to make sure Class#initialize matches those arguments.

我们也可以在new创建新的实例的时候给初始化方法传递参数,但是要确保你传递的参数是可被接受的,请看下面的这个例子:

Ruby:
class CustomClass
  def initialize(string)
    print string
  end
end

var = CustomClass.new("Hello!") # Prints out "Hello!"
# 我们在new后面用括号传递参数,这个参数就是初始化方法的参数。

So in short: Class::new calls Class#initialize, and their arguments are matched up.

Class::new is internal and shouldn’t be changed, but Class#initialize can be overriden and is called upon creation of the object. If you typo def initialize, it won’t be called. This can cause you a severe headache if you don’t notice it if it doesn’t cause any errors, so make sure you spell it right.

There’s another catch with the constructor though - its return value is disregarded. No matter what you return in def initialize, it’s going to return the newly created object. You can’t change that.

关于初始化方法,还有一点你需要注意,在初始化方法中使用返回的时候,返回值会被忽略,也就是说,你使用new时必定会得到这个类的实例,请看下面的例子:

Ruby:
class CustomClass
  def initialize
    print "Hello!"
    return 7
    print "Goodbye!"
  end
end

var = CustomClass.new
# Prints out "Hello!"
var.class #=> CustomClass
# 我们查看var的类,会发现是CustomClass,并不是Fixnum。
# 另外,虽然返回值被忽略了,但是return的中止代码的作用依旧会发生,
# 也就是说,并不会打印出“Goodbye!(再见!)”。

Normally, you’d expect var to be 7 and var.class to be Fixnum because the return value of def initialize is 7, but this return value is disregarded. It does still cancel the rest of the method, though, so “Goodbye!” is never printed.
 
最后编辑:

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
Adding and overwriting methods 增加和覆盖方法

You can open and close a class whenever and wherever you want without any issues. If we wanted, we could define a method like this:

类可以随时打开和关闭,我们也可以随时在类中增加新的方法,请看下面的例子:

Ruby:
class CustomClass
end

class CustomClass
  def say_hello
    print "Hello!"
  end
end

class CustomClass
end

var = CustomClass.new
var.say_hello #=> "Hello!"

It opens and closes the class, opens it again, adds a method, closes, and opens and closes again. It doesn’t actually modify anything. The only time opening and closing directly may be useful is if the class isn’t actually defined yet in the first place.

在上面的例子中,类先是打开,接着关闭,接着打开,接着新增加了一个方法,接着关闭,接着又打开,接着又关闭。
如果我们仅仅只是打开和关闭它的话,实际上不会产生任何影响。
唯一有影响的是第二次打开和关闭的时候,我们为这个类新加了一个实例方法。

If you add a method that has the same name as a method that already exists, it will be overwritten. All calls to that will then call the new method.

但是,如果你新定义的方法是这个类中已有的方法的话,那么已有的方法就会失效,也就是说,因为名字相同,所以已有的方法被覆盖了。
也就是说,在同一个类中,同名的方法只能有一个,后面的同名方法会覆盖之前的同名方法。
我们来看下面的这个例子:

Ruby:
class CustomClass
  def say_hello
    print "Hello!"
  end
end

CustomClass.new.say_hello #=> "Hello!"

class CustomClass
  def say_hello
    print "Hi!"
  end
end

CustomClass.new.say_hello #=> "Hi!"
# 之前定义的方法被后面定义的同名方法覆盖了。

You can also add methods to already existing classes, or overwrite methods that already exist (whether or not that’s a good idea is a different story, but it’s possible).

下面这个例子不知道是什么鬼,很奇怪,但是原文就是这样的。

Ruby:
class String
  def greet
    print "Hello!"
  end
end

"test".greet #=> "Hello!"
# 这里对"test"这个字符串调用了greet这个方法,有点奇怪。
 

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
Inheriting classes 类的继承

By default, a new class inherits Object (which in turn inherits Kernel), which means the inheritance tree for any new class is:

默认情况下,新的类会继承自Object类,请看下面的YourNewClass(你的新类类)的例子:

Ruby:
Kernel -> Object -> YourNewClass

To change which class a new class inherits from, you write < ClassName after the new class name when you define a class:

如果你想要改变继承关系的话,你可以在新创建一个类的时候,就直接为其指定它的父类,请看下面的这个例子:

Ruby:
class One
end

class Two < One
end
# 这里就直接指定Two(二)是One(一)的子类(使用 < XXX,XXX就是父类)。

The Two class now inherits from the One class, so the inheritance trees are as follows:

请看下面的父子类关系图:

Ruby:
Kernel -> Object -> One

and

Ruby:
Kernel -> Object -> One -> Two

So if we defined a method in class One, we could call it in class Two:

所以,我们可以对Two的实例调用One的实例方法,请看下面的例子:

Ruby:
class One
  def say_hello
    print "Hello!"
  end
end

class Two < One
end

Two.new.say_hello #=> "Hello!"

To overwrite this method, you just add the method to class Two and make it do something else.

同时,你也可以在Two中写一个叫做say_hello的方法,这样当你对Two的实例运行say_hello的时候,就会调用Two本身的这个say_hello实例方法。

To open a class with inheritance back up later, you can either include the inheritance part of leave it out - that doesn’t matter.

你只能在Two被创建的最开始为其指定父类,在以后你打开或者关闭Two时,并不需要特别写出来Two继承自One,请看下面的例子:

Ruby:
class One
end

class Two < One
end

class Two
end

class Two < One
end

However, if a class is going to inherit from a different class, whenever that class is defined (the first time it’s opened), it has to inherit from that class. You can’t not-inherit and then inherit later anyway.

Ruby:
class One
end

class Two
end

class Two < One #=> Error: Superclass mismatch for class Two
end
 
最后编辑:

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
The ‘super’ keyword “超级”关键词

When you use super in a method, it will look at the superclasses of the current class for a method with the same name as the one it’s called it, and call that:

当你想要部分的扩展一个已经存在在父类中的方法,而不是用子类中的同名方法完全覆盖父类中的方法的时候,你就可以使用super(超级,或者说父类)关键词。
你可能会奇怪为什么是super,这其实是因为父类的英文是Superclass。
我们来看下面的这个例子:

Ruby:
class One
  def hi
    p "Hi!"
  end
end

class Two < One
  def hi
    p "Hello!"
  end
end

Two.new.hi #=> "Hello!"

class Two
  def hi
    p "Hello!"
    super # 调用父类的同名方法。
  end
  # 这里重新定义的hi(嗨)方法覆盖了前面Two中定义的hi。
end

Two.new.hi # Prints "Hello!", then calls One#hi which prints out "Hi!"
# 程序会先运行Two里的hi方法,而hi方法里面有super,super会指向父类中
# 的同名方法,所以其实也就相当于额外又运行了父类中的hi。
# 所以最后的结果是先打印出“Hello!”,再打印出“Hi!”。

If none of the superclasses have a method by that name, it will raise an error.

如果所有的父类中都不存在同名方法,那么使用super就会报错,请看下面的这个例子:

Ruby:
class One
end

class Two < One
  def hi
    p "Hello!"
    super
  end
end

Two.new.hi # Prints "Hello!", then raises an "Undefined superclass method 'hi" error.
# 程序会先打印出“Hello!”,然后报错。

You can also give arguments to super.

super也可以传递参数,我们来看下面的这个例子:

Ruby:
class One
  def self.greet(string)
    print string
  end
end

class Two < One
  def self.greet
    print "Hello!"
    super("Hi!")
  end
end

Two.greet #=> "Hello!", then "Hi!"

本章完。
 
最后编辑:

在线成员

论坛统计

主题
474
消息
2,137
成员
2,909
最新成员
小灵喵~