在Ruby中,setter方法和虚拟方法有什么区别?

3
我正在学习Ruby,并试图理解一些词汇。在某个地方,我得到了一个设定符号也是虚拟方法的概念,即它们是同义词。但现在我认为我错了。我想知道这些与工厂方法有什么关系。能帮忙吗?
维基百科对虚拟方法的定义如下:
在面向对象编程中,比如C++等语言,虚函数或虚方法是一个可继承且可重写的函数或方法,动态派发得以实现。
自从去年6月以来,我一直在努力学习Ruby,但我不知道这是什么意思。
我对设定符号的概念有点更清晰了。我一直认为它只是设置实例变量值的任何方法。因此attr_writer:foo是一个设定符号,也许外部于类的方法改变foo的值也是一个设定符号。对吗?
但这不是“虚拟方法”的意思,对吗?所以基本上,我正在寻找区别的解释,但我找不到(或者说我无法理解)。
而且,所谓的“工厂方法”也可以被描述为一种方法,用于根据一组设定符号指定的特定类型创建对象,从类的外部(即定义类的代码)创建对象。
3个回答

6

我曾经认为setter方法也是虚拟方法,即它们是同义词。

这是一个逻辑错误:狗也是哺乳动物,但这并不意味着它们是同义词。

同样,在Ruby中,setter方法也是虚拟方法(因为在Ruby中所有方法都是虚拟的),但它们不是同义词。由于Ruby中只有虚拟方法,你可以说:setter方法也是方法。现在,很明显这并不一定意味着方法也是setter方法,对吧?

维基百科关于虚拟方法的定义:

在面向对象编程中,例如在C++等语言中,虚函数或虚方法是可继承和可重写的函数或方法,并且可以进行动态分派。

在Ruby中这个术语没有意义,因为在Ruby中所有方法都是虚拟的,所以没有必要区分虚拟方法和非虚拟方法。

在OOP中,“虚拟”这个术语适用于在运行时动态分派并且可以被重写的语言“东西”。

class Foo
  def to_s
    foo
  end

  def foo
    'Foo'
  end
end

class Bar < Foo
  def foo
    'Bar'
  end
end

Bar.new.to_s
#=> 'Bar'

正如您所看到的,Bar.new.to_s返回字符串'Bar',即使to_sFoo中定义并仅调用foo。但是,尽管to_sFoo中定义,它不会调用Foofoo,而是调用Barfoo,因为所涉及的对象具有类BarBar已经覆盖foo的定义,并且调用被动态地分派到当前对象具有的任何类。
Alan Kay创造了“面向对象”这个术语,并使用了一个消息隐喻,我认为这使得像这样的事情更容易理解:对象通过发送消息彼此通信。它的工作方式就像在现实世界中向某人发送消息一样:您无法知道接收者如何处理消息,您能观察到的只是您获得的响应。当您向某人发送消息时,他们将根据自己的知识解释消息中的请求。

如果你想象一下你和朋友之间的交流:

  1. 你向朋友发送消息“将自己转换为字符串”。
  2. 你的朋友不知道这是什么意思,于是他问他的上级,他告诉他,这意味着“向自己发送消息'foo'”。
  3. 你的朋友向自己发送消息“foo”。
  4. 你的朋友有自己对“foo”的理解,所以他不需要查找它的含义。

其他语言也有其他虚拟的“东西”,例如Newspeak有虚拟超类。

因此,如果我有这个:

class Foo < Array
  # … stuff
end

class Bar
  def Array
    return SomeClassLikeArray
  end

  def bar
    Foo.new
  end
end

Bar.new.bar
# this will be a `Foo` which has `SomeClassLikeArray` as its superclass

我对setter方法有了更好的认识。我一直在想它只是设置实例变量值的任何方法。
是和不是。
它是一个看起来设置实例变量的方法。你并不知道那个方法实际上做了什么。(记住消息传递的比喻:你只能观察到朋友的反应,你不知道朋友实际上用消息做了什么!)
例如,在Web框架中,setter方法可能实际上写入数据库而不是设置实例变量。
更技术性地说,在Ruby中,通常,以=结尾的方法是setter方法。
所以attr_writer :foo是一个setter方法,
不,那不是一个setter方法。它创建了一个名为foo=的setter方法
也许一个外部于类的方法改变了foo的值也是一个setter方法。是这样吗?

这并不是我们通常所说的setter方法。在Ruby中也根本不可能,因为只有对象本身才能访问它的实例变量。

即使在允许的语言中,这也是糟糕的设计:对象应该执行操作而不是存储东西。它关乎行为。你应该告诉对象执行动作

但这不是“虚拟方法”所指的意思,是吧?所以,基本上,我正在寻找区别的解释,但我找不到任何(或者,我无法理解)。

这两个概念完全不相关,讨论它们的区别并没有什么意义。

虚拟方法是可以被覆盖的方法。Setter方法是设置内容的方法。你可以有一个可以被覆盖的setter方法,一个不能被覆盖的setter方法,一个可以被覆盖的非setter方法和一个不能被覆盖的非setter方法。

特别地,在Ruby中,所有方法都是虚拟的,所以所有的setter方法都是虚拟的(因为所有的setter方法都是方法),但就此而已。

这也是事实,一个所谓的“工厂方法”可以被描述为一种从类的外部(即定义类的代码)使用集合的setter方法创建特定类型对象的方法。因此,有一个名为“工厂方法”的设计模式,但您正在谈论更一般的创建对象的方法概念。是的,一个创建对象的方法有时被称为“工厂方法”。在Ruby中,最常用的工厂方法是new大致如下
class Class
  def new(*args, &block)
    obj = allocate
    obj.initialize(*args, &block)
    return obj
  end
end

实际上,initialize是一个私有方法,因此我们需要使用反射来规避访问保护,但这并不改变该方法的要点:

class Class
  def new(*args, &block)
    obj = allocate
    obj.__send__(:initialize, *args, &block)
    return obj
  end
end

如果提到Alan Kay、消息传递和Gilad Bracha的Newspeak,将会获得100个赞!也许可以在Newspeak示例前加上免责声明,说明这不是有效的Ruby代码? - akuhn

5

虚方法

在一个类上定义的方法,在其子类上重新定义,以便调度基于接收者的类型。例如:

class A; def m; puts "A"; end; end
class B < A; def m; puts "B"; end; end
class C < A; end
[A, B, C].map(&:new).each(&:m)
#⇒ "A"
#  "B"
#  "A"

话虽如此,无论一个人是否拥有对象,在确定该对象是从A类派生的任何类的实例时,她都不应该烦恼类型检查。如果在这个特定的类上没有定义方法,则整个继承树将被查找,直到找到方法定义并调用该方法。

Setter方法

首先,attr_writer绝不是setter。它是一个帮助动态生成setter的函数。setter本身是一个设置变量的方法。相反的是getter。

class A; def set(v); @v = v; end; def get; @v; end; end
instance = A.new
instance.set(42)
instance.get
#⇒ 42

工厂方法

它通常用于创建实例。工厂方法可以在类定义内声明(这在单例模式中被广泛使用):

class A
  def m; puts "A"; end
  # note A.produce, to make it a class method
  def A.produce; A.new; end
  # or self.produce, to make it a class method
  # def self.produce; A.new; end # ⇐ the same as above
end
A.produce
#⇒ #<A:××××××>
A.produce.m
#⇒ "A"

或者在类定义之外:

class A; def m; puts "A"; end; end
def produceA; A.new; end
# or from withing other class
class B; def produceA; A.new; end; end

produceA.m
#⇒ 42
B.new.produceA.m
#⇒ 42

请注意下面Cary Swoveland对attr_writer的非常有价值的评论。


1
非常好的回答,Mudsie!也许我可以详细说明一下Module::attr_writer方法。作为一个类方法,attr_writer在解析类定义时执行。它仅仅创建了一个实例方法name=:nameattr_writer的参数),该方法“设置”实例变量@name的值。它创建的实例方法(一个setter)是def name=(str); @name = str; end。因此,attr_writer是一个辅助方法,自动化了一个你本来需要手动完成的常见任务。 - Cary Swoveland

1
所有 Ruby 中的方法都是“虚拟的”。
我使用引号,因为在 Ruby 中没有这样的区别。该维基百科文章中讨论的概念仅适用于 C++,不适用于 Ruby。例如,Ruby 也没有构造函数。
你的直觉是正确的,“工厂方法”必须调用 setter 方法或将初始化委托给实例方法,因为在 Ruby 中实例变量严格私有。
事实上,new就是这样一个工厂方法,它将初始化委托给一个名为initialize的实例方法。它是用本地代码实现的,并执行以下操作:
class Object 
  def self.new
    object = allocate
    object.initialize # delegate initialization to an instance method
    return object
  end
end

从技术上讲,一个人可以使用instance_variable_set,所以实例变量并不是严格私有的。虽然我很感激这只是与工厂方法相关的一点点,但你可以使用它来“配置”一个对象实例而不需要使用设置器。 - Michael Kohl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接