从同一类构造的对象可以具有唯一的方法定义吗?


14

我知道这似乎是一个奇怪的问题,因为两个或更多共享同一类的对象的要点是它们的行为是相同的,即它们的方法是相同的。

但是,我很好奇是否有任何OOP语言可以让您以可以为对象的字段分配不同值的相同方式重新定义对象的方法。结果将是从同一类构造的对象不再表现出完全相同的行为。

如果我没记错的话,您可以使用此JavaScript吗?我问这个问题,为什么有人要这样做呢?


9
技术术语是“基于原型”。搜索它会为您提供有关这种OOP风格的更多信息。(请注意,很多人认为这样的结构不是正确的“类”,因为它们没有统一的属性和方法。)
Kilian Foth,2014年

1
您的意思是就像单击按钮的两个不同实例在单击时如何做不同的事情,因为它们每个都连接到不同的按钮处理程序?当然,人们总是会争辩说他们在做同样的事情-他们称之为按钮处理程序。无论如何,如果您的语言支持委托或函数指针,那么这很容易-您拥有一些保存委托或函数指针的property / field / member变量,并且方法的实现会调用该函数,然后您便可以使用它。
凯特·格雷戈里

2
这很复杂:)在使用吊索函数引用很普遍的语言中,很容易将一些代码传递给setClickHandler()方法,并使同一个类的不同实例执行截然不同的事情。在没有方便的lambda表达式的语言中,仅为新的处理程序创建新的匿名子类会更容易。传统上,重写方法被认为是类的标志,而设置属性的值不是必需的,但是特别是对于处理函数,这两种方法具有非常相似的效果,因此区分成为一场关于单词的战争。
Kilian Foth,2014年

1
不完全是您要问的问题,但这听起来像是Strategy设计模式。在这里,您有一个类的实例,可以在运行时有效地更改其类型。这有点离题,因为不是允许这种情况的语言,但值得一提,因为您说“为什么有人要这样做”
tony 2014年

基于@Killian Forth原型的OOP没有定义的类,因此与OP的要求完全不匹配。关键区别在于类不是实例。
2014年

Answers:


9

大多数(基于类)OOP语言中的方法都是按类型固定的。

JavaScript是基于原型的,而不是基于类的,因此您可以基于每个实例覆盖方法,因为“类”和对象之间没有硬性区别;实际上,JavaScript中的“类”是一个对象,类似于实例如何工作的模板。

任何支持一流功能的语言,例如Scala,Java 8,C#(通过委托)等,都可以像按实例方法重写那样工作。您必须定义一个具有函数类型的字段,然后在每个实例中覆盖它。

Scala还有另一个可能性。在Scala中,您可以创建对象单例(使用object关键字而不是class关键字),因此您可以扩展您的类并覆盖方法,从而导致该基类的新实例被覆盖。

为什么有人这样做?可能有很多原因。我可能需要比使用各种字段组合所允许的行为更严格地定义行为。它还可以使代码解耦和组织得更好。但是,总的来说,我认为这些情况比较少见,并且通常使用字段值可以找到更简单的解决方案。


您的第一句话没有道理。基于类的OOP与固定类型有什么关系?
Bergi 2014年

我的意思是每种类型的方法集和定义都是固定的,换句话说,要添加或更改方法,必须创建一个新类型(例如,通过子类型化)。
eques

@Bergi:在基于类的OOP中,“类”和“类型”一词本质上是同一件事。由于允许整数类型具有值“ hello”没有意义,因此让Employee类型具有不属于Employee类的值或方法也没有意义。
slebetman 2014年

@slebetman:我的意思是说,基于类的OOP语言不一定具有强大的静态强制类型。
Bergi 2014年

@Bergi:这就是上面答案中“最”的意思(最意味着不是全部)。我只是评论您的“它必须做什么”评论。
slebetman 2014年

6

很难猜出问题的动机,因此一些可能的答案可能会或可能不会解决您的真正兴趣。

即使在某些非原型语言中,也可以近似这种效果。

例如,在Java中,匿名内部类非常接近您要描述的内部类-您可以创建并实例化原始类的子类,从而仅覆盖所需的一个或多个方法。结果类将是instanceof原始类,但将不是同一类。

至于为什么要这样做呢?借助Java 8 lambda表达式,我认为许多最佳用例都消失了。至少对于Java的早期版本,这可以避免繁琐的,狭窄用途的类的泛滥。也就是说,当您有大量相关用例时,仅在很小的功能上有所不同,您可以几乎(即时)动态创建它们,并在需要时注入行为差异。

也就是说,即使在J8之前,也可以将其重构为将差异转换为一个或三个字段,并将其注入构造函数中。当然,使用J8,方法本身可以注入到类中,尽管当另一个重构可能更简洁(如果不那么酷)时可能会这样做。


6

您要求提供任何提供基于实例的方法的语言。Java脚本已经有了一个答案,因此让我们看看如何在Common Lisp中完成它,在这里您可以使用EQL-specializers:

;; define a class
(defclass some-class () ())

;; declare a generic method
(defgeneric some-method (x))

;; specialize the method for SOME-CLASS
(defmethod some-method ((x some-class)) 'default-result)

;; create an instance named *MY-OBJECT* of SOME-CLASS
(defparameter *my-object* (make-instance 'some-class))

;; specialize SOME-METHOD for that specific instance
(defmethod some-method ((x (eql *my-object*))) 'specific-result)

;; Call the method on that instance
(some-method *my-object*)
=> SPECIFIC-RESULT

;; Call the method on a new instance
(some-method (make-instance 'some-class))
=> DEFAULT-RESULT

为什么?

当要进行调度的自变量具有eql有意义的类型时,EQL-specializers很有用:数字,符号等。通常来说,您不需要它,而只需要定义尽可能多的子类即可。您的问题所需要的。但是有时,您只需要根据参数(例如符号)进行调度:case表达式将限于调度函数中的已知情况,而方法可以随时添加和删除。

另外,当您要临时检查正在运行的应用程序中特定对象发生什么情况时,实例的特殊化对于调试目的很有用。


5

您也可以在Ruby中使用单例对象执行此操作:

class A
  def do_something
    puts "Hello!"
  end
end

obj = A.new
obj.do_something

def obj.do_something
  puts "Hello world!"
end

obj.do_something

产生:

Hello!
Hello world!

至于用途,这实际上就是Ruby进行类和模块方法的方式。例如:

def SomeClass
  def self.hello
    puts "Hello!"
  end
end

实际上是helloClass对象上定义单例方法SomeClass


您是否要使用模块和扩展方法来扩展答案?(只是为了展示使用新方法扩展对象的其他方法)?
knut

@knut:我很确定extend实际上只是为对象创建了单例类,然后将模块导入到单例类中。
Linuxios 2014年

5

您可以将每个实例的方法视为允许您在运行时组装自己的类。这可以消除很多粘合代码,该代码除了将两个类放在一起相互交谈外没有其他目的。 Mixins是针对相同类型问题的结构化解决方案。

您会受到blub悖论的困扰,从本质上讲,除非您在实际程序中使用过语言功能,否则很难看到语言功能的价值。因此,寻找您认为可行的机会,尝试一下,看看会发生什么。

在您的代码中查找只有一种方法不同的类组。查找其唯一目的是将两个其他类组合成不同组合的类。寻找除了将调用传递给另一个对象之外什么都不做的方法。寻找使用复杂的创建模式实例化的类。这些都是按实例方法替换的潜在候选者。


1

其他答案显示了这是动态面向对象语言的共同特征,以及如何在具有一流函数对象的静态语言中对其进行简单仿真(例如,c#中的委托,c ++中覆盖operator()的对象) 。在缺少这种功能的静态语言中,这很难,但是仍然可以通过结合使用策略模式和将方法的实现委托给策略的方法来实现。实际上,这与您在c#中使用委托进行的操作相同,但是语法有点混乱。


0

您可以使用C#和大多数其他类似的语言来做类似的事情。

public class MyClass{
    public Func<A,B> MyABFunc {get;set;}
    public Action<B> MyBAction {get;set;}
    public MyClass(){
        //todo assign MyAFunc and MyBAction
    }
}

1
程序员是关于概念性问题的,答案应能解释问题。抛出代码转储而不是进行解释就像将代码从IDE复制到白板一样:它看起来很熟悉,甚至有时是可以理解的,但是感觉很奇怪……只是很奇怪。白板没有编译器
1414

0

从概念上讲,即使在像Java这样的语言中,一个类的所有实例都必须具有相同的方法,也可以通过添加一个额外的间接层(可能与嵌套类结合使用)使它们看起来好像没有。

例如,如果一个类Foo可以定义一个QuackerBase包含方法的静态抽象嵌套类quack(Foo),以及从派生的其他几个静态嵌套类QuackerBase,每个类都有其自己的定义quack(Foo),则如果外部类具有quackertype 字段QuackerBase,则它可以设置该字段以标识其任何嵌套类的(可能是单例)实例。完成此操作后,调用quacker.quack(this)将执行quack实例已分配给该字段的类的方法。

因为这是一种相当常见的模式,所以Java包含自动声明适当类型的机制。这样的机制实际上并不能完成仅使用虚拟方法和可选的嵌套静态类无法完成的任何工作,但是它们极大地减少了产生一个类的唯一目的是代表一个方法运行一个类所必需的样板数量。另一堂课。


0

我相信这是“动态”语言的定义,例如ruby,groovy和Javascript(以及许多其他语言)。动态是(至少部分地)指动态重新定义类实例如何动态运行的能力。

通常,这不是一个很好的面向对象的实践,但是对于许多动态语言程序员来说,面向对象的原则并不是他们的首要任务。

它确实简化了一些棘手的操作,例如Monkey-Patching,在其中您可以调整类实例,以允许您以他们无法预见的方式与封闭的库进行交互。


0

我并不是说这是一件好事,但这在Python中是微不足道的。我想不起一个好用例,但我确定它们存在。

    class Foo(object):
        def __init__(self, thing):
            if thing == "Foo":
                def print_something():
                    print "Foo"
            else:
                def print_something():
                    print "Bar"
            self.print_something = print_something

    Foo(thing="Foo").print_something()
    Foo(thing="Bar").print_something()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.