Главная » Java, Советы » Указатель на функцию заменяйте классом и интерфейсом

0

 

Язык С поддерживает указатели на функции (function pointer), что позволяет программе хранить и передавать возможность вызова конкретной функции. Указатели на функции обычно применяются для того, чтобы разрешить клиенту, вызвавшему функцию, уточнить схему ее работы, для этого он передает ей указатель на вторую функцию. Иногда это называют обратным вызовом (callback). Например, функция qsort из стандартной библиотеки С получает указатель на функцию-компаратор (comparator), которую затем использует для сравнения элементов, подлежащих сортировке. Функция-компаратор принимает два параметра, каждый из которых является указателем на некий элемент. Она возвращает отрицательное целое число, если элемент, на который указывает первый параметр, оказался меньше элемента, на который указывает второй параметр, нуль, если элементы равны между собой, и положительное целое число, если первый элемент больше второго. Передавая указатель на различные функции-компараторы, клиент может получать различный порядок сортировки. Как демонстрирует шаблон Strategy [Сатта95, стр. 315], функция-компаратор представляет алгоритм сортировки элементов.

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

class StringLengthComparator {

public int compare(String s1, String s2) {

return s1.1ength() – s2.1ength();

                     }

}

 

Этот класс передает единственный метод, который получает две строки и возвращает отрицательное число, если первая строка короче второй, нуль, если две строки имеют одинаковую длину, и положительное число, если первая строка длиннее второй. Данный метод – ни что иное как компаратор, который, вместо более привычного лексикографического упорядочения, задает упорядочение строк по длине. Ссылка на объект StringLengthComparator служит для этого компаратора в качестве "указателя на функцию", что позволяет использовать его для любой пары строк. Иными словами, экземпляр класса StrlngLengthComparator – это определенная методика (concrete strategy) сравнения строк.

 

Как часто бывает с классами конкретных методик сравнения, класс StringLengthComparator не имеет состояния: у него нет полей, а потому все его экземпляры функционально эквивалентны друг другу. Таким образом, во избежание расходов на создание ненужных объектов можно сделать этот класс синглтоном (статьи 4 и 2):

class StringLengthComparator {

private StringLengthComparator() { }

public static final StringLengthComparator

INSTANCE = new StringLengthComparator();

public int compare(String s1, String s2)

return s1.length() – s2.length();

       }

}

Для того чтобы передать методу экземпляр класса StringLengthComparator, нам необходим соответствующий тип параметра. Использовать непосредственно тип StringLengthComparator нехорошо, поскольку это лишит клиентов возможности выбирать какие-либо другие алгоритмы сравнения. Вместо этого следует определить интерфейс Comparator и переделать класс StriпgLепgthСоmраrаtоr таким образом, чтобы он реализовывал этот интерфейс. Другими словами, необходимо определить интерфейс методики сравнения (strategy interface), который должен соответствовать классу конкретной стратегии:

// Интерфейс методики сравнения

public interface Comparator {

public int compare(Object 01, Object 02);

}

Оказывается, что представленное определение интерфейса Соmраrator есть в пакете java.util. Никакого волшебства в этом нет, вы могли точно так же определить его сами. Так, для того чтобы можно было сравнивать не только строки, но и другие объекты, метод compare в интерфейсе принимает параметры типа Object, а не String. Следовательно, приведенный выше класс Str1ngLengthComparator необходимо слегка изменить, чтобы реализовать интерфейс Comparator. Перед вызовом метода length параметры типа Object нужно привести к типу String.

Классы конкретных методик сравнения часто создаются с помощью анонимных классов (статья 18). Так, следующий оператор сортирует массив строк по их длине:

Arrays.sort(stringArray, new Comparator() {

public int compare(Object 01, Object 02) {

String s1 = (String)o1;

String s2 = (String)o2;

return s1.length() – s2.length(); }

});

 

Поскольку интерфейс методики сравнения используется как тип для всех экземпляров конкретных методик сравнения, для того чтобы предоставить конкретную методику сравнения, нет необходимости делать соответствующий класс стратегии открытым. Вместо этого "класс-хозяин" (host) может передать открытое статическое поле (или статический метод генерации), тип которого соответствует интерфейсу методики сравнения, сам же класс методики сравнения может оставаться закрытым классом, вложенным в класс-хозяин. В следующем примере вместо анонимного класса используется статический класс-член, что позволяет реализовать в классе методики сравнения второй интерфейс – Serializable:

// Предоставление конкретной методики сравнения

class Host {

// Основная часть класса опущена

private static plass StrLenCmp

implements Comparator, Serializable {

public int compare(Object o1, Object o2) {

       String s1 = (String)o1;

String s2 = (String)o2;

return s1.1ength() – s2.length(); }

}

// Возвращаемый компаратор является сериализуемым

 public static final Comparator

STRING_LENGTH_COMPARATOR = new StrLenCmp(); }

Представленный шаблон используется в классе String для того, чтобы через его поле CASE_INSENSIТIVE_ORDER передавать компаратор строк, не зависящий от регистра.

Подведем итоги, первоначально указатели на функции в языке С использовались для реализации шаблона Strategy. для того чтобы реализовать этот шаблон в языке программирования Java, необходимо создать интерфейс, представляющий стратегии, а затем для каждой конкретной стратегии нужно построить класс, реализующий этот Интерфейс. Если конкретная стратегия применяется только один раз, ее класс обычно декларируется и реализуется с помощью анонимного класса. Если же конкретная стратегия передается для многократного использования, ее класс обычно становится закрытым статическим классом-членом и передается через поле public static final, чей тип соответствует интерфейсу стратегии.

 

Источник: Джошуа Блох, Java TM Эффективное программирование, Издательство «Лори»

По теме:

  • Комментарии