Können Objekte, die aus derselben Klasse erstellt wurden, eindeutige Methodendefinitionen haben?

14

Ich weiß, das scheint eine seltsame Frage zu sein, da der Punkt, an dem zwei oder mehr Objekte dieselbe Klasse teilen, darin besteht, dass sie dasselbe Verhalten haben, dh dass ihre Methoden identisch sind.

Ich bin jedoch gespannt, ob es OOP-Sprachen gibt, mit denen Sie die Methoden von Objekten auf dieselbe Weise neu definieren können, wie Sie unterschiedliche Werte für ihre Felder zuweisen können. Das Ergebnis wären Objekte aus derselben Klasse, die nicht mehr genau dasselbe Verhalten aufweisen.

Wenn ich mich nicht irre, kannst du dieses JavaScript? Zusammen mit dieser Frage frage ich, warum sollte jemand dies tun wollen?

Niko Bellic
quelle
9
Der Fachbegriff ist "prototypbasiert". Wenn Sie danach suchen, erhalten Sie viel Material über diesen OOP-Geschmack. (Beachten Sie, dass viele Leute solche Strukturen nicht als richtige "Klassen" betrachten, gerade weil sie keine einheitlichen Attribute und Methoden haben.)
Kilian Foth
1
Sie meinen, wie zwei verschiedene Instanzen einer Schaltfläche unterschiedliche Aktionen ausführen können, wenn sie angeklickt werden, weil sie jeweils mit einer anderen Schaltflächenbehandlungsroutine verbunden sind? Man könnte natürlich immer behaupten, dass sie dasselbe tun - sie nennen ihren Button-Handler. Wenn Ihre Sprache Delegaten oder Funktionszeiger unterstützt, ist dies ganz einfach: Sie haben eine Eigenschafts- / Feld- / Elementvariable, die den Delegaten- oder Funktionszeiger enthält, und die Implementierung der Methode ruft dies auf und schon kann es losgehen.
Kate Gregory
2
Es ist kompliziert :) In Sprachen, in denen Verweise auf Schleuderfunktionen häufig vorkommen, ist es einfach, Code an eine setClickHandler()Methode zu übergeben und verschiedene Instanzen derselben Klasse dazu zu bringen, sehr unterschiedliche Dinge zu tun. In Sprachen, die keine passenden Lambda-Ausdrücke haben, ist es einfacher, eine neue anonyme Unterklasse nur für einen neuen Handler zu erstellen. In der Vergangenheit galt das Überschreiben von Methoden als Kennzeichen einer neuen Klasse, während das Festlegen der Werte von Attributen nicht als Kennzeichen galt. Insbesondere bei Handlerfunktionen wirken sich die beiden Methoden jedoch sehr ähnlich aus, sodass die Unterscheidung zu einem Krieg um Wörter wird.
Kilian Foth
1
Nicht genau das, wonach Sie fragen, aber das klingt nach dem Entwurfsmuster der Strategie. Hier haben Sie eine Instanz einer Klasse, deren Typ zur Laufzeit geändert wird. Es ist ein bisschen abseits des Themas, weil es nicht die Sprache ist, die dies erlaubt, aber es ist erwähnenswert, weil Sie sagten, "warum sollte jemand dies tun wollen"
Tony
@Killian Forth Prototyp-basiertes OOP hat per Definition keine Klassen und entspricht daher nicht ganz den Anforderungen des OP. Der Hauptunterschied ist, dass eine Klasse keine Instanz ist.
eques

Antworten:

9

Methoden in den meisten (klassenbasierten) OOP-Sprachen sind nach Typ festgelegt.

JavaScript ist prototypbasiert und nicht klassenbasiert. Daher können Sie Methoden für einzelne Instanzen außer Kraft setzen, da es keine klare Unterscheidung zwischen "Klasse" und Objekt gibt. In Wirklichkeit ist eine "Klasse" in JavaScript ein Objekt, das wie eine Vorlage für die Funktionsweise der Instanzen aussieht.

Jede Sprache, die erstklassige Funktionen wie Scala, Java 8, C # (über Delegates) usw. zulässt, kann sich so verhalten, als hätten Sie Methodenüberschreibungen pro Instanz. Sie müssten ein Feld mit einem Funktionstyp definieren und es dann in jeder Instanz überschreiben.

Scala hat eine andere Möglichkeit; In Scala können Sie Objekt-Singletons erstellen (mithilfe des Schlüsselworts object anstelle des Schlüsselworts class), um Ihre Klasse zu erweitern und die Methoden zu überschreiben. Dies führt zu einer neuen Instanz dieser Basisklasse mit Überschreibungen.

Warum sollte jemand das tun? Es könnte Dutzende von Gründen geben. Es könnte sein, dass das Verhalten für mich enger definiert werden muss, als es die Verwendung verschiedener Feldkombinationen erlauben würde. Es könnte auch den Code entkoppelt und besser organisiert halten. Im Allgemeinen halte ich diese Fälle jedoch für seltener und es gibt oft eine einfachere Lösung mit Feldwerten.

gleich
quelle
Dein erster Satz ergibt keinen Sinn. Was hat klassenbasiertes OOP mit festen Typen zu tun?
Bergi
Ich meine, dass pro Typ die Menge und Definitionen von Methoden festgelegt sind, oder mit anderen Worten, um eine Methode hinzuzufügen oder zu ändern, müssen Sie einen neuen Typ erstellen (z. B. durch Subtypisierung).
10.11.14
@Bergi: In klassenbasiertem OOP bedeuten die Wörter "Klasse" und "Typ" im Wesentlichen dasselbe. Da es keinen Sinn macht, dem Typ Integer den Wert "Hallo" zuzuweisen, ist es auch nicht sinnvoll, den Typ Employee mit Werten oder Methoden zu versehen, die nicht zur Klasse Employee gehören.
Slebetman
@slebetman: Ich habe gemeint, dass eine klassenbasierte OOP-Sprache nicht unbedingt eine starke, statisch erzwungene Typisierung hat.
Bergi
@Bergi: Das ist die Bedeutung von "most" in der obigen Antwort (most bedeutet nicht all). Ich kommentiere nur Ihren Kommentar "Was hat es zu tun?".
Slebetman
6

Es ist schwierig, die Motivation für Ihre Frage zu erraten, und daher können einige mögliche Antworten Ihr wirkliches Interesse ansprechen oder nicht.

Selbst in einigen Nicht-Prototypensprachen ist es möglich, diesen Effekt anzunähern.

In Java entspricht beispielsweise eine anonyme innere Klasse ziemlich genau dem, was Sie beschreiben. Sie können eine Unterklasse des Originals erstellen und instanziieren und dabei nur die Methode oder Methoden überschreiben, die Sie möchten. Die resultierende Klasse wird instanceofdie ursprüngliche Klasse sein, wird jedoch nicht dieselbe Klasse sein.

Warum möchten Sie das tun? Ich denke, dass mit Java 8 Lambda-Ausdrücken viele der besten Anwendungsfälle verschwinden. Zumindest mit früheren Java-Versionen kann so eine Verbreitung von trivialen Klassen mit engem Verwendungszweck vermieden werden. Das heißt, wenn Sie eine große Anzahl verwandter Anwendungsfälle haben, die sich nur in einer winzigen funktionalen Weise unterscheiden, können Sie sie (fast) im Handumdrehen erstellen, wobei die Verhaltensunterschiede zum gewünschten Zeitpunkt hinzugefügt werden.

Das heißt, selbst vor J8 kann dies oft überarbeitet werden, um die Differenz in ein oder drei Felder zu verschieben und sie in den Konstruktor einzufügen. Mit J8 kann natürlich die Methode selbst in die Klasse eingebunden werden, obwohl es eine Versuchung geben kann, dies zu tun, wenn ein anderes Refactoring möglicherweise sauberer (wenn nicht so cool) ist.

schnitz
quelle
6

Sie haben nach einer Sprache gefragt, die instanzbezogene Methoden bereitstellt. Es gibt bereits eine Antwort für Javascript. Sehen wir uns also an, wie es in Common Lisp gemacht wird, wo Sie EQL-Spezialisierer verwenden können:

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

Warum?

EQL-Spezialisierer sind nützlich, wenn das Argument, das dem Versand unterliegt, einen Typ haben soll, für den eqles Sinn macht: eine Zahl, ein Symbol usw. Im Allgemeinen brauchen Sie es nicht, und Sie müssen einfach so viele Unterklassen wie definieren benötigt von Ihrem Problem. Manchmal müssen Sie jedoch nur nach einem Parameter versenden, bei dem es sich beispielsweise um ein Symbol handelt: Ein caseAusdruck ist in der Dispatching-Funktion auf die bekannten Fälle beschränkt, während Methoden jederzeit hinzugefügt und entfernt werden können.

Die Spezialisierung auf Instanzen ist auch für Debugging-Zwecke hilfreich, wenn Sie vorübergehend überprüfen möchten, was mit einem bestimmten Objekt in Ihrer ausgeführten Anwendung geschieht.

Core-Dump
quelle
5

Sie können dies auch in Ruby mit Singleton-Objekten tun:

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

Produziert:

Hello!
Hello world!

Genau so verfährt Ruby mit Klassen- und Modulmethoden. Beispielsweise:

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

Definiert tatsächlich eine Singleton-Methode hellofür das ClassObjekt SomeClass.

Linuxios
quelle
Möchten Sie Ihre Antwort mit Modulen und Erweiterungsmethoden erweitern? (Nur um einige andere Möglichkeiten aufzuzeigen, um Objekte mit neuen Methoden zu erweitern)?
Knut
@knut: Ich bin mir ziemlich sicher, dass extendeigentlich nur die Singleton-Klasse für das Objekt erstellt und dann das Modul in die Singleton-Klasse importiert wird.
Linuxios
5

Sie können sich Methoden pro Instanz vorstellen, mit denen Sie zur Laufzeit Ihre eigene Klasse zusammenstellen können. Dies kann eine Menge Kleber-Code eliminieren, der keinen anderen Zweck hat, als zwei Klassen zusammenzufügen, um miteinander zu sprechen. Mixins sind eine etwas strukturiertere Lösung für die gleichen Probleme.

Du leidest ein wenig unter leidest Paradoxon Blubs , im Wesentlichen, dass es schwierig ist, den Wert eines Sprachfeatures zu erkennen, bis Sie dieses Feature in einem echten Programm verwendet haben. Suchen Sie also nach Gelegenheiten, bei denen Sie glauben, dass sie funktionieren könnten, probieren Sie sie aus und sehen Sie, was passiert.

Suchen Sie in Ihrem Code nach Gruppen von Klassen, die sich nur durch eine Methode unterscheiden. Suchen Sie nach Klassen, deren einziger Zweck darin besteht, zwei andere Klassen in unterschiedlichen Kombinationen zu kombinieren. Suchen Sie nach Methoden, die nichts anderes tun, als einen Aufruf an ein anderes Objekt weiterzuleiten. Suchen Sie nach Klassen, die mithilfe komplexer Erstellungsmuster instanziiert werden . Dies sind alles mögliche Kandidaten, die durch Methoden pro Instanz ersetzt werden müssen.

Karl Bielefeldt
quelle
1

Andere Antworten haben gezeigt, wie dies ein gemeinsames Merkmal von dynamischen objektorientierten Sprachen ist und wie es trivial in einer statischen Sprache emuliert werden kann, die erstklassige Funktionsobjekte enthält (z. B. Delegate in c #, Objekte, die operator () in c ++ überschreiben). . In statischen Sprachen, in denen eine solche Funktion fehlt, ist dies schwieriger, kann aber dennoch durch die Verwendung einer Kombination aus dem Strategiemuster und einer Methode erreicht werden, bei der die Implementierung einfach an die Strategie delegiert wird. Dies ist in der Tat dasselbe, was Sie in c # mit Delegaten tun würden, aber die Syntax ist etwas chaotischer.

Jules
quelle
0

Sie können so etwas in C # und den meisten ähnlichen Sprachen tun.

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
quelle
1
Bei Programmierern geht es um konzeptionelle Fragen, und von Antworten wird erwartet, dass sie die Dinge erklären. Das Werfen von Code-Dumps anstelle von Erklärungen ist wie das Kopieren von Code von IDE auf Whiteboard: Es mag vertraut und manchmal sogar verständlich aussehen, aber es fühlt sich seltsam an ... einfach seltsam. Whiteboard hat keinen Compiler
Mücke
0

Obwohl in einer Sprache wie Java alle Instanzen einer Klasse die gleichen Methoden haben müssen, ist es konzeptionell möglich, durch Hinzufügen einer zusätzlichen Indirektionsebene, möglicherweise in Kombination mit verschachtelten Klassen, den Eindruck zu erwecken, dass dies nicht der Fall ist.

Wenn eine Klasse Foobeispielsweise eine statische abstrakte verschachtelte Klasse definieren kann, QuackerBasedie eine Methode enthält quack(Foo), sowie mehrere andere statische verschachtelte Klassen, von denen QuackerBasejede eine eigene Definition von hat quack(Foo), dann kann es sein, dass die äußere Klasse ein Feld quackervom Typ QuackerBasehat Setzen Sie dieses Feld, um eine (möglicherweise einzelne) Instanz einer ihrer verschachtelten Klassen zu identifizieren. Wenn dies geschehen ist, quacker.quack(this)wird beim Aufrufen der Befehl ausgeführtquack Methode der Klasse ausgeführt, deren Instanz diesem Feld zugewiesen wurde.

Da dies ein weit verbreitetes Muster ist, enthält Java Mechanismen, um die entsprechenden Typen automatisch zu deklarieren. Solche Mechanismen tun eigentlich nichts, was mit virtuellen Methoden und optional verschachtelten statischen Klassen nicht möglich wäre, aber sie reduzieren die Menge an Boilerplate, die erforderlich ist, um eine Klasse zu erstellen, deren einziger Zweck es ist, eine einzelne Methode im Auftrag von auszuführen eine andere Klasse.

Superkatze
quelle
0

Ich glaube, das ist die Definition einer "dynamischen" Sprache wie Ruby, Groovy & Javascript (und vielen vielen anderen). Dynamisch bezieht sich (zumindest teilweise) auf die Fähigkeit, das Verhalten einer Klasseninstanz im laufenden Betrieb dynamisch neu zu definieren.

Es ist im Allgemeinen keine großartige OO-Praxis, aber für viele dynamische Sprachprogrammierer stehen OO-Prinzipien nicht an erster Stelle.

Es vereinfacht einige knifflige Vorgänge wie Monkey-Patching, bei denen Sie möglicherweise eine Klasseninstanz optimieren, um mit einer geschlossenen Bibliothek auf eine Weise zu interagieren, die sie nicht vorausgesehen haben.

Bill K
quelle
0

Ich sage nicht, dass es eine gute Sache ist, aber das ist in Python trivial möglich. Ich kann keinen guten Anwendungsfall auf den Kopf stellen, aber ich bin mir sicher, dass es sie gibt.

    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
quelle