前言
在刚学编程没多久就听说过设计模式的大名,不过由于当时还是个彻彻底底的菜鸟,并没有去触碰。直到在开始工作中对简单的业务代码较为熟悉之后,才正式的接触设计模式。当时最早接触的设计模式是工厂模式,不过本文讲的是单例模式,这里就留着下篇文章中在讲解。至于为什么先讲解单例模式? 那是因为单例模式是设计模式中最简单的... 。凡事总有个先后顺序,所以就先易后难了。好了,废话不多说了,开始进入正片。
设计模式简介
说明:这里说了的简介就是真的 “简介”。
什么是设计模式
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。
为什么使用设计模式
使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
设计模式类型
设计模式有23种类型。按照主要分类可以分为三大类:
一、创建型模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
单例模式
工厂模式
抽象工厂模式
建造者模式
原型模式
二、结构型模式
这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
适配器模式
桥接模式
过滤器模式
组合模式
装饰器模式
外观模式
享元模式
代理模式
三、行为型模式
这些设计模式特别关注对象之间的通信。
责任链模式
命令模式
解释器模式
迭代器模式
中介者模式
备忘录模式
观察者模式
状态模式
空对象模式
策略模式
模板模式
访问者模式
设计模式的原则
设计模式的六大原则
1.开闭原则:对扩展开放,对修改关闭。
2.里氏代换原则:对开闭原则的补充。任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。
3.依赖倒转原则:针对接口编程,依赖于抽象而不依赖于具体。
4.接口隔离原则:尽量使用多个隔离的接口,为了降低类之间的耦合度。
5.迪米特法则:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6.合成复用原则:尽量使用合成/聚合的方式,而不是使用继承。
单例模式
什么是单例模式
保证一个系统中的某个类只有一个实例而且该实例易于外界访问。例如Windows界面的任务管理器就可以看做是一个单例。
单例模式的使用场景
在程序中比较常用的是数据库连接池、线程池、日志对象等等。
单例模式使用
最早我们在学习单例模式的时候,基本都会接触这两种模式:饿汉式和饱汉式(懒汉式)。 那我们先来看看这两个模式的实现。
饿汉式 定义一个私有的构造方法,并将自身的实例对象设置为一个私有属性,并加上static和final修饰符,然后通过公共的静态方法调用返回实例。
class SingletonTest1 {private SingletonTest1() {}private static final SingletonTest1 instance = new SingletonTest1();public static SingletonTest1 getInstance() {return instance;}}
饱汉式 定义一个私有的构造方法,定义一个该类静态私有的变量,然后定义一个公共的静态方法,对该类的值进行空判断,不为空直接返回,否则重新构建一个。
class SingletonTest2 { private SingletonTest2() { } private static SingletonTest2 instance; public static SingletonTest2 getInstance() { if (instance == null) {
instance = new SingletonTest2();
} return instance; }
}
简单的介绍了这两种的模式,然后我们再来看看这两种模式的优缺点吧。 饿汉式
优点:写起来很简单,并且不会因为不加synchronized关键字而造成的线程不安全问题。
缺点:当该类被加载的时候,会初始化该实例和静态变量并被创建并分配内存空间,并且会一直占用内存。
饱汉式
优点:写起来很简单,在第一次调用的时候才会初始化,节省了内存。
缺点:线程不安全,多个线程调用可能会出现多个实例。
总结:书写简单,线程不安全,效率还行。
虽然 饱汉式可以通过加上synchronized关键字保证线程安全。但是效率方法来说还不说是最优。
这里在介绍下个人认为在JDK1.5之前最优的两种写法,一种是静态内部类,另一种是双重锁检查。
静态内部类 定义一个私有的构造方法,定义一个该类私有静态的内部类,然后在内部类中定义一个该类的静态变量,然后通过公共的final修饰的静态方法调用返回实例。
classSingletonTest4 {
private SingletonTest4(){
}
private static class SingletonTest5{
private static SingletonTest4 instance = new SingletonTest4();
}
public static final SingletonTest4 getInstance(){
return SingletonTest5.instance;
} }
因为该类的内部类是私有的,除了对外公布的公共静态方法getInstance(),是无法访问的。因为它是延迟加载,所以读取读取实例的时候不会进行同步,几乎没有性能的缺陷,而且还是线程安全的,并且不依赖JDK的版本。
双重锁检查 定义一个私有构造方法,通过volatile定义静态私有变量,保证了该变量的可见性,然后定义一个共有的静态方法,第一次对该对象实例化时与否判断,不为空直接返回,提升效率;然后使用synchronized 进行同步代码块,防止对象未初始化时,在多线程访问该对象在第一次创建后,再次重复的被创建;然后第二次对该对象实例化时与否判断,如果未初始化,则初始化,否则直接返回该实例。
class SingletonTest6 { private SingletonTest6() { } private static volatile SingletonTest6 instance;public static SingletonTest6 getIstance() { if (instance == null) {synchronized (SingletonTest6.class) {if (instance == null) {instance = new SingletonTest6(); } } } return instance; } }
这种模式在很长的一段时间内可以说是最优的了,内存占用低,效率高,线程安全,多线程操作原子性。但是有个缺点就是书写麻烦,对新手不太友好。
JDK1.5之后出现了枚举,并且完美支持单例模式,并且线程安全、效率高!但是这些不是最重要的,最重要的是书写超级简单!究竟有多简单,看下面的示例应该就可以了解一下了。。。
枚举单例
enum SingletonTest7{ INSTANCE; }
对的,你没看错,就这点代码,其它不需要了。。。 枚举需要在JDK1.5之后的版本,它无偿提供序列化机制,绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候。这种方法也被Effective Java作者Josh Bloch 所提倡。
总结
单例模式的几种使用就到这了,那么我们来总结下使用单例模式需要注意什么(不包括枚举)。
1.构造方法私有化(private);
2.定义一个私有(private)静态(static)实例化对象;
3.对外提供一个公共(public)静态(static)的方法得到该实例;