目的
android框架搭建的时候设计模式会起到很大的作用,在搭建初期的项目框架时,合理利用设计模式的一些理念和技巧,能让你在构建整个项目框架的时候更加游刃有余.学习设计模式的初衷不仅仅是能让你的代码结构更加整洁易读,而且能降低各模块之间的耦合性,使你的框架有更好的可拓展性,这样在后期的需求不断变动的时候,就不必对整个代码结构和框架做大的改动,当然,随着后期业务的增多,相应的代码重构还是要有,在此设计模式的学习及框架的搭建只是为了减少后期大幅度重构代码的工作,毕竟,程序员,都是懒人.
网上有很多关于设计模式的介绍,但是对于设计模式在项目中的运用比较少,尤其是对android项目的运用,这次,我打算从头梳理一遍设计模式,在梳理过程中,尽量贴合实际应用需要,在项目中去阐述设计模式在各个地方和模块的使用和技巧,也算是重新学习一遍吧.
单例模式的定义
首先关于单例模式,定义相当简单,单例模式即指创建单一的实例对象,作为全局变量在各处引用.单例的意义在于,节省部分内存开支,相信在android开发过程中大家都遇到过内存溢出和泄露的问题.使用单例模式就不得不说一下内存泄露和内存溢出的问题了.
关于内存泄露与垃圾回收
所谓的内存泄露是指什么呢?我们都知道,java虚拟机中内存是分为两块的(静态的内存不在这次考虑范围内,只说明问题,后续可以讨论java内存构成问题)即堆内存和栈内存,堆内存和栈内存都为动态分配地址。我们通常所定义的变量,分为基本数据类型和引用变量,基本数据类型包括int、long、double、boolean、double等常用的基本变量,这些包括他们的封包变量即Integer、Double、Boolean等在声明及赋值时,java会直接从栈内直接分配内存,而像自定义的实体,在声明及通过关键子new来创建实例时,会在内存中分配两个地址,栈内会分配给引用变量一个地址作为声明及指针存放的空间(指针概念请了解C/C++相关链表指针概念),然后指针指向的位置即为通过关键字new 创建实例的具体数据在堆内存的位置。再回到内存泄露和内存溢出的问题上来讲,内存泄露即指在java虚拟机分配给你定义的变量内存后,无法释放你申请的这个内存,简单来讲就是,你在创建引用变量的时候,并没有在使用完成后释放这个内存,当然你可能说java 的gc回收机制会帮你完成,但是,无疑这样效率就会极端低下.单个内存泄露可能效果不明显,但是大量的内存泄露就会导致内存损耗严重,另外,gc垃圾回收时会阻塞主线程,这就造成了UI卡顿,这也是一部分UI卡顿的原因.(具体java gc回收机制相对比较复杂,在此我们不做更深入的了解,只做说明,如果想更深入了解java gc机制及过程建议查询相关资料).至于内存溢出,相对而言更好理解,即指申请内存的时候,系统分配的内存不足就会引起内存溢出.这种溢出从某种程度上讲,也有一部分内存泄露导致内存不足的原因.所以,总结而言就是,过度的创建对象,定义不必要的变量会导致内存消耗增加,大量的内存资源被浪费,引起内存泄露.而这也正是我们为什么要用单例模式来处理一些全局的变量和工具类的意义.当然,单例模式只是从一定程度上避免内存的问题,并不是能从根本上解决问题之道.
好了,扯了这么多终于又回归到咱们的单例模式这个设计理念上,设计模式作为一个最经典的设计模式.被大量的研究和探讨.相继也出现了各种版本,在此就对各个版本做一下简单介绍.
经典模式的单例(懒汉模式)
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
public static Singleton getInstance() {
if(null==singleton) {
singleton = new Singleton();
}
return singleton;
}
}
经典模式会将构造器私有,防止外部通过new关键字穿件对象实体,对外提供静态方法getInstantce来获取单一实例,调用方法时做非空判断保证在调用时至少已经创建出对象.
当然,众所周知的这种方式是有弊端的,弊端就在于多线程无法保证单例模式的唯一性。
关于线程的cpu调度
在这里我们不得不提一下线程的本质,大家都知道线程是cpu调度的基本单位,为什么这么说呢,很简单,线程的本质就是cpu会分配一个最小执行片段时间,采用轮询的方式去不停的执行线程,分配资源,从某种程度上来讲,单核cpu本质上应该是没有真正并发执行的过程,单核cpu只是通过将线程执行时间碎片化,轮询执行调度,从用户的角度来讲,看上去是同时执行的两个线程其实都是有先后的。当然多核cpu就会有真正的并发了.不过这些和我们说的经典单例的弊端并没有啥米关系,经典单例的问题在于数据同步.即两个线程都经过if判断之后进入方法体时依次执行,这样就会创建多次对象,即不能保证单例的唯一性.具体请参考下图
example.png改进地方法想必大家都是到的,最简单的方式就是添加synchronized同步锁(关于互斥锁等线程同步相关我们后续再讨论).而这也必将导致的一个问题就是,效率的低下,一般来讲,我们作为单例的变量很少涉及线程问题,当然秉持着严谨的态度我们依然还是需要保证在多线程的情况下的问题,这样的话,我们只能继续修改单例的写法,以保证我们的需求.这样我们就不得不介绍另一种单例模式,恶汉模式.
所谓恶汉模式,即初始化的时候就已经创建对象,这样就不需要坐非空判断,则也不会出现线程同步问题了.具体代码如下.
恶汉模式
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
}
当然,单例的其他写法网上也有也行,笔者个人认为这三种方法较为常用,剩余其他的写法笔者未做深入研究,开发过程中也比较少见,故此也不再讨论.
项目中应用
开篇说要将单例的设计模式运用在框架搭建中,笔者写了一段关于单例应用于用户管理工具的代码,希望大家一块学习,有不足的地方希望大家批评指正.
附上笔者代码:
packagecom.ss.single;
import java.io.Serializable;
import java.util.Map;
/**
* Created by Administrator on 2016/11/23.
* 用户实体类
*/
public class User implements Serializable {
//用户姓名
private String userName;
//用户昵称
private String nickName;
//用户密码
private String pwd;
//用户权限
private String permission;
//用户相关资料键值对
private Map<String,String> datas;
public void setUserName(String userName) {
this.userName= userName;
}
public void setNickName(String nickName) {
this.nickName= nickName;
}
public void setPwd(String pwd) {
this.pwd= pwd;
}
public void setPermission(String permission) {
this.permission= permission;
}
public void setDatas(Map datas) {
this.datas= datas;
}
public String getUserName() {
return userName;
}
public String getNickName() {
return nickName;
}
public String getPwd() {
return pwd;
}
public String getPermission() {
return permission;
}
public Map getDatas() {
return datas;
}
@Override
public String toString() {
return"User{"+
"userName='"+userName+'\''+
", nickName='"+nickName+'\''+
", pwd='"+pwd+'\''+
", permission='"+permission+'\''+
", datas="+datas+
'}';
}
}
用户管理工具类.
packagecom.ss.single;
importandroid.support.annotation.NonNull;
/**
* Created by Administrator on 2016/11/23.
*/
public class UserHelper {
private static UserHelperhelper = new UserHelper();
private User user;
private UserHelper() {
}
/**
* 获取当前userhelper实例
*
*@return
*/
public static UserHelper getInstance() {
return helper;
}
/**
* 获取当前用户
*
*@return
*/
public User getCurrentUser() {
return user;
}
/**
* 替换/初始化当前用户
*
*@param user
*/
public void replaceUser(User user) {
this.user= user;
}
/**
* 退出当前登录
*/
public void logout() {
user=null;
}
/**
* 判断本地登录是否成功
*
*@param userName
*@param pwd
*@return
*/
public boolean localLogin(@NonNull String userName,@NonNull String pwd) {
if(null!=user? userName.equals(user.getUserName()) && pwd.equals(user.getPwd()) :false)
return true;
return false;
}
}
参考书籍:HeadFirst设计模式.
网友评论