也就是说,基于静态类的实现,在不能响应消息的情况下,得出的结论是:目前的对象不具有所要求的特性,但是如果尝试从原型链里获取,依然可能得到结果,或者该对象经过一系列变化以后拥有该特性。
关于ECMAScript,具体的实现就是:使用基于委托的原型。 然而,正如我们将从规范和实现里看到的,他们也有自身的特性。
Concatenative模型
老实说,有必要在说句话关于另外一种情况(尽快在ECMASCript没有用到):当原型从其它对象复杂原来代替原生对象这种情况。这种情况代码重用是在对象创建阶段对一个对象的真正复制(克隆)而不是委托。这种原型被称为concatenative原型。复制对象所有原型的特性,可以进一步完全改变其属性和方法,同样作为原型可以改变自己(在基于委托的模型中,这个改变不会改变现有存在的对象行为,而是改变它的原型特性)。 这种方法的优点是可以减少调度和委托的时间,而缺点是内存使用率搞。
Duck类型
回来动态弱类型变化的对象,与基于静态类的模型相比,检验它是否可以做这些事和对象有什么类型(类)无关,而是是否能够相应消息有关(即在检查以后是否有能力做它是必须的) 。
例如:
复制代码 代码如下:
// 在基于静态来的模型里
if (object instanceof SomeClass) {
// 一些行为是运行的
}
// 在动态实现里
// 对象在此时是什么类型并不重要
// 因为突变、类型、特性可以自由重复的转变。
// 重要的对象是否可以响应test消息
if (isFunction(object.test)) // ECMAScript
if object.respond_to?(:test) // Ruby
if hasattr(object, 'test'): // Python
这就是所谓的Dock类型 。 也就是说,物体在check的时候可以通过自己的特性来识别,而不是对象在层次结构中的位置或他们属于任何具体类型。
基于原型的关键概念
让我们来看一下这种方式的主要特点:
1.基本概念是对象
2.对象是完全动态可变的(理论上完全可以从一个类型转化到另一个类型)
3.对象没有描述自己的结构和行为的严格类,对象不需要类
4.对象没有类类但可以可以有原型,他们如果不能响应消息的话可以委托给原型
5.在运行时随时可以改变对象的原型;
6.在基于委托的模型中,改变原型的特点,将影响到与该原型相关的所有对象;
7.在concatenative原型模型中,原型是从其他对象克隆的原始副本,并进一步成为完全独立的副本原件,原型特性的变换不会影响从它克隆的对象
8.如果不能响应消息,它的调用者可以采取额外的措施(例如,改变调度)
9.对象的失败可以不由它们的层次和所属哪个类来决定,而是由当前特性来决定
不过,还有一个模型,我们也应该考虑。
基于动态类
我们认为,在上面例子里展示的区别“类VS原型 ”在这个基于动态类的模型中不是那么重要,(尤其是如果原型链是不变的,为更准确区分,还是有必要考虑一个静态类)。 作为例子,它也可以使用Python或Ruby(或其他类似的语言)。 这些语言都使用基于动态类的范式。 然而,在某些方面,我们是可以看到基于原型实现的某些功能。
在下面例子中,我们可以看到仅仅是基于委托的原型,我们可以放大一个类(原型),从而影响到所有与这个类相关的对象,我们也可以在运行时动态地改变这个对象的类(为委托提供一个新对象)等等。
复制代码 代码如下:
# Python
class A(object):
def __init__(self, a):
self.a = a
def square(self):
return self.a * self.a
a = A(10) # 创建实例
print(a.a) # 10
A.b = 20 # 为类提供一个新属性
print(a.b) # 20 – 可以在"a"实例里访问到
a.b = 30 # 创建a自身的属性
print(a.b) # 30
del a.b # 删除自身的属性
print(a.b) # 20 - 再次从类里获取(原型)
# 就像基于原型的模型
# 可以在运行时改变对象的原型
class B(object): # 空类B
pass
b = B() # B的实例
b.__class__ = A # 动态改变类(原型)
b.a = 10 # 创建新属性
print(b.square()) # 100 - A类的方法这时候可用
# 可以显示删除类上的引用
del A
del B
# 但对象依然有隐式的引用,并且这些方法依然可用
print(b.square()) # 100
# 但这时候不能再改变类了
# 这是实现的特性
b.__class__ = dict # error