单例模式
- 定义:确保一个类只有一个实例,并且自动实例化为系统提供这个实例。
- 要点:
代码
饿汉方式。指全局的单例实例在类装载时构建。
123456789101112131415161718public class Singleton {private static final Singleton singleton = new Singleton();//限制产生多个对象private Singleton(){}//通过该方法获得实例对象public static Singleton getSingleton(){return singleton;}//类中其他方法,尽量是staticpublic static void doSomething(){}}懒汉方式。指全局的单例实例在第一次被使用时构建。
123456789101112131415public class Singleton {private static volatile Singleton INSTANCE = null;// Private constructor suppresses// default public constructorprivate Singleton() {}//thread safe and performance promotepublic static synchronized Singleton getInstance() {if(INSTANCE == null){INSTANCE = new Singleton();}return INSTANCE;}}懒汉方式的优点:单例只有在需要使用的时候才会实例化,一定程度节约资源。缺点:第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用时都进行同步,造成不必要的同步开销。
DCL (Double Check Lock)实现单例
1234567891011121314151617181920public class Singleton {private static volatile Singleton INSTANCE = null;// Private constructor suppresses// default public constructorprivate Singleton() {}//thread safe and performance promotepublic static Singleton getInstance() {if(INSTANCE == null){synchronized(Singleton.class){//when more than two threads run into the first null check same time, to avoid instanced more than one time, it needs to be checked again.if(INSTANCE == null){INSTANCE = new Singleton();}}}return INSTANCE;}}第一层判断是为了避免不必要的同步,第二层判断为了在null的情况下创建实例。
但是由于INSTANCE = new Singleton();
不是原子的,大致有以下三步:- 1)给singleton的实例分配内存
- 2)调用singleton的构造函数,初始化成员变量
3)将INSTANCE对象指向分配的内存空间
由于jdk1.5之前JMM(java内存模型)中cache,寄存器到主内存回写顺序的规定,2)和3)顺序先后是无法保证的。如果在3执行完毕,2执行之前切换到另一个线程就会导致单例未实例化。即DCL失效问题。
在jdk1.5之后sun调整了JVM,将INSTANCE的定义改成
private volatile static Singleton INSTANCE=null
就可以保证单例每次都是从内存中读取。DCL的优点:资源利用率高,第一次获取单例的时候才会实例化,效率高。缺点:第一次加载的时候反应稍慢,也由于java内存模型的原因偶尔会失败。高并发环境下也有一定缺陷,虽然发生概率较小。
DCL是使用最多的单例模式。静态内部类实现单例:推荐,线程安全以及延迟单例的实例化。
1234567891011public class Singleton {private Singleton(){}//通过该方法获得实例对象public static Singleton getSingleton(){return SingletonHolder.singleton;}private static class SingletonHolder{private static final Singleton singleton=new Singleton();}}枚举单例:优点在于写法简单,线程安全,同时反序列化的时候不会创建新的对象。
123456public enum SingletonEnum{INSTANCE;public void doSomething(){}}上述方法中除了枚举外要防止反序列化时重新生成对象就必须加入以下方法:
123private Object readResolve() throws ObjectStreamExecption{return INSTANCE;}容器实现单例:多种单例类型注入到一个同意的管理类中,并通过key获取。
123456789101112public class SingletonManager{private static Map<String,Object> objMap=new HashMap<String,Object>();private SingletonManager(){}public static void registerService(String key,Object INSTANCE){if(!objMap.containsKey(key)){objMap.put(key,INSTANCE);}}public static Object getService(String key){return objMap.get(key);}}
Android中较为推荐的是双重检验锁和静态内部类实现的单例模式。
Adapter中的观察者模式
什么是观察者模式
结构
观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
观察者模式所涉及的角色有:
观察者模式的python代码实现:
|
|
在listview中,我们知道,当数据变化时,listview呈现的内容是会更新的。这实际上是用到了adapter中的观察者模式(当然listview中利用了adapter模式,这个以后再讲)。adapter内部有一个可观察者类,listview则作为他的一个观察者,将adapter设置给listview时,listview会被注册到这个观察者对象中。接下来我们就从adapter的源码入手,分析一下。
从以上程序中我们可以看出,设置adapter时创建了一个AdapterDataSetObserver对象,并注册到adapter中。刚才不是说listview是观察者吗?这会儿怎么成了AdapterDataSetObserver了。我们先放下这个疑问继续往下看。
首先我们看常用的adapter基类BaseAdapter,部分代码如下:
从以上可以看出,注册观察者实际上调用了DataSetObeservable对应的函数。DataSetObeservable拥有一个观察者集合,当可观察者改变时,就会通知观察者做出相应的处理。
当adapter的数据变化时,我们会调用adapter的notifyDataSetChanged函数,该函数又会调用DataSetObeservable对象的notifyChanged()函数通知所有观察者数据发生了变化,使观察者进行相应的操作。代码如下:
对listview来说这个观察者就是AdapterDataSetObeserver对象,该类声明在AdapterView中,也是listview的一个父类。AdapterDataSetObeserver代码如下
在AdapterDataSetObeserver的onChanged()函数中会调用viewGroup的requestLayout()进行重新策略,布局,绘制整个listview的item view,执行完之后,整个listview的元素就发生了变化。
现在我们回到之前的额问题,就是listview不是观察者,而AdapterDataSetObeserver才是真正的观察者的问题。在AdapterDataSetObeserver的onChanged()函数中实际调用的是AdapterView中的方法来完成功能,所以AdapterDataSetObeserver只是在外层做了一层封装,真正核心的功能应该是AdapterView。listview就是通过Adapter模式,观察者模式,item view复用机理实现了高效列表显示。
这里对于item view的复用机理介绍一下:
在Adapter中要重写四个函数:
当处理数据量较大时,对于滚出屏幕外的item view会进入listview的一个Recycler中,Recycler将视图缓存。当屏幕滑动加载新的itemview时,如果该视图存在,则直接从缓存中取出,如果过不存在就直接创建新的视图。这也就是为什么用viewholder可以优化listview的原因。
|
|
抽象工厂模式
- 定义:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。
- UML类图:
- 代码示例:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960public abstract class AbstractCreator {//创建A产品家族public abstract AbstractProductA createProductA();//创建B产品家族public abstract AbstractProductB createProductB();}public class Creator1 extends AbstractCreator {//只生产产品等级为1的A产品public AbstractProductA createProductA() {return new ProductA1();}//只生产铲平等级为1的B产品public AbstractProductB createProductB() {return new ProductB1();}}public class Creator2 extends AbstractCreator {//只生产产品等级为2的A产品public AbstractProductA createProductA() {return new ProductA2();}//只生产铲平等级为2的B产品public AbstractProductB createProductB() {return new ProductB2();}}public class Client {public static void main(String[] args) {//定义出两个工厂AbstractCreator creator1 = new Creator1();AbstractCreator creator2 = new Creator2();//产生A1对象AbstractProductA a1 = creator1.createProductA();//产生A2对象AbstractProductA a2 = creator2.createProductA();//产生B1对象AbstractProductB b1 = creator1.createProductB();//产生B2对象AbstractProductB b2 = creator2.createProductB();/** 然后在这里就可以为所欲为了...*/}}
工厂方法模式
- 定义:定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。
- UML类图:
- 举例:女娲造人(详情见《设计模式之禅》第八章)
这里重点给出工厂的代码123public abstract class AbstractHumanFactory {public abstract < T extends Human> T createHuman(Class< T> c);}
这里T必须满足:是class类型,并且是human的实现类。
工厂子类如下:
完整的实现代码模板如下:
1234567891011121314151617181920212223242526272829303132333435363738//抽象产品类abstract class Product{public void method1(){}//公共方法public abstract void method2();//抽象方法}//具体产品类class CreateProduct1 extends Product{public void method2(){}}class CreateProduct2 extends Product{public void method2(){}}//抽象工厂abstract class Creator{public abstract < T extends Product> T createProduct(Class< T> c);}//具体工厂class CreateCreator extends Creator{public < T extends Product> T createProduct(Class< T> c){Product p = null;try{p = (Product) Class.forName(c.getName()).newInstance();}catch(Exception e){}return (T)p;}}//场景类class Client{public static void main(String[] args){Creator creator = new CreateCreator();Product p = creator.createProduct(CreateProduct1.class);}}优点:
- 首先:良好的封装性,降低模块间的耦合。比如创建一个产品对象,只需要知道其类名或者约束字符串就可以了。
- 其次:扩展性好。增加产品类时只需要适当修改工厂具体类或者新建工厂类就可以拥抱变化。
- 再次:屏蔽产品类。产品类的实现如何变化,调用者都不用关心,只要求产品接口保持不变。例如:使用JDBC连接数据库,数据库从MySQL切换到Oracle,只需要改动一下驱动的名称。
- 最后:工厂方法模式是典型的解耦框架。高层模块只需要知道产品的抽象类,其他的实
现类都不用关心,符合迪米特法则,我不需要的就不要去交流;也符合依赖倒置原则,只依
赖产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类
使用场景
- 工厂方法模式是new一个对象的替代品,所以在需要new 对象的地方都可以使用。但需慎重考虑是否要增加一个工厂类进行管理,以免增加代码复杂度。
- 需要灵活的、可扩展的框架时,可以考虑。ex:设计一个连接邮件服务器的框架,POP3、IMAP、HTTP三协议的连接方式作为产品类来处理。当某些邮件服务器扩展了webservice协议时,只需要增加webservice的产品类
- 可以用在异构项目中。
ex:通过webservice与一个非java的项目交互,虽然webServcie号称是可以做到异构系统的同构化,但是在实际的开发中,会碰到类型问题、WSDL文件的支持问题等。从WSDL中产生的对象都认为是一个产品,然后由具体工厂管理,减少与外围的耦合。 - 可以使用在测试框架中。ex:测试类A,就需要把与A有联系的B也同时产生出来。可以使用工厂方法模式把B虚拟出来,避免A与B 的耦合。可对比JMock、EasyMock。
- 扩展
- 简单工厂模式:一个模块只需要一个工厂类,没必要new工厂实例出来,只需要使用静态方法即可。
- 简单工厂模式:一个模块只需要一个工厂类,没必要new工厂实例出来,只需要使用静态方法即可。
|
|
多工厂类:项目比较复杂,初始化一个对象比较费力(设定初始值),所有产品类都放在一个工厂方法中进行初始化会使代码结构不清晰。为每个产品定义一个创造者,然后由调用者自己去选择与哪个工厂方法关联。
12345678910111213141516171819//具体工厂1class CreateCreator1 extends Creator{public Product createProduct(){return new CreateProduct1();}}//具体工厂2class CreateCreator2 extends Creator{public Product createProduct(){return new CreateProduct2();}}//场景类class Client{public static void main(String[] args){Product cp1 = (new CreateCreator1()).createProduct();Product cp2 = (new CreateCreator2()).createProduct();}}替代单例模式.不让通过正常方式new一个产品对象,而是通过工厂用反射的方法创建对象。项目中可以建一个单例构造器,只要输入单例的类型就可以获得唯一的实例。通过获得类构造器,然后设置访问权限,生成一个对象,然后提供外部访问,保证内存
中的对象唯一。1234567891011121314151617181920class Singleton{private Singleton(){}public void doSomething(){}}class SingletonFactory{private static Singleton single;static{try {Class cls = Class.forName(Singleton.class.getName());Constructor constructor = cls.getDeclaredConstructor();constructor.setAccessible(true);single = (Singleton) constructor.newInstance();} catch (Exception e) {e.printStackTrace();}}public static Singleton getSingleton(){return single;}}延迟初始化:一个对象被消费完毕,不立即释放,工厂类保持其初始状态,等待再次被使用。
12345678910111213141516171819public class ProductFactory {// 缓存容器private static final Map< String, Product> prMap = new HashMap< String, Product>();public static synchronized Product createProduct(String type) throws Exception {Product product = null;if (prMap.containsKey(type)){product = prMap.get(type);} else {if (type.equals("Product1")) {product = new ConcreteProduct1();} else {product = new ConcreteProduct2();}// 同时把对象放到缓存容器中prMap.put(type, product);}return product;}}
延迟加载框架是可以扩展的,例如限制某一个产品类的最大实例化数量,可以通过判断
Map中已有的对象数量来实现,这样的处理是非常有意义的,例如JDBC连接数据库,都会
要求设置一个MaxConnections最大连接数量,该数量就是内存中最大实例化的数量。