Ist es in Ordnung, wenn eine Klasse ihre eigene öffentliche Methode verwendet?

23

Hintergrund

Ich habe zurzeit eine Situation, in der ich ein Objekt habe , das von einem Gerät gesendet und empfangen wird. Diese Nachricht hat mehrere Konstrukte wie folgt:

public void ReverseData()
public void ScheduleTransmission()

Die ScheduleTransmissionMethode muss die ReverseDataMethode immer dann aufrufen , wenn sie aufgerufen wird. Es gibt jedoch Zeiten, in denen ich ReverseDataextern aufrufen muss (und das Hinzufügen sollte vollständig außerhalb des Namespaces erfolgen ), von wo aus das Objekt in der Anwendung instanziiert wird.

Was das "Empfangen" betrifft, meine ich, dass ReverseDataes in einem object_receivedEvent-Handler extern aufgerufen wird, um die Daten rückgängig zu machen.

Frage

Ist es allgemein akzeptabel, dass ein Objekt seine eigenen öffentlichen Methoden aufruft?

Schnüffeln
quelle
5
Es ist nichts Falsches daran, eine öffentliche Methode von sich aus aufzurufen. Aufgrund Ihrer Methodennamen klingt ReverseData () für mich jedoch ein bisschen gefährlich, eine öffentliche Methode zu sein, wenn die internen Daten umgekehrt werden. Was ist, wenn ReverseData () außerhalb des Objekts aufgerufen und dann erneut mit ScheduleTransmission () aufgerufen wird?
Kaan
@ Kaan Dies sind nicht die wirklichen Namen meiner Methoden, aber eng verwandt. Tatsächlich kehrt "Daten umkehren" nur 8 Bits des gesamten Wortes um und erfolgt, wenn wir empfangen und senden.
Snoop
Die Frage steht noch. Was ist, wenn diese 8 Bits vor der geplanten Übertragung zweimal umgekehrt werden? Dies fühlt sich wie ein riesiges Loch in der öffentlichen Oberfläche an. Wenn Sie an die öffentliche Schnittstelle denken, die ich in meiner Antwort erwähne, wird dieses Problem möglicherweise deutlich.
Daniel T.
2
@StevieV Ich glaube, dies verstärkt nur die Besorgnis, die Kaan aufwirft. Es hört sich so an, als würden Sie eine Methode verfügbar machen, die den Status des Objekts ändert, und der Status hängt in erster Linie davon ab, wie oft die Methode aufgerufen wird. Dies ist ein Albtraum, wenn Sie versuchen, den Status des Objekts im gesamten Code zu verfolgen. Es klingt für mich eher so, als würden Sie von separaten Datentypen aus diesen konzeptionellen Zuständen profitieren , sodass Sie erkennen können, was in einem bestimmten Codeteil enthalten ist, ohne sich darum kümmern zu müssen.
jpmc26
2
@StevieV so etwas wie Data und SerializedData. Daten werden im Code angezeigt, und SerializedData wird über das Netzwerk gesendet. Dann lassen Sie Reverse Data (bzw. Serialize / Deserialize Data) von einem Typ in den anderen transformieren.
Csiz

Antworten:

33

Ich würde sagen, es ist nicht nur akzeptabel, sondern wird auch empfohlen, insbesondere wenn Sie Erweiterungen zulassen möchten. Um Erweiterungen der Klasse in C # zu unterstützen, müssen Sie die Methode gemäß den nachstehenden Kommentaren als virtuell kennzeichnen. Möglicherweise möchten Sie dies jedoch dokumentieren, damit sich niemand wundert, wenn das Überschreiben von ReverseData () die Funktionsweise von ScheduleTransmission () ändert.

Es kommt wirklich auf das Design der Klasse an. ReverseData () klingt nach einem grundlegenden Verhalten Ihrer Klasse. Wenn Sie dieses Verhalten an anderen Stellen verwenden müssen, möchten Sie wahrscheinlich keine anderen Versionen davon haben. Sie müssen nur darauf achten, dass keine für ScheduleTransmission () spezifischen Details in ReverseData () gelangen. Das wird Probleme verursachen. Aber da Sie dies bereits außerhalb der Klasse verwenden, haben Sie sich das wahrscheinlich schon überlegt.

JimmyJames
quelle
Wow, kam mir komisch vor, aber das hilft. Möchte auch sehen, was andere Leute sagen. Vielen Dank.
Snoop
2
"Damit sich niemand wundert, wenn das Überschreiben von ReverseData () die Funktionsweise von ScheduleTransmission () ändert" : nein, nicht wirklich. Zumindest nicht in C # (beachten Sie, dass die Frage ein C # -Tag hat). Es ReverseData()sei denn, es ist abstrakt, egal was Sie in der untergeordneten Klasse tun, es ist immer die ursprüngliche Methode, die aufgerufen wird.
Arseni Mourzenko
2
@MainMa Oder virtual... Und wenn Sie die Methode überschreiben, muss es per Definition virtualoder sein abstract.
Pokechu22
1
@ Pokechu22: virtualfunktioniert ja auch. In allen Fällen lautet die Signatur laut OP, daher ist public void ReverseData()der Teil der Antwort über übergeordnetes Material leicht irreführend.
Arseni Mourzenko
@ MainMa Sie sagen, wenn eine Basisklassenmethode eine virtuelle Methode aufruft, verhält sie sich nicht wie gewohnt virtuell, es sei denn abstract? Ich habe Antworten auf abstractvs nachgeschlagen virtualund das Erwähnte nicht gesehen: eher abstrakt bedeutet nur, dass in dieser Basis keine Version angegeben ist.
JDługosz
20

Absolut.

Die Sichtbarkeit einer Methode hat nur den Zweck, den Zugriff auf eine Methode außerhalb der Klasse oder innerhalb einer untergeordneten Klasse zuzulassen oder zu verweigern. Öffentliche, geschützte und private Methoden können immer innerhalb der Klasse selbst aufgerufen werden.

Es ist nichts Falsches daran, öffentliche Methoden aufzurufen. Die Abbildung in Ihrer Frage ist das perfekte Beispiel für eine Situation, in der zwei öffentliche Methoden genau das sind, was Sie tun sollten.

Sie können jedoch sorgfältig auf die folgenden Muster achten:

  1. Eine Methode Hello()ruft folgendes auf World(string, int, int):

    Hello()
    {
        this.World("Some magic value here", 0, 100);
    }

    Vermeiden Sie dieses Muster. Verwenden Sie stattdessen optionale Argumente . Optionale Argumente erleichtern die Auffindbarkeit: Der Aufrufer, der eingibt, Hello(wird nicht unbedingt wissen, dass es eine Methode gibt, mit der die Methode mit Standardwerten aufgerufen werden kann. Optionale Argumente sind ebenfalls selbstdokumentierend. World()zeigt dem Anrufer nicht die tatsächlichen Standardwerte an.

  2. Eine Methode Hello(ComplexEntity)ruft folgendes auf World(string, int, int):

    Hello(ComplexEntity entity)
    {
        this.World(entity.Name, entity.Start, entity.Finish);
    }

    Verwenden Sie stattdessen Überladungen . Gleicher Grund: bessere Auffindbarkeit. Der Anrufer kann alle Überladungen durch IntelliSense sofort sehen und die richtige auswählen.

  3. Eine Methode ruft einfach andere öffentliche Methoden auf, ohne einen wesentlichen Wert hinzuzufügen.

    Denke nochmal nach. Benötigen Sie diese Methode wirklich? Oder sollten Sie es entfernen und den Aufrufer die verschiedenen Methoden aufrufen lassen? Ein Tipp: Wenn der Name der Methode nicht richtig oder schwer zu finden ist, sollten Sie ihn wahrscheinlich entfernen.

  4. Eine Methode validiert die Eingabe und ruft dann die andere Methode auf:

    Hello(string name, int start, int end)
    {
        if (name == null) throw new ArgumentNullException(...);
        if (start < 0) throw new OutOfRangeException(...);
        ...
        if (end < start) throw new ArgumentException(...);
    
        this.World(name, start, end);
    }

    Sollte stattdessen Worldseine Parameter selbst validieren oder privat sein.

Arseni Mourzenko
quelle
Würden die gleichen Überlegungen gelten, wenn ReverseData tatsächlich intern wäre und im gesamten Namespace verwendet würde, der Instanzen von "object" enthält?
Snoop
1
@StevieV: ja natürlich.
Arseni Mourzenko
@MainMa Ich verstehe nicht, was der Grund hinter den Punkten 1 und 2 ist
Kapol
@Kapol: danke für dein feedback. Ich habe meine Antwort bearbeitet, indem ich Erläuterungen zu den ersten beiden Punkten hinzugefügt habe.
Arseni Mourzenko
Selbst in Fall 1 bevorzuge ich normalerweise Überladungen gegenüber optionalen Argumenten. Wenn die Optionen so sind, dass die Verwendung von Überladungen keine Option ist oder besonders viele Überladungen erzeugt, würde ich einen benutzerdefinierten Typ erstellen und diesen als Argument verwenden (wie in Vorschlag 2) oder den Code umgestalten.
Brian
8

Wenn etwas öffentlich ist, kann es jederzeit von jedem System aufgerufen werden. Es gibt keinen Grund, warum Sie nicht auch eines dieser Systeme sein können!

In hochoptimierten Bibliotheken möchten Sie das in angegebene Idiom kopieren java.util.ArrayList#ensureCapacity(int).

Kapazität sicherstellen

  • ensureCapacity ist öffentlich
  • ensureCapacity verfügt über alle erforderlichen Grenzwertprüfungen und Standardwerte usw.
  • ensureCapacity Anrufe ensureExplicitCapacity

verifyCapacityInternal

  • ensureCapacityInternal ist privat
  • ensureCapacityInternal Die Fehlerprüfung ist minimal, da alle Eingaben aus der Klasse stammen
  • ensureCapacityInternal Ruft AUCH an ensureExplicitCapacity

verifyExplicitCapacity

  • ensureExplicitCapacity ist AUCH privat
  • ensureExplicitCapacityhat keine Fehlerprüfung
  • ensureExplicitCapacity erledigt die eigentliche Arbeit
  • ensureExplicitCapacitywird nur von ensureCapacityund angerufenensureCapacityInternal

Auf diese Weise erhält Code, dem Sie vertrauen, privilegierten (und schnelleren!) Zugriff, da Sie wissen, dass seine Eingaben gut sind. Code, dem Sie nicht vertrauen, durchläuft eine gewisse Strenge, um seine Integrität und Bombe zu überprüfen oder Standardeinstellungen bereitzustellen oder auf andere Weise mit schlechten Eingaben umzugehen. Beide leiten zu dem Ort, an dem tatsächlich gearbeitet wird.

Dies wird jedoch in ArrayList verwendet, einer der am häufigsten verwendeten Klassen im JDK. Möglicherweise erfordert Ihr Fall nicht diese Komplexität und Genauigkeit. Kurz gesagt, wenn alle ensureCapacityInternalAnrufe durch ensureCapacityAnrufe ersetzt würden, wäre die Leistung immer noch sehr, sehr gut. Dies ist eine Mikrooptimierung, die wahrscheinlich erst nach eingehender Prüfung vorgenommen wird.

corsiKa
quelle
3

Es wurden bereits mehrere gute Antworten gegeben, und ich stimme ihnen auch zu, dass ein Objekt seine öffentlichen Methoden möglicherweise von seinen anderen Methoden aufruft. Es gibt jedoch eine kleine Einschränkung beim Design, auf die Sie achten müssen.

Öffentliche Methoden haben normalerweise den Auftrag, "das Objekt in einen konsistenten Zustand zu versetzen, etwas Sinnvolles zu tun, das Objekt in einem (möglicherweise anderen) konsistenten Zustand zu belassen". Hier kann "konsistent" zum Beispiel bedeuten, dass das Lengthvon a List<T>nicht größer als sein ist Capacityund dass das Verweisen der Elemente auf Indizes von 0bis Length-1nicht werfen wird.

In den Methoden des Objekts befindet sich das Objekt möglicherweise in einem inkonsistenten Zustand. Wenn Sie also eine Ihrer öffentlichen Methoden aufrufen, kann dies sehr falsch sein, da dies nicht im Hinblick auf eine solche Möglichkeit geschrieben wurde. Wenn Sie also vorhaben, Ihre öffentlichen Methoden von Ihren anderen Methoden aufzurufen, stellen Sie sicher, dass ihr Vertrag lautet: "Nehmen Sie das Objekt in einen bestimmten Zustand, tun Sie etwas Sinnvolles, belassen Sie das Objekt in einem (möglicherweise anderen) (möglicherweise inkonsistenten) Zustand - aber nur, wenn der Anfangszustand erreicht ist state war inkonsistent) state ".

Joker_vD
quelle
1

Ein weiteres Beispiel für den Aufruf einer öffentlichen Methode innerhalb einer anderen öffentlichen Methode ist ein CanExecute / Execute-Ansatz. Ich benutze es, wenn ich sowohl eine Validierung als auch eine invariante Konservierung benötige .

Aber im Allgemeinen bin ich immer vorsichtig. Wenn eine Methode a()innerhalb von method aufgerufen wird b(), bedeutet dies, dass method a()ein Implementierungsdetail von ist b(). Sehr oft zeigt es an, dass sie zu verschiedenen Abstraktionsebenen gehören. Und die Tatsache, dass beide öffentlich sind, lässt mich fragen, ob es sich um einen Verstoß gegen das Prinzip der einmaligen Verantwortung handelt oder nicht.

Zapadlo
quelle
-5

Tut mir leid, aber ich muss den meisten anderen "Ja, Sie können" -Antworten nicht zustimmen und Folgendes sagen:

Ich würde eine Klasse davon abhalten, eine öffentliche Methode von einer anderen aufzurufen

Es gibt ein paar mögliche Probleme mit dieser Praxis.

1: Endlosschleife in inhertited Klasse

Ihre Basisklasse ruft also method1 von method2 auf, aber dann erben Sie oder jemand anderes davon und verbergen method1 mit einer neuen Methode, die method2 aufruft.

2: Ereignisse, Protokollierung usw.

Ich habe zB eine Methode Add1, die ein Ereignis '1 added!' auslöst. Ich möchte wahrscheinlich nicht, dass die Add10-Methode dieses Ereignis auslöst, zehnmal in ein Protokoll schreibt oder was auch immer.

3: Threading und andere Deadlocks

Beispiel: InsertComplexData öffnet eine Datenbankverbindung, startet eine Transaktion, sperrt eine Tabelle, ruft dann InsertSimpleData auf, öffnet eine Verbindung, startet eine Transaktion und wartet, bis die Tabelle entsperrt ist.

Ich bin mir sicher, dass es weitere Gründe gibt. Eine der anderen Antworten betraf "Sie bearbeiten Methode1 und sind überrascht, dass Methode2 sich anders verhält".

Im Allgemeinen ist es besser, wenn Sie zwei öffentliche Methoden haben, die Code gemeinsam nutzen, beide eine private Methode aufrufen zu lassen, anstatt die eine die andere aufzurufen.

Bearbeiten ----

Lassen Sie uns auf den speziellen Fall im OP eingehen.

Wir haben nicht viele Details, aber wir wissen, dass ReverseData von einer Ereignisbehandlungsroutine wie auch von der ScheduleTransmission-Methode aufgerufen wird.

Ich gehe davon aus, dass umgekehrte Daten auch den internen Zustand des Objekts ändern

In diesem Fall würde ich denken, dass Thread-Sicherheit wichtig wäre, und daher gilt mein dritter Einwand gegen die Praxis.

Um den ReverseData-Thread sicherer zu machen, können Sie eine Sperre hinzufügen. Wenn ScheduleTransmission jedoch auch threadsicher sein muss, möchten Sie dieselbe Sperre freigeben.

Am einfachsten ist es, den ReverseData-Code in eine private Methode zu verschieben und von beiden öffentlichen Methoden aufrufen zu lassen. Anschließend können Sie die lock-Anweisung in die öffentlichen Methoden einfügen und ein Sperrobjekt freigeben.

Offensichtlich kann man argumentieren, "das wird niemals passieren!" oder "Ich könnte die Sperre auf eine andere Weise programmieren", aber der Punkt bei einer guten Codierungspraxis besteht darin, den Code zunächst gut zu strukturieren.

In akademischen Begriffen würde ich sagen, dass dies das L in soliden verletzt. Öffentliche Methoden sind mehr als nur öffentlich zugänglich. Sie können auch von ihren Erben geändert werden. Ihr Code sollte für Änderungen geschlossen sein, was bedeutet, dass Sie überlegen müssen, was Sie sowohl mit öffentlichen als auch mit geschützten Methoden tun.

Hier ist noch einer: Sie verletzen möglicherweise auch DDD. Wenn es sich bei Ihrem Objekt um ein Domänenobjekt handelt, sollten die öffentlichen Methoden Domänenbegriffe sein, die für das Unternehmen von Bedeutung sind. In diesem Fall ist es sehr unwahrscheinlich, dass "ein Dutzend Eier kaufen" dasselbe ist wie "1 Ei 12-mal kaufen", auch wenn es so beginnt.

Ewan
quelle
3
Diese Fragen klingen eher nach schlecht durchdachter Architektur als nach einer allgemeinen Verurteilung des Entwurfsprinzips. (Vielleicht ist das Aufrufen von "1 hinzugefügt" jedes Mal in Ordnung. Wenn dies nicht der Fall ist, sollten wir anders programmieren. Wenn zwei Methoden versuchen, dieselbe Ressource exklusiv zu sperren, sollten sie sich nicht gegenseitig aufrufen, oder wir haben sie schlecht geschrieben .) Anstatt schlechte Implementierungen zu präsentieren, sollten Sie sich auf fundiertere Argumente konzentrieren, die den Zugriff auf öffentliche Methoden als universelles Prinzip behandeln. Zum Beispiel, wenn man guten, ordentlichen Code annimmt, warum ist es schlecht?
Doppelgreener
Wenn dies nicht der Fall ist, haben Sie keinen Ort, an dem Sie die Veranstaltung durchführen können. Ebenso ist mit # 3 alles in Ordnung, bis eine Ihrer Methoden in der Kette eine Transaktion benötigt, dann sind Sie geschraubt
Ewan
Alle diese Probleme spiegeln eher die schlechte Qualität des Codes wider, als dass das allgemeine Prinzip fehlerhaft ist. Alle drei Fälle sind nur schlechter Code. Sie können durch eine Version von "Tu das nicht, tu das stattdessen" (vielleicht mit der Frage "Was willst du genau?") Gelöst werden und stellen das allgemeine Prinzip einer Klasse, die ihr eigenes Publikum aufruft, nicht in Frage Methoden. Das vage Potenzial für eine Endlosschleife ist hier das einzige, das prinzipiell zur Sache kommt und auch dann nicht gründlich angegangen wird.
Doppelgreener
Das einzige, was der 'Code' in meinen Beispielfreigaben ist, ist eine öffentliche Methode, die eine andere aufruft. Wenn Sie denken, dass sein "schlechter Code" gut ist! Das ist meine Behauptung
Ewan
Dass Sie mit einem Hammer Ihre eigene Hand brechen können, bedeutet nicht, dass der Hammer schlecht ist. Es bedeutet, dass Sie keine Dinge tun müssen, die Ihnen die Hand brechen.
Doppelgreener