Хотя они являются малоиспользуемой функцией ReasonML, объекты обеспечивают поддержку наследования и абстрактных классов (известных как виртуальные классы).

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

Полиморфизм с использованием объектов и типов

Полиморфизм — это способность объекта или метода принимать множество форм. По сути, функция или метод являются полиморфными, если они могут принимать более одного типа параметров.

В следующем простом примере я продемонстрирую функцию, которая может принимать любой тип pet.

type pet = {
.
getName: string,
talk: unit
}

Затем мы можем реализовать это столько раз, сколько захотим. Обратите внимание, что методы могут быть реализованы по-разному!

class cat = (name) => {
  as _;
  pub getName = String.trim(name);
  pub talk =  Js.log("Nya!");
};
class dog = (name) => {
  as _;
  pub getName = String.capitalize_ascii(name);
  pub talk =  Js.log2("Woof!", name);
};

Поскольку классы cat и dog реализуют сигнатуру pet, мы можем написать функцию, которая работает с домашнее животное, и он принимает как кошек, так и собак. Это работает, поскольку — в отличие от записей — объекты просто должны соответствовать подписи, и многие разные типы могут соответствовать одной и той же подписи. Иногда это может быть нежелательно, что является хорошей причиной предпочесть записи, если только вам не требуется эта функциональность.

let myCat: cat = new cat(" Cuddles ");
let myDog: dog = new dog("spot");
let greet: pet => string = p => "Hello " ++ p#getName;
let doubleGreet: string = greet(myCat) ++ " and " ++ greet(myDog);

Абстракция с использованием виртуальных классов

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

Мы определяем виртуальный класс следующим образом:

class virtual pet (name: string) = {
  as self;
  pri getName = name |> String.trim |> String.capitalize_ascii;
  pub virtual talk: unit;
};
let myVirtualPet = new pet("davey"); // does not compile!

Здесь есть несколько вещей, на которые следует обратить внимание:

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

Наследование от виртуального класса

Вместо создания виртуальных классов мы должны определить новый класс, который наследуется от виртуального класса. Обратите внимание, как в этом случае мы можем получить доступ к частному методу getName из определения класса; однако к этому нельзя получить доступ из объекта dog или pet.

class dog (name) = {
  as self;
  inherit (class pet)(name);
  pub talk = Js.log(self#getName ++ ", says Woof!");
};

let myDog: pet = new dog("Spot");
myDog#talk; // prints 'Spot, says Woof!'
myDog#getName; // does not compile :(
class cat (name) = {
  as _;
  inherit (class pet)(name);
  pub talk = "Nya!");
};
let myCat: pet = new cat("Kitten");
myCat#talk; // prints 'Nya!'

Как видите, метод talk реализован по-разному для обоих экземпляров. Таким образом, домашнее животное имеет полиморфное поведение, и если мы не знаем, что это за экземпляр, мы не можем определить, как оно себя ведет.

Спасибо за внимание, эта статья представляет собой короткий фрагмент, основанный на статье, которую я написал в рамках серии статей об использовании объектов в ReasonML на itazura.io. Если вы хотите узнать больше, например, об использовании наследования без виртуальных типов или о множественном наследовании (также известном как проблема бриллианта), то, пожалуйста, прочитайте исходную статью.