OOP-Prinzipien und Methodennamen

22
class Boxer:

    def punch(self, punching_bag, strength):
        punching_bag.punch(strength)


class PunchingBag:

    def punch(self, strength):
        print "Punching bag punched with strength", strength

boxer = Boxer()
punching_bag = PunchingBag()

boxer.punch(punching_bag, 2)

Kein Zweifel, das punchist ein guter Methodenname im Falle eines Boxers. Aber ist der Name punchauch gut für die Methode des Boxsacks? In beiden Fällen meine ich Schlag als Befehl (dh Schlag).

Klima
quelle

Antworten:

23

Eine gute Faustregel ist, dass Methodennamen Verben oder Prädikate sein sollten, sodass das Objekt, auf das Sie sie aufrufen ( selfin der Standard-Python-Konvention thisin den meisten anderen Sprachen), zum Thema wird.

Diese Regel file.closeist falsch, es sei denn, Sie gehen von dem mentalen Modell aus, dass die Datei sich selbst schließt oder dass das fileObjekt nicht die Datei selbst darstellt, sondern ein Datei-Handle oder eine Art Proxy-Objekt.

Ein Boxsack schlägt sich aber nie von selbst, punchingBag.punch()ist also in beiden Fällen falsch. be_punched()ist technisch korrekt, aber hässlich. receive_punch()könnte funktionieren, oder handle_punch(). Ein anderer Ansatz, der in JavaScript sehr beliebt ist, besteht darin, solche Methodenaufrufe als Ereignisse zu behandeln, und die Konvention besteht darin, den Ereignisnamen mit dem Präfix "on" zu verwenden, sodass dies on_punched()oder ist on_hit(). Alternativ können Sie die Konvention übernehmen, dass frühere Partizipien Passiv anzeigen, und nach dieser Konvention wäre der Methodenname gerecht punched().

Ein weiterer zu berücksichtigender Aspekt ist, ob der Boxsack tatsächlich weiß, was ihn getroffen hat: Macht es einen Unterschied, ob Sie ihn schlagen, mit einem Stock schlagen oder mit einem Lastwagen hineinfahren? Wenn ja, was ist der Unterschied? Können Sie den Unterschied auf ein Argument reduzieren, oder benötigen Sie verschiedene Methoden für verschiedene Arten der empfangenen Bestrafung? Eine einzelne Methode mit einem generischen Parameter ist wahrscheinlich die eleganteste Lösung, da sie die Gradkopplung niedrig hält, und eine solche Methode sollte nicht punched()oder genannt werden handle_punch(), sondern eher etwas generischeres wie receive_hit(). Mit einer solchen Methode können Sie alle Arten von Akteuren implementieren, die Boxsäcke treffen können, ohne den Boxsack selbst zu ändern.

tdammers
quelle
4
@Artur: ja und nein. Dateien können sich (konzeptionell gesehen) schließen, wenn sie gefragt werden. Arrays können sich selbst sortieren; Boxsäcke schlagen sich aber nicht.
tdammers
2
Okay, wenn unser Boxsack mit wahnsinniger Geschwindigkeit gegen die Wand stößt, ist es die Wand, die ihn stößt, oder hat er einen Schlag auf sich selbst und tatsächlich auf sich selbst erfahren?
1
@tdammers: Ihr Vorschlag zur Verallgemeinerung könnte auch zu einer Schnittstelle führen, die aufgerufen wird Hitable.
Jens Piegsa
2
@Artur: Ich denke, hier bricht die OOP-Annahme zusammen, dass jeder Satz ein natürliches Thema hat und dass diese Idee auf die Programmierung anwendbar ist.
tdammers
1
Die Hauptfrage ist also. Wenn sich Dateien schließen können, Arrays sich selbst sortieren können usw., warum können sich Boxsäcke nicht selbst lochen? Gibt es einen wirklichen Unterschied oder ist es nur so, dass wir uns im ersten Fall daran gewöhnt haben und im zweiten Fall nicht?
Clime
6

Ich denke, es ist eine konzeptionelle Frage (wie wir über die Welt denken). Es ist in Ordnung zu sagen:

  • Schau, die Tür schließt sich. door.close()
  • Wow, das Papier faltet sich von alleine. paper.fold()
  • Was zum Teufel?! Diese Akte auf dem Schreibtisch hat sich gerade geschlossen, niemand ist in der Nähe. file.close()

Es ist seltsam zu sagen:

  • Dieser Boxsack im Fitnessstudio hat sich gerade selbst geschlagen. bag.punch()

Es müsste etwas haben, mit dem es sich an erster Stelle schlagen kann (z. B. Arme). Sie würden wahrscheinlich sagen:

  • Der Boxsack hat begonnen, sich von selbst zu bewegen, als hätte ihn jemand geschlagen. punching_bag.move()

Es ist in Ordnung für programmatische Objekte, Dinge zu tun, die normalerweise andere mit ihnen machen (in der "realen Welt"). Aber ich denke, es sollte immer mindestens einen Sinn ergeben, dass das Ding es mit sich selbst macht . Sie sollten es sich leicht vorstellen können, ohne dunkel zu werden (wie im Fall von punching_bag).

Klima
quelle
2

Es ist Geschmackssache, denke ich. Punching bagDie punch()Methode von ist zumindest konsistent mit file.close()oder frame.move()im Sinne des Erlebens von Handlungen an sich. Die größere Frage wäre, warum Boxerüberhaupt eine punch(something)Methode zur Verfügung steht.


quelle
Ich mag Ihren Punkt über file.close (). Das war etwas, worauf ich hinauswollte. Vielleicht hat der Boxer eine Schlagmethode, weil es auch einen Trainer gibt, der den Boxer trainiert. Tatsächlich habe ich nur versucht, ein Beispiel für eine Aktion (Nachricht) zu geben, die durch mehrere Objekte geleitet wird, wobei das letzte Objekt "Objekt einer Aktion" ist. Ich habe ein kleines Problem mit list.append (4), account.deposit (50), file.close (), paper.fold () vs. boxer.punch (), dog.bark (), logger.log () etc .
clime
Beim Durchlaufen mehrerer Objekte gibt es zwei Fälle: Sie verwenden gebundenen Kontext (self) und Sie tun dies nicht. Wenn Sie das tun, sollten Sie Ihre Methoden sein Coach.sayPunchToBoxer(), Boxer.punchNearestBag()und Bag.punch(). Andernfalls müssen Sie jedes Mal raten, was passiert, wenn Sie anrufen Coach.punch(). Die allgemeine Regel lautet: Wenn das Objekt, für das eine Aktion ausgeführt wird, nicht im Methodennamen angegeben ist, ist der Empfänger dieses Objekt.
Nun, ich denke das ist auch in Ordnung: coach.say_punch (boxer, punching_bag), boxer.punch (punching_bag). dh der Empfänger steht nicht im Methodennamen, sondern in den Parametern.
Clime
1
Klar, ich meinte, dass der Aktionsempfänger anhand der Anrufanweisung zu erraten sein sollte.
2

Sie haben zwei verschiedene Nachrichten: eine, um einem Objekt das Lochen zu befehlen, und eine, um einem Objekt mitzuteilen, dass es gelocht wurde. Bedenken Sie, dass ein Boxer-Objekt wahrscheinlich auf beide antworten muss . Anders . Das ist ein guter Grund, ihnen andere Namen zu geben.

Meine Neigung wäre punch(boxer, object, strength), die entgegengesetzte Methode zu behalten und umzubenennen punched. Sie könnten es handle_punchoder so etwas nennen, aber dann ist es immer noch zweideutig, ob es sich um einen Punch-Befehl oder die Benachrichtigung handelt, dass er gepuncht wurde.

user2313838
quelle
Ein guter Punkt für Boxer, der sowohl Punsch als auch so etwas wie handle_punch benötigt (wäre defendin diesem speziellen Fall). Aber Boxsack wird niemals so bidirektional sein. Und es gibt bereits diese file.close () ...
Clime
defendist ein Befehl. Dies ist eine mögliche Aktion, auf die ein Objekt reagieren könnte punched, aber Sie möchten nicht, dass andere Objekte defenddirekt aufgerufen werden.
user2313838
2

Ihr Ansatz wird schließlich zu einem sehr gekoppelten Code führen.

Um Eric Lippert hier ideal zusammenzufassen, möchte man, dass der Boxer in der Lage ist, sehr viele Dinge zu schlagen. Wenn der Boxsack die Signatur der Boxerfunktion ist, bedeutet dies, dass der Boxer mit dem unmittelbaren Wissen von All erstellt wird (das heißt, dass er stanzbar ist). Plus einen Schlag zu geben und einen Schlag zu erhalten sind zwei SEHR verschiedene Dinge, daher sollten sie nicht den gleichen Namen haben.

Ich würde dies eher als Boxer modellieren, der einen Schlag erzeugt (ein anderes Objekt, das die Attribute Kraft, Reichweite, Richtung usw. des Schlags enthält).

Dann muss der Boxsack mit einer Methode wie onPunch, die dieses Punch-Objekt empfängt, die Wirkung des Punchs auf sich selbst berechnen können.

Vor diesem Hintergrund ist der Name der Dinge sehr wichtig. Es muss zu dem mentalen Modell passen, das Sie von der Situation haben. Wenn Sie versuchen zu erklären, wie etwas passieren kann, das auf den ersten Blick keinen Sinn ergibt, oder wenn es Ihnen am schwersten fällt, etwas zu benennen, ist Ihr Modell möglicherweise falsch und muss geändert werden.

Es ist schwierig, ein Modell zu ändern, nachdem Sie begonnen haben. Im Allgemeinen neigen die Leute dazu, die Realität so zu ändern, dass sie zum Modell passt. Das Problem dabei ist, dass die Welt, die Sie erstellen, immer komplexer wird und die Interaktionen immer schwieriger zu implementieren sind, wenn Sie Dinge biegen, die passen (z. B. einen Boxsack, der Dinge stanzen kann). Sie werden irgendwann einen Punkt erreichen, an dem das Hinzufügen des Trivialsten zum Albtraum von Veränderungen und Fehlern wird. Diese konzeptionelle technische Verschuldung kann einen sehr hohen Preis haben, selbst wenn die anfänglichen Kosten zu diesem Zeitpunkt als das günstigste angesehen wurden.

Newtopian
quelle
1

Dies ist das Problem, das ich als "Objekt / Subjekt" -Verwirrung bezeichne, und es ist ziemlich weit verbreitet.

Sätze haben im Allgemeinen ein Subjekt , das das Verb auf seinem Zielobjekt ausführt .

Was die Programmierung angeht, so ist das Einzige, was die Dinge tatsächlich tut, der Computer. Oder praktisch ein Prozess, ein Faden oder eine Faser. Die Objekte werden standardmäßig nicht animiert. Sie haben keine eigenen Threads, so dass sie eigentlich nichts tun können.

Dies bedeutet, dass Methoden auf sie einwirken, sie sind das Ziel der Aktion und nicht derjenige, der die Aktion ausführt. Deshalb nennen wir sie "Objekte", nicht "Subjekte"!

Wenn Sie sagen, File.closees ist nicht die Datei, die sich selbst schließt, ist es der aktuelle Thread, der die Datei schließt. Wenn Sie sagen Array.sort, sortiert der aktuell ausgeführte Thread das Array. Wenn Sie sagen HttpServer.sendRequest, sendet der aktuell ausgeführte Thread die Anfrage an den Server (nicht umgekehrt!). Ähnliches PunchingBag.punchbedeutet, dass der aktuell laufende Faden den Beutel stanzt.

Das heißt, wenn Sie möchten Boxer, dass der Punch in der Lage ist, dann muss es eine Unterklasse von a sein, Threaddamit er in seiner Thread-Funktion Dinge wie Boxsäcke ausführen kann.

Manchmal ist es jedoch auch sinnvoll zu sagen, dass sich der Boxsack selbst stanzt, wenn jedes Objekt einen eigenen Thread hat. Möglicherweise möchten Sie Rennbedingungen vermeiden und Methodenaufrufe als Nachrichtenübergabe implementieren: Sie stanzen den Beutel, indem Sie die punchNachricht senden selbst sendet Ihnen dann die punch successfulNachricht zurück, aber das ist nur ein Implementierungsdetail.

Calmarius
quelle
0

Ich bin damit einverstanden, dass "punch" ein guter Methodenname für die Boxer-Klasse ist, da er (mit einigen Optimierungen) für andere Objekte wiederverwendet werden kann. Es wird auch genau beschrieben, dass ein Objekt einer Klasse eine Aktion für ein anderes Objekt ausführt. Allerdings würde ich die Methode in "doPunch" umbenennen, um die Beziehung klarer darzustellen.

Für die PunchingBag-Klasse ist der Methodenname jedoch entweder zu vage oder ein wenig ungenau. Wenn ich "Punsch" sehe, denke ich, dass etwas etwas anderes schlägt. In diesem Fall reagiert das PunchingBag-Objekt jedoch auf einen Schlag eines Objekts (in diesem Fall eines Boxer-Objekts). Daher würde ich die Methode hier in "isPunched" umbenennen, um zu veranschaulichen, dass das Objekt auf einen Schlag reagiert.

Dies ist jedoch meine Interpretation, wie ich die Methoden benennen würde. Es ist alles eine Frage des Geschmacks und der Standards, denen Sie folgen.

Marvon
quelle
3
isPunchedist wirklich irreführend (je nach Framework-Benennungsschema mehr oder weniger).
Es ist üblich, dass die Methode auf das Objekt angewendet wird, für das sie aufgerufen wird. Was ist los mit gerade punch()?
Nun, ich verstehe vollkommen, dass es notwendig ist, die Aktionsrichtung zu spezifizieren, aber ich denke, dass OOP und seine Philosophie etwas an sich haben, das dies unnötig macht. Eine Art Abstraktion, die mit der berühmten Erklärung verbunden ist, dass Objekte sich gegenseitig "Botschaften senden".
Clime
Wenn anhand des Methodennamens nicht ersichtlich ist, was die Methode tut, liegt ein Problem mit dem Namen vor. Es ist kein Problem mit OO oder dem verwendeten Paradigma. Das ist der Grund, warum punch () auf einem Boxsack in dem Kontext, in dem Sie ihn verwenden möchten, falsch ist. Was bedeutet es, wenn Sie Schlag zu einem Sandsack sagen? Es ist auch der Grund, warum Sie von keiner Philosophie annehmen können, dass etwas in Situationen, in denen die Annahme Mehrdeutigkeiten erzeugt, unnötig ist. Es gibt Fälle, in denen Faustregeln gelten, und Situationen, in denen sie nicht gelten. Wenn Faustregeln immer funktionieren würden, würden sie Regeln genannt (ohne das "Daumen").
Dunk
-2

hmmmm. Ich frage Boxsack als Klasse, weil Sie sich nicht wirklich für den Boxsack interessieren - Sie interessieren sich für die Wirkung und Stärke der Boxerfaust. Die Methoden sollten sich also auf das beziehen, was auch immer die Messung und Berichterstattung der Schlagstöße ist. auch wenn dies aus dem "Boxsack" kommt, sollte die Benennung dennoch die Verantwortung offenbaren - wie punchImpactMeter usw.

cartalot
quelle
-3

Der Boxer schlägt den Boxsack -> boxer.punch

Der Boxsack wird vom Boxer gestanzt -> punchingbag.get_punch

P3Dr0
quelle
3
Dies scheint nichts Wesentliches zu bieten über Punkte, die in den vorherigen 6 Antworten gemacht und erklärt wurden
Mücke