动态类型的类型,其中对象的方法和属性确定有效的语义,而不是其从特定类或特定接口的实现继承
用简单的话
当我看到一只鸟走路像鸭子,游泳像鸭子,嘎嘎像鸭子一样时,我称那只鸟为鸭子
在具有动态类型的语言中,此功能允许创建的功能不检查传递的对象的类型,而是依赖于其中存在的特定方法/属性,并在找不到这些属性时抛出运行时异常。 例如,在groovy中,我们可以使用一种方法来打印有关某个实体的信息
def printEntity = {entity ->println 'id: ${entity.id}, name: ${entity.name}'
}
假设我们有以下课程
class Entity {Long idString name
}
这样我们就可以调用我们的函数
printEntity(new Entity(id: 10L, name: 'MyName1'))
id: 10, name: MyName1
但是同时我们可以将map作为参数传递
printEntity(['id':10L, 'name':'MyName2'])
id: 10, name: MyName2
使用一些元编程魔术,我们甚至可以编写以下内容
class Ghost {def propertyMissing(String name) {if (name == 'id') {return -1L} else if (name == 'name') {return 'StubName'}}
}
而且我们仍然可以调用我们的函数
printEntity(new Ghost())
id: -1, name: StubName
欢迎来到真实的世界
幸运的是,这个概念不仅可以用于具有动态类型的语言,而且可以用于具有更严格类型模型的语言,例如Java。 Wikipedia很好地说明了使用Proxy类在Java中进行鸭子输入的实现。
好吧,你说,除了让自己成为最聪明的专家以外,这的实际用途是什么:)让我展示一些使用鸭子类型技术在Java中解决的现实生活任务。
从一开始,我就拥有一个简单的报表生成器,该报表生成器查询产品的数据库并输出某些实体的ID和名称。 但随后客户说:“我还想链接到我们网站上的实体详细信息页面。 美丽的SEO友好链接。 你能对我做吗? “当然”,我说。 深入研究我们的代码库后,我发现很酷的函数generateSeoUrl()可以完成这项工作。 该函数采用Entity类型的一个参数,即interface。 因此,我的目的是观察Entity的实现,并尝试使用其中之一进行报告生成。 当我发现它们全部都是某些自制的ORM工具的一部分并且他们的构造函数接受查询DB以获取有关产品的全部信息后,我感到非常惊讶。
因此,如果我使用的是Entity实现,则必须在报表的每一行中处理一个额外的查询,这是不可接受的,因为报表由大量的行组成。 因此,我决定尝试其他方法并实现Entity接口,该方法覆盖了generateSeoUrl()使用的方法。 我单击了IDE快捷方式,再次感到惊讶。 实体有大约50(!!!)个方法。 好吧,我已经知道generateSeoUrl()函数仅使用getEntityId()和getName(),但是再说一次,使用具有50个空方法的新类来覆盖其中的2个做有用的动作对我来说不是一个好主意。
因此,我决定停止尝试编码,并开始思考:)扩展某些Entity实现以防止查询数据库或复制+粘贴generateSeoUrl()并根据我的需要采用它,但是这些选择仍然很漂亮。 特别是当我提醒鸭子打字时。 我对自己说,我们有一个采用Entity实例但仅使用此接口的两种方法的函数,因此要完成我的任务,我需要看起来像Entity并能够处理getEntityId()和getName()方法的东西。
由于实体ID和名称已经存在于用于生成报告的数据中,因此我可以在新类中重用它们以对getEntityId()和getName()的数据进行存根。 为了实现鸭子类型,我们需要创建Proxy,该Proxy还实现InvocationHandler接口和静态方法来检索Proxy实例。 我班的最终代码看起来像
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;}@Overridepublic 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时,我正在使用以下代码
Long entityId;
String name;
....
Entity entity = ReportEntitySupport.newIntance(entityId, name);
String seoUrl = generateSeoUrl(entity);
....
聚苯乙烯
这篇文章仅说明了一些不常见的Java语言概念可以成功地用于完成现实生活中的任务,从而提高您的编程技能并使代码更漂亮。
参考: 鸭子在Java中打字? 嗯,这不完全是我们JCG合作伙伴 Evgeny Shepelyuk在jk的博客博客上获得的。
翻译自: https://www.javacodegeeks.com/2012/09/duck-typing-in-java-well-not-exactly.html