Jakie są role singletonów, klas abstrakcyjnych i interfejsów?

13

Studiuję OOP w C ++ i chociaż jestem świadomy definicji tych 3 pojęć, tak naprawdę nie jestem w stanie zrozumieć, kiedy i jak go używać.

Użyjmy tej klasy na przykład:

class Person{
    private:
             string name;
             int age;
    public:
             Person(string p1, int p2){this->name=p1; this->age=p2;}
             ~Person(){}

             void set_name (string parameter){this->name=parameter;}                 
             void set_age (int parameter){this->age=parameter;}

             string get_name (){return this->name;}
             int get_age (){return this->age;}

             };

1. Singleton

JAK działa ograniczenie klasy, aby mieć tylko jeden obiekt?

CAN zaprojektować klasy, która miałaby TYLKO 2 instancje? A może 3?

KIEDY stosowanie singletonu jest zalecane / konieczne? Czy to dobra praktyka?

2. Klasa abstrakcyjna

O ile wiem, jeśli jest tylko jedna funkcja wirtualna, klasa staje się abstrakcyjna. Więc dodając

virtual void print ()=0;

zrobiłby to, prawda?

DLACZEGO potrzebujesz klasy, której obiekt nie jest wymagany?

3. interfejs

Jeśli interfejs jest klasą abstrakcyjną, w której wszystkie metody są czysto wirtualnymi funkcjami, to

JAKA jest główna różnica między nimi dwoma?

Z góry dziękuję!

appoll
źródło
2
Singleton jest kontrowersyjny, poszukaj go na tej stronie, aby uzyskać różne opinie.
Winston Ewert
2
Warto również zauważyć, że chociaż klasy abstrakcyjne są częścią języka, ani singletony, ani interfejsy nie są. Są to wzorce, które ludzie wdrażają. Singleton w szczególności jest czymś, co wymaga odrobiny sprytnego hakowania, aby zadziałało. (Chociaż oczywiście można utworzyć singletona tylko zgodnie z konwencją).
Gort the Robot
1
Pojedynczo proszę.
JeffO

Odpowiedzi:

17

1. Singleton

Ograniczasz liczbę instancji, ponieważ konstruktor będzie prywatny, co oznacza, że ​​tylko metody statyczne mogą tworzyć instancje tej klasy (istnieją inne brudne sztuczki, aby to osiągnąć, ale nie dajmy się zwieść).

Stworzenie klasy, która będzie miała tylko 2 lub 3 instancje, jest całkowicie wykonalne. Powinieneś używać singletona, ilekroć poczujesz konieczność posiadania tylko jednej instancji tej klasy w całym systemie. Zazwyczaj dzieje się tak w przypadku klas, które mają zachowanie „kierownika”.

Jeśli chcesz dowiedzieć się więcej o Singletonach, możesz zacząć w Wikipedii, a zwłaszcza w C ++ w tym poście .

Zdecydowanie jest kilka dobrych i złych rzeczy w tym schemacie, ale ta dyskusja należy gdzie indziej.

2. Klasy abstrakcyjne

Tak to prawda. Tylko jedna wirtualna metoda oznaczy klasę jako abstrakcyjną.

Będziesz używał tego rodzaju klas, jeśli masz większą hierarchię klas, w której najwyższe klasy nie powinny być tworzone.

Załóżmy, że definiujesz klasę ssaków, a następnie dziedziczysz ją dla psów i kotów. Jeśli się nad tym zastanowić, nie ma większego sensu, aby mieć czystą instancję Ssaka, ponieważ najpierw trzeba wiedzieć, co to naprawdę jest Ssak.

Potencjalnie istnieje metoda o nazwie MakeSound (), która będzie miała sens tylko w klasach odziedziczonych, ale nie ma wspólnego dźwięku, który mogą wytwarzać wszystkie ssaki (jest to tylko przykład, który nie próbuje tutaj uzasadnić dźwięków ssaków).

Oznacza to, że Ssak powinien być klasą abstrakcyjną, ponieważ będzie miał pewne wspólne zachowanie zaimplementowane u wszystkich ssaków, ale tak naprawdę nie powinien być tworzony. To podstawowa koncepcja klas abstrakcyjnych, ale zdecydowanie powinieneś się jej nauczyć.

3. Interfejsy

W C ++ nie ma czystych interfejsów w tym samym sensie, co w Javie lub C #. Jedynym sposobem na stworzenie takiego jest posiadanie czystej, abstrakcyjnej klasy, która naśladuje większość zachowań, których oczekujesz od interfejsu.

Zasadniczo zachowaniem, którego szukasz, jest zdefiniowanie umowy, z którą inne obiekty mogą wchodzić w interakcje bez dbania o implementację. Kiedy tworzysz klasę czysto abstrakcyjną, oznacza to, że cała implementacja należy gdzie indziej, więc celem tej klasy jest tylko kontrakt, który ona definiuje. Jest to bardzo potężna koncepcja w OO i zdecydowanie powinieneś się nią bardziej przyjrzeć.

Możesz przeczytać o specyfikacji interfejsu dla C # w MSDN, aby uzyskać lepszy pomysł:

http://msdn.microsoft.com/en-us/library/ms173156.aspx

C ++ zapewni takie samo zachowanie, mając czystą abstrakcyjną klasę.

Alex
źródło
2
Czysta abstrakcyjna klasa bazowa daje wszystko, co robi interfejs. Interfejsy istnieją w Javie (i C #), ponieważ projektanci języków chcieli zapobiec wielokrotnemu dziedziczeniu (z powodu tworzonych przez niego bólów głowy), ale rozpoznali bardzo powszechne stosowanie wielokrotnego dziedziczenia, które nie jest problematyczne.
Gort the Robot
@StevenBurnap: Ale nie w C ++, który jest kontekstem pytania.
DeadMG
3
Pyta o C ++ i interfejsy. „Interfejs” nie jest cechą językową C ++, ale ludzie z pewnością tworzą interfejsy w C ++, które działają dokładnie tak samo jak interfejsy Java, wykorzystując abstrakcyjne klasy podstawowe. Zrobili to, zanim Java istniała.
Gort the Robot
Zobacz accu.org/index.php/journals/233 .
Gort the Robot
1
To samo dotyczy Singletonów. W C ++ oba są wzorami projektowymi, a nie funkcjami językowymi. Nie oznacza to, że ludzie nie mówią o interfejsach w C ++ io tym, do czego służą. Koncepcja „interfejsu” pochodzi z systemów składowych, takich jak Corba i COM, które pierwotnie opracowano do użycia w czystym C. W C ++ interfejsy są zazwyczaj implementowane przy użyciu abstrakcyjnych klas bazowych, w których wszystkie metody są wirtualne. Jego funkcjonalność jest identyczna z interfejsem Java. Jako taka, koncepcja interfejsu Java jest celowo podzbiorem klas abstrakcyjnych C ++.
Gort the Robot
8

Większość osób już wyjaśniła, czym są singletony / klasy abstrakcyjne. Mam nadzieję, że przedstawię nieco inną perspektywę i podam kilka praktycznych przykładów.

Singletony - jeśli chcesz, aby cały kod wywołujący używał jednej instancji zmiennych, niezależnie od przyczyn, masz następujące opcje:

  • Zmienne globalne - oczywiście brak enkapsulacji, większość kodu połączona z globalsami ... źle
  • Klasa ze wszystkimi funkcjami statycznymi - nieco lepsza niż proste globale, ale ta decyzja projektowa wciąż prowadzi cię do ścieżki, w której kod opiera się na globalnych danych i może być bardzo trudno zmienić później. Nie możesz również korzystać z funkcji OO, takich jak polimorfizm, jeśli wszystko, co masz, to funkcje statyczne
  • Singleton - chociaż istnieje tylko jedna instancja klasy, faktyczna implementacja klasy nie musi wiedzieć nic o tym, że jest globalna. Więc dzisiaj możesz mieć klasę, która jest singletonem, jutro możesz po prostu upublicznić jego konstruktora i pozwolić klientom tworzyć wiele kopii. Większość kodu klienta, który odwołuje się do singletonu, nie musiałaby się zmieniać, a implementacja samego singletonu nie musiałaby się zmieniać. Jedyną zmianą jest sposób, w jaki kod klienta uzyskuje referencje singleton w pierwszej kolejności.

Ze wszystkich złych i złych opcji, jeśli potrzebujesz globalnych danych, singleton jest O wiele lepszym podejściem niż którekolwiek z poprzednich dwóch. Pozwala także zachować otwarte opcje, jeśli jutro zmienisz zdanie i zdecydujesz się zastosować odwrócenie kontroli zamiast globalnych danych.

Więc gdzie użyłbyś singletona? Oto kilka przykładów:

  • Rejestrowanie - jeśli chcesz, aby cały proces miał jeden dziennik, możesz utworzyć obiekt dziennika i przekazywać go wszędzie. Ale co, jeśli masz 100 000 000 wierszy starszego kodu aplikacji? zmodyfikować je wszystkie? Możesz też po prostu wprowadzić następujące informacje i rozpocząć korzystanie z nich w dowolnym miejscu:

    CLog::GetInstance().write( "my log message goes here" );
  • Pamięć podręczna połączenia z serwerem - to było coś, co musiałem przedstawić w naszej aplikacji. Nasza baza kodu, a było jej dużo, wykorzystywana do łączenia się z serwerami, kiedy tylko zechce. Przez większość czasu było to w porządku, chyba że w sieci występowało jakieś opóźnienie. Potrzebowaliśmy rozwiązania i przeprojektowanie 10-letniej aplikacji nie było tak naprawdę na stole. Napisałem singleton CServerConnectionManager. Następnie przeszukałem kod i zastąpiłem wywołania CoCreateInstanceWithAuth identycznym wywołaniem podpisu, które wywołało moją klasę. Teraz po pierwszej próbie połączenie zostało zapisane w pamięci podręcznej, a przez resztę czasu próby „połączenia” były natychmiastowe. Niektórzy twierdzą, że singletony są złe. Mówię, że uratowali mi tyłek.

  • Podczas debugowania często bardzo przydatna jest globalna tablica obiektów. Mamy kilka zajęć, które chcielibyśmy śledzić. Wszystkie pochodzą z tej samej klasy podstawowej. Podczas tworzenia instancji wywołują tablicę obiektów singleton i rejestrują się. Gdy zostaną zniszczone, wyrejestrowują się. Mogę podejść do dowolnej maszyny, dołączyć się do procesu i stworzyć listę działających obiektów. Byłem w produkcie od ponad pół dekady i nigdy nie czułem, że kiedykolwiek potrzebujemy 2 „globalnych” tabel obiektów.

  • Mamy pewne stosunkowo złożone klasy narzędzi do analizowania składni łańcuchów, które opierają się na wyrażeniach regularnych. Klasy wyrażeń regularnych muszą zostać zainicjowane, zanim będą mogły wykonywać dopasowania. Inicjalizacja jest nieco kosztowna, ponieważ wtedy FSM jest generowany na podstawie łańcucha analizy. Jednak po tym do klasy wyrażeń regularnych można bezpiecznie uzyskać dostęp przez 100 wątków, ponieważ po zbudowaniu FSM nigdy się nie zmienia. Te klasy parsera wewnętrznie używają singletonów, aby mieć pewność, że inicjalizacja nastąpi tylko raz. To znacznie poprawiło wydajność i nigdy nie spowodowało żadnych problemów z powodu „złych singletonów”.

Powiedziawszy to wszystko, musisz pamiętać, kiedy i gdzie stosować singletony. 9 na 10 razy jest lepsze rozwiązanie i zdecydowanie powinieneś go użyć. Są jednak chwile, kiedy singleton jest absolutnie właściwym wyborem projektowym.

Następny temat ... interfejsy i klasy abstrakcyjne. Po pierwsze, jak wspomniano inni, interfejs JEST klasą abstrakcyjną, ale wykracza poza to, wymuszając, że nie ma absolutnie żadnej implementacji. W niektórych językach słowo kluczowe interfejsu jest częścią języka. W C ++ używamy po prostu klas abstrakcyjnych. Microsoft VC ++ zrobił jeden krok, aby zdefiniować to gdzieś wewnętrznie:

typedef struct interface;

... więc nadal możesz używać słowa kluczowego interfejsu (będzie nawet podświetlone jako „prawdziwe” słowo kluczowe), ale jeśli chodzi o rzeczywisty kompilator, jest to po prostu struct.

Gdzie byś tego użył? Wróćmy do mojego przykładu działającej tabeli obiektów. Powiedzmy, że klasa podstawowa ma ...

virtual void print () = 0;

Oto twoja klasa abstrakcyjna. Wszystkie klasy, które używają tabeli obiektów wykonawczych, będą pochodzić z tej samej klasy podstawowej. Klasa podstawowa zawiera wspólny kod do rejestracji / wyrejestrowania. Ale sam nigdy nie zostanie utworzony. Teraz mogę mieć klasy wyprowadzające (np. Żądania, detektory, obiekty połączenia klienta ...), każdy zaimplementuje print (), aby po dołączeniu do procesu i zapytaniu go, co się dzieje, każdy obiekt zgłosi swój własny stan.

Przykłady abstrakcyjnych klas / interfejsów są niezliczone i zdecydowanie używasz ich (lub powinieneś używać) o wiele, dużo częściej niż singletonów. Krótko mówiąc, pozwalają pisać kod, który działa z typami podstawowymi i nie jest powiązany z faktyczną implementacją. Pozwala to na modyfikację implementacji później bez konieczności zmiany zbyt dużej ilości kodu.

Oto inny przykład. Powiedzmy, że mam klasę, która implementuje program rejestrujący, CLog. Ta klasa zapisuje do pliku na dysku lokalnym. Zaczynam używać tej klasy w moim dotychczasowym 100 000 wierszy kodu. Wszędzie wokoło. Życie jest dobre, dopóki ktoś nie powie: hej, napiszmy do bazy danych zamiast pliku. Teraz tworzę nową klasę, nazwijmy ją CDbLog i zapisuję w bazie danych. Czy potrafisz sobie wyobrazić trudność przejścia przez 100 000 linii i zmiany wszystkiego z CLog na CDbLog? Alternatywnie, mógłbym mieć:

interface ILogger {
    virtual void write( const char* format, ... ) = 0;
};

class CLog : public ILogger { ... };

class CDbLog : public ILogger { ... };

class CLogFactory {
    ILogger* GetLog();
};

Jeśli cały kod używa interfejsu ILogger, wszystko, co musiałbym zmienić, to wewnętrzna implementacja CLogFactory :: GetLog (). Reszta kodu działałaby po prostu automatycznie, bez konieczności kiwnięcia palcem.

Aby uzyskać więcej informacji na temat interfejsów i dobrego projektowania OO, zdecydowanie polecam zwinne zasady, wzorce i praktyki wuja Boba w języku C # . Książka jest pełna przykładów, które wykorzystują abstrakcje i zawierają wyjaśnienia wszystkiego w prostym języku.

DXM
źródło
4

KIEDY stosowanie singletonu jest zalecane / konieczne? Czy to dobra praktyka?

Nigdy. Co gorsza, są absolutną suką, której można się pozbyć, więc popełnienie tego błędu może cię prześladować przez wiele, wiele lat.

Różnica między klasami abstrakcyjnymi a interfejsami jest absolutnie niczym w C ++. Zwykle masz interfejsy do określania niektórych zachowań klasy pochodnej, ale bez konieczności określania wszystkich z nich. Dzięki temu kod jest bardziej elastyczny, ponieważ możesz zamienić dowolną klasę, która spełnia bardziej ograniczoną specyfikację. Interfejsy w czasie wykonywania są używane, gdy potrzebujesz abstrakcji w czasie wykonywania.

DeadMG
źródło
Interfejsy są podzbiorem klas abstrakcyjnych. Interfejs jest klasą abstrakcyjną bez zdefiniowanych metod. (Klasa abstrakcyjna bez kodu).
Gort the Robot
1
@StevenBurnap: Może w innym języku.
DeadMG
4
„Interfejs” to po prostu konwencja w C ++. Kiedy go zobaczyłem, jest to klasa abstrakcyjna z wyłącznie czystymi metodami wirtualnymi i bez właściwości. Oczywiście możesz napisać dowolną starą klasę i uderzyć „I” przed nazwą.
Gort the Robot
W ten sposób spodziewałem się, że ludzie odpowiedzą na ten post. Jedno pytanie na raz. W każdym razie, dziękuję bardzo za podzielenie się swoją wiedzą. Ta społeczność jest warta zainwestowania czasu.
appoll
3

Singleton jest przydatny, gdy nie chcesz wielu kopii konkretnego obiektu, musi istnieć tylko jedna instancja tej klasy - jest używana w przypadku obiektów, które utrzymują stan globalny, muszą w jakiś sposób radzić sobie z kodem nieprzynoszącym ponownie itp.

Singleton, który ma stałą liczbę 2 lub więcej instancji, jest multitonem , pomyśl o puli połączeń z bazą danych itp.

Interfejs określa dobrze zdefiniowany interfejs API, który pomaga modelować interakcję między obiektami. W niektórych przypadkach możesz mieć grupę klas, które mają pewne wspólne funkcje - jeśli tak, zamiast duplikować je w implementacjach, możesz dodać definicje metod do interfejsu, zamieniając je w klasę abstrakcyjną .

Możesz nawet mieć klasę abstrakcyjną, w której wszystkie metody są zaimplementowane, ale oznaczysz ją jako abstrakcyjną, aby wskazać, że nie powinna być używana w obecnej postaci bez podklasy.

Uwaga: Interfejs i klasa abstrakcyjna nie są bardzo różne w świecie C ++ z wielokrotnym dziedziczeniem itp., Ale mają różne znaczenia w Javie i in.

Alok
źródło
Bardzo dobrze powiedziane! +1
jmort253
3

Jeśli przestaniesz o tym myśleć, chodzi o polimorfizm. Chcesz raz napisać fragment kodu, który może zrobić więcej niż myślisz, w zależności od tego, co go przekażesz.

Załóżmy, że mamy funkcję podobną do następującego kodu w języku Python:

function foo(objs):
    for obj in objs:
        obj.printToScreen()

class HappyWidget:
    def printToScreen(self):
        print "I am a happy widget"

class SadWidget:
    def printToScreen(self):
        print "I am a sad widget"

Zaletą tej funkcji jest to, że będzie ona w stanie obsłużyć dowolną listę obiektów, o ile obiekty te implementują metodę „printToScreen”. Możesz przekazać mu listę szczęśliwych widżetów, listę smutnych widżetów, a nawet listę zawierającą ich mieszankę, a funkcja foo nadal będzie mogła poprawnie wykonywać swoje zadania.

Odnosimy się do tego typu ograniczenia konieczności posiadania zestawu metod zaimplementowanych (w tym przypadku printToScreen) jako interfejsu, a obiekty, które implementują wszystkie metody, mają zaimplementować interfejs.

Gdybyśmy mówili o dynamicznym języku typu „kaczka”, takim jak Python, w zasadzie już byśmy skończyli. Jednak system typów statycznych C ++ wymaga, abyśmy nadali klasę obiektom w naszej funkcji i będzie on mógł pracować tylko z podklasami tej klasy początkowej.

void foo( Printable *objs[], int n){ //Please correctme if I messed up on the type signature
    for(int i=0; i<n; i++){
        objs[i]->printToScreen();
    }
}

W naszym przypadku jedynym powodem, dla którego istnieje klasa Printable, jest miejsce dla metody printToScreen. Ponieważ nie istnieje wspólna implementacja między klasami, które implementują metodę printToScreen, sensowne jest uczynienie z Printable klasy abstrakcyjnej, która jest używana tylko jako sposób grupowania podobnych klas we wspólnej hierarchii.

W C ++ koncepcje klas absctract i interfejsów są nieco rozmyte. Jeśli chcesz je lepiej zdefiniować, myślisz o klasach abstrakcyjnych, podczas gdy interfejsy zwykle oznaczają bardziej ogólną, międzyjęzykową koncepcję zestawu widocznych metod, które przedstawia obiekt. (Chociaż niektóre języki, takie jak Java, używają terminu interfejs, aby odnosić się do czegoś bardziej bezpośrednio, jak abstrakcyjna klasa podstawowa)

Zasadniczo konkretne klasy określają sposób implementacji obiektów, podczas gdy klasy abstrakcyjne określają sposób, w jaki łączą się one z resztą kodu. Aby twoje funkcje były bardziej polimorficzne, powinieneś próbować otrzymać wskaźnik do abstrakcyjnej nadklasy, ilekroć byłoby to uzasadnione.


Jeśli chodzi o Singletony, są one naprawdę bezużyteczne, ponieważ często można je zastąpić tylko grupą statycznych metod lub zwykłych starych funkcji. Czasami jednak masz jakieś ograniczenia, które zmuszają cię do korzystania z obiektu, nawet jeśli tak naprawdę nie chcesz go używać, więc tupot singletonu jest odpowiedni.


BTW, niektóre osoby mogły komentować, że słowo „interfejs” ma szczególne znaczenie w języku Java. Myślę jednak, że na razie lepiej trzymać się bardziej ogólnej definicji.

hugomg
źródło
1

Interfejsy

Trudno zrozumieć cel narzędzia, które rozwiązuje problem, którego nigdy nie miałeś. Nie rozumiałem interfejsów przez jakiś czas po rozpoczęciu programowania. Zrozumiemy, co zrobili, ale nie wiedziałem, dlaczego chcesz z nich skorzystać.

Oto problem - wiesz, co chcesz zrobić, ale masz na to wiele sposobów lub możesz zmienić sposób, w jaki to zrobisz później. Byłoby miło, gdybyś mógł wcielić się w rolę nieświadomego menedżera - wydawać zamówienia i osiągać pożądane wyniki bez dbania o to, jak to się robi.

Załóżmy, że masz małą witrynę internetową i zapisujesz wszystkie informacje o swoich użytkownikach w pliku csv. Nie jest to najbardziej wyrafinowane rozwiązanie, ale działa wystarczająco dobrze, aby przechowywać dane użytkownika twojej mamy. Później Twoja strona startuje i masz 10 000 użytkowników. Może nadszedł czas, aby użyć odpowiedniej bazy danych.

Gdybyś na początku był sprytny, zobaczyłbyś, że to nadchodzi, i nie wykonałeś połączeń, aby zapisać bezpośrednio w csv. Zamiast tego pomyślałbyś o tym, co musisz zrobić, bez względu na to, jak to zostało zaimplementowane. Powiedzmy store()i retrieve(). Państwo dokonać Persisterinterfejs z abstrakcyjnych metod store()i retrieve()i utworzyć CsvPersisterpodklasę że faktycznie implementuje te metody.

Później możesz utworzyć plik, DbPersisterktóry implementuje faktyczne przechowywanie i pobieranie danych zupełnie inaczej niż w przypadku klasy csv.

Wspaniałą rzeczą jest to, że wszystko, co musisz teraz zrobić, to zmienić

Persister* prst = new CsvPersister();

do

Persister* prst = new DbPersister();

i gotowe. Twoje wezwania do prst.store()i prst.retrieve()nadal będą działać, są po prostu inaczej przetwarzane „za kulisami”.

Teraz nadal musiałeś tworzyć implementacje cvs i db, więc nie doświadczyłeś jeszcze luksusu bycia szefem. Rzeczywiste korzyści są widoczne, gdy używasz interfejsów utworzonych przez kogoś innego. Jeśli ktoś inny był na tyle uprzejmy, aby stworzyć CsvPersister()i DbPersister()już, to wystarczy wybrać jeden i wywołać niezbędne metody. Jeśli zdecydujesz się użyć drugiego później lub w innym projekcie, już wiesz, jak to działa.

Jestem naprawdę zardzewiały na moim C ++, więc użyję tylko ogólnych przykładów programowania. Kontenery są doskonałym przykładem tego, jak interfejsy ułatwiają życie.

Możesz mieć Array, LinkedList, BinaryTree, z itp wszystkie podklasy Container, która ma metody, takie jak insert(), find(), delete().

Teraz, gdy dodajesz coś na środku połączonej listy, nie musisz nawet wiedzieć, czym jest połączona lista. Wystarczy zadzwonić, myLinkedList->insert(4)a magicznie iteruje się po liście i umieszcza ją tam. Nawet jeśli wiesz, jak działa połączona lista (co naprawdę powinieneś), nie musisz sprawdzać jej konkretnych funkcji, ponieważ prawdopodobnie już wiesz, co to za korzystanie z innej Containerwcześniej.

Klasy abstrakcyjne

Klasy abstrakcyjne są bardzo podobne do interfejsów (technicznie interfejsy klasami abstrakcyjnymi, ale tutaj mam na myśli klasy podstawowe, które mają rozwinięte niektóre z ich metod.

Załóżmy, że tworzysz grę i musisz wykryć, kiedy wrogowie znajdują się w odległości uderzenia gracza. Możesz utworzyć klasę podstawową, Enemyktóra ma metodę inRange(). Chociaż istnieje wiele różnych cech przeciwników, metoda sprawdzania ich zasięgu jest spójna. Dlatego twoja Enemyklasa będzie miała dopracowaną metodę sprawdzania zasięgu, ale czyste metody wirtualne dla innych rzeczy, które nie mają podobieństw między typami wroga.

Zaletą jest to, że jeśli zepsujesz kod wykrywania zasięgu lub chcesz go ulepszyć, musisz go zmienić tylko w jednym miejscu.

Oczywiście istnieje wiele innych powodów interfejsów i abstrakcyjnych klas bazowych, ale są to niektóre powody, dla których możesz z nich korzystać.

Singletony

Używam ich od czasu do czasu i nigdy mnie nie spalili. Nie oznacza to, że w pewnym momencie nie zrujnują mi życia na podstawie doświadczeń innych ludzi.

Oto dobra dyskusja na temat stanu globalnego od bardziej doświadczonych i ostrożnych ludzi: Dlaczego państwo globalne jest tak złe?

Dziekan
źródło
1

W królestwie zwierząt istnieją różne zwierzęta, które są ssakami. Tutaj ssak jest klasą podstawową i wywodzą się z niego różne zwierzęta.

Czy widziałeś kiedyś przechodzącego ssaka? Tak, wiele razy, jestem pewien - jednak były one wszystkie rodzaje ssaków nie byli?

Nigdy nie widziałeś czegoś, co dosłownie było tylko ssakiem. Były to wszystkie typy ssaków.

Klasa ssak jest wymagana do zdefiniowania różnych cech i grup, ale nie istnieje jako istota fizyczna.

Dlatego jest to abstrakcyjna klasa bazowa.

Jak poruszają się ssaki? Czy chodzą, pływają, latają itp.?

Nie ma sposobu, aby wiedzieć na poziomie ssaków, ale wszystkie ssaki muszą się w jakiś sposób poruszać (powiedzmy, że jest to prawo biologiczne, aby ułatwić przykład).

Dlatego MoveAround () jest funkcją wirtualną, ponieważ każdy ssak, który wywodzi się z tej klasy, musi być w stanie zaimplementować ją inaczej.

Jednak fakt, że każdy ssak MUSI zdefiniować MoveAround, ponieważ wszystkie ssaki muszą się poruszyć i nie można tego zrobić na poziomie ssaka. Musi być zaimplementowany przez wszystkie klasy potomne, ale tam nie ma znaczenia w klasie podstawowej.

Dlatego MoveAround jest czysto wirtualną funkcją.

Jeśli masz całą klasę, która zezwala na aktywność, ale nie jest w stanie zdefiniować na najwyższym poziomie, jak to zrobić, wszystkie funkcje są czysto wirtualne i jest to interfejs.
Na przykład - jeśli mamy grę, w której zakodujesz robota i prześlesz go mi do walki na polu bitwy, muszę znać nazwy funkcji i prototypy, które należy wywołać. Nie dbam o to, jak wdrażasz go po swojej stronie, dopóki „interfejs” jest wyraźny. Dlatego mogę zapewnić ci interfejs, z którego będziesz mógł napisać swojego zabójczego robota.

Stefan
źródło