суббота, 29 октября 2011 г.

Чисто программные заморочки

Думаю не всем кто когда-либо будет читать этот блог интересны такие темы, но все же по ходу дела я буду писать и о таких вещах. Если вам не интересно программирование, не разбираетесь и не хотите - просто не читайте.

 

Два дня убил на рефакторинг. Но надеюсь оно того стоило. Хотя по большому счету явно видно что придется перелопачивать все как минимум еще раз. Строго говоря это немного больше чем рефакторинг, но как еще назвать эту работу непонятно. Знаете - подскажите.

На закуску изменил механизм загрузки данных в клиент. Раньше это был обходной маневр через универсальный интерфейс (_._), только чтобы отладить графику, теперь - почти рабочий механизм по синхронизации модели клиента и сервера. Основная же возня была с рефлексией и аннотациями.

 

Надо сказать что и тем и другим я никогда ранее не пользовался и весьма смутно представлял как это работает. И подозреваю что мало кто с этим реально сталкивается, потому что в интернете вопросов на эту тему больше чем ответов, да и вопросов то немного. Пришлось покурить мануалы, а на затылке образовалась мозоль - никак не избавлюсь от привычки чесать репу. Успехи перемежались со зверскими матами в адрес писателей документации - ей богу, иной раз простенький пример объясняет некоторые вещи проще чем килограмм буковок.

 

Нафига козе боян?

Иными словами - зачем мне понадобилась рефлексия и аннотирование? Ну поскольку стандартный механизм сериализации мне не годится, нужно делать что-то свое. У меня уже больше дюжины классов, которые нужно как-то "сериализовать". Это значит при традиционном подходе для каждого класса нужно написать свой метод сохранения и восстановления. При этом не забыть аналогичные вызовы суперклассов если таковые имеются.

И добавляя каждый новый класс - повторять все это снова и снова. И каждый метод нужно отладить.

А если потом я захочу поменять механизм этой "сериализации", то вот тут и начнется самая мартышкина работа. А я ведь обязательно захочу, наверняка со временем появятся новые идеи... И придется опять прошерстить все классы (которых будет уже и не дюжина) и заново все переделать, и отладить.

 

Это и приводит нас к рефлексии и аннотированию. В данном случае одно органично дополняет другое. Как это работает:

я помечаю в своих классах нужные мне поля аннотациями:

@PersistentField, или  @PersistentField(strict=false) или @PersistentField(strict=false, forowner=true)

class Moon {
@PersistentField
double radius;
@PersistentName
double radius;

// А это поле не сериализуем
JComponent view;
}

и создаю интерфейс аннотаций

@Retention(RetentionPolicy.RUNTIME) // Важно! - эта строчка говорит компилятору сохранить данные об аннотациях в 
// в готовой программе
public @interface PersistentField {
boolean strict() default true; // выдавать ли это поле в ограниченной выдаче
boolean forowner() default true; // выдавать это поле хозяину объекта
}

компилятору на это начхать. Но теперь через рефлексию я могу обнаружить эти пометки и сделать для этих полей соответствующую обработку. А сама рефлексия позволяет сделать одну общую на все классы процедуру "сериализации" и соответственно хватит одной на "десериализацию". В них я могу получить список всех полей объекта и надругаться над ними сохранить/восстановить.

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

Пусть object - это ваш объект черт его знает какого класса 

Class cl=object.getClass() // узнаем его класс

Fields[] flds=cl.getDeclaredFields() // Узнаем какие поля в нём объявлены (выдает все, включая константы, private и static)

Class scl=cl.getSuperclass(); // получаем суперкласс (null = нету) - его поля нам тоже нужны, getDeclaredFields для потомка их не возвращает, нужно делать рекурсию

f.isAnnotationPresent(PersistentField.class) // f это Field. Проверяем - есть ли наша аннотация

PersistentField annotation=f.getAnnotation(PersistentField.class); // получаем аннотацию - из нее можно теперь добыть параметры strict и forowner

f.setAccessible(true); // чтобы менять значение даже в приватных полях

String name=f.getName();  // думаю понятно

Object value=f.get(object);
f.set(object, data); // Легко засунуть данные в объект, если вы имеете их в виде подходящего объекта
Class fdc=f.getType(); // декларированный тип поля.

String data // данные которые вы  хотите засунуть в поле, преобразовав из строки в double, int и т.п.
fdc=primitives.get(fdc.getName()); // primitives это Map. 
// Задает классы обертки для примитивных типов  "int" -> Integer.class "double"->Double.class и т.п. 
//-  придется сделать самим
obj=fdc.getConstructor(new Class[]{String.class}).newInstance(data); //
// Получаем конструктор с параметром типа String (они есть для типов Boolean, Integer,Long,Float,...)
// и вызываем его с нашей строкой.

Для работы с массивами понадобится еще

Class cc=fdc.getComponentType()
Object arr=Array.newInstance(cc, len);
Array.set(arr,i,data); // data - объект уже подходящего типа, см выше как получать

Все остальное - достаточно хорошо описано или очевидно. Вроде isArray isPrimitive и т.п. Курить документацию JDK на предмет Class, Field, Method, Array, Constructor, Annotation

Да... еще мне пригодилось getInterfaces, но нужно иметь ввиду что возвращаются интерфейсы указанные именно в том классе который запрашивается. Т.е если ваш класс реализует некий Interface1, но это было указано в его суперклассе, а не в нем самом, то этот интерфейс нужно искать именно в суперклассе, что совсем не очевидно.

 

Комментариев нет:

Отправить комментарий