工做一年,维护工程项目的同时一直写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的过程式代码