Рубрики
Без рубрики

Полиморфизм: Эликсир против Ява

Введение в полиморфизм в Elixir с использованием протоколов и их связь с иерархиями классов в Java. С пометкой “эликсир”, “ява”, “ооп”.

Вступление

Этот пост направлен на улучшение понимания протоколов Elixir путем сравнения их с иерархиями классов в Java. Хотя эти два языка сильно отличаются друг от друга, у них есть общая мощная особенность: полиморфизм.

Когда вы впервые начинаете изучать Elixir, первое, что люди говорят, что вам нужно сделать, это сосредоточиться на функциональном программировании (FP). Это может означать отказ от некоторых старых привычек, сформировавшихся в результате использования традиционно объектно-ориентированных языков, таких как Java и Python, и немного иного мышления при написании кода и разработке программного обеспечения.

Хотя каждый разработчик Elixir расскажет вам, насколько хорош FP и почему он им нравится, менее известной особенностью для начинающих разработчиков Elixir является поддержка полиморфизма с помощью протоколов.

Полиморфизм – это концепция, лежащая в основе объектно-ориентированного программирования, что делает ее особенностью, которая, по-видимому, противоречит ядру языка Elixir. Мы поймем, почему это полезно, и сравним их со знакомыми базовыми функциями Java.

Для кого предназначена эта статья?

В этой статье рассказывается об использовании протоколов в Elixir для достижения полиморфной функциональности. Если вы никогда раньше не слышали об Эликсире, обязательно посетите веб-сайт Эликсира для получения более подробной информации об этом захватывающем молодом языке.

Если вы вообще знакомы с объектно-ориентированным программированием (ООП) на Java, этот пост поможет выявить сходства между иерархиями классов Java и протоколами Elixir.

Что такое полиморфизм?

Полиморфизм – это когда наша программа отправляет функции или методы на основе типа или класса данных.

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

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

Это позволяет нам отделить наш код, не привязывая нашу реализацию одного модуля к реализации другого модуля.

Иерархии классов Java

Давайте воспользуемся здесь простым примером. Американцы измеряют свой рост в дюймах, в то время как весь остальной мир использует сантиметры. Я уверен, что в некоторых частях мира используются другие единицы измерения, но для простоты мы будем придерживаться этого предположения.

Давайте определим интерфейс для измерения американцев или кого-либо из остального мира в сантиметрах или дюймах:

public interface MeasuredPerson {
  double measureInInches();
  double measureInCentimetres();
}

Теперь мы можем реализовать этот интерфейс в нескольких классах, которые представляют людей из Америки и остального мира:

public class American implements MeasuredPerson {
  private String name;
  private double heightInInches;

  public American(String name, double heightInInches) {
    this.name = name;
    this.heightInInches = heightInInches;
  }

  public double measureInInches() { return heightInInches; }
  public double measureInCentimetres() { return heightInInches * 2.54; }
}

public class RestOfTheWorld implements MeasuredPerson {
  private String name;
  private double heightInCentimetres;

  public American(String name, double heightInCentimetres) {
    this.name = name;
    this.heightInCentimetres = heightInCentimetres;
  }

  public double measureInInches() { return heightInCentimetres * 0.39; }
  public double measureInCentimetres() { return heightInCentimetres; }
}

Теперь, когда у нас есть пара классов, которые мы можем использовать, мы можем написать метод, который принимает Измеренного человека и выводит его рост в дюймах.

public class Main {
  public static void main(string[] args) {
    American american = new American("John", 72);
    RestOfTheWorld restOfTheWorld = new RestOfTheWorld("Jean", 178);

    printInches(american);
    printInches(restOfTheWorld);
  }

  private void printInches(MeasuredPerson measuredPerson) {
    System.out.println("The person's height is: " + measuredPerson.measureInInches());
  }
}

Этот код выведет рост в дюймах как американца, так и человека из остального мира. Метод печать в дюймах отправляет вызов измерение в дюймах во время выполнения на основе базового типа данного объекта!

Протоколы эликсира

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

Типы эликсиров включают Список , Карту и Ключевое слово , но они также могут включать пользовательские структуры, которые мы определяем. Ниже приведен пример двух пользовательских структур, аналогичных нашим классам Java, которые мы определили выше:

defmodule Person.American do
  defstruct [:name, :height_in_inches]
end

defmodule Person.RestOfTheWorld do
  defstruct [:name, :height_in_centimetres]
end

Американцы измеряют свой рост в дюймах, а весь остальной мир измеряет свой рост в сантиметрах. Что, если мы хотим измерить человека, не беспокоясь о том, из Америки он или откуда-то еще в мире?

Ключевое слово defprotocol

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

defprotocol Person.Height do
  @doc """
  Accepts the person to measure as the first argument, and accepts either
  `:inches` or `:centimetres` as the second argument to indicate 
  which unit of measurement to return.
  """
  def measure(person, unit_of_measurement)
end

Обратите внимание, что мы не предоставляем блок do для нашей функции мера/2 . В следующем разделе будет показано, как мы определяем реализацию для нашего нового протокола.

Ключевое слово defimpl

Следующие модули используют ключевое слово defimpl для обеспечения реализации наших пользовательских структур персоналий:

defimpl Person.Height, for: Person.American do
  def measure(%Person.American{height_in_inches: height_in_inches}, :inches) do
    height_in_inches
  end

  def measure(%Person.American{height_in_inches: height_in_inches}, :centimetres) do
    height_in_inches * 2.54
  end
end

defimpl Person.Height, for: Person.RestOfTheWorld do
  def measure(%Person.RestOfTheWorld{height_in_centimetres: height_in_centimetres}, :inches) do
    height_in_centimetres * 0.39
  end

  def measure(%Person.RestOfTheWorld{height_in_centimetres: height_in_centimetres}, :centimetres) do
    height_in_centimetres
  end
end

defimpl принимает модуль протокола (определенный с помощью defprotocol ) и сопоставление модуля с типом, для которого мы хотим предоставить реализацию. Таким образом, первый аргумент наших функций measure/2 , определенных в наших модулях реализации, является структурой такого типа.

Есть ключевое отличие от нашей реализации Java: вместо реализации поведения внутри Person. Американец и Человек. Остальной мир модули, мы реализуем поведение в отдельном модуле. Поскольку сами структуры не могут иметь поведения (т.Е. вы не можете вызывать american.measure(:сантиметры) ), вместо этого структура должна быть передана функции; см. Ниже, как мы это делаем.

Связывая все это воедино

Теперь, когда мы определили пару модулей реализации для нашего Человека. Американец и Человек. Остальной мир структуры, мы можем назвать Человеком.Рост.мера/2 функция для измерения данного человека в нашей предпочтительной единице измерения!

%Person.American{name: "John", height_in_inches: 72}
|> Person.Height.measure(:centimetres)
|> IO.inspect()
# 182.88

%Person.RestOfTheWorld{name: "Jean", height_in_centimetres: 178}
|> Person.Height.measure(:inches)
|> IO.inspect()
# 70.0787

Сила протоколов

Здесь мы использовали простой пример, чтобы показать, как мы можем создавать ваши собственные протоколы. Но в чем тут дело?

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

Большим преимуществом протоколов Elixir перед иерархиями классов Java является то, что мы можем предоставлять реализации наших собственных протоколов для структур из внешних библиотек. В Java вам придется редактировать исходный код других библиотек, чтобы реализовать свой пользовательский интерфейс. Но в Elixir все, что нам нужно сделать, это предоставить реализацию для типа (структуры), независимо от того, какая библиотека/фреймворк предоставляет этот тип!

Знакомый пример

В Elixir мы используем модуль Перечисление для обхода списков и карт. Все, что мы передаем в качестве первого аргумента функциям в Перечисление модуль должен иметь реализацию для Перечисляемого протокола! Чтобы узнать больше об этом ознакомьтесь с Перечисляемыми документами .

Ресурсы

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

  • Протоколы эликсира : введение в протоколы эликсира на официальном сайте Elixir lang.
  • Эликсир Протокол документы : если вы хотите глубже погрузиться в то, как работают протоколы, это хорошее место для начала. Документы по Эликсиру написаны так хорошо, что обычно я не смотрю в другое место.
  • Программный эликсир.6 : эта книга – один из лучших инструментов в моем арсенале. Я не мог бы порекомендовать лучшую книгу как для начинающих, так и для экспертов. Я все время использую его в качестве ориентира.

Я написал этот пост для тех из нас, кто знаком с возможностями ООП, и для демонстрации потрясающей функции в Elixir, которая подходит всем, кто знаком с ООП на Java. Спасибо за чтение!

Оригинал: “https://dev.to/hugecoderguy/polymorphism-elixir-vs-java-14i0”