Как я могу объяснить полезность Наследования? [закрыто]

16

При попытке объяснить концепцию наследования в ООП, типичным примером часто является пример млекопитающих. ИМХО, это действительно плохой пример, потому что это заставит новичков неправильно использовать эту концепцию. Более того, это не обычный дизайн, с которым они столкнутся в своей повседневной работе.

Итак, что будет хорошей, простой и конкретной проблемой, которая решается с помощью Inheritance?

Пьер Ватле
источник
1
"общий пример часто млекопитающие"? Что вы имеете в виду? Можете ли вы предоставить ссылку, ссылку или цитату для этого?
С.Лотт
3
Что может быть лучшим реальным примером, чтобы объяснить полезность Inheritance = детский доверительный фонд Билла Гейтса?
Мартин Беккет
1
@ Крис: как этот вопрос не является конструктивным? Вы заявляете, что один спрашивающий и 14 ответчиков напрасно тратят время?
Дан Даскалеску
@DanDascalescu - это было закрыто два года назад в ответ на флаг, заявляющий, что «пожалуйста, рассмотрите закрытие как неконструктивное: судя по нагромождениям ответов, это похоже на типичный вопрос списка / опроса». Если вы считаете, что это неправильно, отредактируйте его, чтобы было ясно, что это не так, и пусть сообщество решит вопрос через очередь повторного открытия.
ChrisF

Ответы:

15

Нет ничего плохого в чисто академическом примере, подобном млекопитающим. Мне также нравится пример прямоугольника / квадрата, потому что он указывает, почему реальные таксономии не всегда напрямую переводятся в ожидаемые отношения наследования.

На мой взгляд, наиболее каноническим примером на каждый день является GUI инструментарий. Это то, что все использовали, но новички, возможно, не рассуждали о том, как они работают под капотом. Вы можете говорить о том, какое поведение является общим для всех контейнеров, всех виджетов, событий и т. Д., Не требуя детального знания какой-либо конкретной реализации.

Карл Билефельдт
источник
5
+1 для наборов инструментов GUI ... и мне также нравится пример форм, с одним Shape в качестве основы с минимальным draw () и потомками с настраиваемыми draw () s.
Яти Сагаде
14

Мой реальный пример - доменная модель простого HR-приложения. Я говорю, что мы можем создать базовый класс с именем Employee , потому что, конечно, менеджеры тоже являются сотрудниками.

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.
    }
}

Затем я объясняю, что разработчики - это сотрудники , тестеры - это сотрудники , руководители проектов - сотрудники . Таким образом, они все могут наследовать от класса работников.

Саид Нямати
источник
2
Чтобы продемонстрировать преимущества наследования, было бы также интересно показать, как разработчики отличаются от сотрудников. Если нет никакой разницы, тогда вообще не нужно создавать класс Developer.
Дэвид
3
Кажется, что Employeeи abstractкласс тоже может быть .
StuperUser
+1 это пример, который использовал большинство моих учителей, и мне очень понравилось. это имело полный смысл и дало реальный пример того, как использовать наследование.
Дэвид Петерман
18
За исключением того, что это никогда не работает на практике, потому что всегда есть по крайней мере один человек, который должен быть и a, Developerи a Tester. Другая похожая ситуация - это база данных контактов, где у вас есть Customerи Supplier, но, как скажет вам любой, кто создал такую ​​систему, всегда есть случай, когда a Company- это то и другое. Вот почему большинство этих примеров ведут вас в неправильном направлении.
Скотт Уитлок
11

Инкапсулируйте то, что меняется ... покажите им шаблон шаблонного метода , он демонстрирует полезность наследования, помещая общее поведение в базовый класс и инкапсулируя различное поведение в подклассы.

UI controlsи Streamsтакже являются очень хорошим примером полезности наследования.

сокол
источник
Я думаю, что фабрика была бы лучшим примером.
Let_Me_Be
1
@Let_Me_Be: я думаю, что отношения между фабрикой и наследованием слишком косвенны по своей природе. Конечно, он генерирует конкретные типы и возвращает абстрактные / базовые типы, но он также может возвращать только интерфейсный тип! Имхо, это не лучше, чем классический пример с животными.
Сокол
@Let_Me_Be: Кроме того, абстрактная фабрика является довольно сложным примером, включающим различные иерархии наследования (одна для элементов, другая для фабрик). Я думаю, что это хорошее использование наследования, но не хороший и простой пример.
Сокол
3

Помнить

Каждый экземпляр объекта является конкретным примером полезности наследования!

Если вы имеете в виду именно наследование классов , то теперь вы находитесь в мире таксономий, и они будут сильно различаться в зависимости от целей системы, которая их использует. В примере с животными / млекопитающими используется общая и, как мы надеемся, знакомая таксономия из биологии, но она (как вы упомянули) почти бесполезна для подавляющего большинства задач программирования.

Так что попробуйте что-нибудь универсальное: понятие программы. Каждая программа запускается, запускается и заканчивается. У каждой программы есть имя и необязательные параметры командной строки. Таким образом, базовый класс Program был бы очень полезен для запуска выполнения, сбора и обработки аргументов командной строки, запуска основной логики и корректного завершения работы.

Вот почему так много объектно-ориентированных языков программирования предоставляют класс Program или что-то, что ведет себя точно так же, как класс Program.

Стивен А. Лоу
источник
Итак, вы программируете программу, в которой много программ? :) По моему опыту, объекты Programm почти всегда являются одиночными, которые не имеют наследования, поэтому imho они не лучший пример.
Keppla
@keppla: вы когда-нибудь использовали Java или .NET? .NET имеет явный класс Program, Java неявный. Они не одиночки
Стивен А. Лоу
Я использовал Java, около версии 1.4.2. В то время существовал только статический void main, поэтому я думаю, что он немного изменился. Какова типичная причина иметь более одного экземпляра класса Programm?
Кеппла
@keppla: статическая пустота main в java неявно заставляет входной класс представлять программу. Каждый пользователь, который запускает вашу программу, создает ее новый экземпляр. На данный момент у меня работает три экземпляра Google Chrome, четыре документа Word, три блокнота и два проводника Windows. Если бы они все были одиночками, я бы никогда не смог этого сделать.
Стивен А. Лоу
1
Я думаю, что вы немного растягиваете определение. class Programm { public static void main(String[] args) { system.out.println('hello world'); }}минимальная Java-программа Когда я звоню, нет экземпляра Программы. Программа не наследует ни от чего. Когда я запускаю 3 процесса (как вы делаете с crhome), может быть 3 программы, но в их отдельных областях памяти остается только одна программа. Имхо, синглтон подразумевает «только один экземпляр на процесс», а не на машину. Если это так, было бы невозможно создавать синглтоны, ничто не мешает вам выполнить любой код дважды.
Кеппла
3

Я работаю с камерами на работе. У нас есть устройства, которые подключаются к разным моделям, поэтому у нас есть абстрактный «класс камеры», и каждая модель наследуется от этого класса для поддержки определенных функций этой камеры. Это пример из реального мира, и его нетрудно понять.

Лукас
источник
2
Это может сломаться, если у вас есть, например, модель, которая одновременно является Cameraи a Phone(как мы сейчас делаем в наших карманах). От какого базового класса он должен наследоваться? Или она не должна просто реализовать оба ICameraи IPhoneинтерфейсы? (ха-ха)
Скотт Уитлок
2
@ Скотт: Вы не можете реализовать интерфейс IPhone, или Apple подаст на вас в суд.
Мейсон Уилер
3

Пример Химических Элементов

Это еще один пример из моего мозга:

класс Element_
{
    двойной атомный вес; // Атомный вес элемента
    двойной атомный номер; // Атомный номер элемента
    Свойства строки; // Свойства элемента
    // Другие, если есть
}


Изотоп класса расширяет Element_ // Изотопы элемента могут существовать
{
    двойной период полураспада;
   // Другие, если есть

}
отметка
источник
2
Хотя atomicNumber может (должен?) Быть целым числом ...
Эндрю
Я бы не использовал наследство для этого. isotopeне частный случай Elemenet. Я предпочел бы иметь Elementсобственность на Isotope.
CodesInChaos
2

Реальные примеры почти всегда ошибаются, потому что они приводят примеры, где всегда есть вероятность того, что что-то есть TypeAи то, и другое, TypeBно иерархия единого наследования во многих языках не позволяет этого.

Чем больше я программирую, тем больше ухожу от наследства.

Даже слово «наследовать» здесь используется неправильно. Например, вы наследуете около 50% черт вашего отца и 50% черт вашей матери. На самом деле ваша ДНК - это составная часть половины ДНК вашего отца и половины ДНК вашей матери. Это потому, что биология на самом деле предпочитает композицию наследованию , и вы тоже должны.

Простая реализация интерфейсов, или, что еще лучше, «типизирование утки», плюс внедрение зависимостей, - гораздо лучшая вещь для обучения людей, плохо знакомых с объектно-ориентированным программированием.

Скотт Уитлок
источник
1

Я бы просто показал им пример из жизни. Например, в большинстве сред пользовательского интерфейса вы создаете свой собственный класс "Dialog", "Window" или "Control".

Неманья Трифунович
источник
1

Хорошим примером является функция сравнения в сортировке:

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);

Единственная проблема в том, что новички слишком часто думают, что производительность важнее хорошего кода ...

ТР1
источник
0

Мой пример из реального мира - это транспортное средство:

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)
    {

    }
}

Этот пример может быть настолько подробным, насколько вам угодно, и к транспортным средствам прикреплены всевозможные свойства, объясняющие использование любых модификаторов, которым вы, возможно, захотите научить.

Джоэл Этертон
источник
2
Я всегда ненавидел использование транспортных средств в качестве примера, поскольку новый программист не приближается к пониманию того, как наследование можно использовать для улучшения кода. Транспортные средства - это чрезвычайно сложные машины, которые вызывают в памяти множество неабстрактных идей в уме непрограммиста. Попытка описать это в коде заставляет рядового новичка поверить в то, что в примере не учитывается много деталей, и создает ощущение, что они не приблизились к тому, чтобы заставить что-то работать. Я говорю это на собственном опыте, поскольку именно так я себя чувствовал, когда кто-то пытался использовать машины, чтобы объяснить это мне.
Rewalk
@ Stargazer712: Я использую транспортные средства в первую очередь потому, что они могут быть настолько сложными или простыми, насколько вам нравится. Я оставляю это на усмотрение инструктора, чтобы определить уровень его ученика. Я объяснил базовый ООП моей жене (у которой нет опыта программирования), используя простые свойства транспортного средства, описывающие общие основы. У всех транспортных средств есть двери, у всех транспортных средств есть колеса, и т. Д. Пример объекта не может быть обвинен в плохом плане урока.
Джоэл Этертон
твоя жена не пыталась писать код Я могу очень смело сказать , что примеры транспортных средств не сделали ничего , чтобы помочь мне понять наследство. Что бы вы ни использовали для описания наследования, оно должно быть полным и практичным . Цель не в том, чтобы описать это так, чтобы непрограммист мог понять это. Цель состоит в том, чтобы описать его таким образом, чтобы его мог использовать начинающий программист , и единственный способ сделать это - показать новичкам примеры того, как его может использовать профессиональный программист.
Rewalk
@ Stargazer712: Я бы поставил вашу неспособность изначально понять наследование на плохой план урока. Я также использовал транспортные средства, чтобы объяснить наследование юниорам, с которыми я работаю, и у меня никогда не было проблем с этой концепцией. На мой взгляд, если план урока тщательный и правильно составлен, объект транспортного средства является одновременно и полным, и практичным. Один случайный парень в Интернете не изменит этого перед лицом примерно тридцати молодых специалистов и молодых разработчиков, которым я учил ООП. Если вам не нравится автомобиль, проголосуйте вниз и двигайтесь дальше.
Джоэл Этертон
Как пожелаешь ....
riwalk
0

Этот пример не-млекопитающих, не-птиц, не-рыб может помочь:

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 */
}

потом

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());
    }       

}

ПРИМЕЧАНИЕ: только не говорите секрет: человек расширяет Млекопитающее.

Тулаинс Кордова
источник
1
Работает до тех пор, пока один из ваших сотрудников не станет пациентом.
Скотт Уитлок
В этом случае, мне кажется, имеет смысл объявить Patient и Employee как интерфейсы, а не абстрактные классы. Это дает вам гибкость, когда Person реализует несколько интерфейсов.
Джин Ким
@JinKim - Я полностью согласен, это лучший подход.
Скотт Уитлок
@JinKim Они не являются эксклюзивными. Вы рассматриваете человека как сотрудника или как пациента в любой момент времени, но не одновременно. Два интерфейса - это нормально, но когда вы называете конкретный класс, реализующий оба, EmployeePatient? Сколько комбинаций у вас будет?
Тулаинс Кордова
Вы можете называть конкретный класс как хотите. Если в коде предполагается, что он имеет дело только с сотрудниками, вы объявляете ссылку как сотрудник. (т. е. Сотрудник сотрудника = new Person ();) Если код ожидает только взаимодействия с пациентом, вы объявляете ссылку как пациента. Редко вы хотите объявить ссылку непосредственно как конкретный класс.
Джин Ким
0

Как насчет иерархии алгебраических выражений. Это хорошо, потому что включает в себя как наследование, так и состав:

+--------------------+------------------------+
| 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

За исключением корневого выражения Constant, все остальные выражения являются выражениями и содержат одно или несколько выражений.

edalorzo
источник
-1

Я буду использовать птиц в качестве примера

как курица, утка, орел

Я объясню, у обоих есть когти, клевки и крылья, но их атрибуты разные.

Цыплята не могут летать, не умеют плавать, могут есть червей, могут есть зерно

Утки не могут летать, могут плавать, могут есть зерно, не могут есть червей

Орел может летать, не умеет плавать, может есть червей, не может есть зерно

Раджу твой Пепе
источник
4
Однажды я прочитал, что композиция и интерфейсы, вероятно, являются лучшим способом передать концепцию такого типа, например, летать, плавать и т. Д.
dreza
Утка не может летать ?!
Адам Кэмерон
-3

Ваш типичный rails-clone предоставляет много практических примеров : у вас есть (абстрактный) класс базовой модели, который инкапсулирует все манипуляции с данными, и у вас есть базовый класс контроллера, который инкапсулирует все HTTP-коммуникации.

keppla
источник
Хотите уточнить, почему этот ответ плохой?
keppla