Мультиметоды - это механизм выбора метода на основе не только динамического типа его получателя, как при использовании виртуальных методов, но и динамических типов его аргументов. По сути, этот механизм обобщает виртуальные функции и перегрузку.
При правильном использовании этот механизм позволяет писать более компактный и читабельный код, однако, как обычно и бывает, ценой потери производительности.
Хотя вопрос о значительности этих потерь остается открытым, и, я думаю, схож с вопросом о производительности в случае с виртуальных функций. В Groovy выбор метода для вызова осуществляется на основе динамических типов аргументов, а не статических, как, например, в Java. В частности, рассмотрим два следующих участка кода.
На Java:
class SomeClass {
public void doSomething( Object o ) {
System.out.println( "Object" );
}
public void doSomething( String s ) {
System.out.println( "String" );
}
}
....
SomeClass someObject = ...
....
Object a1 = new Integer(1);
Object a2 = new String("str");
someObject.doSomething( a1 ); // Вызывается первый метод
someObject.doSomething( a2 ); // Вызывается первый метод, статический тип a2 - Object
В результате выполнения этого кода, на консоль будет выведены две строки “Object”.
Теперь, похожий код на Groovy:
class SomeClass {
def doSomething( Object o ) {
println 'Object'
}
def doSomething( String o ) {
println 'String'
}
}
....
SomeClass someObject = ...
....
Object a1 = new Integer(1);
Object a2 = new String("str");
someObject.doSomething( a1 ); // Вызывается первый метод
someObject.doSomething( a2 ); // Вызывается второй метод, динамический тип a2 - String
В результате выполнения этого кода, на консоль будет выведены две строки: “Object” и “String”
Примеры использования мультиметодов
Здесь я рассмотрю парочку более-менее приближенных к реальности примеров, когда вышеописанная особенность может действительно облегчить жизнь.
Обработка взаимодействий объектов
Классический пример использования мультиметодов - обработка взаимодействия различных объектов. Например, у нас есть базовый класс SpaceObject и его различные наследники: SpaceShip, Asteroid, Rocket.
Объекты могут сталкиваться. При столкновении, должен вызываться метод collide одного, объекта, у которого тип аргумента - тип второго объекта. В частности, на Groovy, реализация выглядит просто:
class Asteroid implements SpaceObject {
def collide( Asteroid other ) {
// Столкновение астероид - астероид
}
def collide( SpaceShip other ) {
// Столкновение астероид - корабль
}
def collide( Rocket other ) {
// Столкновение астероид - ракета
}
}
Аналогично, определив столкновения для остальных типов объектов, можно просто писать следующий код, не заботясь о статических типах аргументов:
SpaceObject o1 = ...
SpaceObject o2 = ...
o1.collide( o2 );
В языках, использующих статическую типизацию, например, Java или C++, для реализации подобного поведения необходимо писать в каждом объекте метод-заглушку, который принимает произвольный объект и осуществляет диспетчеризацию. Для этого используется проверка типа во время выполнения (dynamic_cast в C++ и instanceof в Java) или же, если взаимодействие коммутативно, вызовом виртуальной функции аргумента.
Сравнение объектов на равенство
Более часто встречающаяся задача - проверка равенства объектов. Например, метод equals в Java. Обычно его определение выглядит следующим образом:
class SomeClass {
public boolean equals( Object other ) {
if ( other == null ) return false;
if ( !(other instanceof SomeClass) ) return false; // Проверяем класс объекта
SomeClass o = (SomeClass)other; // Преобразуем объект к нужному классу
... // Собственно, сравнение объектов
}
}
Безусловно, эти лишние три строки в каждом классе не увеличивают читабельность кода. Теперь посмотрим, как это может быть написано на Groovy с учетом мультиметодов.
class SomeClass {
boolean equals( SomeClass other ) {
... // Собственно, сравнение объектов
}
}
Никаких лишних преобразований! Все дело в том, что этот метод будет вызван в случае, если аргумент имеет подходящий динамический тип. В остальных случаях будет вызвана реализация по умолчанию из класса Object, которая вернет false.