Jak mogę wyjaśnić przydatność dziedziczenia? [Zamknięte]

16

Próbując wyjaśnić pojęcie dziedziczenia w OOP, częstym przykładem jest często przykład ssaków. IMHO, to naprawdę zły przykład, ponieważ doprowadzi początkujących do niewłaściwego korzystania z tej koncepcji. Co więcej, nie jest to powszechny projekt, z którym będą mieli do czynienia w codziennej pracy projektowej.

Więc jaki będzie ładny, prosty i konkretny problem, który zostanie rozwiązany za pomocą Dziedziczenia?

Pierre Watelet
źródło
1
„częstym przykładem są często ssaki”? Co masz na myśli? Czy możesz podać link, odniesienie lub ofertę?
S.Lott,
3
Jaki byłby najlepszy prawdziwy przykład na wyjaśnienie przydatności funduszu powierniczego dla dzieci Billa Gatesa?
Martin Beckett,
1
@Chris: jak to pytanie nie jest konstruktywne? Czy deklarujesz, że jeden pytający i 14 osób odpowiadających marnuje czas wszystkich?
Dan Dascalescu,
@DanDascalescu - został zamknięty dwa lata temu w odpowiedzi na flagę stwierdzającą: „rozważ zamknięcie jako nie konstruktywne: sądząc po odpowiedziach, wygląda to jak typowe pytanie o listę / ankietę”. Jeśli uważasz, że to źle, edytuj to, aby było jasne, że tak nie jest i pozwól społeczności podjąć decyzję za pośrednictwem kolejki recenzji.
ChrisF

Odpowiedzi:

15

Nie ma nic złego w czysto akademickim przykładzie, takim jak ssaki. Podoba mi się również przykład w kształcie prostokąta / kwadratu, ponieważ pokazuje, dlaczego taksonomie w świecie rzeczywistym nie zawsze bezpośrednio przekładają się na oczekiwane relacje dziedziczenia.

Moim zdaniem najbardziej kanonicznym przykładem każdego dnia jest zestaw narzędzi GUI. Jest to coś, z czego wszyscy korzystali, ale początkujący może nie rozumieli, jak działają pod maską. Możesz porozmawiać o tym, jakie zachowania są wspólne dla wszystkich kontenerów, wszystkich widżetów, zdarzeń itp. Bez konieczności szczegółowej wiedzy na temat dowolnej implementacji.

Karl Bielefeldt
źródło
5
+1 dla zestawów narzędzi GUI ... i podoba mi się również przykład kształtów, z jednym Kształtem jako podstawą z minimalnym rysunkiem () i kształtami potomnymi z niestandardowymi rysunkami ().
yati sagade
14

Moim prawdziwym przykładem jest model domeny prostej aplikacji HR. Mówię, że możemy stworzyć klasę podstawową o nazwie Pracownik , ponieważ oczywiście menedżerowie też są pracownikami.

public class Employee
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public int Code { get; set; }

    public string GetInsuranceHistory()
    {
        // Retrieving insurance history based on employee code.
    }
}

Następnie wyjaśniam, że programiści to pracownicy , testerzy to pracownicy , kierownicy projektów to pracownicy . W ten sposób wszyscy mogą dziedziczyć po klasie pracowników.

Saeed Neamati
źródło
2
Aby pokazać zalety dziedziczenia, interesujące może być również pokazanie, jak programiści różnią się od pracowników. Jeśli nie ma różnicy, nie ma potrzeby tworzenia klasy programisty.
David
3
Wydaje się, że klasa też Employeemoże być abstract.
StuperUser
+1 to przykład, z którego korzystała większość moich nauczycieli i bardzo mi się podobało. miało to pełny sens i dało prawdziwy przykład na to, jak korzystać z dziedziczenia.
David Peterman,
18
Tyle że to nigdy nie działa w praktyce, ponieważ zawsze jest co najmniej jedna osoba, która musi być zarówno a, jak Developeri a Tester. Inną podobną sytuacją jest baza danych kontaktów, w której masz Customeri Supplier, ale jak każdy, kto stworzył taki system, powie ci, zawsze istnieje przypadek, w którym jedno i Companydrugie. Dlatego większość z tych przykładów prowadzi cię w złym kierunku.
Scott Whitlock,
11

Hermetyzuj to, co się różni ... pokaż im wzorzec metody szablonu , demonstruje on przydatność dziedziczenia poprzez umieszczenie wspólnego zachowania w klasie bazowej i hermetyzowanie różnych zachowań w podklasach.

UI controlsi Streamssą również bardzo dobrym przykładem przydatności dziedziczenia.

Sokół
źródło
Myślę, że fabryka byłaby lepszym przykładem.
Let_Me_Be
1
@Let_Me_Be: Myślę, że związek między fabryką a spadkiem ma zbyt pośredni charakter. Jasne, produkuje konkretne typy i zwraca typy abstrakcyjne / podstawowe, ale może również zwrócić tylko typ interfejsu! Imho nie jest lepsze niż klasyczny przykład zwierząt.
Falcon
@Let_Me_Be: Również abstrakcyjna fabryka jest dość złożonym przykładem obejmującym różne hierarchie dziedziczenia (jedna dla towarów, druga dla fabryk). Myślę, że jest to dobre wykorzystanie dziedziczenia, ale nie jest to dobry i prosty przykład.
Falcon
3

Zapamiętaj

Każde wystąpienie obiektu jest konkretnym przykładem przydatności dziedziczenia!

Jeśli masz na myśli konkretnie klasę dziedziczenie , teraz znajdujesz się w świecie taksonomii, które różnią się drastycznie od celów systemu, który ich używa. Przykład zwierząt / ssaków wykorzystuje wspólną i miejmy nadzieję znaną taksonomię z biologii, ale jest (jak wspomniałeś) prawie bezużyteczny w przypadku większości problemów programistycznych.

Spróbuj więc czegoś uniwersalnego: pojęcia programu. Każdy program uruchamia się, uruchamia i kończy. Każdy program ma nazwę i opcjonalne parametry wiersza polecenia. Podstawowa klasa programu byłaby więc bardzo przydatna, aby rozpocząć wykonywanie, przechwytywać i przetwarzać argumenty wiersza poleceń, uruchamiać główną logikę i wdzięcznie zamykać.

Dlatego tak wiele obiektowych języków programowania zapewnia klasę Program lub coś, co zachowuje się dokładnie jak klasa Program.

Steven A. Lowe
źródło
Więc programujesz program, który ma wiele programów? :) Z mojego doświadczenia wynika, że ​​Obiekty Programm prawie zawsze są singletonami, które nie mają dziedziczenia, więc imho nie są najlepszym przykładem.
keppla
@keppla: czy kiedykolwiek korzystałeś z Javy lub .NET? .NET ma jawną klasę Program, Java jest niejawna. Nie są singletonami
Steven A. Lowe
korzystałem z Javy w wersji 1.4.2. Wtedy istniała tylko statyczna pustka główna, więc myślę, że trochę się zmieniła. Jaki byłby typowy powód posiadania więcej niż jednej instancji klasy Programm?
keppla,
@keppla: static void main java domyślnie sprawia, że ​​klasa wejściowa reprezentuje program. Każdy użytkownik, który uruchamia Twój program, tworzy nową jego instancję. W tej chwili mam trzy wystąpienia Google Chrome, cztery dokumenty Word, trzy Notatniki i dwa Eksploratory Windows. Gdyby wszyscy byli singletonami, nigdy bym tego nie zrobił.
Steven A. Lowe
1
myślę, że trochę rozszerzasz definicję. class Programm { public static void main(String[] args) { system.out.println('hello world'); }}to minimalny program Java. Kiedy go nazywam, nie ma wystąpienia programu. Program nie dziedziczy po niczym. Kiedy uruchamiam 3 procesy (tak jak robisz to z crhome), mogą istnieć 3 programy, ale w ich poszczególnych obszarach pamięci wciąż jest tylko jeden program. Imho, singleton oznacza „Tylko jedną instancję na proces”, a nie na maszynę. Jeśli tak, to nie byłoby możliwe tworzenie singletonów, nic nie stoi na przeszkodzie, aby uruchomić kod dwukrotnie.
keppla,
3

Pracuję z aparatami w pracy. Mamy urządzenia, które łączą się z różnymi modelami, więc mamy abstrakcyjną „klasę kamery”, a każdy model dziedziczy z tej klasy, aby obsługiwać określone funkcje tej kamery. To przykład z prawdziwego świata i nietrudny do zrozumienia.

Lucas
źródło
2
To może się zepsuć, jeśli masz, np. Model, który jest zarówno a Camerai a Phone(jak wszyscy teraz robimy w naszych kieszeniach). Z jakiej klasy podstawowej powinien dziedziczyć? A może nie powinien po prostu implementować zarówno interfejsu, jak ICamerai IPhoneinterfejsów? (ha ha)
Scott Whitlock
2
@Scott: Nie możesz wdrożyć interfejsu IPhone, inaczej zostaniesz pozwany przez Apple.
Mason Wheeler,
3

Przykład pierwiastków chemicznych

Oto kolejny przykład, który wyskoczył mi z mózgu:

klasa Element_
{
    podwójna masa atomowa; // Masa atomowa pierwiastka
    double atomicNumber; // Liczba atomowa elementu
    Właściwości łańcucha; // Właściwości elementu
    // Inne, jeśli istnieją
}


klasa Izotop rozszerza Element_ // Mogą istnieć izotopy elementu
{
    podwójny okres półtrwania;
   // Inne, jeśli istnieją

}
znak
źródło
2
Chociaż atomicNumber może (powinien?) Prawdopodobnie być liczbą całkowitą ...
Andrew
Nie użyłbym do tego dziedziczenia. isotopenie jest szczególnym przypadkiem Elemenet. Wolałbym mieć Elementwłasność na Isotope.
CodesInChaos
2

Przykłady ze świata rzeczywistego prawie zawsze źle to rozumieją, ponieważ podają przykłady, w których zawsze istnieje możliwość, że coś jest jednocześnie, TypeAa TypeBjedna hierarchia dziedziczenia wielu języków nie pozwala na to.

Im więcej programuję, tym bardziej uciekam od dziedziczenia.

Nawet słowo „dziedziczenie” jest tu używane niewłaściwie. Na przykład dziedziczysz około 50% cech ojca i 50% cech matki. Naprawdę twoje DNA składa się z połowy DNA twojego ojca i połowy DNA twojej matki. To dlatego, że biologia w rzeczywistości preferuje kompozycję nad dziedziczeniem , a ty też powinieneś.

Po prostu implementacja interfejsów, a nawet lepiej, „pisanie kaczych znaków” oraz wstrzykiwanie zależności, jest o wiele lepszą rzeczą, aby uczyć ludzi, którzy są nowicjuszami w programowaniu obiektowym.

Scott Whitlock
źródło
1

Po prostu pokażę im przykład z życia. Na przykład, w większości frameworków interfejsu użytkownika wywodzisz się z pewnego rodzaju klasy „Dialog”, „Window” lub „Control”, aby stworzyć własną.

Nemanja Trifunovic
źródło
1

Dobrym przykładem jest funkcja porównania w sortowaniu:

template<class T>
class CompareInterface {
public:
   virtual bool Compare(T t1, T t2) const=0;
};
class FloatCompare : public CompareInterface<float> { };
class CompareImplementation : public FloatCompare {
public:
   bool Compare(float t1, float t2) const { return t1<t2; }
};
template<class T>
void Sort(T*array, int size, CompareInterface<T> &compare);

Jedynym problemem jest to, że początkujący zbyt często uważają, że wydajność jest ważniejsza niż dobry kod ...

tp1
źródło
0

Moim prawdziwym przykładem jest pojazd:

public class Vehicle
{
    public Vehicle(int doors, int wheels)
    {
        // I describe things that should be
        // established and "unchangeable" 
        // when the class is first "made"
        NumberOfDoors = doors;
        NumberOfWheels = wheels;
    }

    public void RollWindowsUp()
    {
        WindowsUp = true;
    }

    // I cover modifiers on properties to show
    // how to protect certain things from being
    // overridden
    public int NumberOfDoors { get; private set; }
    public int NumberOfWheels { get; private set; }

    public string Color { get; set; }
    public bool WindowsUp { get; set; }
    public int Speed { get; set; }
}

public class Car : Vehicle
{
    public Car : base(4, 4)
    {

    }
}

public class SemiTruck : Vehicle
{
    public SemiTruck : base(2, 18)
    {

    }
}

Ten przykład może być tak szczegółowy, jak tylko chcesz, a do pojazdów dołączone są różnego rodzaju właściwości wyjaśniające użycie dowolnych modyfikatorów, których możesz chcieć nauczyć.

Joel Etherton
źródło
2
Zawsze nienawidziłem używania pojazdów jako przykładu, ponieważ nowy programista nie przybliża się do zrozumienia, w jaki sposób można wykorzystać dziedziczenie do ulepszenia kodu. Pojazdy to niezwykle skomplikowane maszyny, które przywołują na myśl wiele nieabstrakcyjnych pomysłów w umyśle nieprogramisty. Próba opisania tego w kodzie sprawia, że ​​przeciętny początkujący uważa, że ​​pominięto wiele szczegółów z tego przykładu i daje wrażenie, że nie są oni bliżsi znalezienia czegoś do pracy. Mówię to z doświadczenia, ponieważ tak właśnie czułem się, gdy ktoś próbował użyć pojazdów, aby mi to wyjaśnić.
riwalk
@ Stargazer712: Używam pojazdów głównie dlatego, że mogą być tak skomplikowane lub tak proste, jak tylko chcesz. Decyzję instruktora pozostawiam do ustalenia poziomu jego ucznia. Wyjaśniłem podstawowy OOP mojej żonie (która nie ma doświadczenia w programowaniu), używając prostych właściwości pojazdu opisującego wspólne podstawy. Wszystkie pojazdy mają drzwi, wszystkie pojazdy mają koła itp. Przykładu obiektu nie można winić za zły plan lekcji.
Joel Etherton,
twoja żona nie próbowała pisać kodu. Mogę bardzo bezpiecznie powiedzieć, że przykłady pojazdów nie zrobiły nic, co pomogłoby mi zrozumieć dziedziczenie. Cokolwiek użyjesz do opisania spadku, musi ono być kompletne i praktyczne . Celem nie jest opisanie tego w taki sposób, aby nie-programista mógł to zrozumieć. Celem jest opisanie tego w taki sposób, aby początkujący programista mógł z niego korzystać , a jedynym sposobem, aby to zrobić, jest pokazanie początkującym przykładom, w jaki sposób mógłby go używać profesjonalny programista.
riwalk
@ Stargazer712: Odłożyłbym twoją niezdolność do początkowego zrozumienia dziedziczenia na złym planie lekcji. Użyłem również pojazdów, aby wyjaśnić dziedziczenie młodszym, z którymi pracuję, i nigdy nie miałem problemu z napotkaniem tej koncepcji. Moim zdaniem, jeśli plan lekcji jest dokładny i właściwie skonstruowany, obiekt pojazdu jest zarówno kompletny, jak i praktyczny. 1 przypadkowy facet w Internecie nie zmieni tego w obliczu około 30 stażystów i młodszych deweloperów, których uczyłem OOP. Jeśli pojazd ci się nie podoba, zmniejsz głos i przejdź dalej.
Joel Etherton,
Jak sobie życzysz ....
riwalk
0

Ten przykład, który nie jest ssakiem, ptakiem i ptakiem, może nie pomóc:

public abstract class Person {

    /* this contains thing all persons have, like name, gender, home addr, etc. */

    public Object getHomeAddr() { ... }

    public Person getName() { ... }

}

public class Employee extends Person{

    /* It adds things like date of contract, salary, position, etc */

    public Object getAccount() { ... }

}

public abstract class Patient extends Person {
    /* It adds things like medical history, etc */
}

Następnie

public static void main(String[] args) {

    /* you can send Xmas cards to patients and employees home addresses */

    List<Person> employeesAndPatients = Factory.getListOfEmployeesAndPatients();

    for (Person p: employeesAndPatients){
        sendXmasCard(p.getName(),p.getHomeAddr());
    }

    /* or you can proccess payment to employees */

    List<Employee> employees = Factory.getListOfEmployees();

    for (Employee e: employees){
        proccessPayment(e.getName(),e.getAccount());
    }       

}

UWAGA: Po prostu nie zdradzaj tajemnicy: Osoba rozszerza Ssak.

Tulains Córdova
źródło
1
Działa, dopóki jeden z Twoich pracowników nie będzie również pacjentem.
Scott Whitlock,
W tym przypadku myślę, że bardziej sensowne jest zadeklarowanie Pacjenta i Pracownika jako interfejs zamiast klas abstrakcyjnych. Daje to elastyczność, dzięki której osoba może implementować wiele interfejsów.
Jin Kim
@ JinKim - całkowicie się zgadzam, to jest lepsze podejście.
Scott Whitlock,
@JinKim Nie są wyłączne. W danym momencie traktujesz osobę jako pracownika lub pacjenta, ale nie w tym samym czasie. Dwa interfejsy są OK, ale kiedy nazywacie konkretną klasę implementującą oba, EmployeePatient? Ile masz kombinacji?
Tulains Córdova
Możesz nazwać konkretną klasę, jak chcesz. Jeśli kod przewiduje tylko kontakt z pracownikami, deklarujesz referencję jako pracownika. (tj. pracownik pracownika = nowa osoba ();) Jeśli kod przewiduje tylko kontakt z pacjentem, deklarujesz referencję jako pacjent. Rzadko chcesz zadeklarować odwołanie bezpośrednio jako konkretną klasę.
Jin Kim
0

A może hierarchia wyrażeń algebraicznych. Jest dobry, ponieważ obejmuje zarówno dziedziczenie, jak i kompozycję:

+--------------------+------------------------+
| Expression         |<------------------+    |
+--------------------+----------+        |    |
| + evaluate(): int  |<---+     |        |    |
+--------------------+    |     |        |    |
          ^               |     |        |    |
          |               |     |        |    |
   +--------------+  +---------------+  +-------------+  ...
   | Constant     |  | Negation      |  | Addition    |
   +--------------+  +---------------+  +-------------+
   | -value: int  |  |               |  |             |
   +--------------+  +---------------+  +-------------+
   | +evaluate()  |  | +evaluate()   |  | +evaluate() |
   | +toString()  |  | +toString()   |  | +toString() |
   +--------------+  +---------------+  +-------------+

   Addition(Constant(5), Negation(Addition(Constant(3),Constant(2))))
   (5 + -(3 + 2)) = 0

Z wyjątkiem wyrażenia głównego Constant wszystkie pozostałe wyrażenia są zarówno Wyrażeniami, jak i zawierają jedno lub więcej wyrażeń.

edalorzo
źródło
-1

Wykorzystam ptaki jako przykład

jak kurczak, kaczka, orzeł

Wyjaśnię, że oba mają pazury, dziobaki i skrzydła, ale ich atrybuty są różne.

Kurczaki nie potrafią latać, nie potrafią pływać, mogą jeść robaków, mogą jeść ziarna

Kaczki nie mogą latać, mogą pływać, mogą jeść ziarna, nie mogą jeść robaków

Orzeł potrafi latać, nie umie pływać, może jeść robaki, nie może jeść zbóż

Raju yourPepe
źródło
4
Kiedyś przeczytałem, że kompozycja i interfejsy są prawdopodobnie najlepszym sposobem na przekazanie tego rodzaju koncepcji, tj.
Latanie
Kaczka nie może latać ?!
Adam Cameron,
-3

Typowy klon-szyna zawiera wiele praktycznych przykładów : masz (abstrakcyjną) klasę modelu bazowego, która zawiera wszystkie operacje na danych i masz klasę bazowego kontrolera, która obejmuje całą komunikację HTTP.

keppla
źródło
Chcesz wyjaśnić, dlaczego ta odpowiedź jest zła?
keppla