¿Pueden los objetos construidos a partir de la misma clase tener definiciones de método únicas?

14

Sé que esto parece una pregunta extraña, ya que el punto de dos o más objetos que comparten la misma clase es que su comportamiento es el mismo, es decir, sus métodos son idénticos.

Sin embargo, tengo curiosidad por saber si hay algún lenguaje OOP que le permita redefinir los métodos de los objetos de la misma manera que puede asignar diferentes valores para sus campos. El resultado serían objetos construidos a partir de la misma clase que ya no exhiben exactamente el mismo comportamiento.

Si no me equivoco, ¿puedes hacer este JavaScript? Junto con esta pregunta, pregunto, ¿por qué alguien querría hacer esto?

Niko Bellic
fuente
99
El término técnico está "basado en prototipos". Buscarlo te dará mucho material sobre este sabor de OOP. (Tenga en cuenta que mucha gente no considera tales estructuras como "clases" apropiadas, precisamente porque no tienen atributos y métodos uniformes.)
Kilian Foth
1
¿Te refieres a cómo dos instancias diferentes de un botón pueden hacer cosas diferentes cuando se hace clic, porque cada una está conectada a un controlador de botón diferente? Por supuesto, uno siempre podría argumentar que hacen lo mismo: llaman a su manejador de botones. De todos modos, si su idioma admite delegados o punteros de función, entonces esto es fácil: tiene alguna variable de propiedad / campo / miembro que contiene el puntero de delegado o función, y la implementación del método lo llama y listo.
Kate Gregory
2
Es complicado :) En los lenguajes donde las referencias de funciones de eslingas son comunes, es fácil pasar algo de código a un setClickHandler()método y hacer que diferentes instancias de la misma clase hagan cosas muy diferentes. En los idiomas que no tienen expresiones lambda convenientes, es más fácil crear una nueva subclase anónima solo para un nuevo controlador. Tradicionalmente, los métodos primordiales se consideraban el sello distintivo de una nueva clase, mientras que establecer los valores de los atributos no lo era, pero especialmente con las funciones del controlador, los dos tienen efectos muy similares, por lo que la distinción se convierte en una guerra sobre las palabras.
Kilian Foth
1
No es exactamente lo que está preguntando, pero esto suena como el patrón de diseño de la Estrategia. Aquí tiene una instancia de una clase que efectivamente cambia su tipo en tiempo de ejecución. Está un poco fuera de tema porque no es el lenguaje lo que permite esto, pero vale la pena mencionarlo porque dijiste "por qué alguien querría hacer esto"
Tony
@Killian Forth La OOP basada en prototipos no tiene clases por definición y, por lo tanto, no coincide exactamente con lo que pregunta el OP. La diferencia clave es que una clase no es una instancia.
Eques

Respuestas:

9

Los métodos en la mayoría de los lenguajes OOP (basados ​​en clases) se fijan por tipo.

JavaScript está basado en prototipos, no en clases, por lo que puede anular los métodos en una base por instancia porque no existe una distinción clara entre "clase" y objeto; en realidad, una "clase" en JavaScript es un objeto que es como una plantilla de cómo deberían funcionar las instancias.

Cualquier lenguaje que permita funciones de primera clase, Scala, Java 8, C # (a través de delegados), etc., puede actuar como si tuviera anulaciones de método por instancia; tendría que definir un campo con un tipo de función y luego anularlo en cada instancia.

Scala tiene otra posibilidad; En Scala, puede crear objetos únicos (usando la palabra clave del objeto en lugar de la palabra clave de la clase), para que pueda extender su clase y anular los métodos, lo que resulta en una nueva instancia de esa clase base con anulaciones.

¿Por que alguien haria esto? Podría haber docenas de razones. Puede ser que el comportamiento necesite estar más estrictamente definido de lo que permitiría usar varias combinaciones de campo. También podría mantener el código desacoplado y organizado mejor. Sin embargo, en general, creo que estos casos son más raros y, a menudo, hay una solución más sencilla utilizando valores de campo.

eques
fuente
Tu primera oración no tiene sentido. ¿Qué tiene que ver la OOP basada en clases con los tipos fijos?
Bergi
Quiero decir que por tipo, el conjunto y las definiciones de los métodos son fijos o, en otras palabras, para agregar o cambiar un método, debe crear un nuevo tipo (por ejemplo, subtipando).
eques
@Bergi: En OOP basado en clases, la palabra "clase" y "tipo" significan esencialmente lo mismo. Como no tiene sentido permitir que el tipo entero tenga el valor "hola", tampoco tiene sentido que el tipo Empleado tenga valores o métodos que no pertenezcan a la clase Empleado.
slebetman
@slebetman: quise decir que un lenguaje de OOP basado en clases no necesariamente tiene una escritura fuerte y forzada estáticamente.
Bergi
@ Bergi: Ese es el significado de "la mayoría" en la respuesta anterior (la mayoría significa no todos). Simplemente estoy comentando tu comentario sobre "qué tiene que hacer".
slebetman
6

Es difícil adivinar la motivación de su pregunta, por lo que algunas respuestas posibles podrían abordar su interés real o no.

Incluso en algunos lenguajes no prototipo es posible aproximar este efecto.

En Java, por ejemplo, una clase interna anónima está bastante cerca de lo que está describiendo: puede crear e instanciar una subclase del original, anulando solo el método o métodos que desee. La clase resultante será instanceofla clase original, pero no será la misma clase.

¿Por qué querrías hacer esto? Con las expresiones lambda de Java 8, creo que muchos de los mejores casos de uso desaparecen. Con versiones anteriores de Java, al menos, esto puede evitar una proliferación de clases triviales de uso restringido. Es decir, cuando tiene una gran cantidad de casos de uso relacionados, que difieren solo en una pequeña forma funcional, puede crearlos casi sobre la marcha (casi), con la diferencia de comportamiento inyectada en el momento en que la necesita.

Dicho esto, incluso antes de J8, esto a menudo se puede refactorizar para cambiar la diferencia en un campo o tres, e inyectarlos en el constructor. Con J8, por supuesto, el método en sí se puede inyectar en la clase, aunque puede haber una tentación de hacerlo cuando otra refactorización puede ser más limpia (si no tan genial).

Schnitz
fuente
6

Solicitó cualquier idioma que proporcione métodos por instancia. Ya hay una respuesta para Javascript, así que veamos cómo se hace en Common Lisp, donde puede usar los especialistas en EQL:

;; 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

¿Por qué?

EQL-specializers es útil cuando se supone que el argumento sujeto a despacho tiene un tipo para el que eqltiene sentido: un número, un símbolo, etc. En términos generales, no lo necesita, y simplemente tiene que definir tantas subclases como desee. Necesitado por su problema. Pero a veces, solo necesita despachar de acuerdo con un parámetro que es, por ejemplo, un símbolo: una caseexpresión se limitaría a los casos conocidos en la función de despacho, mientras que los métodos se pueden agregar y eliminar en cualquier momento.

Además, la especialización en instancias es útil para fines de depuración, cuando desea inspeccionar temporalmente lo que sucede con un objeto específico en su aplicación en ejecución.

volcado de memoria
fuente
5

También puedes hacer esto en Ruby usando objetos singleton:

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

Produce:

Hello!
Hello world!

En cuanto a los usos, así es como Ruby hace los métodos de clase y módulo. Por ejemplo:

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

En realidad está definiendo un método singleton helloen el Classobjeto SomeClass.

Linuxios
fuente
¿Desea ampliar su respuesta con módulos y método extendido? (¿Solo para mostrar otras formas de extender objetos con nuevos métodos)?
knut
@knut: estoy bastante seguro de que en extendrealidad solo crea la clase singleton para el objeto y luego importa el módulo a la clase singleton.
Linuxios
5

Puede pensar que los métodos por instancia le permiten ensamblar su propia clase en tiempo de ejecución. Esto puede eliminar una gran cantidad de código de pegamento, ese código que no tiene otro propósito que juntar dos clases para hablar entre sí. Los mixins son una solución algo más estructurada para el mismo tipo de problemas.

Estás sufriendo un poco por la paradoja del blub , esencialmente porque es difícil ver el valor de una función del lenguaje hasta que la hayas utilizado en un programa real. Así que busca oportunidades donde creas que podría funcionar, pruébalo y mira qué sucede.

Busque en su código grupos de clases que difieran solo por un método. Busque clases cuyo único propósito sea combinar otras dos clases en diferentes combinaciones. Busque métodos que no hagan nada más que pasar una llamada a otro objeto. Busque clases que se instancian utilizando patrones de creación complejos . Esos son todos los posibles candidatos para ser reemplazados por métodos por instancia.

Karl Bielefeldt
fuente
1

Otras respuestas han mostrado cómo esta es una característica común de los lenguajes dinámicos orientados a objetos, y cómo se puede emular trivialmente en un lenguaje estático que tiene objetos de función de primera clase (por ejemplo, delegados en c #, objetos que anulan el operador () en c ++) . En lenguajes estáticos que carecen de dicha función, es más difícil, pero aún se puede lograr mediante el uso de una combinación del patrón de Estrategia y un método que simplemente delegue su implementación a la estrategia. Esto es, de hecho, lo mismo que haría en C # con los delegados, pero la sintaxis es un poco más complicada.

Jules
fuente
0

Puede hacer algo así en C # y en la mayoría de los lenguajes similares.

public class MyClass{
    public Func<A,B> MyABFunc {get;set;}
    public Action<B> MyBAction {get;set;}
    public MyClass(){
        //todo assign MyAFunc and MyBAction
    }
}
Esben Skov Pedersen
fuente
1
Los programadores tratan sobre preguntas conceptuales y se espera que las respuestas expliquen las cosas. Lanzar volcados de código en lugar de una explicación es como copiar código del IDE a la pizarra: puede parecer familiar e incluso a veces comprensible, pero se siente extraño ... simplemente extraño. Whiteboard no tiene compilador
mosquito
0

Conceptualmente, aunque en un lenguaje como Java todas las instancias de una clase deben tener los mismos métodos, es posible hacer que parezca que no lo hacen agregando una capa adicional de indirección, posiblemente en combinación con clases anidadas.

Por ejemplo, si una clase Foopuede definir una clase anidada abstracta estática QuackerBaseque contiene un método quack(Foo), así como varias otras clases anidadas estáticas derivadas QuackerBase, cada una con su propia definición de quack(Foo), entonces si la clase externa tiene un campo quackerde tipo QuackerBase, entonces puede configura ese campo para identificar una instancia (posiblemente única) de cualquiera de sus clases anidadas. Después de hacerlo, la invocación quacker.quack(this)ejecutará el quackmétodo de la clase cuya instancia ha sido asignada a ese campo.

Debido a que este es un patrón bastante común, Java incluye mecanismos para declarar automáticamente los tipos apropiados. Dichos mecanismos realmente no están haciendo nada que no se pueda hacer simplemente usando métodos virtuales y clases estáticas anidadas opcionalmente, pero reducen en gran medida la cantidad de repeticiones necesarias para producir una clase cuyo único propósito es ejecutar un método único en nombre de otra clase.

Super gato
fuente
0

Creo que esa es la definición de un lenguaje "dinámico" como ruby, groovy & Javascript (y muchos, muchos otros). Dinámico se refiere (al menos en parte) a la capacidad de redifinar dinámicamente cómo podría comportarse una instancia de clase sobre la marcha.

No es una gran práctica de OO en general, pero para muchos programadores de lenguaje dinámico, los principios de OO no son su máxima prioridad.

Simplifica algunas operaciones difíciles, como Monkey-Patching, donde puede ajustar una instancia de clase para permitirle interactuar con una biblioteca cerrada de una manera que no previeron.

Bill K
fuente
0

No digo que sea algo bueno, pero esto es trivialmente posible en Python. No puedo pensar en un buen caso de uso en la parte superior de mi cabeza, pero estoy seguro de que existen.

    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()
Singletoned
fuente