Мультиметоды в Groovy

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

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

В 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.

Также, на схожие темы

Ссылки

 Подписаться на RSS

 #  #  #  #  #  #  #  #  #  #

Добавить комментарий