Введение в ООП с примерами на C#. Часть первая. Все, что нужно знать о полиморфизме

Рассказывает Akhil Mittal


Я много писал на смежные темы, вроде концепции MVC, Entity Framework, паттерна «Репозиторий» и т.п. Моим приоритетом всегда было полное раскрытие темы, чтобы читателю не приходилось гуглить недостающие детали. Этот цикл статей опишет абсолютно все концепции ООП, которые могут интересовать начинающих разработчиков. Однако эта статья предназначена не только для тех, кто начинает свой путь в программировании: она написана и для опытных программистов, которым может потребоваться освежить свои знания.

Сразу скажу, далеко в теорию мы вдаваться не будем — нас интересуют специфичные вопросы. Где это будет нужно, я буду сопровождать повествование кодом на C#.

Что такое ООП и в чём его плюсы?

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

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

Что за концепции ООП?

Сейчас коротко о принципах, которые мы позже рассмотрим в подробностях:

  • Абстракция данных: подробности внутренней логики скрыты от конечного пользователя. Пользователю не нужно знать, как работает те или иные классы и методы, чтоб их использовать. Подходящим примером из реальной жизни будет велосипед — когда мы ездим на нём или меняем деталь, нам не нужно знать, как педаль приводит его в движение или как закреплена цепь.
  • Наследование: самый популярный принцип ООП. Наследование делает возможным повторное использование кода — если какой-то класс уже имеет какую-то логику и функции, нам не нужно переписывать всё это заново для создания нового класса, мы можем просто включить старый класс в новый, целиком.
  • Инкапсуляция: включение в класс объектов другого класса, вопросы доступа к ним, их видимости.
  • Полиморфизм: «поли» значит «много», а «морфизм» — «изменение» или «вариативность», таким образом, «полиморфизм» — это свойство одних и тех же объектов и методов принимать разные формы.
  • Обмен сообщениями: способность одних объектов вызывать методы других объектов, передавая им управление.

Ладно, тут мы коснулись большого количества теории, настало время действовать. Я надеюсь, это будет интересно.

Полиморфизм

В этой статье мы рассмотрим буквально все сценарии использования полиморфизма, использование параметров и разные возможные типы мышления во время написания кода.

Перегрузка методов

  • Давайте создадим консольное приложение InheritanceAndPolymorphism и класс Overload.cs с тремя методами DisplayOverload с параметрами, как ниже:
    В главном методе Program.cs теперь напишем следующее:
    И теперь, когда мы это запустим, вывод будет следующим:

    DisplayOverload 100
    DisplayOverload method overloading
    DisplayOverload method overloading100

    Класс Overload содержит три метода, и все они называются DisplayOverload, они различаются только типами параметров. В C# (как и в большистве других языков) мы можем создавать методы с одинаковыми именами, но разными параметрами, это и называется «перегрузка методов». Это значит, что нам нет нужды запоминать кучу имён методов, которые совершают одинаковые действия с разными типами данных.

    Что нужно запомнить: метод идентифицируется не только по имени, но и по его параметрам.

  • Если же мы запустим следующий код:
    Мы получим ошибку компиляции:

    Error: Type ‘InheritanceAndPolymorphism.Overload’ already defines a member called ‘DisplayOverload’ with the same parameter types

    Здесь вы можете видеть две функции, которые различаются только по возвращаемому типу, и скомпилировать это нельзя.

    Что нужно запомнить: метод не идентифицируется по возвращаемому типу, это не полиморфизм.

  • Если мы попробуем скомпилировать
    …то у нас это не получится:

    Error: Type ‘InheritanceAndPolymorphism.Overload’ already defines a member called ‘DisplayOverload’ with the same parameter types

    Здесь присутствуют два метода, принимающих целое число в качестве аргумента, с той лишь разницей, что один из них помечен как статический.

    Что нужно запомнить: модификаторы вроде static также не являются свойствами, идентифицирующими метод.

  • Если мы запустим нижеследующий код, в надежде, что теперь-то идентификаторы у методов будут разными:
    То нас ждёт разочарование:

    Error: Cannot define overloaded method ‘DisplayOverload’ because it differs from another method only on ref and out

    Что нужно запомнить: на идентификатор метода оказывают влияние только его имя и параметры (их тип, количество). Модификаторы доступа не влияют. Двух методов с одинаковыми идентификаторами существовать не может.

Роль ключевого слова params в полиморфизме

Параметры могут быть четырёх разных видов:

  • переданное значение;
  • преданная ссылка;
  • параметр для вывода;
  • массив параметров.

С первыми тремя мы, вроде, разобрались, теперь подробнее взглянем на четвёртый.

  • Если мы запустим следующий код:
То получим две ошибки:

Error1: The parameter name ‘a’ is a duplicate

Error2: A local variable named ‘a’ cannot be declared in this scope because it would give a different meaning to ‘a’, which is already used in a ‘parent or current’ scope to denote something else

Отсюда следуют вывод: имена параметров должны быть уникальны. Также не могут быть одинаковыми имя параметра метода и имя переменной, созданной в этом же методе.

  • Теперь попробуем запустить следующий код:

Overload.cs

Program.cs

Мы получим следующий вывод:

Akhil
Akhil 1
Akhil 2
Akhil 3

Мы можем передавать одинаковые ссылочные параметры столько раз, сколько захотим. В методе Display строка name имеет значение «Akhil». Когда мы меняем значение x на «Akhil1», на самом деле мы меняем значение name, т.к. через параметр x передана ссылка именно на него. То же и с y — все эти три переменных ссылаются на одно место в памяти.

  • Теперь самое интересное:

Overload.cs

Program.cs

Это даст нам такой вывод:

Akhil 100
Mittal 100
OOP 100
Akhil 200

Нам часто может потребоваться передать методу n параметров. В C# такую возможность предоставляет ключевое слово params.

Важно: это ключевое слово может быть применено только к последнему аргументу метода, так что метод ниже работать не будет:

  • В случае DisplayOverload первый аргумент должен быть целым числом, а остальные — сколь угодно много строк или наоборот, ни одной.

    Вывод:

200 100
300 100
100 200

Важно запомнить: C# достаточно умён, чтоб разделить обычные параметры и массив параметров, даже если они одного типа.

  • Посмотрите на следующие два метода:

    Разница между ними в том, что первый запустится, и такая синтаксическая конструкция будет подразумевать, что в метод будет передаваться n массивов строк. Вторая же выдаст ошибку:

Error: The parameter array must be a single dimensional array

Запомните: массив параметров должен быть одномерным.

  • Следует упомянуть, что последний аргумент не обязательно заполнять отдельными объектами, можно его использовать, будто это обычный аргумент, принимающий массив, то есть:
  • Overload.cs

    Program.cs

    Вывод будет следующим:

    Akhil 3
    Ekta 3
    Arsh 3

    Однако такой код:

    Уже вызовет ошибку:

    Error: The best overloaded method match for ‘InheritanceAndPolymorphism.Overload.DisplayOverload(int, params string[])’ has some invalid arguments

    Error:Argument 2: cannot convert from ‘string[]’ to ‘string’

    Думаю, тут всё понятно — или, или. Смешивать передачу отдельными параметрами и одним массивом нельзя.

    • Теперь рассмотрим поведение следующей программы:

    Overload.cs

    Program.cs

    После её выполнения мы получим в консоли:

    1000

    Это происходит из-за того, что при подобном синтаксисе массив передаётся по ссылке. Однако стоит отметить следующую особенность:

    Результатом выполнения такого кода будет

    102

    Ведь из переданных параметров C# автоматически формирует новый, временный массив.

    • Теперь поговорим о приоритете языка в выборе методов. Предположим, у нас есть такой код:
    C# рассматривает методы с массивом параметров последними, так что во втором случае будет вызван метод, принимающий два целых числа. В первом и третьем случае будет вызван метод с params, так как ничего кроме него запустить невозможно. Таким образом, на выходе мы получим:

    parameterArray
    The two integers 200 300
    parameterArray

    • Теперь кое-что интересное. Как вы думаете, каким будет результат выполнения следующей программы?

    Overload.cs

    Program.cs

    В консоли мы увидим:

    System.Int32 System.String System.Double
    System.Object[] System.Object[] System.Int32 System.String System.Double

    То есть, в первом и в четвёртом случаях массив передаётся именно как массив, заменяя собой objectParamArray, а во втором и третьем случаях массив передаётся как единичный объект, из которого создаётся новый массив из одного элемента.

    В заключение

    В этой статье мы рассмотрели перегрузку методов, особенности компиляции, с ней связанные, и буквально все возможные случаи использования ключевого слова params. В следующей мы рассмотрим наследование. Напоследок ещё раз повторим основные пункты, которые нужно запомнить:

    • Метод идентифицируется не только по имени, но и по его параметрам.
    • Метод не идентифицируется по возвращаемому типу.
    • Модификаторы вроде static также не являются свойствами, идентифицирующими метод.
    • На идентификатор метода оказывают влияние только его имя и параметры (их тип, количество). Модификаторы доступа не влияют. Двух методов с одинаковыми идентификаторами существовать не может.
    • Имена параметров должны быть уникальны. Также не могут быть одинаковыми имя параметра метода и имя переменной, созданной в этом же методе.
    • Ключевое слово params может быть применено только к последнему аргументу метода.
    • C# достаточно умён, чтоб разделить обычные параметры и массив параметров, даже если они одного типа.
    • Массив параметров должен быть одномерным.

    Источник: «Diving in OOP (Day 1) : Polymorphism and Inheritance (Early Binding/Compile Time Polymorphism)»