attributes
该如何理解,为什么说属性是一种方法?
virtual attributes
又该如何理解?
先说属性与方法,前不久我写 理解 JavaScript 的时候刚好谈过这个问题。对象中的属性与方法一直以来让很多初学者混淆不清,其根源在于翻译。为什么这么说呢?英文书籍里当谈到对象的属性时,会出现两种情况:
Attribute:特指用来保存 数据 的属性(如 Ruby 中的实例变量)而不是 行为(如 Ruby 中的方法);
Property:这是一种统称,无论是用来表示 数据 的 Attribute,还是用来表示 行为 的 Method,它们都是对象的 Property;
在英文里这样表述毫无问题,当读者看到 Property 时,就明白这是在泛指对象的属性(包括数据和行为);而看到 Attribute 时,就知道这是在特指保存数据的属性(比如对象的实例变量)。然而在大多数中文书籍里都没有解释过这个区别,而是一概用 属性 一词带过。
这个问题我关注好久了,也发现一些译者在探讨这个问题的解决办法,遗憾地是目前还没找到什么行之有效的替代方案。我想这也是很多人极力建议阅读英文原本的重要理由之一吧?
Property 一词有“财产”的意思,可以引申为“归属物”,所以你说 Method(方法)是一个 Property(属性)是毫无问题的(反过来说某个 Property 是 Method 也(可能)是对的);但是 Attribute 一词则强调的是“特质、特征”的含义,方法是“行为、动作”,所以说 Method 是一个 Attribute 就不太恰当了(反之亦然)。
然后来说说虚拟属性。
一般而言,我们创建对象的时候会通过构造函数把对象的 Attributes 通过参数传递进去,这样对象实例化时就会拥有属于自己的 Attributes 了。像 Rails 则直接把 Model 的 Attributes 定义和数据实体的字段(fields)关联起来,对象即是(数据库里的)数据,数据也是对象。
然而在现实中我们会遇到一些特殊情况,比如说对象的某一个 Attribute 是需要通过组合、转换、处理其他的 Attribute(s) 来得到,这样的话直接通过变量传参进来就不合适了。
例如你要创建一个 长方形 对象,需要它有三个属性:长
,宽
,面积
,长
和宽
好办,创建时传参进来,面积
呢?它不好直接传参,它看起来也不是一个方法,它的值取决于实例化时传入的另外两个参数,于是我们可以自己定义面积
属性的 getter/setter 方法,之后就可以把它当成 Attribute 来使用了(事实上,它就是一个 Attribute)。
我记得镐头书好像有一个点唱机的例子,一首歌播放的长度在实例化的时候传入的参数是秒,但是需要显示成分钟,于是就创造了一个虚拟属性:duration_in_min
。这也是一样的道理。
虚拟属性也有被称作 Computed Property 的(Ember.js 就这么叫的吧,我不确定是不是完全一样,印象中是的)。计算后属性这个词虽然拗口,但我觉得还是比较形象的。
最后我用 JavaScript 来举例和 Ruby 对比一下:
function Person(firstName, lastName) {
this.firstName = firstName; // 定义 Attribute
this.lastName = lastName; // 也是定义 Attribute
this._fullName(); // 初始化 Virtual Attribute
};
// 定义方法
Person.prototype.greeting = function (words) {
console.log([this.fullName, 'says:', words].join(' ')); // 使用了虚拟属性
};
// 定义虚拟属性
Person.prototype._fullName = function () {
this.fullName = [this.firstName, this.lastName].join(' ');
};
注:本例并非 JavaScript 的最佳实践,
_fullName
方法用来生成虚拟属性,不应该直接通过原型暴露给实例对象。在现实中一般会采用某种模块模式,使用闭包把这个私有方法隐藏起来,只向外提供fullName
虚拟属性本身。
在 Ruby 当中,这个问题可以简单地使用private
把私有方法隐藏起来,但是 JavaScript 比较麻烦且写起来占版面,加之与本楼问题无关,所以本例中做简化处理了。
另外,本例中fullName
的求值相当简单,完全没有必要定义_fullName
来实现,这里只是为了呼应上文提到的 getter/setter 而写的示例而已,略显“脱裤子放屁”……实际上,上例可以简化为:
function Person(firstName, lastName) {
this.firstName = firstName; // 定义 Attribute
this.lastName = lastName; // 也是定义 Attribute
this.fullName = [firstName, lastName].join(' '); // 初始化 Virtual Attribute
};
// 定义方法
Person.prototype.greeting = function (words) {
console.log([this.fullName, 'says:', words].join(' ')); // 使用了虚拟属性
};
有木有一不小心给自己挖坑往里跳的赶脚……
@nightire 已经说得蛮清楚了
用 js 来举例的话,ember 的 computed property 确实是很好的例子。
Rails 来说的话,有点 presenter 的意思。很多时候你要用一个从已有属性装饰而来的属性,但是又不想存到数据库里
一个相关的最佳实践http://rails-bestpractices.com/posts/4-add-model-virtual-attribute
An object's instance variables are its attributes, the things that distinguish it from other objects of the same class. It is important to be able to write and read these attributes; doing so requires methods called attribute accessors.
Attributes 是指对象的实例变量。但是我们日常使用中,习惯用 attributes 指代 attributes accessor,也就是实例变量对应的同名 setter/getter 方法,所以也才会有“Ruby 的属性其实是方法”的说法。
Virtual attribute 不存在相对应的同名实例变量,是对其他 attribute 操作而进行封装的实例方法。
示例,firstname,lastname 为属性,fullname 为虚拟属性:
class Person
attr_accessor :firstname, :lastname
def fullname
"#{firstname} #{lastname}"
end
def fullname=(fullname)
names = fullname.split
self.firstname = naems[0]
self.lastname = names[1]
end
end
上面的代码其实等价于:
class Person
def firstname
@firstname
end
def firstname=(firstname)
@firstname = firstname
end
def lastname
@lastname
end
def lastname=(lastname)
@lastname = lastname
end
def fullname
"#{firstname} #{lastname}"
end
def fullname=(fullname)
names = fullname.split
self.firstname = naems[0]
self.lastname = names[1]
end
end
@nightire 的回答太赞了。
但是我看镐头书一直在说 attribute 可以看成一种方法,正如 @reyesyang 所说。
attr_accessor :firstname, :lastname
这段代码来说, :firstname
. :lastname
是symbol
, 也就是下面定义的方法,对不对呢?
def lastname
@lastname
end
def lastname=(lastname)
@lastname
end
那我们,又说对象的 instance variable
也是它的attribute
,所以我就比较迷茫了。