背景
之前我一直以为,Java 注解(annotation)和 Python 或者 JavaScript 里的装饰器(decorator)是类似的东西,毕竟看起来很像嘛,都是一个 @
符号;甚至功能也很像,就是给原本的对象“添加”一些效果。
今天才发现,虽然 Python 和 JavaScript 里的装饰器确实是一回事,但 Java 注解和它们完全不同!
注解 vs 装饰器
装饰器
先来看看 Python 和 JavaScript 里的装饰器吧。之所以叫“装饰器”,的确是因为实现了设计模式里的“装饰器模式”(decorator pattern)。
举个例子,Python 里:
# python
@my_decorator
def my_function():
# do something...
return True
就相当于:
# python
def my_function():
# do something...
return True
my_function = my_decorator(my_function) # 这就是装饰器模式的常规用法,Java 里只能这么用
换句话说,my_decorator
接受一个函数,然后输出另一个(通常来说)与之兼容的函数(在 Java 里相当于需要实现同样的接口),但于此同时额外添加了一些功能。
(为什么不把这些功能直接写到原函数里呢?因为那样就破坏了单依职责原则。不相关的逻辑,就应该放到不同的组件中,装饰器模式可以帮助我们实现这一点。)
由于这种模式使用频率如此之高,为了更好的可读性,Python 就增加了“装饰器”这么一个语法糖。上面两种写法完全是等价的。
JavaScript 也是一样,只不过装饰器在 JavaScript 中仍然是一个实验性 feature。
Java 注解
Java 注解乍一看完全就是一回事啊,给一个对象(或者类)增加一个注解,相当于就是给它额外增加了一些功能,并且(通常来说)也不会破坏兼容性,很好地实现了逻辑分离。
举个简单例子,在单元测试里,可以用 Mockito 提供的 @Mock
注解来声明一个 mock 对象:
@Mock
List<String> mockedList;
@Test
public void simpleTest() {
Mockito.when(mockedList.size()).thenReturn(100);
assertEquals(100, mockedList.size());
}
其实这 @Mock
注解的作用,不就相当于是执行了:
mockedList = Mockito.mock(mockedList);
而已嘛!和上面的装饰器如出一辙呀。
确实如此,但从实现原理上,两者还是截然不同的。
区别就在于,如果你只是定义了一些注解,无论你怎么加,它们都不会对被注解的对象(或者类)产生任何影响。注解真的就只是注解,相当于增加了一些“元信息”而已。
要想让注解产生影响,你必须在另外的地方写一些“注解处理逻辑”。通过 Java 的反射 API,你可以获取对象的注解信息,然后再根据这些注解信息来对该对象进行改造。
优缺点对比
感觉装饰器和注解更有千秋吧:
- 装饰器的优点在于清晰明了。通过查看装饰器的定义代码,你一下子就能看出来它是干嘛的;而注解的声明逻辑和处理逻辑是分散在不同地方的,想要读懂就比较麻烦;
- 注解的好处是更灵活。可以这么说,装饰器能实现的,注解都能实现;而注解能实现的,有时候用装饰器实现可能会比较别扭;
比方说,用 Mockito 你可以写出下面的代码:
@Mock
ComponentA mockedComponentA; // 这是一个 mock 对象
@InjectMocks
ComponentB componentB = new ComponentB(); // 上面的 mockedComponentA 会被用做这个对象的成员,当然,前提是 ComponentB 的定义中有一个 ComponentA 类型的成员
@Test
public void testComponentB() {
// do something
}
假如把 @Mock
和 @InjectMocks
换成装饰器,这个功能即便能实现,感觉也会比较别扭。用注解就比较自然。
小结
其实大多数情况下,两者还是非常相似的。只是没想到,背后的原理这么不一样。