Введение в ООП с примерами на C#. Часть первая. Все, что нужно знать о полиморфизме
Я много писал на смежные темы, вроде концепции MVC, Entity Framework, паттерна «Репозиторий» и т.п. Моим приоритетом всегда было полное раскрытие темы, чтобы читателю не приходилось гуглить недостающие детали. Этот цикл статей опишет абсолютно все концепции ООП, которые могут интересовать начинающих разработчиков. Однако эта статья предназначена не только для тех, кто начинает свой путь в программировании: она написана и для опытных программистов, которым может потребоваться освежить свои знания.
Сразу скажу, далеко в теорию мы вдаваться не будем — нас интересуют специфичные вопросы. Где это будет нужно, я буду сопровождать повествование кодом на C#.
Что такое ООП и в чём его плюсы?
«ООП» значит «Объектно-Ориентированное Программирование». Это такой подход к написанию программ, который основывается на объектах, а не на функциях и процедурах. Эта модель ставит в центр внимания объекты, а не действия, данные, а не логику. Объект — реализация класса. Все реализации одного класса похожи друг на друга, но могут иметь разные параметры и значения. Объекты могут задействовать методы, специфичные для них.
ООП сильно упрощает процесс организации и создания структуры программы. Отдельные объекты, которые можно менять без воздействия на остальные части программы, упрощают также и внесение в программу изменений. Так как с течением времени программы становятся всё более крупными, а их поддержка всё более тяжёлой, эти два аспекта ООП становятся всё более актуальными.
Что за концепции ООП?
Сейчас коротко о принципах, которые мы позже рассмотрим в подробностях:
- Абстракция данных: подробности внутренней логики скрыты от конечного пользователя. Пользователю не нужно знать, как работают те или иные классы и методы, чтоб их использовать. Подходящим примером из реальной жизни будет велосипед — когда мы ездим на нём или меняем деталь, нам не нужно знать, как педаль приводит его в движение или как закреплена цепь.
- Наследование: самый популярный принцип ООП. Наследование делает возможным повторное использование кода — если какой-то класс уже имеет какую-то логику и функции, нам не нужно переписывать всё это заново для создания нового класса, мы можем просто включить старый класс в новый, целиком.
- Инкапсуляция: включение в класс объектов другого класса, вопросы доступа к ним, их видимости.
- Полиморфизм: «поли» значит «много», а «морфизм» — «изменение» или «вариативность», таким образом, «полиморфизм» — это свойство одних и тех же объектов и методов принимать разные формы.
- Обмен сообщениями: способность одних объектов вызывать методы других объектов, передавая им управление.
Ладно, тут мы коснулись большого количества теории, настало время действовать. Я надеюсь, это будет интересно.
Полиморфизм
В этой статье мы рассмотрим буквально все сценарии использования полиморфизма, использование параметров и разные возможные типы мышления во время написания кода.
Перегрузка методов
- Давайте создадим консольное приложение InheritanceAndPolymorphism и класс Overload.cs с тремя методами DisplayOverload с параметрами, как ниже:
public class Overload < public void DisplayOverload(int a)< System.Console.WriteLine("DisplayOverload " + a); >public void DisplayOverload(string a) < System.Console.WriteLine("DisplayOverload " + a); >public void DisplayOverload(string a, int b) < System.Console.WriteLine("DisplayOverload " + a + b); >>
public void DisplayOverload() < >public int DisplayOverload()
Error: Type ‘InheritanceAndPolymorphism.Overload’ already defines a member called ‘DisplayOverload’ with the same parameter types
static void DisplayOverload(int a) < >public void DisplayOverload(int a) < >public void DisplayOverload(string a)
Error: Type ‘InheritanceAndPolymorphism.Overload’ already defines a member called ‘DisplayOverload’ with the same parameter types
private void DisplayOverload(int a) < >private void DisplayOverload(out int a) < a = 100; >private void DisplayOverload(ref int a)
Error: Cannot define overloaded method ‘DisplayOverload’ because it differs from another method only on ref and out
Роль ключевого слова params в полиморфизме
Параметры могут быть четырёх разных видов:
- переданное значение;
- переданная ссылка;
- параметр для вывода;
- массив параметров.
С первыми тремя мы, вроде, разобрались, теперь подробнее взглянем на четвёртый.
public void DisplayOverload(int a, string a) < >public void Display(int a)
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
Отсюда следуют вывод: имена параметров должны быть уникальны. Также не могут быть одинаковыми имя параметра метода и имя переменной, созданной в этом же методе.
public class Overload < private string name = "Akhil"; public void Display() < Display2(ref name, ref name); System.Console.WriteLine(name); >private void Display2(ref string x, ref string y) < System.Console.WriteLine(name); x = "Akhil 1"; System.Console.WriteLine(name); y = "Akhil 2"; System.Console.WriteLine(name); name = "Akhil 3"; >>
Мы получим следующий вывод:
Мы можем передавать одинаковые ссылочные параметры столько раз, сколько захотим. В методе Display строка name имеет значение «Akhil». Когда мы меняем значение x на «Akhil1», на самом деле мы меняем значение name , т.к. через параметр x передана ссылка именно на него. То же и с y — все эти три переменных ссылаются на одно место в памяти.
public class Overload < public void Display() < DisplayOverload(100, "Akhil", "Mittal", "OOP"); DisplayOverload(200, "Akhil"); DisplayOverload(300); >private void DisplayOverload(int a, params string[] parameterArray) < foreach (string str in parameterArray) Console.WriteLine(str + " " + a); >>
Нам часто может потребоваться передать методу n параметров. В C# такую возможность предоставляет ключевое слово params .
Важно: это ключевое слово может быть применено только к последнему аргументу метода, так что метод ниже работать не будет:
private void DisplayOverload(int a, params string[] parameterArray, int b)
- В случае DisplayOverload первый аргумент должен быть целым числом, а остальные — сколь угодно много строк или наоборот, ни одной.
public class Overload < public void Display() < DisplayOverload(100, 200, 300); DisplayOverload(200, 100); DisplayOverload(200); >private void DisplayOverload(int a, params int[] parameterArray) < foreach (var i in parameterArray) Console.WriteLine(i + " " + a); >> //Program.cs тот же, что и в предыдущем примере
private void DisplayOverload(int a, params string[][] parameterArray)
private void DisplayOverload(int a, params string[,] parameterArray)
Разница между ними в том, что первый запустится, и такая синтаксическая конструкция будет подразумевать, что в метод будет передаваться n массивов строк. Вторая же выдаст ошибку:
public class Overload < public void Display() < string[] names = ; DisplayOverload(3, names); > private void DisplayOverload(int a, params string[] parameterArray) < foreach (var s in parameterArray) Console.WriteLine(s + " " + a); >>
public class Overload < public void Display() < string [] names = ; DisplayOverload(2, names, "Ekta"); > private void DisplayOverload(int a, params string[] parameterArray) < foreach (var str in parameterArray) Console.WriteLine(str + " " + a); >>
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’
Думаю, тут всё понятно — или, или. Смешивать передачу отдельными параметрами и одним массивом нельзя.
public class Overload < public void Display() < int[] numbers = ; DisplayOverload(40, numbers); Console.WriteLine(numbers[1]); > private void DisplayOverload(int a, params int[] parameterArray) < parameterArray[1] = 1000; >>
После её выполнения мы получим в консоли:
Это происходит из-за того, что при подобном синтаксисе массив передаётся по ссылке. Однако стоит отметить следующую особенность:
public class Overload < public void Display() < int number = 102; DisplayOverload(200, 1000, number, 200); Console.WriteLine(number); >private void DisplayOverload(int a, params int[] parameterArray) < parameterArray[1] = 3000; >>
Результатом выполнения такого кода будет
Ведь из переданных параметров C# автоматически формирует новый, временный массив.
public class Overload < public void Display() < DisplayOverload(200); DisplayOverload(200, 300); DisplayOverload(200, 300, 500, 600); >private void DisplayOverload(int x, int y) < Console.WriteLine("The two integers " + x + " " + y); >private void DisplayOverload(params int[] parameterArray) < Console.WriteLine("parameterArray"); >> ///Program.cs всё тот же
C# рассматривает методы с массивом параметров последними, так что во втором случае будет вызван метод, принимающий два целых числа. В первом и третьем случае будет вызван метод с params , так как ничего кроме него запустить невозможно. Таким образом, на выходе мы получим:
parameterArray
The two integers 200 300
parameterArray
public class Overload < public static void Display(params object[] objectParamArray) < foreach (object obj in objectParamArray) < Console.Write(obj.GetType().FullName + " "); >Console.WriteLine(); > >
class Program < static void Main(string[] args) < object[] objArray = < 100, "Akhil", 200.300 >; //Массив object obj = objArray; //Массив как объект Overload.Display(objArray); Overload.Display((object)objArray); //Массив, приведённый к объекту Overload.Display(obj); ///Почему бы не пойти глубже? :D Overload.Display((object[])obj); //Массив, как объект, приведённый к массиву Console.ReadKey(); > >
System.Int32 System.String System.Double
System.Object[] System.Object[] System.Int32 System.String System.Double
То есть, в первом и в четвёртом случаях массив передаётся именно как массив, заменяя собой objectParamArray , а во втором и третьем случаях массив передаётся как единичный объект, из которого создаётся новый массив из одного элемента.
В заключение
В этой статье мы рассмотрели перегрузку методов, особенности компиляции, с ней связанные, и буквально все возможные случаи использования ключевого слова params . В следующей мы рассмотрим наследование. Напоследок ещё раз повторим основные пункты, которые нужно запомнить:
- Метод идентифицируется не только по имени, но и по его параметрам.
- Метод не идентифицируется по возвращаемому типу.
- Модификаторы вроде static также не являются свойствами, идентифицирующими метод.
- На идентификатор метода оказывают влияние только его имя и параметры (их тип, количество). Модификаторы доступа не влияют. Двух методов с одинаковыми идентификаторами существовать не может.
- Имена параметров должны быть уникальны. Также не могут быть одинаковыми имя параметра метода и имя переменной, созданной в этом же методе.
- Ключевое слово params может быть применено только к последнему аргументу метода.
- C# достаточно умён, чтоб разделить обычные параметры и массив параметров, даже если они одного типа.
- Массив параметров должен быть одномерным.