2016-04-28 9 views
9

powiedzmy, że mamy następującą hierarchię:Jak zapobiegać wezwanie do wdrożenia podstawowej metody

class Abstract 
{ 
public: 
    virtual void foo() = 0; 
}; 

class Base : public Abstract 
{ 
public: 
    virtual void foo() override; //provides base implementation 
}; 

class Derived : public Base 
{ 
public: 
    virtual void foo() override; //provides derived implementation 
}; 

Jeśli Base::foo() kiedykolwiek nazywa na obiekcie Derived że obiekt będzie utratę synchronizacji, a jego dane zostaną uszkodzone. Dziedziczy on strukturę danych i jej manipulację, ale musi wykonać dodatkowe operacje, dlatego wywołanie tylko tych Base::foo() spowoduje pominięcie tych dodatkowych operacji, w wyniku czego stan Derived zostanie uszkodzony.

Dlatego chciałbym uniknąć bezpośredniego połączenia z Base realizacji foo więc to:

Derived d; 
d.Base::foo(); 

idealnie, powinien dać mi błąd czasu kompilacji niektórych rodzajów. Lub nie rób nic lub w inny sposób można temu zapobiec.

Jednak może to być ja naruszające zasady polimorfizmu i powinno zamiast użyć kompozycję ale to wymagałoby dużo dodatkowego typowania ...

+0

Coś nie tak z 'protected:' zamiast 'public:' dla tej metody w 'Base'?I fwiw, to także wydaje się przyzwoitym kandydatem do redeclarowania jako czysto-wirtualnego w 'Bazie' * i * dostarczaniu implementacji' Base', która nie jest powszechna, ale ma miejsce. Jeśli 'Derived' powinno * zawsze * być zaimplementowane, wydaje się, że przyzwoicie pasuje. – WhozCraig

+0

@WhozCraig Nie wiedziałem, że mogę zapewnić implementację dla czystej wirtualnej metody. Czy to uniemożliwia wywoływanie tej implementacji, jeśli została ona ponownie zaimplementowana przez osoby, które ją dziedziczą? – Resurrection

+1

@Resurrection nie, dlatego 'chronione' na' Base :: foo' wejdzie. Ale to, co * robi *, to wymuszenie klas pochodnych, aby nadal zapewniały przesłonięcia, jednocześnie zapewniając wspólną implementację Base, którą mogą wywołać bez podrzucania dodatkowa funkcja członka do złego samopoczucia. – WhozCraig

Odpowiedz

22

Jak o template method pattern:

class Abstract 
{ 
public: 
    void foo() { foo_impl(); } 
private: 
    virtual void foo_impl() = 0; 
}; 

class Base : public Abstract 
{ 
private: 
    virtual void foo_impl() override; //provides base implementation 
}; 

class Derived : public Base 
{ 
private: 
    virtual void foo_impl() override; //provides derived implementation 
}; 

następnie

void test(Abstract& obj) { 
    obj.foo(); // the correct foo_impl() will be invoked 
} 
Derived d; 
test(d); // impossible to call the foo_impl() of Base 
+8

Ogólnie rzecz biorąc, dobrze jest ukryć polimorfizm za nie-polimorficznymi funkcjami szablonu, nie tylko w tym walizka. – leemes

8

Można zrobić wszystkie metody foo() nie- public, a następnie mieć non - virtual funkcja w klasie Abstract, która po prostu wywołuje foo.

+1

Dla "może" przeczytać "prawdopodobnie powinno" (jako ogólna zasada - nawet jeśli nie próbujesz zapobiec strzelaniu sobie w stopę). –

+1

@Martin Bonner - true, oblig. link [Non Virtual Interface] (https://en.wikipedia.org/wiki/Non-virtual_interface_pattern) etc .... –

12

Można zwiedzić template method pattern. Pozwala to na większą kontrolę nad realizacją zaangażowanych metod.

class Abstract 
{ 
public: 
    virtual void foo() = 0; 
}; 

class Base : public Abstract 
{ 
protected: 
    virtual void foo_impl() = 0; 
public: 
    //provides base implementation and calls foo_impl() 
    virtual void foo() final override { /*...*/ foo_impl(); } 
}; 

class Derived : public Base 
{ 
protected: 
    virtual void foo_impl() override; //provides derived implementation 
}; 

Wzór jest widoczny w bibliotece iostreams z sync() i pubsync() metod.

Aby uniemożliwić bezpośrednie połączenia i zachować spójny stan, musisz uzyskać final implementację metody foo we właściwym miejscu w stosie. Jeśli chcesz zabronić bezpośredniego połączenia z góry hierarchii, możesz przenieść metody _impl.

Zobacz także interfejs inny niż wirtualny, the NVI pattern.


Należy pamiętać również, że metody nadrzędne nie muszą mieć ten sam specyfikator dostępu jako klasy Abstract. Można również po prostu tworzyć metody w pochodnych klasach private lub protected;

class Abstract 
{ 
public: 
    virtual void foo() = 0; 
}; 

class Base : public Abstract 
{ 
    virtual void foo() override; //provides base implementation 
}; 

class Derived : public Base 
{ 
    virtual void foo() override; //provides derived implementation 
}; 

Uwaga: jeżeli nie ma inaczej, zmieniając specyfikator dostępu można uznać za złe design - tak w zasadzie, jeśli nie zmieni specyfikator dostępu, nie powinien powinien być dobry powód, aby to zrobić.

+0

Dzięki za napiwek! Użyłem tego wzorca wcześniej, ale z jakiegoś powodu nie przyszło mi do głowy, że rozwiąże mój obecny problem. W każdym razie myślę, że wirtualny foo jest przesadą w tym przypadku, a nie-wirtualny foo w Abstrakt z wirtualnym foo_impl wystarczyłby tak, jak sugerują inni. Ale tak, to jest, kiedy chciałbym pozwolić na prawdziwie pełną kontrolę. – Resurrection

+0

Jasne, jaki poziom haka w szablonie metody szablonowej zależy od tego, co jeszcze chcesz zrobić. Umieściłem go tam, odkąd wspomniałeś o tym punkcie w pytaniu. Utrzymanie klasy tylko z czystymi funkcjami wirtualnymi może być również korzystne (minimalizuje zależność linkera, np. Ponad granicę dll). – Niall

+0

Interesujący punkt. Rozważę to przy przepisywaniu. Jeśli chodzi o edycję: jest to z pewnością możliwe (chociaż czytam gdzie indziej, to źle jest zaprojektować takie identyfikatory), ale nie w tym przypadku, ponieważ baza musi działać tak, jak przewidywał to Abstract, więc nie mogę ukryć publicznego interfejsu lub jego części w tym walizka. – Resurrection