Les objets construits à partir de la même classe peuvent-ils avoir des définitions de méthode uniques?

14

Je sais que cela semble être une question étrange, car l'intérêt de deux ou plusieurs objets partageant la même classe est que leur comportement est le même, c'est-à-dire que leurs méthodes sont identiques.

Cependant, je suis curieux de savoir s'il existe des langages POO qui vous permettent de redéfinir les méthodes des objets de la même manière que vous pouvez attribuer différentes valeurs à leurs champs. Le résultat serait des objets construits à partir de la même classe ne présentant plus exactement le même comportement.

Si je ne me trompe pas, vous pouvez faire ce JavaScript? Parallèlement à cette question, je demande, pourquoi quelqu'un voudrait-il faire cela?

Niko Bellic
la source
9
Le terme technique est "basé sur un prototype". La recherche vous donnera beaucoup d'informations sur cette saveur de POO. (Sachez que beaucoup de gens ne considèrent pas ces structures comme des "classes" appropriées, précisément parce qu'elles n'ont pas d'attributs et de méthodes uniformes.)
Kilian Foth
1
vous voulez dire comment deux instances différentes d'un bouton peuvent faire des choses différentes quand on clique dessus, car elles sont chacune connectées à un gestionnaire de bouton différent? Bien sûr, on pourrait toujours prétendre qu'ils font la même chose - ils appellent leur gestionnaire de boutons. Quoi qu'il en soit, si votre langage prend en charge les délégués ou les pointeurs de fonction, cela est facile - vous avez une variable de propriété / champ / membre qui contient le pointeur de délégué ou de fonction, et l'implémentation de la méthode appelle cela et vous partez.
Kate Gregory
2
C'est compliqué :) Dans les langages où les références aux fonctions d'élingage sont courantes, il est facile de passer du code dans une setClickHandler()méthode et de faire faire des choses très différentes à différentes instances de la même classe. Dans les langues qui n'ont pas d'expressions lambda pratiques, il est plus facile de créer une nouvelle sous-classe anonyme juste pour un nouveau gestionnaire. Traditionnellement, les méthodes prioritaires étaient considérées comme la marque d'une nouvelle classe, alors que la définition des valeurs des attributs ne l'était pas, mais en particulier avec les fonctions de gestionnaire, les deux ont des effets très similaires, de sorte que la distinction devient une guerre des mots.
Kilian Foth du
1
Pas exactement ce que vous demandez, mais cela ressemble au modèle de conception de stratégie. Ici, vous avez une instance d'une classe qui change effectivement son type au moment de l'exécution. C'est un peu hors sujet car ce n'est pas le langage qui le permet mais mérite d'être mentionné car vous avez dit "pourquoi quelqu'un voudrait-il faire ça"
tony
La POO basée sur le prototype @Killian Forth n'a pas de classes par définition et ne correspond donc pas tout à fait à ce que l'OP demande. La principale différence est qu'une classe n'est pas une instance.
eques

Réponses:

9

Les méthodes de la plupart des langages POO (basés sur les classes) sont fixées par type.

JavaScript est basé sur un prototype et non sur une classe et vous pouvez donc remplacer les méthodes sur une base par instance car il n'y a pas de distinction stricte entre "classe" et objet; en réalité, une "classe" en JavaScript est un objet qui est comme un modèle de fonctionnement des instances.

Tout langage qui permet des fonctions de première classe, Scala, Java 8, C # (via les délégués), etc., peut agir comme si vous aviez des substitutions de méthode par instance; vous devez définir un champ avec un type de fonction, puis le remplacer dans chaque instance.

Scala a une autre possibilité; Dans Scala, vous pouvez créer des singletons d'objet (en utilisant le mot-clé object au lieu du mot-clé class), afin de pouvoir étendre votre classe et remplacer les méthodes, résultant en une nouvelle instance de cette classe de base avec des remplacements.

Pourquoi quelqu'un ferait cela? Il pourrait y avoir des dizaines de raisons. Il se pourrait que le comportement me soit plus étroitement défini que ne le permettrait l'utilisation de diverses combinaisons de champs. Il pourrait également maintenir le code découplé et mieux organisé. En général cependant, je pense que ces cas sont plus rares et il existe souvent une solution plus simple en utilisant des valeurs de champ.

eques
la source
Votre première phrase n'a pas de sens. Qu'est-ce que la POO basée sur les classes a à voir avec les types fixes?
Bergi
Je veux dire que par type l'ensemble et les définitions des méthodes sont fixes ou en d'autres termes, pour ajouter ou modifier une méthode, vous devez créer un nouveau type (par exemple par sous-typage).
eques
@Bergi: Dans la POO basée sur une classe, le mot "classe" et "type" signifient essentiellement la même chose. Puisqu'il n'est pas logique de permettre au type entier d'avoir la valeur "bonjour", il n'a pas non plus de sens que le type Employee ait des valeurs ou des méthodes qui n'appartiennent pas à la classe Employee.
slebetman
@slebetman: Je voulais dire qu'un langage OOP basé sur une classe n'a pas nécessairement un typage fort et imposé statiquement.
Bergi
@Bergi: C'est le sens de "la plupart" dans la réponse ci-dessus (la plupart signifie pas tous). Je commente simplement votre commentaire "qu'est-ce que cela a à faire".
slebetman
6

Il est difficile de deviner la motivation de votre question, et donc certaines réponses possibles pourraient ou non répondre à votre intérêt réel.

Même dans certains langages non prototypes, il est possible d'approximer cet effet.

En Java, par exemple, une classe interne anonyme est assez proche de ce que vous décrivez - vous pouvez créer et instancier une sous-classe de l'original, en remplaçant uniquement la ou les méthodes que vous souhaitez. La classe résultante sera instanceofla classe d'origine, mais ne sera pas la même classe.

Quant à savoir pourquoi vous voudriez faire cela? Avec les expressions lambda Java 8, je pense que la plupart des meilleurs cas d'utilisation disparaissent. Avec les versions antérieures de Java, au moins, cela peut éviter une prolifération de classes triviales à usage étroit. C'est-à-dire que lorsque vous avez un grand nombre de cas d'utilisation liés, qui ne diffèrent que de manière minuscule, vous pouvez les créer presque à la volée (presque), avec la différence de comportement injectée au moment où vous en avez besoin.

Cela dit, même avant J8, cela peut souvent être refactorisé pour déplacer la différence dans un champ ou trois, et les injecter dans le constructeur. Avec J8, bien sûr, la méthode elle-même peut être injectée dans la classe, bien qu'il puisse y avoir une tentation de le faire lorsqu'un autre refactoring peut être plus propre (si ce n'est pas aussi cool).

schnitz
la source
6

Vous avez demandé n'importe quelle langue fournissant des méthodes par instance. Il existe déjà une réponse pour Javascript, alors voyons comment cela se fait en Common Lisp, où vous pouvez utiliser les spécialistes 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

Pourquoi?

Les spécialistes EQL sont utiles lorsque l'argument soumis à la répartition est censé avoir un type qui a du eqlsens: un nombre, un symbole, etc. En règle générale, vous n'en avez pas besoin et vous devez simplement définir autant de sous-classes que nécessaire par votre problème. Mais parfois, il suffit de répartir en fonction d'un paramètre qui est, par exemple, un symbole: une caseexpression serait limitée aux cas connus dans la fonction de répartition, tandis que les méthodes peuvent être ajoutées et supprimées à tout moment.

En outre, la spécialisation sur les instances est utile à des fins de débogage, lorsque vous souhaitez inspecter temporairement ce qui se passe avec un objet spécifique dans votre application en cours d'exécution.

coredump
la source
5

Vous pouvez également le faire dans Ruby en utilisant des objets 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

Produit:

Hello!
Hello world!

Quant aux utilisations, c'est en fait ainsi que Ruby fait les méthodes de classe et de module. Par exemple:

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

Définit en fait une méthode singleton hellosur l' Classobjet SomeClass.

Linuxios
la source
Voulez-vous étendre votre réponse avec des modules et une méthode d'extension? (Juste pour montrer d'autres façons d'étendre des objets avec de nouvelles méthodes)?
knut
@knut: Je suis à peu près sûr qu'en extendréalité, il suffit de créer la classe singleton pour l'objet, puis d'importer le module dans la classe singleton.
Linuxios
5

Vous pouvez considérer les méthodes par instance comme vous permettant d'assembler votre propre classe au moment de l'exécution. Cela peut éliminer beaucoup de code de colle, ce code qui n'a d'autre but que de mettre deux classes ensemble pour se parler. Les mixins sont une solution un peu plus structurée aux mêmes types de problèmes.

Vous souffrez un peu du paradoxe du blub , essentiellement qu'il est difficile de voir la valeur d'une fonctionnalité de langage avant d'avoir utilisé cette fonctionnalité dans un vrai programme. Recherchez donc les opportunités où vous pensez que cela pourrait fonctionner, essayez-les et voyez ce qui se passe.

Recherchez dans votre code des groupes de classes qui ne diffèrent que par une seule méthode. Recherchez des classes dont le seul but est de combiner deux autres classes dans des combinaisons différentes. Recherchez des méthodes qui ne font rien d'autre que de passer un appel à un autre objet. Recherchez des classes instanciées à l'aide de modèles de création complexes . Ce sont tous des candidats potentiels à remplacer par des méthodes par instance.

Karl Bielefeldt
la source
1

D'autres réponses ont montré comment cela est une caractéristique commune des langages dynamiques orientés objet, et comment il peut être émulé trivialement dans un langage statique qui a des objets fonction de première classe (par exemple les délégués en c #, les objets qui remplacent operator () en c ++) . Dans les langages statiques qui n'ont pas une telle fonction, c'est plus difficile, mais cela peut toujours être réalisé en utilisant une combinaison du modèle de stratégie et d'une méthode qui délègue simplement sa mise en œuvre à la stratégie. C'est en fait la même chose que vous feriez en c # avec les délégués, mais la syntaxe est un peu plus compliquée.

Jules
la source
0

Vous pouvez faire quelque chose comme ça en C # et dans la plupart des autres langages similaires.

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
la source
1
Les programmeurs concernent les questions conceptuelles et les réponses sont censées expliquer les choses. Lancer des vidages de code au lieu d'explications, c'est comme copier du code de l'IDE vers le tableau blanc: cela peut sembler familier et même parfois compréhensible, mais cela semble bizarre ... juste bizarre. Le tableau blanc n'a pas de compilateur
gnat
0

Conceptuellement, même si dans un langage comme Java toutes les instances d'une classe doivent avoir les mêmes méthodes, il est possible de les faire apparaître comme si elles n'en avaient pas en ajoutant une couche supplémentaire d'indirection, éventuellement en combinaison avec des classes imbriquées.

Par exemple, si une classe Foopeut définir une classe imbriquée abstraite statique QuackerBasequi contient une méthode quack(Foo), ainsi que plusieurs autres classes imbriquées statiques dérivant de QuackerBase, chacune avec sa propre définition de quack(Foo), alors si la classe externe a un champ quackerde type QuackerBase, alors elle peut définissez ce champ pour identifier une instance (éventuellement singleton) de l'une de ses classes imbriquées. Après cela, l'invocation quacker.quack(this)exécutera la quackméthode de la classe dont l'instance a été affectée à ce champ.

Comme il s'agit d'un modèle assez courant, Java inclut des mécanismes pour déclarer automatiquement les types appropriés. De tels mécanismes ne font vraiment rien qui ne pourrait pas être fait en utilisant simplement des méthodes virtuelles et des classes statiques éventuellement imbriquées, mais ils réduisent considérablement la quantité de passe-partout nécessaire pour produire une classe dont le seul but est d'exécuter une seule méthode au nom de une autre classe.

supercat
la source
0

Je crois que c'est la définition d'un langage "dynamique" comme ruby, groovy & Javascript (et bien d'autres). Dynamique fait référence (au moins en partie) à la capacité de redéfinir dynamiquement comment une instance de classe peut se comporter à la volée.

Ce n'est pas une grande pratique OO en général, mais pour de nombreux programmeurs de langage dynamique, les principes OO ne sont pas leur priorité absolue.

Cela simplifie certaines opérations délicates comme Monkey-Patching où vous pouvez modifier une instance de classe pour vous permettre d'interagir avec une bibliothèque fermée d'une manière qu'ils n'avaient pas prévue.

Bill K
la source
0

Je ne dis pas que c'est une bonne chose à faire, mais cela est trivialement possible en Python. Je ne peux pas penser à un bon cas d'utilisation du haut de ma tête, mais je suis sûr qu'ils existent.

    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
la source