设计模式-抽象类,只是想为你做更多

如果说面向对象中的接口是把所有的事情扔给你的话,那么抽象类显然是想要为你做一些事情,如果实在有一部分做不了再扔给你。

相信对于大部分业务开发的场景下都不太会需要自己去写抽象类。但在一些特殊场景我们还是不得不搞个抽象类。

还是从实际的使用场景来说起吧。

实际案例

案例一、封装email组件。

public abstract class AbstractEmail implements SendEmailable {
  .......
   public AbstractEmail(boolean builtIn, String from, String pwd, String protocol, String host) {
      if (!builtIn) {
         this.from = from;
         this.protocol = protocol;
         this.pwd = pwd;
         this.host = host;
         this.builtIn = builtIn;
      }
   }

   public AbstractEmail() {
   }

   protected void configure() {
      if (builtIn) {
         from = EmailProperties.getFrom();
         pwd = EmailProperties.getPwd();
         host = EmailProperties.getHost();
         storePath = EmailProperties.getStorePath();
         protocol = EmailProperties.getProtocol();
      }
   }

 
   public synchronized SendResult send(String subject, String content, List<FileDataSource> fileDataSources,
         String receiver) throws SendEmailException {
      configure();
      setSession();
      setDebug(true);
      setTransport();
      connect();
      SendResult result = sendSingle(subject, content, fileDataSources, receiver);
      close();
      return result;
   }

   private SendResult sendSingle(String subject, String content, List<FileDataSource> fileDataSources, String receiver)
         throws SendEmailException {
      Message msg;
      try {
         msg = createMail(subject, content, fileDataSources, receiver);
         setBCCAndCC(msg);
      } catch (Exception e1) {
         throw new SendEmailException(e1);
      }
      SendResult result = new SendResult();
      List<String> success = new ArrayList<String>();
      List<String> fail = new ArrayList<String>();
      try {
         send1(msg);
         success.add(receiver);
      } catch (MessagingException e) {
         log.error(e);
         fail.add(receiver);
      }
      result.setSuccess(success);
      result.setFail(fail);
      return result;
   }

   ............

  .......

   protected abstract Message createMail(String subject, String content, List<FileDataSource> fileDataSources,
         String receiver) throws Exception;

   private void send1(Message msg) throws MessagingException {
      ts.sendMessage(msg, msg.getAllRecipients());
      
   }

   private void close() throws SendEmailException {
      try {
         ts.close();
      } catch (MessagingException e) {
         throw new SendEmailException("messaging Exception", e);
      }

   }

   

   private void connect() throws SendEmailException {
      try {
         ts.connect(host, from, pwd);
      } catch (MessagingException e) {
         throw new SendEmailException("messaging Exception", e);
      }

   }

.............

}

上面的代码是两年前封装邮件组件的时候用到的。分析发现,

发邮件这个动作主要执行以下几个步骤:

 public synchronized SendResult send(String subject, String content, List<FileDataSource> fileDataSources,
         String receiver) throws SendEmailException {
      configure();
      setSession();
      setDebug(true);
      setTransport();
      connect();
      SendResult result = sendSingle(subject, content, fileDataSources, receiver);
      close();
      return result;
   }

分别是:

1、configure():配置发邮件所需的基本参数。

protected void configure() {
   if (builtIn) {
      from = EmailProperties.getFrom();
      pwd = EmailProperties.getPwd();
      host = EmailProperties.getHost();
      storePath = EmailProperties.getStorePath();
      protocol = EmailProperties.getProtocol();
   }
}

2、setSession():

properties.put("mail.transport.protocol", protocol);

3、setDebug();

private void setDebug(boolean debug) {
   session.setDebug(true);
}

4、setTransport();

private void setTransport() throws SendEmailException {
   try {
      ts = session.getTransport();
   } catch (NoSuchProviderException e) {
      throw new SendEmailException("No such provider", e);
   }
}

5、connect();

private void connect() throws SendEmailException {
   try {
      ts.connect(host, from, pwd);
   } catch (MessagingException e) {
      throw new SendEmailException("messaging Exception", e);
   }

}

6、send();

private SendResult sendSingle(String subject, String content, List<FileDataSource> fileDataSources, String receiver)
      throws SendEmailException {
   Message msg;
   try {
      msg = createMail(subject, content, fileDataSources, receiver);
      setBCCAndCC(msg);
   } catch (Exception e1) {
      throw new SendEmailException(e1);
   }
   SendResult result = new SendResult();
   List<String> success = new ArrayList<String>();
   List<String> fail = new ArrayList<String>();
   try {
      send1(msg);
      success.add(receiver);
   } catch (MessagingException e) {
      log.error(e);
      fail.add(receiver);
   }
   result.setSuccess(success);
   result.setFail(fail);
   return result;
}

7、close();

private void close() throws SendEmailException {
   try {
      ts.close();
   } catch (MessagingException e) {
      throw new SendEmailException("messaging Exception", e);
   }

}

一共就这么七个步骤。发现没?大部分步骤都是统一的。但只有send这个动作中的createEmail所创建的message是不一样的。那么这时候你就可以把它这个七个步骤的send中的createEmail搞成一个抽象方法,然后其他的标准动作就被封装在抽象类中,然后在具体的子类中来创建具体的邮件message。作为一个必须要实现的动作,于是我们就在本抽象类中没有对createEmail做具体的实现,而是直接搞成抽象方法交由具体的子类来实现。

案例二、数据访问

public abstract class DBHttpHandler extends HttpHandler {

   protected static final Logger logger = LogManager.getLogger(DBHttpHandler.class);
   protected String verb="GET";

   protected Object obj;


   @Override
   public void doGet(Request request, Response response) {
      verb="GET";
      dbStatement(request, response);

   }

   protected void dbStatement(Request request, Response response) {
      Connection conn = DefaultDbConnection.getConnection();
      if (conn == null) {
         logger.e("The database connection is null!");
      }
      boolean autoCommit = true;
      try {
         autoCommit = conn.getAutoCommit();
         conn.setAutoCommit(false);
         doIt(conn, request, response);
         conn.commit();
         conn.setAutoCommit(autoCommit);
      } catch (Exception ex) {
         try {
            conn.rollback();
         } catch (SQLException e) {
            logger.e(e.getMessage());
         }
         logger.e(ex.getMessage());
      } finally {
         try {
            conn.close();
         } catch (SQLException e) {
            throw new CommonException("sql_exception", "Dbcp connection can't be closed");
         }

      }
      write(request,response);

   }
   /**
    * 如果你想要返回更多的信息。你可以重写这个方法来做更多的操作。
    * @param request
    * @param response
    */
   protected void write(Request request, Response response) {
      Ison gson=new Ison();
      if(null!=obj){
         response.write(gson.toJson(obj));
      }
   }

   @Override
   public void doPost(Request request, Response response) {
      verb="POST";
      dbStatement(request, response);

   }

   public abstract void doIt(Connection conn, Request request, Response response);

}

上面这个案例是之前在做数据访问的时候遇到的。每次和数据库访问时都必须要写有关数据库的事务提交和回滚代码。

这显然是重复工作,于是我们通过抽象类编写一个模板类,把事务这些重复代码固定在抽象类中,然后把具体的数据库操作暴露给子类去实现,这样的话,子类就可以写得很简单了,也许只需要一行代码就实现了数据库访问了:

public class List extends DBHttpHandler{
   @Override
   public void doIt(Connection conn, Request request, Response response) {
      obj =  DBClient.list(conn, "select * from user where 1=?","1");
   }

}

发现没?上面的两个案例中的代码非常像下面这个图:

模板!就像你小时候画的那些黑板报一样。模板都是固定的,只有一部分空白处需要你写一些字就可以了。

模板类,也就是抽象类,为你做好通用的数据库的事务,然后子类只需要做具体的数据库操作就可以了。

模板类为你把几个通用的步骤全都搞好,只有某一个步骤比较具体,把这个具体的实现交由子类来完成。这样既减轻了你的重复性劳动,同样也约束了代码的统一性。而不至于每个人写着功能一样但有细节略有不同的重复代码。

比如上面那个数据库操作中,如果没有模板类dbhttphandler,那么每个开发者都重复写着事务代码,一般情况下,只要有一个写了以后,其他人就是复制一模一样的代码去改改。这显然是重复的,而且有时候部分同学不会去复制别人的代码,自己写事务代码的时候,异常捕获又会没有考虑到位,埋藏了一些隐患。而现在我们把这些重复代码统一到模板类则会让代码质量可控。

这就是典型的抽象类的用法。也是设计模式中的模板模式的典型做法。

总结以上,什么时候适合使用抽象类呢?

1、当有几个步骤需要完成,但某个具体步骤需要具体实现,其他步骤则是通用的时候。

2、一些本类型但又重复编写的代码可以放到抽象类,然后把具体的不一样的那一部分暴露为抽象方法,让具体类去实现该抽象方法。

抽象类或模板模式带来的好处:

1、减少编写重复代码。

2、让流程标准化。

你看到的抽象类都是怎么来的?

1、需求明确后,设计出来的。

2、需求不明确,渐进清晰后,重构出来的。比如上面的数据库事务模板就是重构出来的。

抽象类和接口的比较

上一文中说设计模式中的接口的内容。有读者就说到了抽象类和接口。也许有人认为这两者似乎可以互相替代。也许在功能上可以这样去实现,但本号认为,二者的出发点还是不同的。

接口是为了标准化,而抽象类则为了重用。

语法上的区别这里就不说了。谈谈我个人对这二者的理解吧。

有人可能认为说“java8中的支持了对接口的默认方法实现,这就是为了让接口和抽象方法更加接近”。我不这么认为,我认为为接口提供默认方法只是为了解决过去我们定义好接口后,总是习惯于要提供一个默认实现,也就是类似DefaultXxx这样的默认实现逻辑。基于这种现实需求,java8开始从语法上支持了你可以为接口定义默认方法。

接口和抽象类他们还是原来的出发点。前者负责解决标准问题,后者则负责解决重用问题。

模板模式典型代码回顾

我们再回头看看模板模式的典型代码示例吧。

第一步、搞个抽象类

public abstract class Game {
   abstract void initialize();
   abstract void startPlay();
   abstract void endPlay();

   //template method
   public final void play(){

      //initialize the game
      initialize();

      //start game
      startPlay();

      //end game
      endPlay();
   }
}

第二步、写两个具体的子类

public class Cricket extends Game {

   @Override
   void endPlay() {
      System.out.println("Cricket Game Finished!");
   }

   @Override
   void initialize() {
      System.out.println("Cricket Game Initialized! Start playing.");
   }

   @Override
   void startPlay() {
      System.out.println("Cricket Game Started. Enjoy the game!");
   }
}
public class Football extends Game {

   @Override
   void endPlay() {
      System.out.println("Football Game Finished!");
   }

   @Override
   void initialize() {
      System.out.println("Football Game Initialized! Start playing.");
   }

   @Override
   void startPlay() {
      System.out.println("Football Game Started. Enjoy the game!");
   }
}

第三步、写个main

public class TemplatePatternDemo {
   public static void main(String[] args) {

      Game game = new Cricket();
      game.play();
      System.out.println();
      game = new Football();
      game.play();
   }
}

如果你直接看上面的例子也许对模板模式并没什么感觉,特别是对于初学者来说,甚至觉得模板类在自己的开发中并没什么用途。但如果你看了上面的举的两个实际开发案例,特别是数据库访问操作那一段,你也许就会有更深切的体会。

当然了,不可否认的是,抽象类在你绝大多数业务开发中并不会用到,但如果哪天你需要封装一些东西的时候,也许设计模式就开始发挥作用了,那时候抽象类也许就会涉及到了。

抽象类,只是想为你做更多!

原文发布于微信公众号 - ImportSource(importsource)

原文发表时间:2017-05-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

Go语言的 10 个实用技术--转

十条有用的 Go 技术   这里是我过去几年中编写的大量 Go 代码的经验总结而来的自己的最佳实践。我相信它们具有弹性的。这里的弹性是指:   某个应用需要适配...

3677
来自专栏技术点滴

设计模式学习心得

设计模式学习心得 《设计模式:可复用面向对象软件的基础》一书以更贴近读者思维的角度描述了GOF的23个设计模式。按照书中介绍的每个设计模式的内容,结合网上搜集的...

2117
来自专栏落影的专栏

iOS开发笔记(一)

前言 iOS开发笔记(一) iOS开发笔记(二) iOS开发笔记(三) iOS开发笔记(四) 《开发笔记》系列记录一些开发中遇到的问题以及思考。 本文主...

3327
来自专栏C语言及其他语言

【编程经验】C语言程序真正的启动函数(文末有福利,第二弹)

又到了小编和大家说到的时间了,闲话少说,直接上正题,今天就讲讲“C语言程序真正的启动函数”。 为什么要用”真正”这个词?因为我们从...

33012
来自专栏华仔的技术笔记

浅谈Objective-C设计模式(Design Pattern)import "Singleton.h"

3327
来自专栏C/C++基础

CVTE2016春季实习校招技术一面回忆(C++后台开发岗)

2016.3.15,参加了CVTE的技术面,很不幸,我和我的两位小伙伴均跪在了一面。先将当日的面试内容汇总如下,供后来者参考。我们三人各自也都总结了失败的原因,...

591
来自专栏Golang语言社区

十条有用的 Golang语言 技术

十条有用的 Go 技术 这里是我过去几年中编写的大量 Go 代码的经验总结而来的自己的最佳实践。我相信它们具有弹性的。这里的弹性是指: 某个应用需要适配一个灵...

4179
来自专栏小黄人打代码

在Java中如何解析JSON格式数据?

1775
来自专栏大闲人柴毛毛

柴毛毛大话设计模式——开发常用的设计模式梳理

写在最前 本文是笔者的一点经验总结,主要介绍几种在Web开发中使用频率较高的设计模式。 本文篇幅较长,建议各位同学挑选感兴趣的设计模式阅读。 在阅读的同时,也...

4897
来自专栏chenssy

【死磕Java并发】—–J.U.C之并发工具类:Exchanger

前面三篇博客分别介绍了CyclicBarrier、CountDownLatch、Semaphore,现在介绍并发工具类中的最后一个Exchange。Exchan...

3356

扫码关注云+社区

领取腾讯云代金券