我的Java Web之路 - Java基础(5)- 抽象类和抽象方法

文章目录

    • 介绍
    • Servlet类图
    • 为什么需要抽象类
    • 抽象方法
    • 抽象的层次
    • 总结

介绍

前面的文章粗略的看了一下Tomcat提供的servlet-api.jar包中的Servlet有关的源码,这涉及到了抽象类这个基本概念,本篇文章我们就介绍一下它。

Servlet类图

根据我们看的那几个类和接口的源码,我们可以用类图(这方面涉及到一种叫做统一建模语言UML的标准,暂且不深入讨论)来表示它们之间的关系。

我是用微软的Visio软件来画这种图(关于这个软件的使用也暂且不介绍),可能并不一定满足UML的标准,但核心的东西表示出来就行了:
我的Java Web之路 - Java基础(5)- 抽象类和抽象方法_第1张图片
是不是比源码清晰多了呢!而且还很形象,实线箭头表示继承/扩展(某个类),虚线箭头表示实现(某几个接口),memberName表示属性域或方法,不过我已经省略了,<<抽象类>>是我手动加的,为了区分普通类。

事实上,在软件研发过程中,通常都是先把类图和其他的图设计出来,然后再进行代码的编写实现,或者通过工具软件把这些设计图直接转换成代码。这些设计图就好比建筑工程里面的建筑蓝图。

我们现在是通过源码返推出设计图,这样有助于记忆和理解一个软件的结构/架构、流程等等。

为什么需要抽象类

以前的文章讲过,其实就是人脑中的概念在代码中的体现,那么抽象类当然就是指抽象的概念啊。

什么是抽象?下面是百度汉语给出的答案:

1.不具体,太笼统,细节不明确。
2.哲学范畴。指在认识上把事物的规定、属性、关系从复杂的整体中抽取出来的过程和结果。与“具体”相对。
3.一种脱离实际地、孤立片面地观察问题的方法。
4.指撇开事物的非本质属性,而把本质属性抽取出来的过程和方法。与“概括”相对。是形成概念的一种方法。

现实生活中有太多这样的例子,比如水果、动物、交通工具等等,这些概念都是不具体、太笼统、细节不明确的。而我们的HttpServletGenericServlet也是如此。

HttpServlet是哪些细节不明确呢?当然就是需要用户去实现的业务细节啊,比如具体如何处理HTTP的GET、POST、PUT、DELETE等请求啊。GET什么东西/资源,技术上是指网页、图像、文件、音频、视频、代码等等,业务上又可能是某种商品的细节啊、某篇新闻报道或小说啊、某部电影或某首流行音乐啊等等。

HttpServlet明确的是它是使用HTTP协议来与客户端软件进行通信,所以它提供了与HTTP各个方法对应的成员方法来处理请求。处理的请求是HTTP请求HttpServletRequest,返回的响应HTTP响应HttpServletResponse。事实上,这只是一种约定而已,你完全可以在doGet方法中处理一些提交数据或更新数据或删除数据的业务逻辑。然而,这是极不提倡的,约定不就是需要大家一起遵守的吗!否则,你构建的代码将混乱不堪,给自己和别人维护代码带来极大的困难。代码的可维护性很大程度上取决于约定的遵守程度。

所以,正是因为存在不具体,细节不明确的概念,才需要抽象类。从目标上来说,我们提供抽象类是为了给下游的软件研发人员实现进一步的细节,我们只是预留一个已经明确的约定而已。

抽象方法

GenericServlet也是一个抽象类,那它有哪些细节不明确呢?查看它的源码可以看到它没有HttpServletdoXXXX之类的方法,跟请求和响应相关的方法是下面一个很奇怪的方法,该方法只有方法签名,而没有方法体:

@Override
public abstract void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException;

这就是抽象方法,也就是GenericServlet细节不明确的地方,它的参数是ServletRequestServletResponse,少了HTTP,就是说它连使用何种通信协议都不明确。这意味着这个抽象类没有约定要使用HTTP协议,你可以使用任何其他协议(事实上很少用到)。

那这个方法是什么意思呢?从方法名来看就是提供某种服务。OK,这就对了,不明确的就是提供何种服务啊,就需要用户去进一步实现这个方法啊。

抽象方法从语义上来说就是实现细节不明确的方法,从技术上来讲就只需加一个abstract关键字且不实现方法体即可。

从这个角度看,这个service方法也太抽象了吧,等于什么都没有提供嘛,我们也可以提供一个叫doSomething的抽象方法啊,那这还叫抽象吗?别忘了service方法还有参数呢,ServletRequestServletResponse可是规定了很多特定的方法,当然它们也是抽象方法,不过至少从方法名上可以窥其一二。不过,不得不说service这个方法名起得还是相当不错,语义上就是服务嘛,至于什么服务,就是用户需要实现的啊,可以是用户的注册登录、某个商品的搜索、订单的提交和支付、视频的观看等等。

抽象的层次

既然HttpServlet继承/扩展了GenericServlet,那HttpServlet包含的细节应该比GenericServlet更明确一些,明确在哪呢?就是明确了它是使用HTTP协议的,因此它的service方法是这样的:

public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException {

    HttpServletRequest  request;
    HttpServletResponse response;

    try {
        request = (HttpServletRequest) req;//这里是重点
        response = (HttpServletResponse) res;//这里是重点
    } catch (ClassCastException e) {
        throw new ServletException(lStrings.getString("http.non_http"));
    }
    service(request, response);//这里是重点
}

首先,因为是继承/扩展GenericServlet,所以这个service方法覆盖GenericServlet中的service方法,同时提供了方法体(就是实现了GenericServlet中的service方法)。最主要的逻辑是将ServletRequest req, ServletResponse res强制转换成了HttpServletRequestHttpServletResponse类型,因为只要是进入到HttpServlet的这个service方法,那么可以明确的就是使用了HTTP协议,因此可以进行这种转换(当然,这段强制转换代码被异常处理包围着,就是以防万一转换的不是HTTP协议的Servlet请求和响应,那么就抛出异常,关于异常,我们以后再讨论)。转换之后,又调用了HttpServlet重载的另外一个service方法,该方法的参数就是HttpServletRequestHttpServletResponse类型了(关于方法覆盖和重载,可以看看我的这篇文章)。我们再来看看这个service方法:

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    String method = req.getMethod();//这里是重点

    if (method.equals(METHOD_GET)) {//这里是重点
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            // servlet doesn't support if-modified-since, no reason
            // to go through further expensive logic
            doGet(req, resp);//这里是重点
        } else {
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            } catch (IllegalArgumentException iae) {
                // Invalid date header - proceed as if none was set
                ifModifiedSince = -1;
            }
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                // If the servlet mod time is later, call doGet()
                // Round down to the nearest second for a proper compare
                // A ifModifiedSince of -1 will always be less
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);//这里是重点
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }

    } else if (method.equals(METHOD_HEAD)) {//这里是重点
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);//这里是重点

    } else if (method.equals(METHOD_POST)) {//这里是重点
        doPost(req, resp);//这里是重点

    } else if (method.equals(METHOD_PUT)) {//这里是重点
        doPut(req, resp);//这里是重点

    } else if (method.equals(METHOD_DELETE)) {//这里是重点
        doDelete(req, resp);//这里是重点

    } else if (method.equals(METHOD_OPTIONS)) {//这里是重点
        doOptions(req,resp);//这里是重点

    } else if (method.equals(METHOD_TRACE)) {//这里是重点
        doTrace(req,resp);//这里是重点

    } else {
        //
        // Note that this means NO servlet supports whatever
        // method was requested, anywhere on this server.
        //

        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);

        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

我们只看重点语句,可以看到,这就是从HTTP请求中拿到HTTP方法后,判断是GET、HEAD、POST等等之中的哪一个之后,再调用相应的doXXXX方法。因此,HttpServlet这个抽象类比GenericServlet这个抽象类更加明确一些,明确的是使用HTTP协议来处理请求和响应,但还是不够明确,就是如何处理HTTP协议中的GET、POST之类的请求,这些请求中执行什么业务逻辑,是用户登录还是订单提交支付,还是看电影听音乐等等。这就是我们开发HelloWorld这个Servlet的目的。

可见,抽象具有层次性,这也符合我们的思维习惯。我们设计类的时候,也应该尽量遵从这种思维习惯,尽量把相关的部分抽象出来形成一个层次。基于这一点,我们甚至可以在继承/扩展HttpServlet的时候,继续设计一个抽象类,再往下扩展时又设计一个抽象类。。。直到你判断业务该是设计具体类的时候。

总结

好,以后我们遇到abstract关键字就不会陌生了,就是抽象类或抽象方法的意思;我们也可以设计自己的抽象类和抽象方法了。

  • 类图是一个很好的设计、交流的工具,我们也可以尝试为源码画画类图,这样有助于记忆和理解;
  • 因为存在不具体、细节不明确的概念,所以需要抽象类,这就符合人的思维层次性;
  • 抽象类的目标为了给下游的软件研发人员去实现进一步的细节(即给别人继承/扩展之用的),我们只是根据已经明确的细节预留一个约定而已;
  • 抽象是具有层次性的,因而也就形成了继承/扩展层次
  • 分层的思想很重要,也可以称之为分层模式、分层范式,大的有架构上的分层,小的有方法上的分层;
  • Java中抽象类使用abstract关键字定义
  • Java中抽象方法使用abstract关键字定义且不能实现方法体,因为一丁点细节都不知道啊,唯一知道的就是它的参数和返回值;
  • 拥有抽象方法的类必须定义为抽象类;
  • 但抽象类不必有抽象方法,它可以有具体细节,因为可以提供具体的但很粗糙的默认方法实现,HttpServlet就是这样,但这样的方法往往是需要被覆盖的,它们通常使用protected关键字;
  • 抽象类不能生成对象/实例,但可以声明一个抽象类的引用类型的变量,该变量可以指向一个具体类的对象/实例;
  • 具体类能直接使用new关键字生成对象/实例,语义上就是细节很明确的,不过具体类也可以被继承/扩展,它的方法也能被覆盖。
  • 所谓语义,就是在语言上的含义,它通常是分层的依据,说直接一点就是类名、方法名一定要在一定程度上符合或表达或反映各个层次的细节,不该放在该层的就不要放到该层,这又体现了单一职责原则

你可能感兴趣的:(Java基础)