工做一年,維護工程項目的同時一直寫CURD,最近學習DDD,結合以前本身寫的開源項目,深思咱們這種CURD的編程方式的弊端,和朋友討論後,發現咱們歷來沒有面向對象開發,因此寫這篇文章,但願更多人去思考面向對象,不僅是停留在背書上java
下面以開發一個常規的登陸模塊爲例,模擬實現一個登陸功能,一步步地去說明其中的弊端和從新解釋面向對象程序員
建立模型數據庫
@Data @NoArgsConstructor class User{ private Integer Id; private String name; private String password;//加密過的密碼 private Integer status;//帳號狀態 } class UserRepository{ User getByName(String name); }
咱們都知道mvc,因此會這麼寫編程
class UserController{ @RequestMapping("/login") public void login(String name,String password){ userService.login(name,password); } } class UserService{ public void login(String name,String password); } class UserServiceImpl implements UserService{ public void login(String name,String password){ //1.查出這個用戶 User user = userRepo.getByName(name); //2.檢查狀態 if(user.getStatus()!=1){ //登陸失敗 } //3.檢查密碼 if(!Objects.equals(md5(password),user.getPassword())){ //登陸失敗 } //登陸後續 } }
雖然這個login方法有點醜,這仍是沒有打點,日誌,生成登陸態的狀況下。咱們全部的業務都寫在了UserService裏面,可能不少人不以爲這樣寫有什麼問題。若是代碼寫多一點的程序員,可能會把每一步都抽成一個方法設計模式
public void login(String name,String password){ //1.查出這個用戶 User user = userRepo.getByName(name); //2.檢查狀態 checkUserStatus(); //3.檢查密碼 checkPassword(); //登陸後續 }
這樣看是好看不少,可是換湯不換藥,維護過工程項目的同窗都會發現,項目裏基本都是這種代碼,維護起來成本極高:數據結構
login方法被抽成幾個方法,login方法是簡單了,service卻臃腫了mvc
service臃腫後開始拆分service,再不濟開始創建多一層manage之類的app
複用極其困難,由於checkUserStatus這種方法每每是私有,而且這種抽離對其它業務場景是否合適也很差說單元測試
在代碼開始出現冗餘時,會開始寫一些帶有業務邏輯的Utils,把污染擴散到Utils學習
因爲複用極其困難,開始出現多個相似功能的方法,分佈在不一樣類裏,後繼維護項目的人很難分清相似方法的區別
由於很差統一表達語義,DTO等對象會在service層氾濫,controller和service耦合嚴重,致使分層變得沒有意義
1,2實際上是一個死循環,最後直接反映到項目難以維護上
在多數據源,多事務的狀況下,難以肯定事務邊界,容易出現事務不能回滾的狀況
單元測試的編寫是個噩夢,嘗試寫單測的同窗應該深有體會
爲何會這樣呢?由於咱們到這裏爲止,依然仍是面向過程編程,徹底沒有面向對象的思惟。代碼其實都是堆起來,責任和邊界不清晰,致使複用很難,維護變動的成本很高,因此項目通過多人維護後會變得更嚴重。惟一像面向對象的代碼就是User user = userRepo.getByName(name)這一句了
爲何說這一句有面向對象的意味?由於這行含義十分明顯,誰作了什麼,我以爲這是一個很好的判斷原則,在scala裏面,是能夠把a.do(thing)
寫成a do thing
,主語肯定了責任,邊界。在這裏,用戶repo獲取(生成)一個用戶對象。雖然咱們一直在說OO,什麼封裝繼承多態,六大原則,張口就來,可是一寫起代碼就變成過程式開發。不少人說設計模式很難學,用不上,很大緣由是連對象是什麼都沒概念,還怎麼談面向對象設計
有人會問,上面的User不是對象嗎?這個問題我在學校的時候也被別人問過,當時也以爲很疑惑。當時的問題是這樣的,你以爲上面的User和下面這個有區別嗎
struct User { int id; char name[50]; char password[50]; int status; } user;
是的,這是c語言的結構體。你固然不會說這個是對象。這裏有個誤區,咱們平時說的Java對象,其實指的是面嚮對象語言Java裏類的實例,並不等同於面向對象裏的對象。因此上面java對象也不見得是真的OO對象
能夠看一下維基百科關於對象的說法
OO的對象應該是data+behavior,因此咱們上面的User對象沒有行爲,只是一個數據結構。試想一下,我是用戶,校驗密碼應該是我本身的事,我用什麼加密應該也是我來決定,甚至我加不加密也是我說了算。一樣的,個人狀態應該也是我來管理,咱們的User能夠改形成這樣
@Data @NoArgsConstructor class User{ private Integer Id; private String name; private String password;//加密過的密碼 private Integer status;//帳號狀態 public boolean checkPassword(String pass){ return Objects.equals(md5(pass),this.password); } public boolean isNormal(){ return this.status==1 } //這裏囉嗦一下,有時候咱們不太好把行爲寫到數據庫模型類,能夠單獨創建一個User類,這個User類也就是DDD裏面的領域對象。若是持久層使用JPA,JPA的數據模型類便是領域對象,JPA容許經過註解去把領域對象綁定到數據模型上。 }
這樣,Service的代碼就簡單不少,只須要關注登陸的邏輯,不須要關心細節
public void login(String name,String password){ //1.查出這個用戶 User user = userRepo.getByName(name); //2.檢查狀態 if(!user.isNormal()){ } //3.檢查密碼 if(!user.checkPassword(password)){ } //登陸後續 }
把固有的邏輯由對象自己負責,責任分明,邊界清晰,業務邏輯統一集中,編寫單測更容易
更重要的是,咱們的User對象創建起來,有關用戶相關的邏輯,方法,咱們能夠經過User來表達,而且能夠在各個分層中傳遞,統一業務表達語言,能夠有效遏制DTO在Service層氾濫的問題。後續會說明一下DTO的問題
理解了對象是什麼後,會更好地反思封裝的重要性,進而深刻理解六大原則的含義,開始抽象出接口,在實踐接口的基礎上慢慢地會造成一些手法和技巧,那即是設計模式。而這一切都須要在開發時保持思考,這樣寫是否流程清晰,邊界分明,複用是否容易,最重要的是,是否符合業務的表達,而不是寫出service類do anything的過程式代碼