Статьи

Дак печатать на Java?

Согласно википедии утка набирает:

стиль динамической типизации, в котором методы и свойства объекта определяют допустимую семантику, а не ее наследование от определенного класса или реализацию определенного интерфейса

Проще говоря

Когда я вижу птицу, которая ходит как утка и плавает как утка, а крякает как утка, я называю эту птицу уткой

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

1
2
3
def printEntity = {entity ->
 println 'id: ${entity.id}, name: ${entity.name}'
}

Допустим, у нас есть следующий класс

1
2
3
4
class Entity {
 Long id
 String name
}

Таким образом, мы можем вызвать нашу функцию

1
2
printEntity(new Entity(id: 10L, name: 'MyName1'))
id: 10, name: MyName1

Но в то же время мы могли бы передать карту в качестве аргумента

1
2
printEntity(['id':10L, 'name':'MyName2'])
id: 10, name: MyName2

Используя магию метапрограммирования, мы могли бы написать даже следующее

1
2
3
4
5
6
7
8
9
class Ghost {
 def propertyMissing(String name) {
  if (name == 'id') {
   return -1L
  } else if (name == 'name') {
   return 'StubName'
  }
 }
}

И мы все еще сможем вызвать нашу функцию

1
2
printEntity(new Ghost())
id: -1, name: StubName

Добро пожаловать в реальный мир

К счастью, эта концепция может использоваться не только для языков с динамической типизацией, но и для языков с более строгой моделью типизации, таких как Java. В Википедии есть хороший пример реализации утилитной типизации в Java с использованием класса Proxy.

Ну, вы говорите, что практическое использование этого, кроме ощущения себя самым мудрым гуру 🙂 Позвольте мне показать некоторые реальные жизненные задачи, которые были решены в Java с использованием техники утки.

С самого начала у меня был простой генератор отчетов, который запрашивает БД продуктов и выводит id и имя определенной сущности. Но затем клиент говорит: «Я хотел бы также иметь ссылку на страницу сведений о сущности на нашем сайте. Красивая, SEO дружественная ссылка. Не могли бы вы сделать это для меня ». «Конечно», сказал я. После копания нашей кодовой базы я обнаружил классную функцию generateSeoUrl (), которая делает эту работу. Функция принимает один аргумент типа Entity, который является интерфейсом. Поэтому я намеревался наблюдать за реализациями Entity и пытаться использовать одну из них для генерации отчетов. Насколько я был удивлен, обнаружив, что все они являются частью какого-то собственного инструмента ORM, и их конструкторы принимают базу данных запросов для получения всей информации о продукте.

Поэтому, если бы я использовал реализации Entity, мне приходилось иметь дело с одним дополнительным запросом на строку моего отчета, и это недопустимо, так как отчет состоял из огромного количества строк. Поэтому я решил попробовать другой подход и реализовать интерфейс Entity, переопределяя методы, которые используются generateSeoUrl (). Я нажал на ярлык IDE и снова удивился. У сущности было около 50 (!!!) методов. Что ж, я уже знал, что только функции getEntityId () и getName () используются функцией generateSeoUrl (), но опять же, наличие нового класса с 50 пустыми методами для переопределения двух из них, выполняющих полезные действия, мне показалось не очень хорошей идеей.

Таким образом, я решил прекратить попытки кодирования и начать думать 🙂 Расширьте некоторые из реализаций Entity, чтобы предотвратить запросы к БД или copy + paste generateSeoUrl (), и примените их для своих нужд, были варианты, но все же это было не красиво. Особенно, когда мне напомнили утку. Я сказал себе, что у нас есть функция, которая принимает экземпляр Entity, но использует только два метода этого интерфейса, поэтому для выполнения моей задачи мне нужно нечто, похожее на Entity и способное обрабатывать методы getEntityId () и getName ().

Так как entityId и name уже присутствовали в данных, используемых для генерации моего отчета, я мог повторно использовать их в моем новом классе, чтобы заглушки данных для getEntityId () и getName (). Чтобы добиться утки, нам нужно создать Proxy, который также реализует интерфейс InvocationHandler и статический метод для извлечения экземпляра Proxy. Итоговый код моего класса выглядит

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ReportEntitySupport implements InvocationHandler {
 
    public static Entity newInstance(Long entityId, String name) {
        return (Entity) Proxy.newProxyInstance(
                Product.class.getClassLoader(),
                Product.class.getInterfaces(),
                new ReportEntitySupport(entityId, name)
        );
    }
 
    private final String name;
    private final Long entityId;
 
    private ReportEntitySupport(Long entityId, String name) {
        this.name = name;
        this.entityId = entityId;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals('getName')) {
            return this.name;
        } else if (method.getName().equals('getEntityId')) {
            return this.entityId;
        }
        return null;
    }
}

Так как это использовать?

Внутри моего класса генератора отчетов при переборе ResultSet я использую следующие

1
2
3
4
5
6
Long entityId;
String name;
....
Entity entity = ReportEntitySupport.newIntance(entityId, name);
String seoUrl = generateSeoUrl(entity);
....

PS

Этот пост только иллюстрирует, что некоторые необычные для языка Java концепции могут быть успешно применены для выполнения реальных задач, улучшая ваши навыки программирования и делая ваш код более красивым.

Ссылка: Утиная печать на Java? Ну, не совсем от нашего партнера JCG Евгения Шепелюка в блоге jk .