JSP+Servlet培训班作业管理系统[13] -不止于CRUD的设计

PS:稍微讲点题外话,本来猫哥想的是JSP+Servlet培训班作业管理系统就按最最简单的实现,后来发现一句句代码硬敲实在是太费劲,所以折腾出了个后端设计。等设计到本篇所讲的这一版,猫哥发现不能再修改了,因为设计之路是无穷尽的,包容的功能越多就越复杂,最后可能发现自己是在设计一种模式、或者设计一种框架了,没那个必要,因为毕竟目前大家的水平也远远达不到设计框架的水准(当然也可以硬设计一个难用的框架),但是从设计的过程来讲,反而起到了对框架加深理解的作用,无心插柳柳成荫,这个好。纯属PS….

在前一篇中,我们实现了通用CRUD后端的设计,根据设定的规则,针对实体开发增删改查功能时很多重复代码得到了省略。但是遇到非CRUD的功能,比如用户登录,比如查看人员时只查看学生,比如学生选课等等,都是CRUD后端无法实现的,需要另外写Servlet和对应的逻辑,这就导致CRUD后端相当的鸡肋。

那么,为啥导致了鸡肋,我们想要的是大腿啊。其实从设计开始,就目光短浅了,设计之处,就把后端定位为“解决实体对应的增删改查”功能的后端,后边在实现的时候,就是按这个实现的,所以面对超出设计的功能时,实现必然也跟不上。

那么我们重新来设计下整个思路,首先我们把整个网站的作用抽象为“请求-应答”,这个够抽象了吧。然后我们依然借助于Servlet来接受请求,并返回应答,但是我们把Servlet接收到请求后,如何返回应答定义为一个Action(动作)。也就是说Servlet仅承担输入、输出功能,具体逻辑交给Action来处理。

好的,那么Action应该具备一个处理的功能吧,没问题,我们先设计一个接口规范Action需要遵循的标准如下:

package inter;
import action.support.ActionContext;
import exception.MyException;
//Servlet分配任务后Action类应实现的功能
public interface IAction {
    //执行具体动作,并返回消息,期间可能抛出异常
    public ActionContext execute()throws MyException;
}

IAction简单的解释几点:

1,IAction是一个接口,接口可以认为是一种规范,实现我这个接口的类必须遵循这种规范。所谓的规范就是要会一种“功夫”,此处的功夫就是execute,即执行具体的动作处理业务逻辑。

2,执行动作必然需要得到相关的信息,同时呢,还得返回处理的结果,这些信息猫哥把他们封装在了ActionContext中,所谓的ActionContext就是动作上下文的意思,上下文的意思就是运行环境,所以翻译为:动作执行所需要的环境。小伙伴们若是将其命名为ActionParam也无可厚非,毕竟萝卜白菜各有所爱,但是猫哥觉得context这个词很是高端,爱不释手啊。

3,既然是执行具体的业务逻辑,肯定可能有异常,比如数据库连接异常,参数异常等等,那么的话就给execute添加一个thows MyException表示“俺办execute这事时可能会出事,不过出事我不管,谁让我办事谁负责”,这个非常合理,主谋负主要责任,杀手么,人家手持免责声明呢,法律意识强,人生就是棒!

OK,看来大家还真不能小瞧了小小的一个接口,接口即是一种程序结构的基础逻辑定义,又是一种可靠的规范(实现接口的类必须实现接口定义的方法),你找个有厨师证的人可能他不会炒菜,你找个实现了IAction规范的类它必然会execute,就是这么叼。

好的,所以不管是什么动作,我就实现IAction接口,然后根据具体的消息作不同的处理就是了。

那么下面一个关键的问题,就是接口中出现的ActionContext ,动作上下文。也就是动作的输入和输出了,前面我们讲了Servlet只管承担输入、输出功能,动作负责接受输入、并输出结果。那么最简单的ActionContext可以设计如下:

public class ActionContext {
    private Map<String,Object> inputParams=null;//需要携带的参数
    private Map<String,Object> outputParams=null;//需要返回的结果
        //get set省略...

OK,这个太强了,管它Servlet有多少输入参数,直接逐一放在inputParams里面。处理完之后想返回多少结果,直接放outputParams里面。比如删除用户输入参数类似method=delete&entityType=User&entityId=1
那么我么可以

inputParams.put("operationType","delete");
inputParams.put("entityType","User");
inputParams.put("entityId",1);

好的,实际上猫哥是这样设计的:

package action.support;
import java.util.List;
import java.util.Map;
public class ActionContext {
    //请求信息,注意因为前四个参数常用,所以猫哥并未把它们放在inputParams里面,实际上也可以放在里面
    private String operationType=null;//操作类型比如view add edit save delete 此处可拓展
    private String entityType=null;//实体类型 Course Job Lesson Role User Work
    private String page=null;//页码
    private int entityId=-1;//实体对应唯一编号
    private Map<String,Object> inputParams=null;//需要携带的参数
    //返回信息,注意因为actionUrl基本上都需要返回,所以单独设置一个参数
    private String actionUrl=null;//需要跳转到网页
    private Map<String,Object> outputParams=null;//需要返回的结果
    public Map<String, Object> getInputParams() {
        return inputParams;
    }
    public void setInputParams(Map<String, Object> inputParams) {
        this.inputParams = inputParams;
    }
    public Map<String, Object> getOutputParams() {
        return outputParams;
    }
    public void setOutputParams(Map<String, Object> outputParams) {
        this.outputParams = outputParams;
    }
    public String getEntityType() {
        return entityType;
    }
    public void setEntityType(String entityType) {
        this.entityType = entityType;
    }
    public String getOperationType() {
        return operationType;
    }
    public void setOperationType(String operationType) {
        this.operationType = operationType;
    }
    public int getEntityId() {
        return entityId;
    }
    public void setEntityId(int entityId) {
        this.entityId = entityId;
    }
    public String getPage() {
        return page;
    }
    public void setPage(String page) {
        this.page = page;
    }
    public String getActionUrl() {
        return actionUrl;
    }
    public void setActionUrl(String actionUrl) {
        this.actionUrl = actionUrl;
    }
}

注意哈,之所以变得这么复杂,纯属猫哥把常用的参数从Params中提取出来了,便于快速使用。其中需要特别注意的是private String actionUrl=null;这个参数,它负责携带动作执行完成后需要显示的网页的地址。

那么问题就来了,所有的Action都需要ActionContext吗,答案是肯定的,因为起码所有的Action都得有个处理结果返回吧,至少得返回Action执行完要跳哪个网页吧,所以所有的Action必然得有个ActionContext属性。OK,那么我们每次写Action都得设置上这个属性,然后实现其set、get方法,烦不烦?

非常烦!!基于避免重复的原则,猫哥新增一类:

package action.support;
import inter.IAction;
/**
 * 抽象类Action 目的是为了子类自动具有ActionContext类型的成员
 * @author 猫哥
 * @date 2017.2.10
 * 注意该抽象类继承了IAction接口,所以子类也必遵循IAction标准:实现execute
 */
public abstract class Action implements IAction{
    protected ActionContext context;
    public Action(ActionContext context){
        this.context=context;
    }
}

这个类的注释已经写的非常清楚了,有了这个类,子类直接就具备ActionContext属性,并且由于该类遵循IAction规范,所以也子类也必须实现execute。

至此基本的设计都完成了,基本流程也清晰如下:

1,Servlet接受参数
2,生成参数对应的ActionContext
3,交给对应的Action处理
4,Action处理完成后,返回带结果的ActionContext
5,根据ActionContext返回信息,显示网页

其中有两块变化的内容,即2、3,由不同的请求决定了2、3中的内容不一样,所以2和3应该单独放在一个固定的类中处理,并且该类跟请求密切相关,其实最好是形成一个xml配置文件,新增一种context-Action,先保存到配置文件,然后根据配置文件装配context然后调用Action,猫哥为了简单直接写为一个ActionController类,也就是控制逻辑的核心类,跟控制相关的功能放于其中。

好的,所有设计讨论完毕,欲知如何实现,且听下回分解。

点赞