- •Содержание
- •1. Введение
- •2. Создание и уничтожение объектов
- •3. Методы, общие для всех объектов
- •4. Классы и интерфейсы
- •5. Замена конструкций на языке c
- •6. Методы
- •7. Общие вопросы программирования
- •8. Исключения
- •9. Потоки
- •10. Сериализация
- •11. Литература
- •Глава 1
- •Глава 2 Создание и уничтожение объектов
- •Рассмотрите возможность замены конструкторов статическими методами генерации.
- •Свойство синглтона обеспечивайте закрытым конструктором
- •Отсутствие экземпляров обеспечивает закрытый конструктор
- •Не создавайте дублирующих объектов
- •Уничтожайте устаревшие ссыпки (на объекты)
- •Остерегайтесь методов flnalize
- •Глава 3 Методы, общие для всех объектов
- •Переопределяя метод equals, соблюдайте общие соглашения
- •Переопределяя метод equals, всегда переопределяйте hashCode
- •Всегда переопределяйте метод toStrlng
- •Подумайте над реализацией интерфейса ComparabIe
- •Глава 4 Классы и интерфейсы
- •Сводите к минимуму доступность классов и членов
- •Предпочитайте постоянство
- •Предпочитайте компоновку наследованию
- •Предпочитайте интерфейсы абстрактным классам.
- •Используйте интерфейсы только для определения типов
- •Предпочитайте статические классы-члены нестатическим
- •Глава 5
- •3Амена констрvкций на языке с
- •3Аменяйте структуру классом
- •3Амеияйте объедииеиие иерархией классов
- •/* Помещает данные в одно из полей объединения ... */
- •3Аменяйте конструкцию enum классом
- •Указатель на функцию заменяйте кпассом и интерфейсом
- •Глава 6 Методы
- •Проверяйте достоверность параметров
- •При необходимости создавайте резервные копии
- •Тщательно проектируйте сигнатуру метода
- •Перегружая методы, соблюдайте осторожность
- •Возвращайте массив нулевой длины, а не null
- •Для всех открытых элементов арi пишите dос - комментарии
- •Глава 7 Общие вопросы программирования
- •Сводите к минимуму область видимости локальных переменных
- •Изучите библиотеки и пользуйтесь ими
- •Не используйте строку там, где более уместен иной тип
- •При конкатенации строк опасайтесь потери производительности
- •Для ссыпки на объект используйте его интерфейс
- •Соблюдайте осторожность при использовании машинно-зависимых методов
- •Соблюдайте осторожность при оптимизации
- •Глава 8 Исключения
- •Используйте исключения лишь в исключительных ситуациях
- •Применяйте обрабатываемые исключения для восстановления, для программных ошибок используйте исключения времени выполнения
- •Избегайте ненужных обрабатываемых исключений
- •Предпочитайте стандартные исключения
- •Инициируйте исключения, соответствующие абстракции
- •Для каждого метода документируйте все инициируемые исключения
- •В описание исключения добавляйте информацию о сбое
- •Добивайтесь атомарности методов по отношению к сбоям
- •Не игнорируйте исключений
- •Глава 9 Потоки
- •Синхронизируйте доступ потоков к совместно используемым изменяемым данным
- •Избегайте избыточной синхронизации
- •Никогда не вызывайте метод wait вне цикла
- •Не попадайте в зависимость от планировщика потоков
- •При работе с потоками документируйте уровень безопасности
- •Избегайте группировки потоков
- •Глава 10 Сериализация
- •Соблюдайте осторожность при реализации интерфейса SerializabIe
- •Рассмотрите возможность использования специализированной сериализованной формы
- •Метод readObject должен создаваться с защитой
- •При необходимости создавайте метод readResolue
Указатель на функцию заменяйте кпассом и интерфейсом
Язык С поддерживает указатели на функции (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) сравнения строк.
109
Как часто бывает с классами конкретных методик сравнения, класс 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(); }
});
110
Поскольку интерфейс методики сравнения используется как тип для всех экземпляров конкретных методик сравнения, для того чтобы предоставить конкретную методику сравнения, нет необходимости делать соответствующий класс стратегии открытым. Вместо этого "класс-хозяин" (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, чей тип соответствует интерфейсу стратегии.
111