2016-05-04 12 views
1

Ich habe einen Code, in dem abgeleitete Klassen Funktionen implementieren, die Erweiterungen derselben in Basisklassen sind. Im folgenden Beispiel möchte ich, dass die Funktion do_work eine Reihe von Aufgaben ausführt. In der abgeleiteten Klasse Derived enthält die Funktion do_work die gesamte Arbeit der Basisklasse Basedo_work Funktion, einschließlich einer zusätzlichen Aufgabe.Der beste Entwurf für eine abgeleitete Klasse, die Funktionen aus einer Basisklasse erweitert

Was ist die eleganteste Art, dies aus Sicht des Software-Designs zu tun? Verwenden Sie OPTION 1 oder verwenden Sie OPTION 2?

#include <iostream> 

class Base 
{ 
    public: 
     virtual void do_work() 
     { 
      do_base_task1(); 
      do_base_task2(); 
     } 
    protected: 
     void do_base_task1() { std::cout << "Doing base task1" << std::endl; } 
     void do_base_task2() { std::cout << "Doing base task2" << std::endl; } 
}; 

class Derived : public Base 
{ 
    public: 
     void do_work() 
     { 
      // OPTION 1 
      do_base_task1(); 
      do_base_task2(); 
      // END OF OPTION 1 

      // OPTION 2 
      Base::do_work(); 
      // END OF OPTION 2 

      do_extra_task(); 
     } 
    protected: 
     void do_extra_task() { std::cout << "Doing derived task" << std::endl; } 
}; 

int main() 
{ 
    Base base; 
    base.do_work(); 

    Derived derived; 
    derived.do_work(); 

    return 0; 
} 
+1

Ich würde Option 2 vorschlagen. Option 1 verstößt gegen das DRY-Prinzip. Sehen Sie sich das [Vorlagenmethodenmuster] (https://en.wikipedia.org/wiki/Template_method_pattern) an. – smkanadl

+0

Ich würde Option 2 bevorzugen, da dies bedeutet, dass Sie 'do_base_task1()' und 'do_base_task2()' private Member-Funktionen statt geschützt - sie sollten nur innerhalb von 'Basis' zugänglich sein, da die abgeleiteten Klassen kein Geschäft haben, sie anzurufen . – ArchbishopOfBanterbury

+0

Mit Option 2 können Sie außerdem die geschützten Methoden privat machen, was bei der Kapselung hilft. – JETM

Antwort

2

Meiner Meinung hummble die effizienteste Version ist:

void do_work() { 
    Base::do_work(); 
    do_extra_task(); 
} 

Da diese Art und Weise, wenn Sie Base::do_work Implementierung ändern Sie nicht die Änderungen in der abgeleiteten Klasse Memberfunktion Derived::do_work auch passieren müssen .

1

Zusätzlich Antwort auf 101.010 ist, ermöglicht

void do_work() { 
    Base::do_work(); 
    do_extra_task(); 
} 

tun Sie do_base_task1() und do_base_task2() privaten Member-Funktionen und nicht geschützt zu machen, welche Struktur und Verkapselung verbessert. Die Derived Klasse hat kein Geschäft, das diese zwei Methoden aufruft, so dass sie stattdessen private werden und eine öffentliche Schnittstellenmethode zum Aufrufen dieser Methoden bereitstellen (d. H. Base::do_work()) ist ein besseres Design.

1

OPTION 3:

class Base 
{ 
    public: 
    void do_work() // not virtual 
    { 
     do_base_task1(); 
     do_base_task2(); 
     do_extra_task(); 
    } 

    //... base tasks as before 

    protected: 
    virtual void do_extra_task(){} 
}; 

class Derived : public base 
{ 
    //no do_work in here now 
    protected: 
    void do_extra_task() { std::cout << "Doing derived task" << std::endl; } 
} 

in Haupt in der gleichen Art und Weise genannt, wie in Ihrem Beispiel

diese Weise wissen Sie, dass alle abgeleiteten Klassen werden die Basis anrufen arbeiten (Sie können es nicht vergessen in einer der abgeleiteten Klassen) und wenn eine bestimmte abgeleitete Klasse keine zusätzliche Arbeit zu tun hat, muss sie do_work oder do_extra_tasks nicht definieren. Weniger Wiederholungen, weil Sie do_work nicht wiederholt definieren.

Dies ist das nicht virtuelle Schnittstellenmuster.

+0

Dies würde Menschen nicht erlauben, zusätzliche Funktionalität hinzuzufügen, ohne die Basisklasse zu bearbeiten. – Chiel

+1

Warum nicht? Sie fügen die zusätzliche Funktionalität hinzu, indem Sie die virtuelle Funktion do_extra_task in Ihren abgeleiteten Klassen überschreiben. Es verhindert jedoch, dass die abgeleiteten Klassen eine do_work schreiben, die die Basisklassenaktionen vermeidet. Wenn sie also optional sind, wäre dies nicht das zu verwendende Muster, aber wenn alle abgeleiteten Klassen die Basisklassenarbeit aufrufen müssen, ist dieser Code selbstdokumentierend in dieser Hinsicht. Dies ist ein etabliertes Muster. http://www.gotw.ca/publications/mill18.htm – ROX

1

Noch eine andere Option:

#include<iostream> 

struct B { 
    virtual void doWork() = 0; 
}; 

template<class D> 
struct T: B { 
    void doWork() override { 
     taskT(); 
     static_cast<D*>(this)->taskD(); 
    } 

private: 
    void taskT() { std::cout << "taskT" << std::endl; } 
}; 

struct S: T<S> { 
    void taskD() { std::cout << "taskD" << std::endl; } 
}; 

int main() { 
    B *b = new S; 
    b->doWork(); 
} 

Diese auf CRTP Idiom basiert.
Wenn Sie eine kleine Bibliothek entwerfen, übernimmt diese Option vollständig die Aufgabe, die richtige Methode aufzurufen, wenn sie aufgerufen werden soll.
Der Benutzer Ihrer Bibliothek weiß sogar nicht, dass die Methoden in der Basisklasse existieren, aber man weiß, dass die Methode der abgeleiteten Klasse vertraglich aufgerufen wird.
Noch mehr, taskD ist nicht Teil der Schnittstelle der Basisklasse, so dass Sie die zusätzliche Aufgabe nicht aus einer Referenz auf B ausführen können (es sei denn, Sie rufen doWork, und es führt tatsächlich alle Aufgaben in der richtigen Reihenfolge).

Verwandte Themen