注:本文参考《深入浅出设计模式》和网上资料,并对某些文字以自己的理解进行了适当的修改。个人觉得本文应作为入门学习,了解大体框架,具体的设计模式有待详细研究。
一、面向对象的设计原则(此书七大原则)
1. 单一指责原则(SRP,Single Responsibility Principle)
系统里的每一个对象都应该只有一个单独的职责,而所有对象所关注的就是自身职责的完成。每个类应该只有一个职责,对外只能提供一种功能,而引起类变化的原因应该只有一个。2. 开闭原则(OCP,Open Close Principle)
对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。3. 依赖注入原则(DIP,Dependence Inversion Principle)
面对接口编程,依赖于抽象而不依赖于具体。4. 里氏替换原则(LSP,Liskov Substitution Principle)
任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。LSP是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。5. 迪米特法则(最少知道原则)(DP,Demeter Principle)(LOD,Law of Demeter)
一个对象应当尽量少的与其他对象之间发生相互作用,使得系统功能模块相对独立(降低各个对象之间的耦合),提高系统可维护性。6. 接口隔离原则(ISP,Interface Segregation Principle)
使用多个隔离的接口,比使用单个接口要好。不应该强迫客户程序依赖它们不需要使用的方法。(一个接口应该只提供一种对外功能,不应该把所有的操作都封装到一个接口当中)7.优先使用组合而不是继承原则(CARP,Composite/Aggregate Reuse Principle)
二、设计模式的分类
1. 创建型模式,共五种
单例模式(Singleton)
简单工厂模式(Simple Factory),此模式并不在23种设计模式之中
工厂方法模式(Factory Method)
抽象工厂模式(Abstract Factory)
原型模式(Prototype)
创建者模式(Builder)
2. 结构型模式,共七种
适配器模式(Adapter)
门面模式(Facade)
代理模式(Proxy)
合成模式(Composite)
享元模式(Flyweight)
装饰模式(Decorator)
桥接模式(Bridge)
3. 行为型模式,共11种
策略模式(Strategy)
迭代器模式(Iterator)
模版方法模式(Template Method)
中介者模式(Mediator)
访问者模式(Vistor)
职责链模式(Chain of Responsibility)
状态模式(State)
解释器模式(Interpreter)
观察者模式(Observer)
命令模式(Command)
备忘录模式(Memento)
三、JAVA23种设计模式
1.单例模式(Singleton)
单例模式在java中的Runtime类、数据库连接池和日志管理中典型应用,总结起来就是当程序运行时,需要保证一个对象只有一个实例存在时,就应该用到单例模式。这样的模式有几个好处:
某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
package singleton;
public class Singleton {
private static Singleton instance = new Singleton();
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 静态工程方法,创建实例 */
public static Singleton getInstance() {
return instance;
}
}
public class Singleton {
/* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
private static Singleton instance = null;
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 静态工程方法,创建实例 */
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return instance;
}
}
此懒汉模式在单线程的程序应用中是没有任何问题的,但是在多线程程序中就会出现问题(当多个线程都进行if(instance == null)判断时,就会产生多个该类的示例)。我们首先会想到对getInstance方法加synchronized关键字,如下:
// 增加同步机制
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
但是因为这种写法是将synchronized机制放在了获取实例的方法上,导致程序每取一次实例,都将进入synchronized机制,效率低。
事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,Double-checked locking(双检测锁)机制:
public static Singleton getInstance() {
if (instance == null) {
// 同步机制放在产生实例的代码前
synchronized (instance) {
if (instance == null) { //对是否为null再次判断
instance = new Singleton();
}
}
}
return instance;
}
但是,这样的情况,还是有可能有问题的,看下面的情况:
在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了。
我们以A、B两个线程为例:
- A、B线程同时进入了第一个if判断
- A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
- 由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
- B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
- 此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
所以程序还是有可能发生错误,其实程序在运行过程是很复杂的,从这点我们就可以看出,尤其是在写多线程环境下的程序更有难度,有挑战性。我们对该程序做进一步优化:
public class Singleton {
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 此处使用一个内部类来维护单例 */
private static class SingletonFactory {
private static Singleton instance = new Singleton();
}
/* 获取实例 */
public static Singleton getInstance() {
return SingletonFactory.instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return getInstance();
}
}
其实说它完美,也不一定,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。所以说,十分完美的东西是没有的,我们只能根据实际情况,选择最适合自己应用场景的实现方法。也有人这样实现:因为我们只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加synchronized关键字,也是可以的:
public class Singleton {
private static Singleton instance = null;
private Singleton () {
}
private static synchronized void syncInit() {
if (instance == null) {
instance = new Singleton ();
}
}
public static Singleton getInstance() {
if (instance == null) {
syncInit();
}
return instance;
}
}
首先,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有static修饰的方法,所以即使实现了也是非静态的)
其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。
再次,单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是static,无法被覆写。
最后一点,单例类比较灵活,毕竟从实现上只是一个普通的Java类,只要满足单例的基本需求,你可以在里面随心所欲的实现一些其它功能,但是静态类不行。
从上面这些概括中,基本可以看出二者的区别,但是,从另一方面讲,我们上面最后实现的那个单例模式,内部就是用一个静态类来实现的,所以,二者有很大的关联,只是我们考虑问题的层面不同罢了。两种思想的结合,才能造就出完美的解决方案,就像HashMap采用数组+链表来实现一样,其实生活中很多事情都是这样,单用不同的方法来处理问题,总是有优点也有缺点,最完美的方法是,结合各个方法的优点,才能最好的解决问题!