设计模式-抽象类,只是想为你做更多
如果说面向对象中的接口是把所有的事情扔给你的话,那么抽象类显然是想要为你做一些事情,如果实在有一部分做不了再扔给你。
相信对于大部分业务开发的场景下都不太会需要自己去写抽象类。但在一些特殊场景我们还是不得不搞个抽象类。
还是从实际的使用场景来说起吧。
实际案例
案例一、封装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();
}
}
如果你直接看上面的例子也许对模板模式并没什么感觉,特别是对于初学者来说,甚至觉得模板类在自己的开发中并没什么用途。但如果你看了上面的举的两个实际开发案例,特别是数据库访问操作那一段,你也许就会有更深切的体会。
当然了,不可否认的是,抽象类在你绝大多数业务开发中并不会用到,但如果哪天你需要封装一些东西的时候,也许设计模式就开始发挥作用了,那时候抽象类也许就会涉及到了。
抽象类,只是想为你做更多!
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 搞它!!!深入了解安装及管理程序,学会使用rpm工具 虚拟机实验下载安装Apache
- (转)java中equals和等号(==)的区别浅谈
- 搞它!!!linux账号和权限管理
- 搞它!!!详细介绍linux磁盘管理和文件系统
- 详谈JAVA中的file类与IO流
- Java中的StringTokenizer类
- 搞它!!!Linux系统安全及应用以弱口令检测
- 搞它!!!linux配置本地yum源
- JAVA网络编程TCP通信
- 使用ExecutorService实现线程池
- 搞它!!!linux服务器硬件及RAID 配置实战
- 认识XML
- 基于oracle的sql(结构化查询语言)指令
- oracle约束条件
- oracle常用函数介绍