前言
学习android一段时间了,为了进一步了解android的应用是如何设计开发的,决定详细研究几个开源的android应用。从一些开源应用中吸收点东西,一边进行量的积累,一边探索android的学习研究方向。这里我首先选择了jwood的
Standup Timer 项目。
在Standup Timer的 数据访问层net.johnpwood.android.standuptimer.dao使用到了单件模式,我们首先来看一下net.johnpwood.android.standuptimer.dao的项目结构,理解各个类的职责,然后再详细了解其中的设计。
DAO的包的结构
DAO在Standup Timer中充当数据访问层的职责,数据库采用的是SQLite。我们来逐个了解一下上图中各类的职责,他们是负责干什么的!
CannoUpdateMeetingException.java
它继承自RuntimeException类,负责抛出对应的异常。注:private static final long serialVersionUID = 1L;用来表明类序列化时的不同版本间的兼容性,1L为默认值。DuplicateTeamException.java和InvalidTeamNameException.java相同。
DAOFactory.java
这是有所改变的工厂类(与原本的简单工厂模式不太一样,最大的区别在于多态的使用上),负责生产MeetingDAO 、TeamDAO类。
DAOHelper.java
继承自SQLiteOpenHelper,并且实现了DatabaseConstants接口,负责在第一调用时生成对应表和表字段,以及更新数据库。
DatebaseConstants.java
一个包含数据库名称和版本常量的接口,由DAOHelper实现。将数据库名称和版本分离处理,方便日后的版本升级甚至数据库名变更。将数据库名和版本的管理从DAOHelper中分离处理,降低了耦合度,使DAOHelper职责更单一,符合单一职责原则(其实我很难说明将这两个常量放置于接口当中是否妥当)。
MeetingDAO.java
继承自DAOHelper.java负责会议表的具体数据访问业务。因为DAOHelper.java实现了DatebaseConstants接口,所有在MeetingDAO的构造函数当中可以直接调用DATABASE_NAME和DATABASE_VERSION。如果getWritableDatabase()是第一次调用,系统将自动创建数据库和数据库表字段。
代码
public
MeetingDAO(Context ctx) {
super
(ctx, DATABASE_NAME,
null
, DATABASE_VERSION);
}
public
Meeting save(Meeting meeting) {
if
(meeting.getId()
==
null
) {
SQLiteDatabase db
=
getWritableDatabase();
return
createNewMeeting(db, meeting);
}
else
{
String msg
=
"
Attempting to update an existing meeting. Meeting entries cannot be updated.
"
;
Logger.w(msg);
throw
new
CannotUpdateMeetingException(msg);
}
}
TeamDAO.java与MeetingDAO的基本一样。
DAOFactroy
我们先来看看在Model对DAO的调用代码:
代码
private
static
DAOFactory daoFactory
=
DAOFactory.getInstance();
public
Meeting save(Context context) {
MeetingDAO dao
=
null
;
Meeting meeting
=
null
;
try
{
dao
=
daoFactory.getMeetingDAO(context);
meeting
=
dao.save(
this
);
}
catch
(Exception e) {
Logger.e(e.getMessage());
}
finally
{
dao.close();
}
return
meeting;
}
在Model 中 DAOFactory 的实例是由DaoFactory.getMeetingDAO(Context) 方法返回的,而对应的数据库访问类则由 daoFcatroy.getXXXXDAO(Context)方法返回。如果你看过一些有关设计模式的文章就很容易发现,这是单件模式。
下面来分析一下DAOFactory的代码:
代码
public
class
DAOFactory {
private
static
DAOFactory instance
=
null
;
public
static
DAOFactory getInstance() {
if
(instance
==
null
) {
instance
=
new
DAOFactory();
}
return
instance;
}
private
DAOFactory() {
}
}
private DAOFactroy(){} 构造函数私有表明 DAOFactory 无法通过new关键字来实例化。要实例化只能通过静态的方法 getInstance()。在getInstance()方法中首先判断句柄instance是否为null,为空表示DAOFactory的实例尚未创建,然后调用 new DAOFactory() 创建实例(注意!因为是在 DAOFactory内部,所以可以访问private DAOFactory()成员),如果不为null则直接返回已创建的DAOFactory实例。这便是简单的单件模式设计方法(这里并没有考虑到多线程并发的问题,实际上就android的Standup Timer项目本身而言也不存在多线程的并发问题)。单件模式是一种比较简单的设计模式,比较容易理解,大家可以网上搜索一下,有很多相关的文章。
我们再来看看 MeetDAO 和 TeamDAO的相关代码;
代码
private
boolean
cacheDAOInstances
=
false
;
private
TeamDAO cachedTeamDAO
=
null
;
private
MeetingDAO cachedMeetingDAO
=
null
;
public
TeamDAO getTeamDAO(Context context) {
if
(cacheDAOInstances) {
if
(cachedTeamDAO
==
null
) {
cachedTeamDAO
=
new
TeamDAO(getProperDAOContext(context));
}
return
cachedTeamDAO;
}
else
{
return
new
TeamDAO(getProperDAOContext(context));
}
}
public
MeetingDAO getMeetingDAO(Context context) {
if
(cacheDAOInstances) {
if
(cachedMeetingDAO
==
null
) {
cachedMeetingDAO
=
new
MeetingDAO(getProperDAOContext(context));
}
return
cachedMeetingDAO;
}
else
{
return
new
MeetingDAO(getProperDAOContext(context));
}
}
private Context getProperDAOContext(Context context) {
if (globalContext != null) {
return globalContext;
} else {
return context;
}
}
cacheDAOInstances是布尔值,相当于一个开关,用以判定是否需要对DAO类使用缓存(单件模式)。如果为True则进入单件模式的构建,如果为False 着直接返回一个新的DAO实例。这里通过getMeetingDAO 和getTeamDAO两个方法返回对应的DAO实例。另外getProperDAOContext()方法是负责解决DAOFactory 内的私有变量private Context globalContext 和方法的参数 context 间需要调用哪一个上下文context的冲突。其实我们也可以改写DAOFactory的生成方式:
代码
private
static
DAOFactory instance
=
null
;
private
Context globalContext
=
null
;
public
static
DAOFactory getInstance(Context context) {
if
(instance
==
null
) {
instance
=
new
DAOFactory(context);
}
return
instance;
}
private
DAOFactory() {
globalContext
=
context;
}
这样在其他的方法中就可以省去 context参数。