Verstößt es gegen ein OOP-Prinzip, wenn eine Mitgliedsfunktion keine Klasseneigenschaften / Mitgliedsvariablen verwendet?

8

Ich habe eine vorhandene Klasse, die interagiert und eine Datei öffnen, lesen oder schreiben kann. Ich muss eine Dateimodifikation abrufen, um eine neue Methode hinzuzufügen

Angenommen, dies ist meine Klassendefinition, in der ich eine neue Methode hinzufügen möchte.

class IO_file
{
 std::string m_file_name;
 public:
 IO();
 IO(std::string file_name);

+ time_t get_mtime(file_name);
+       OR
+ time_t get_mtime();
};

Ich habe zwei Möglichkeiten -

  1. Erstellen Sie ein leeres Objekt und übergeben Sie den Dateinamen in dem Methodenargument, für das ich die Änderungszeit der Datei abrufen möchte.

  2. Übergeben Sie den Dateinamen bei der Objektkonstruktion und rufen Sie einfach die Elementfunktion auf, die für die Elementvariable ausgeführt wird.

Beide Optionen erfüllen den Zweck. Ich glaube auch, dass der zweite Ansatz besser ist als der erste. Aber was ich nicht verstehe ist wie ? Ist der erste Ansatz schlechtes Design, da keine Mitgliedsvariable verwendet wird? Welches Prinzip des objektorientierten Designs verletzt es? Wenn eine Mitgliedsfunktion die Mitgliedsvariable nicht verwendet, sollte diese Mitgliedsfunktion immer statisch gemacht werden?

Irsis
quelle
Bei OOP geht es darum, Bedenken zu trennen, beispielsweise den Client von der Implementierung über eine Schnittstelle zu trennen - aber auch die Auswahl der Implementierung von der Verwendung einer Schnittstelle zu trennen und gleichzeitig mehrere Implementierungen zu ermöglichen, die möglicherweise dynamisch nebeneinander existieren. Anhand des Strategiebeispiels von @ CandiedOrange können die Auswahl der Strategie und der Verbraucher der Strategie getrennt werden. Während bei statischen Methoden die Client- und Implementierungsprobleme getrennt sind, besteht eine enge Kopplung des Clients an die Wahl der (einzelnen) Implementierung.
Erik Eidt
2
Es ist schlechtes Design, das ich machen kann IO("somefile.txt").get_mtime("someotherfile.txt"). Was bedeutet das überhaupt?
user253751

Antworten:

8

Verstößt es gegen einen OOP-Principal, wenn eine Member-Funktion keine Klasseneigenschaften / Member-Variablen verwendet?

Nein.

OOP ist es egal, ob Ihre Mitgliedsfunktion Klasseneigenschaften oder Mitgliedsvariablen verwendet oder nicht verwendet. OOP kümmert sich um Polymorphismus und nicht um die Implementierung einer harten Codierung. Statische Funktionen haben ihre Verwendung, aber eine Funktion sollte nicht statisch sein, nur weil sie nicht vom Objektstatus abhängt. Wenn das dein Denken ist, aber beschuldige OOP nicht, weil diese Idee nicht von OOP kam.

Ist [es] schlechtes Design [um] Mitgliedsvariablen nicht zu verwenden?

Wenn Sie sich den Status nicht von Anruf zu Anruf merken müssen, gibt es keinen guten Grund, den Status zu verwenden.

Welches Prinzip des objektorientierten Designs verstößt es?

Keiner.

Wenn eine Mitgliedsfunktion die Mitgliedsvariable nicht verwendet, sollte diese Mitgliedsfunktion immer statisch gemacht werden?

Nein. Bei diesem Denken geht der Implikationspfeil in die falsche Richtung.

  • Eine statische Funktion kann nicht auf den Instanzstatus zugreifen

  • Wenn die Funktion nicht auf den Instanzstatus zugreifen muss, kann die Funktion statisch oder nicht statisch sein

Es liegt ganz bei Ihnen, die Funktion hier statisch zu machen. Aber es wird es eher wie ein globales, wenn Sie dies tun. Bevor Sie statisch werden, sollten Sie die Funktion in einer zustandslosen Klasse hosten. Es ist flexibler.


Ich habe hier ein OOP-Beispiel für eine Member-Funktion, die keine Klasseneigenschaften oder Member-Variablen verwendet.

Die Mitgliedsfunktion (und ihre zustandslose Klasse) :

#include <iostream>

class Strategy
{
public:
     virtual int execute (int a, int b) = 0; // execute() is a so-called pure virtual 
                                             // function. As a consequence, Strategy 
                                             // is a so-called abstract class.
};

 
Drei verschiedene Implementierungen:

class ConcreteStrategyAdd:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategyAdd's execute()\n";
        return a + b;
    }
};

class ConcreteStrategySubstract:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategySubstract's execute()\n";
        return a - b;
    }
};

class ConcreteStrategyMultiply:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategyMultiply's execute()\n";
        return a * b;
    }
};

 
Ein Ort zum Speichern der Wahl der Implementierung:

class Context
{
private:
    Strategy* pStrategy;

public:

    Context (Strategy& strategy)
        : pStrategy(&strategy)
    {
    }

    void SetStrategy(Strategy& strategy)
    {
        pStrategy = &strategy;
    }

    int executeStrategy(int a, int b)
    {
        return pStrategy->execute(a,b);
    }
};

 
Ein Anwendungsbeispiel

int main()
{
    ConcreteStrategyAdd       concreteStrategyAdd;
    ConcreteStrategySubstract concreteStrategySubstract;
    ConcreteStrategyMultiply  concreteStrategyMultiply;

    Context context(concreteStrategyAdd);
    int resultA = context.executeStrategy(3,4);

    context.SetStrategy(concreteStrategySubstract);
    int resultB = context.executeStrategy(3,4);

    context.SetStrategy(concreteStrategyMultiply);
    int resultC = context.executeStrategy(3,4);

    std::cout << "\nresultA: " << resultA 
              << "\nresultB: " << resultB 
              << "\nresultC: " << resultC 
              << "\n";
}

Ausgänge:

Called ConcreteStrategyAdd's execute()
Called ConcreteStrategySubstract's execute()
Called ConcreteStrategyMultiply's execute()

resultA: 7       
resultB: -1       
resultC: 12

Und das alles, ohne execute()sich um den Zustand eines Objekts zu kümmern. Die StrategyKlasse ist eigentlich staatenlos. Nur Staat ist in Context. Staatenlose Objekte sind in OOP vollkommen in Ordnung.

Diesen Code haben Sie hier gefunden .

candied_orange
quelle
1
+1 In Bezug auf die Statik "Bei diesem Denken geht der Implikationspfeil in die falsche Richtung." ist perfekt. Ich habe in einem Unternehmen gearbeitet, in dem eine Funktion, wenn sie keinen Status benötigte, immer statisch war, was bedeutete, dass etwa 60% (wenn nicht mehr) der App statisch waren. Sprechen Sie über einen Albtraum.
Carson
"Ja wirklich?" In meiner jetzigen Firma machen wir genau das Gegenteil (und ich habe mich dafür eingesetzt). Jede Methode, die statisch gemacht werden kann, ist.
Gardenhead
@gardenhead Wenn Sie ein Gegenargument vorbringen möchten, machen Sie es bitte. Mir zu sagen, dass eine ganze Firma das tut, bedeutet nicht, dass es eine gute Sache ist.
candied_orange
1
@candiedorange Ich stimme zu, es ist kein richtiger Beweis, aber ich wollte nicht in einen Flammenkrieg geraten. Aber da Sie darauf bestehen, ist hier mein Gegenargument: OOP ist schrecklich und je weniger davon verwendet wird, desto besser. Glücklich?
Gardenhead
@ Gardenhead OOP ist schrecklich, also ist statisch gut? Ich kann reinen Funktionscode schreiben, ohne jemals eine Statik zu verwenden. Bist du sicher, dass du keinen Flammenkrieg suchst?
candied_orange
5

get_mtimewäre hier als eigenständige Funktion oder als staticFunktion sinnvoller , als wie Sie hier gezeigt haben.

Die mtimeDatei wird auf den meisten Systemen von einem Aufruf an lstatoder ähnlich gelesen und erfordert keinen offenen Dateideskriptor. Daher ist es nicht sinnvoll, sie als Elementfunktion einer instanziierten Klasse zu haben.

grau verblassen
quelle
Da mtime keinen Dateideskriptor öffnen muss, wollte ich ihn aus diesem Grund unabhängig von der Mitgliedsvariablen m_file_name machen. Daher macht es mehr, es in der Klasse statisch zu machen, in der ich diesen Teil verstanden habe. Aber ich möchte aus akademischer / theortischer Sicht verstehen, gegen welches OPP-Konzept ich mit der 1. Option verstoße.
Irsis
2
@ Rahul Dafür gibt es wirklich keine gute Antwort. Ich meine, ich könnte sagen, dass es die Abstraktion verletzt, indem es eine Abhängigkeit von Instanzdaten impliziert, aber darum geht es bei der Abstraktion nicht wirklich. Ehrlich gesagt, ich denke, Sie machen sich zu viele Sorgen um das Warum. Nehmen Sie einfach als allgemeine Faustregel an, dass eine Mitgliedsfunktion, die nicht auf eine Instanz zugreifen muss, kein Nichtmitglied sein sollte static.
Greyfade
1
+1 aber ja praktisch wusste ich, dass es keine gute Idee war, aber ich wollte mein Verständnis "warum" klären? Die Antwort von CandidOrange erklärt es.
Irsis
2

Der Grund, warum die zweite Option instinktiv besser erscheint (und IMO besser ist), ist, dass Ihre erste Option Ihnen ein Objekt gibt, das eigentlich nichts darstellt.

Wenn Sie den Dateinamen nicht angeben, ist Ihre IO_file-Klasse eigentlich nur ein abstraktes Objekt, das zufällig einer konkreten Datei ähnelt. Wenn Sie beim Aufrufen der Methode den Dateinamen übergeben, können Sie stattdessen auch einfach die gesamte Methode in eine frei schwebende reine Funktion umgestalten. Es hat keinen wirklichen Vorteil, es an ein Objekt gebunden zu halten, das Sie instanziieren müssen, um es zu verwenden. Es ist nur eine Funktion; Das Objekt-Instanziierungs-Boilerplate ist nur ein unpraktischer zusätzlicher Schritt.

Wenn dem Dateinamen jedoch alle Methoden zugewiesen wurden, die Sie für dieses Objekt aufrufen, ähneln sie eher Abfragen zu einer bestimmten Instanz einer Sache. Das ist besser OO, weil Ihre Objekte eine tatsächliche Bedeutung und daher einen Nutzen haben.

Moberemk
quelle
2

Lassen Sie uns dies in C übersetzen. Zuerst haben wir unsere Klasse - jetzt ist es eine Struktur:

struct IO_file {
    char* m_file_name;
};

Definieren wir zur Vereinfachung der Snippets eine Konstruktorfunktion (wobei Speicherlecks vorerst ignoriert werden):

struct IO_file* IO_file(char* file_name) {
    struct IO_file* obj = malloc(sizeof(struct IO_file));
    obj.m_file_name = file_name;
    return obj;
}

Option 2 sieht folgendermaßen aus:

time_t get_mtime(struct IO_file*);

und so verwendet:

time_t mtime = get_mtime(IO_file("some-file"));

Wie wäre es mit Option 1? Nun, es sieht so aus:

time_t get_mtime(struct IO_file* this, char* file_name);

Und wie wird es verwendet? Im Wesentlichen möchten Sie Junk als ersten Parameter übergeben:

time_t mtime = get_mtime((struct IO_file*)1245151325, "some-file");

Macht nicht viel Sinn, oder?

Das Objektsystem von C ++ verbirgt es, aber das Objekt ist auch ein Argument der Methode - ein implizites Zeigerargument mit dem Namen this. Dies ist, was Option 1 schlecht ist - sie hat ein Argument, das per Definition nicht verwendet wird (Argumente, die zufällig nicht verwendet werden, aber möglicherweise bei Überschreibungen verwendet werden, sind in Ordnung). Solche Argumente tragen nichts dazu bei, während sie die Signatur, Definition und Verwendung der Funktion komplizieren und die Leser des Codes verwirren, die darüber nachdenken, was das Argument tun soll, warum es in Ordnung ist, Junk an das Argument weiterzugeben und ob Sie es sind oder nicht Das Übergeben von Junk / NULL/ Leerzeichen an dieses Objekt ist die Ursache für den Fehler, den sie derzeit zu lösen versuchen. Spoiler - ist es nicht, aber sie werden immer noch Zeit damit verschwenden, diese Möglichkeit zu erkunden.

Die Tatsache, dass das Junk-Argument implizit ist, kann den Effekt verringern, ist aber immer noch vorhanden und kann durch die Erstellung der Methode leicht vermieden werden static.

Idan Arye
quelle