среда, 6 января 2010 г.

Полиморфизм

Начнем как водится с начала. Дадим определение понятия полиморфизм:
1. Полиморфизм (от греч. πολύ — «много» и μορφή — «форма», многообразный) в программировании — это возможность использования в одном и том же контексте различных программных сущностей (объектов, типов данных и т. д.) с одинаковым интерфейсом. (Журнал Функциональное программирование).
2. Полиморфи́зм — взаимозаменяемость объектов с одинаковым интерфейсом (Википедия).
3. Полиморфизм — это механизм позволяющий вызывать методы классов объектов, а не классов объектных переменных, ссылающихся на эти объекты (ваш покорный слуга).


На самом деле споры о том, что такое полиморфизм не утихают до сих пор, хотя понятие введено достаточно давно. Начиная с работ Страуструпа (который кстати не дает определения, а приводит кучу примеров).

Так в чем собственно заключается полиморфизм и какое отношение он имеет к позднему связыванию?
Рассмотрим простенький пример:
Пусть у нас стоит задача написать метод начисляющий проценты по банковским счетам. В качестве параметров метода передается массив счетов, в зависимости от типа счета начисляются проценты. Получим нечто вот такое:
    enum BankAccountType { Pensionary, Simple, VIP };

    class BankAccount
    {
      public BankAccountType Type { get; set; }
      public double ValueInAccount { get; set; }
    }

    void CalcPercent(BankAccount[] p_accaunts)
    {
      foreach (BankAccount acount in p_accaunts)
      {
        switch (acount.Type)
        {
          case BankAccountType.Pensionary:
            acount.ValueInAccount *= 1.1;
            break;
          case BankAccountType.Simple:
            acount.ValueInAccount *= 1.03;
            break;
          case BankAccountType.VIP:
            acount.ValueInAccount *= 1.14;
            break;
          default:
            break;
        }
      }
    }


* This source code was highlighted with Source Code Highlighter.

Какие здесь есть плюсы и минусы? Ну основной плюс, это простота и понятность кода, а вот минусов намного больше. Например, алгоритм начисления процентов по счету находится "вне" счета, т.е. класс Счет не выполняет свою функцию собирать данные и способы их обработки. Второй недостаток, это проблема с добавлением новых типов счетов. Нам придется изменять перечисление, метод начисления процентов, опять плохо.
Второй вариант решения задачи, может иметь вид:
    enum BankAccountType { Pensionary, Simple, VIP };

    class BankAccount
    {
      public BankAccountType Type { get; set; }
      public double ValueInAccount { get; private set; }
      public void CalcPercent()
      {
        switch (this.Type)
        {
          case BankAccountType.Pensionary:
            this.ValueInAccount *= 1.1;
            break;
          case BankAccountType.Simple:
            this.ValueInAccount *= 1.03;
            break;
          case BankAccountType.VIP:
            this.ValueInAccount *= 1.14;
            break;
          default:
            break;
        }
      }
    }

    void CalcPercent(BankAccount[] p_accaunts)
    {
      foreach (BankAccount account in p_accaunts)
      {
        account.CalcPercent();
      }
    }


* This source code was highlighted with Source Code Highlighter.

Уже намного лучше. Основная проблема возникнет в случае, если появится новый тип банковского счета, в этом случае придется менять класс Счет. Чего в некоторых случаях хотелось бы избежать (например если вы продаете свою систему, и не хотите проблем с обновлениями для разных банков).
Ну и наконец третий вариант, реализовать с применением полиморфизма.
    abstract class BankAccount
    {
      public BankAccountType Type { get; set; }
      public double ValueInAccount { get; protected set; }
      public abstract void CalcPercent();
    }

    class PensionaryBankAccount : BankAccount
    {

      public override void CalcPercent()
      {
        this.ValueInAccount *= 1.1;
      }
    }

    class SimpleBankAccount : BankAccount
    {

      public override void CalcPercent()
      {
        this.ValueInAccount *= 1.03;
      }
    }

    class VIPBankAccount : BankAccount
    {

      public override void CalcPercent()
      {
        this.ValueInAccount *= 1.14;
      }
    }

    void CalcPercent(BankAccount[] p_accaunts)
    {
      foreach (BankAccount account in p_accaunts)
      {
        account.CalcPercent();
      }
    }


* This source code was highlighted with Source Code Highlighter.

Практически идеален. Единственно при просмотре этого примера возникает вопрос: а какое отношение это все имеет к позднему связыванию?
А самое что ни есть непосредственное! Смотрите, придется ли переписывать наш метод void CalcPercent(BankAccount[] p_accaunts) в случае если появится еще один тип счета? Нет! Этому методу все равно какой алгоритм будет применяться вот в этом месте: account.CalcPercent(). Он вызывает абстрактный метод переопределяемый в потомках! Т.е. не связан ни с классом Пенсионный счет, VIP или каким то новым. Хотя основные достоинства полиморфизма как механизма позднего связывания конечно проявляются при использовании интерфейсов. Так например у класса Array есть метод Sort, главное требование которого заключается в том, чтобы элементы массива передаваемого в метод поддерживали интерфейс IComparable. Например мы можем написать класс:
    class Person : IComparable
    {
      public string LastName;
      public string FirstName;


      #region IComparable Members

      public int CompareTo(object obj)
      {
        return LastName.CompareTo(((Person)obj).LastName);
      }

      #endregion
    }


* This source code was highlighted with Source Code Highlighter.

А дальше массивы от нашего  класса быстро и качественно сортировать:
      Person[] p = new Person[10];
      ...
      Array.Sort(p);


* This source code was highlighted with Source Code Highlighter.

Вполне очевидно, что разработчики метода Sort ничего не знали про класс Person, но их метод великолепно сортирует массив классов Person по фамилиям.

Комментариев нет:

Отправить комментарий