стиль динамической типизации, в котором методы и свойства объекта определяют допустимую семантику, а не ее наследование от определенного класса или реализацию определенного интерфейса
Проще говоря
Когда я вижу птицу, которая ходит как утка и плавает как утка, а крякает как утка, я называю эту птицу уткой
В языках с динамической типизацией эта функция позволяет создавать функции, которые не проверяют тип передаваемого объекта, а вместо этого полагаются на существование определенных методов / свойств внутри него и выдают исключение времени выполнения, когда эти свойства не найдены. Например, в 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 .