Java注释是一个大错误
注釋是在Java 5中引入的,我們都為之興奮。 如此出色的工具可以縮短代碼! 不再有Hibernate / Spring XML配置文件! 只是注釋,就在我們需要它們的代碼中。 沒有更多的標記接口 ,只有運行時保留的 反射可發現注釋! 我也很興奮。 此外,我制作了一些開源庫,這些庫大量使用注釋。 以jcabi-aspects為例。 但是,我不再感到興奮。 而且,我相信注釋是Java設計中的一個大錯誤。
長話短說,注釋存在一個大問題-它們鼓勵我們在對象 外部實現對象功能,這與封裝的原理背道而馳 。 該對象不再是固體,因為它的行為不是完全由其自己的方法定義的-它的某些功能保留在其他地方。 為什么不好? 讓我們看幾個例子。
@Inject
假設我們使用@Inject注釋屬性:
import javax.inject.Inject; public class Books {@Injectprivate final DB db;// some methods here, which use this.db }然后我們有一個注入器,它知道要注入什么:
Injector injector = Guice.createInjector(new AbstractModule() {@Overridepublic void configure() {this.bind(DB.class).toInstance(new Postgres("jdbc:postgresql:5740/main"));}} );現在我們正在做的類的實例Books通過容器:
Books books = injector.getInstance(Books.class);Books類不知道如何以及誰將類DB實例注入其中。 這將在幕后和無法控制的地方發生。 注射即可。 看起來很方便,但是這種態度會對整個代碼庫造成很大的損害。 控件丟失(不是倒置,而是丟失!)。 該對象不再負責。 它不能對發生的事情負責。
相反,這是應該如何做:
class Books {private final DB db;Books(final DB base) {this.db = base;}// some methods here, which use this.db }本文說明了為什么首先要使用依賴注入容器是一個錯誤的主意: 依賴注入容器是代碼污染者 。 注釋基本上激發了我們制造容器并使用它們。 我們將功能移出對象之外,然后將其放入容器或其他地方。 那是因為我們不想一遍又一遍地重復相同的代碼,對嗎? 沒錯,復制是不好的,但是將對象撕裂甚至更糟。 更糟 對于ORM(JPA / Hibernate),也正是如此,在其中正在積極使用注釋。 檢查這篇文章,它解釋了ORM的問題: ORM是一種進攻性的反模式 。 注釋本身并不是主要動機,但它們通過將對象撕裂并在不同位置保留零件來幫助我們和鼓勵我們。 它們是容器,會話,管理器,控制器等。
@XmlElement
要將POJO轉換為XML時,這就是JAXB的工作方式 。 首先,將@XmlElement批注附加到getter:
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Book {private final String title;public Book(final String title) {this.title = title;}@XmlElementpublic String getTitle() {return this.title;} }然后,創建一個編組器,并要求它將Book類的實例轉換為XML:
final Book book = new Book("0132350882", "Clean Code"); final JAXBContext ctx = JAXBContext.newInstance(Book.class); final Marshaller marshaller = ctx.createMarshaller(); marshaller.marshal(book, System.out);誰在創建XML? 不是book 。 課堂以外的其他人Book 。 這是非常錯誤的。 相反,這是應該完成的方式。 首先,不了解XML的類:
class DefaultBook implements Book {private final String title;DefaultBook(final String title) {this.title = title;}@Overridepublic String getTitle() {return this.title;} }然后,將其打印到XML的裝飾器 :
class XmlBook implements Book{private final Book origin;XmlBook(final Book book) {this.origin = book;}@Overridepublic String getTitle() {return this.origin.getTitle();}public String toXML() {return String.format("<book><title>%s</title></book>",this.getTitle());} }現在,為了以XML 印刷書籍,我們執行以下操作:
String xml = new XmlBook(new DefaultBook("Elegant Objects") ).toXML();XML打印功能位于XmlBook 。 如果您不喜歡裝飾器的想法,可以將toXML()方法移至DefaultBook類。 這并不重要。 重要的是,功能始終位于對象內部,即位于對象所屬的位置。 只有對象知道如何將自己打印到XML。 沒有人!
@RetryOnFailure
這是一個示例(來自我自己的庫 ):
import com.jcabi.aspects.RetryOnFailure; class Foo {@RetryOnFailurepublic String load(URL url) {return url.openConnection().getContent();} }編譯后,我們運行一個所謂的AOP編織器 ,該編織器從技術上將我們的代碼轉換為如下形式:
class Foo {public String load(URL url) {while (true) {try {return _Foo.load(url);} catch (Exception ex) {// ignore it}}}class _Foo {public String load(URL url) {return url.openConnection().getContent();}} }我簡化了在失敗時重試方法調用的實際算法,但是我確定您能理解。 AOP引擎AspectJ使用@RetryOnFailure批注作為信號,通知我們必須將該類包裝到另一個類中。 這是在幕后發生的。 我們沒有看到實現重試算法的補充類。 但是AspectJ編織器產生的字節碼包含Foo類的修改版本。
這正是這種方法的問題所在-我們看不到也不控制該補充對象的實例化。 對象組合是對象設計中最重要的過程,它隱藏在幕后的某個地方。 您可能會說,因為它是補充,所以我們不需要看它。 我不同意。 我們必須看到我們的對象是如何構成的。 我們可能不在乎它們如何工作,但是我們必須看到整個合成過程。
更好的設計如下所示(而不是注釋):
Foo foo = new FooThatRetries(new Foo());然后,執行FooThatRetries :
class FooThatRetries implements Foo {private final Foo origin;FooThatRetries(Foo foo) {this.origin = foo;}public String load(URL url) {return new Retry().eval(new Retry.Algorithm<String>() {@Overridepublic String eval() {return FooThatRetries.this.load(url);}});} }現在,執行Retry :
class Retry {public <T> T eval(Retry.Algorithm<T> algo) {while (true) {try {return algo.eval();} catch (Exception ex) {// ignore it}}}interface Algorithm<T> {T eval();} }代碼更長嗎? 是。 比較干凈嗎? 多很多。 我感到遺憾的是,兩年前我開始使用jcabi-aspects時還不了解它。
底線是注釋不好。 不要使用它們。 應該用什么代替呢? 對象組成 。
有什么會比注釋更糟? 配置 。 例如,XML配置。 Spring XML配置機制是糟糕設計的完美示例。 我已經說過很多次了。 讓我再重復一遍-Spring Framework是Java世界中最差的軟件產品之一。 如果您可以遠離它,那么您將對自己有很大幫助。
OOP中不應有任何“配置”。 如果它們是真實對象,我們將無法對其進行配置。 我們只能實例化它們。 實例化的最佳方法是運算符new 。 該運算符是OOP開發人員的關鍵工具。 把它從我們手中奪走并給予我們“配置機制”是不可原諒的罪行 。
- Java注釋是一個大錯誤(在線講座#14); 2016年5月4日; 744意見; 13個贊
- 依賴注入容器不是一個好主意(網絡研討會9); 2015年12月1日; 1264意見; 19個贊
- 為何吸氣與反吸是反模式? (第4場網絡研討會); 2015年7月1日; 3095次點擊; 53個贊
翻譯自: https://www.javacodegeeks.com/2016/11/java-annotations-big-mistake.html
總結
以上是生活随笔為你收集整理的Java注释是一个大错误的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑病毒熊猫(关于熊猫病毒)
- 下一篇: win10电脑注册表修复软件(win10