尺度不变性是指什么不变_不变性如何提供帮助
尺度不變性是指什么不變
在最近的幾篇文章中,包括“ Getters / Setters。 邪惡。 期。” , “對象應該是不可變的”和“依賴注入容器是代碼污染者” ,我普遍將所有可變對象標記為“ setter”(以set開頭的對象方法)。 我的論證主要是基于隱喻和抽象實例。 顯然,這對你們中的許多人來說還不夠令人信服-我收到了一些要求提供更具體和實際示例的請求。
因此,為了說明我對“通過setter進行的可變性”的強烈反對態度,我從Apache那里獲取了一個現有的commons-email Java庫,并以不依賴setter和“對象思考”的方式重新設計了它。 我作為jcabi家族的一部分— jcabi-email發布了我的庫。 讓我們看看從沒有吸氣劑的“純”面向對象,不變的方法中獲得的好處。
如果您使用commons-email發送電子郵件,則代碼外觀如下:
Email email = new SimpleEmail(); email.setHostName("smtp.googlemail.com"); email.setSmtpPort(465); email.setAuthenticator(new DefaultAuthenticator("user", "pwd")); email.setFrom("yegor@teamed.io", "Yegor Bugayenko"); email.addTo("dude@jcabi.com"); email.setSubject("how are you?"); email.setMsg("Dude, how are you?"); email.send();這是使用jcabi-email的相同方法:
Postman postman = new Postman.Default(new SMTP("smtp.googlemail.com", 465, "user", "pwd") ); Envelope envelope = new Envelope.MIME(new Array<Stamp>(new StSender("Yegor Bugayenko <yegor@teamed.io>"),new StRecipient("dude@jcabi.com"),new StSubject("how are you?")),new Array<Enclosure>(new EnPlain("Dude, how are you?")) ); postman.send(envelope);我認為區別很明顯。
在第一個示例中,您正在處理一個怪物類,該類可以為您做所有事情,包括通過SMTP發送MIME消息,創建消息,配置其參數,向其中添加MIME部分等。Common的Email類電子郵件確實是一個巨大的類-33個私有屬性,一百多種方法,大約兩千行代碼。 首先,通過一堆設置器配置類,然后要求它為您send()電子郵件。
在第二個示例中,我們通過七個new調用實例化了七個對象。 Postman負責打包MIME郵件; SMTP負責通過SMTP發送它; 標記( StSender , StRecipient和StSubject )負責在傳遞之前配置MIME消息; EnPlain附件負責為我們要發送的消息創建MIME部分。 我們構造了這七個對象,將它們封裝在一起,然后要求郵遞員為我們send()信封。
可變電子郵件有什么問題?
從用戶的角度來看,幾乎沒有錯。 Email是一門功能強大的類,具有多個控件-只需單擊正確的控件即可完成工作。 但是,從開發人員的角度來看, Email類是一場噩夢。 主要是因為該課程非常大且難以維護。
因為該類非常大 , 所以每次您想通過引入新方法來對其進行擴展時,您都面臨著使該類變得更糟的事實,即更長,更缺乏凝聚力,可讀性更差,更難以維護等。感覺到您正在挖掘骯臟的東西,沒有希望使其變得更清潔。 我敢肯定,您對這種感覺很熟悉-大多數舊版應用程序都是這樣的。 它們具有巨大的多行“類”(實際上是用Java編寫的COBOL程序),這些類是從您之前的幾代程序員那里繼承而來的。 當您開始時,您精力充沛,但是在滾動了這樣的“課堂”幾分鐘后,您會說-“擰緊,幾乎是星期六”。
由于類很大 ,因此不再存在數據隱藏或封裝的問題-超過100種方法可訪問33個變量。 隱藏了什么? 實際上,此Email.java文件是一個大型的程序化2000行腳本,被誤稱為“類”。 一旦通過調用類的方法之一跨越了類的邊界,任何東西都不會被隱藏。 之后,您就可以完全訪問可能需要的所有數據。 為什么這樣不好? 那么,為什么我們首先需要封裝? 為了保護一個程序員免受另一種程序員的攻擊,又稱為防御性編程 。 當我忙于更改MIME消息的主題時,我想確保自己不會被其他方法的活動所干擾,即正在更改發件人并誤觸了我的主題。 封裝可幫助我們縮小問題的范圍,而此Email類的作用恰恰相反。
由于類太大 , 因此它的單元測試比類本身還要復雜。 為什么? 由于其方法和屬性之間存在多種相互依賴關系。 為了測試setCharset()您必須通過調用其他一些方法來準備整個對象,然后必須調用send()以確保要發送的消息實際上使用了您指定的編碼。 因此,為了測試單行方法setCharset()您運行了整個集成測試方案,即通過SMTP發送完整的MIME消息。 顯然,如果其中一種方法發生更改,幾乎每種測試方法都會受到影響。 換句話說,測試非常脆弱,不可靠且過于復雜。
我可以繼續講這個“ 因為班級很大 ”,但是我認為很明顯,一個小而有凝聚力的班級總是比大班級更好。 對于我,您和任何面向對象的程序員而言,這都是顯而易見的。 但是,為什么對于Apache Commons Email的開發人員來說并不那么明顯? 我不認為他們是愚蠢或沒有受過教育的人。 之后怎么樣了?
它是如何發生的以及為什么發生的?
這就是它總是發生的方式。 您開始將類設計為有凝聚力的,堅固的和小巧的。 您的意圖非常積極。 很快,您意識到該類還有其他事情要做。 然后,還有其他事情。 然后,甚至更多。
使您的類越來越強大的最好方法是添加將設置參數注入到類中的setter,以便它可以在內部處理它們,不是嗎?
這是問題的根本原因! 根本原因是我們能夠通過配置方法(也稱為“設置程序”) 將數據插入可變對象中。 當一個對象是可變的并且允許我們在需要時添加setter時,我們將無限制地進行操作。
讓我這樣說吧– 可變的類傾向于增加規模并失去凝聚力 。
如果commons-email作者在開始時就將此Email類設為不可變的,那么他們將無法向其中添加太多方法并封裝太多屬性。 他們將無法將其變成怪物。 為什么? 因為不可變對象僅通過構造函數接受狀態。 您能想象一個33參數的構造函數嗎? 當然不是。
首先,當您使類不可變時,您必須保持其凝聚力,小巧,牢固和強大。 因為不能封裝太多,也不能修改封裝的內容。 只需一個構造函數的兩個或三個參數就可以了。
我如何設計不可變的電子郵件?
在設計jcabi-email時,我從一個簡單的類開始: Postman 。 好吧,這是一個接口,因為我從來沒有做無接口類。 因此, Postman是…… Postman 。 他正在向其他人傳遞消息。 首先,我創建了它的默認版本(為簡潔起見,我省略了ctor):
良好的開端,它有效。 現在怎么辦? 好吧, Message很難構造。 它是JDK中的一個復雜類,需要進行一些操作才能成為HTML電子郵件。 因此,我創建了一個信封,它將為我構建這個復雜的對象(請注意, Postman和Envelope都是不可變的,并在jcabi-aspects中使用@Immutable進行了注釋):
@Immutable interface Envelope {Message unwrap(); }我還重構Postman以接受信封,而不是消息:
@Immutable interface Postman {void send(Envelope env); }到目前為止,一切都很好。 現在,讓我們嘗試創建一個簡單的Envelope實現:
@Immutable class MIME implements Envelope {@Overridepublic Message unwrap() {return new MimeMessage(Session.getDefaultInstance(new Properties()));} }它可以工作,但是沒有任何用處。 它只會創建一個絕對為空的MIME消息并將其返回。 如何為其添加一個主題以及“ To:和“ From:地址(請注意, MIME類也是不可變的):
@Immutable class Envelope.MIME implements Envelope {private final String subject;private final String from;private final Array<String> to;public MIME(String subj, String sender, Iterable<String> rcpts) {this.subject = subj;this.from = sender;this.to = new Array<String>(rcpts);}@Overridepublic Message unwrap() {Message msg = new MimeMessage(Session.getDefaultInstance(new Properties()));msg.setSubject(this.subject);msg.setFrom(new InternetAddress(this.from));for (String email : this.to) {msg.setRecipient(Message.RecipientType.TO,new InternetAddress(email));}return msg;} }看起來正確且有效。 但這仍然太原始了。 CC:和BCC:如何? 電子郵件文字呢? PDF附件怎么樣? 如果我想指定消息的編碼怎么辦? Reply-To呢?
我可以將所有這些參數添加到構造函數中嗎? 請記住,該類是不可變的,因此我無法介紹setReplyTo()方法。 我必須將replyTo參數傳遞給它的構造函數。 這是不可能的,因為構造函數將有太多的參數,并且沒有人可以使用它。
那么,我該怎么辦?
好吧,我開始思考:我們如何將“信封”的概念分解為較小的概念,這就是我所發明的。 就像現實的信封一樣,我的MIME對象將帶有圖章。 Stamp將負責配置對象Message (同樣, Stamp及其所有實現者都是不可變的):
@Immutable interface Stamp {void attach(Message message); }現在,我可以將我的MIME類簡化為以下內容:
@Immutable class Envelope.MIME implements Envelope {private final Array<Stamp> stamps;public MIME(Iterable<Stamp> stmps) {this.stamps = new Array<Stamp>(stmps);}@Overridepublic Message unwrap() {Message msg = new MimeMessage(Session.getDefaultInstance(new Properties()));for (Stamp stamp : this.stamps) {stamp.attach(msg);}return msg;} }現在,我將為該主題創建圖章, To: ,為From: ,為CC: BCC:為BCC: ,等等。 MIME類將保持不變-小,內聚,可讀,固定等。
這里重要的是為什么我決定在班級相對較小的時候決定進行重構。 確實,當我的MIME類只有25行時,我開始擔心這些標記類。
這正是本文的重點- 不變性迫使您設計小型且具有凝聚力的對象 。
沒有不變性,我本來會和commons-email一樣。 我的MIME類的大小會增加,遲早會變得與commons-email中的Email一樣大。 阻止我的唯一原因是必須對其進行重構,因為我無法通過構造函數傳遞所有參數。
沒有不變性,我就不會有那種動力,而我會做Apache開發人員使用commons-email所做的事情—使類膨脹,并將其變成難以維護的怪物。
那是jcabi-email 。 我希望這個例子足以說明問題,并且您將開始使用不可變的對象編寫更簡潔的代碼。
相關文章
您可能還會發現以下有趣的帖子:
- 配對支架
- 避免字符串串聯
- Java代碼中的典型錯誤
- DI容器是代碼污染者
- Getters / Setters。 邪惡。 期。
翻譯自: https://www.javacodegeeks.com/2014/11/how-immutability-helps.html
尺度不變性是指什么不變
總結
以上是生活随笔為你收集整理的尺度不变性是指什么不变_不变性如何提供帮助的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为手机怎么控制电脑远程办公华为手机怎么
- 下一篇: 如何选购二手笔记本电脑如何买卖二手电脑