ООП. Полиморфизм

      Комментарии к записи ООП. Полиморфизм отключены

В этой теме 0 ответов, 1 участник, последнее обновление  Васильев Владимир Сергеевич 3 нед., 6 дн. назад.

  • Автор
    Сообщения
  • #4026

    Из всех принципов объектно-ориентированного программирования этот самый сложно формулируемый, но совершенно незаменимый. Во-первых, что означает это слово?

    Полиморфизм, в переводе с греческого, обозначает множественность форм. В программировании это возможность представлять одно и то же по-разному и, с другой стороны, дать возможность однообразного обращения к тому, что не является идентичным.

    Если говорить о типах данных, то это возможность использовать разные классы как представителей одного и того же класса. Каким образом?

    Возьмём для примера класс Transport. Он реализует основную функциональность – передвижение (Move()).
    Его классы-наследники (Car, Airplane, Tram) по-своему переопределяют метод передвижения. Но везде, где мы будем применять их, мы можем рассчитывать на наличие общей функциональности, но по-своему определённой.

    class Transport
    {
       public virtual void Move()
       {
          //just implementing a moving ability
       }
    }
    class Airplane:Transport
    {
       public override void Move()
       {
          base.Move();
         //fly
       }   
    }
    class Car:Transport
    {
       public override void Move()
       {
          //ride
       }
    }
    class Tram:Transport
    {
       public override void Move()
       {
          //suffer (who knows what is Ukrainian trams, knows what I mean :) )
      }  
    }

    Обратим внимание на модификатор virtual, помечающий метод Move() в базовом классе Transport. Он означает, что мы в праве переопределить реализацию данного метода в любом наследнике, если она нас не устраивает. А также обратим внимание на модификатор override, помечающий метод Move() в классах – наследниках. Здесь он означает, что мы намерены дать своё собственное определение методу базового класса. Эти 2 модификатора используются в паре. Нельзя переопределить метод базового типа, если он не был помечен как virtual. Но, с другой стороны, мы и не обязаны переопределять виртуальный метод базового типа.

    Протестируем полиморфное использование всех типов- наследников класса Transport. Экземпляры всех трёх классов мы помещаем в одну коллекцию , указывая их базовый тип. Затем циклически проходим по каждому объекту и вызываем у каждого метод Move(). Соответственно, подразумевалось, что у каждого объекта, находящегося в этой коллекции, должен быть такой метод, поскольку он есть у их базового типа.

    class Program {
       static void Main() {
           List<Transport> transport = new List<Transport>();
             // заполняем коллекцию
           transport.Add(new Airplane())
            transport.Add(new Car());
           transport.Add(new Tram());
     
          foreach (var t in transport) {
               t.Move();
           }
       }
    }

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

    Продемонстрируем на примере: класс Tester включает единственный метод TestTransportUse() с параметром типа Transport. Создаём в точке входа экземпляр класса Car и передаём его при вызове метода.

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

    class Tester
    {
       public static void TestTransportUse(Transport  t)
       {
          t.Move();
       }
    }
    class Program
    {
       static void Main(string[] args)
       {
          Car car = new Car();
          Tester.TestTransportUse(car);
       }
    }

    Перегрузку методов тоже можно отнести к проявлениям полиморфизма. Мы определяем набор одноимённых методов с разными параметрами и позволяем набором параметров при вызове определять, какой вариант реализации нам нужен. Получается, мы создаём разновидности одного и того же метода, а вызываемая версия определяется динамически.

    Например, у нас есть класс, выполняющий простые арифметические вычисления. Среди прочих, он включает метод для вычисления частного от деления двух параметров типа double. Всё бы ничего, но возвращаемое значение является неокруглённым результатом деления, и хорошо бы в каких-то случая явно задавать, до какого количества знаков после точки нужно округлить значение.

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

    class Calculator
    {
       //другие методы вычислений
       //...
     
       public string Divide(double num1, double num2)
       {
          return (num1 / num2).ToString();
       }
       public string Divide(double num1, double num2, int precision)
       {
          return Math.Round(num1 / num2, precision, MidpointRounding.AwayFromZero).ToString();
       }
    }

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

    class Program
    {
        static void Main(string[] args)
       {
          Calculator calc = new Calculator();
          Console.WriteLine(calc.Divide(5,7));
          Console.WriteLine(calc.Divide(5,7,2));
       }
    }

    Создав экземпляр класса Calculator, мы вызываем оба варианта метода Divide(). Выглядит, как будто это один и тот же метод, только количество параметров отличается.

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

    По теме полиморфизма рекомендую почитать также: Полиморфизм C++. Основы объектно-ориентированного программирования .

Для ответа в этой теме необходимо авторизоваться.