И так представим, что у нас есть некое абстрактное оружие. У оружия есть интерфейс взаимодействия – стрелять. Некая функция-глагол.
Когда мы говорим оружию у нас в руках стрелять, оно стреляет. Но есть разные реализации оружия: дробовик – осыпает врагов дробью, а пистолет делает точные и слабые выстрелы, и при этом у него не кончаются патроны.
В таком случае у нас есть абстракция – оружие. Эту абстракцию ещё можно назвать общим случаем. И есть реализации, более частные случаи: дробовик и пистолет.
Само по себе обобщение не всегда нужно, но даже при описании такой игры мы будем прибегать к описанию взаимодействия с абстрактными оружием, не повторяя одно и тоже для каждого частного случая. Но такое обобщение необходимо в таких строгих системах типов, как в C#.
Давайте представим пример, что игроку в руку попало не оружие, а предмет другого типа, например, противник с мечом. Будет ли такая операция ошибочной в нашей системе? А что будет, если игрок попробует стрельнуть таким “оружием”? Будет баг. Сложно сказать, что произойдёт, но система выйдет из планируемого поведения, и посыпаться может что угодно.
Соответственно, это было бы возможно, если бы у нас не было формально закреплённой абстракции, и наш язык позволял присваивать в некое поле “ТекущееОружие” объект любого типа. Но так нам делать нельзя.
Но с другой стороны, слишком строгий контроль не позволял бы нам делать более общим “ТекущееОружие”, и присваивать туда то объект типа “Пистолет”, то типа “Дробовик”.
Решение и становится полиморфизмом подтипов. Когда некий тип A формирует контракт и либо частично его реализует (в т.ч. и не реализует вообще), либо позволяет переопределить реализацию, и есть некий тип B, который реализует не реализованное, либо переопределяет уже реализованное.
В нашем примере, тип A – это некий тип “Оружие”, который определяет общий интерфейс взаимодействия со всем оружием. А тип B – это, например, тип “Пистолет”, который реализует этот интерфейс, т.е. какие-то конкретные действия при абстрактном “выстреле”.