设计模式(Design Patterns)
设计模式 (Design pattern) 是一套被反复使用多数人知晓的经过分类编目的代码设计经验的总结
使用设计模式是为了可重用代码让代码更容易被他人理解保证代码可靠性
毫无疑问, 设计模式于己于他人于系统都是多赢的, 设计模式使代码编制真正工程化, 设计模式是软件工程的基石, 如同大厦的一块块砖石一样
项目中合理的运用设计模式可以完美的解决很多问题, 每种模式在现在中都有相应的原理来与之对应, 每一个模式描述了一个在我们周围不断重复发生的问题, 以及该问题的核心解决方案, 这也是它能被广泛应用的原因
一设计模式的分类
总体来说设计模式分为三大类:
创建型模式, 共五种: 工厂方法模式抽象工厂模式单例模式建造者模式原型模式
结构型模式, 共七种: 适配器模式装饰器模式代理模式外观模式桥接模式组合模式享元模式
行为型模式, 共十一种: 策略模式模板方法模式观察者模式迭代子模式责任链模式命令模式备忘录模式状态模式访问者模式中介者模式解释器模式
还有两类: 并发型模式和线程池模式
二设计模式的六大原则
1 开闭原则(Open Close Principle)
开闭原则就是说对扩展开放, 对修改关闭在程序需要进行拓展的时候, 不能去修改原有的代码, 实现一个热插拔的效果所以一句话概括就是: 为了使程序的扩展性好, 易于维护和升级想要达到这样的效果, 我们需要使用接口和抽象类, 后面的具体设计中我们会提到这点
2 里氏代换原则(Liskov Substitution Principle)
里氏代换原则 (Liskov Substitution Principle LSP) 面向对象设计的基本原则之一 里氏代换原则中说, 任何基类可以出现的地方, 子类一定可以出现 LSP 是继承复用的基石, 只有当衍生类可以替换掉基类, 软件单位的功能不受到影响时, 基类才能真正被复用, 而衍生类也能够在基类的基础上增加新的行为里氏代换原则是对开 - 闭原则的补充实现开 - 闭原则的关键步骤就是抽象化而基类与子类的继承关系就是抽象化的具体实现, 所以里氏代换原则是对实现抽象化的具体步骤的规范 From Baidu 百科
3 依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础, 具体内容: 针对接口编程, 依赖于抽象而不依赖于具体
4 接口隔离原则(Interface Segregation Principle)
这个原则的意思是: 使用多个隔离的接口, 比使用单个接口要好还是一个降低类之间的耦合度的意思, 从这儿我们看出, 其实设计模式就是一个软件的设计思想, 从大型软件架构出发, 为了升级和维护方便所以上文中多次出现: 降低依赖, 降低耦合
5 迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则, 就是说: 一个实体应当尽量少的与其他实体之间发生相互作用, 使得系统功能模块相对独立
6 合成复用原则(Composite Reuse Principle)
原则是尽量使用合成 / 聚合的方式, 而不是使用继承
三 Java 的 23 中设计模式
详细介绍 Java 中 23 种设计模式的概念, 应用场景等情况, 并结合他们的特点及设计模式的原则进行分析, 具体参考下文:
http://zz563143188.iteye.com/blog/1847029
这里只写介绍单例设计模式
3 单例模式(Singleton)
单例对象 (Singleton) 是一种常用的设计模式
在 Java 应用中, 单例对象能保证在一个 JVM 中, 该对象只有一个实例存在
这样的模式有几个好处:
1 某些类创建比较频繁, 对于一些大型的对象, 这是一笔很大的系统开销
2 省去了 new 操作符, 降低了系统内存的使用频率, 减轻 GC 压力
3 有些类如交易所的核心交易引擎, 控制着交易流程, 如果该类可以创建多个的话, 系统完全乱了(比如一个军队出现了多个司令员同时指挥, 肯定会乱成一团), 所以只有使用单例模式, 才能保证核心交易服务器独立控制整个流程
几个要点:
私有 静态实例
私有 构造方法
get 实例和 new 类分开
对 new 类进行单线程锁
先给出一个我心目中比较完美的例子:
- public class Singleton {
- /* 持有私有静态实例, 防止被引用, 此处赋值为 null, 目的是实现延迟加载 */
- private static Singleton instance = null;
- /* 私有构造方法, 防止被实例化 */
- private Singleton() {
- }
- /* 使用内部类并在创建实例时加单线程锁 */
- private static synchronized void syncInit() {
- if (instance == null) {
- instance = new SingletonTest();
- }
- }
- /* 静态工程方法, 获取实例 */
- public static SingletonTest getInstance() {
- if (instance == null) {
- syncInit();
- }
- return instance;
- }
- }
这个完美的单例模式 Sample 代码是怎么得到的, 下面从头开始讲述设计完善的过程(不看也行, 把上面的例子理解透记住即可):
首先我们写一个简单的单例类:
- 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;
- }
- }
这个类可以满足基本要求, 但是, 像这样毫无线程安全保护的类, 如果我们把它放入多线程的环境下, 肯定就会出现问题了, 如何解决? 我们首先会想到对 getInstance 方法加 synchronized 关键字, 如下:
synchronized 当它用来修饰一个方法或者一个代码块的时候, 能够保证在同一时刻最多只有一个线程执行该段代码
- public static synchronized Singleton getInstance() {
- if (instance == null) {
- instance = new Singleton();
- }
- return instance;
- }
但是, synchronized 关键字锁住的是这个对象, 这样的用法, 在性能上会有所下降, 因为每次调用 getInstance(), 都要对对象上锁, 事实上, 只有在第一次创建对象的时候需要加锁, 之后就不需要了, 所以, 这个地方需要改进我们改成下面这个:
- public static Singleton getInstance() {
- if (instance == null) {
- synchronized (instance) {
- if (instance == null) {
- instance = new Singleton();
- }
- }
- }
- return instance;
- }
似乎解决了之前提到的问题, 将 synchronized 关键字加在了内部, 也就是说当调用的时候是不需要加锁的, 只有在 instance 为 null, 并创建对象的时候才需要加锁, 性能有一定的提升但是, 这样的情况, 还是有可能有问题的, 看下面的情况: 在 Java 指令中创建对象和赋值操作是分开进行的, 也就是说 instance = new Singleton(); 语句是分两步执行的但是 JVM 并不保证这两个操作的先后顺序, 也就是说有可能 JVM 会为新的 Singleton 实例分配空间, 然后直接赋值给 instance 成员, 然后再去初始化这个 Singleton 实例这样就可能出错了, 我们以 AB 两个线程为例:
a>AB 线程同时进入了第一个 if 判断
b>A 首先进入 synchronized 块, 由于 instance 为 null, 所以它执行 instance = new Singleton();
c > 由于 JVM 内部的优化机制, JVM 先画出了一些分配给 Singleton 实例的空白内存, 并赋值给 instance 成员(注意此时 JVM 没有开始初始化这个实例), 然后 A 离开了 synchronized 块
d>B 进入 synchronized 块, 由于 instance 此时不是 null, 因此它马上离开了 synchronized 块并将结果返回给调用该方法的程序
e > 此时 B 线程打算使用 Singleton 实例, 却发现它没有被初始化, 于是错误发生了
所以程序还是有可能发生错误, 其实程序在运行过程是很复杂的, 从这点我们就可以看出, 尤其是在写多线程环境下的程序更有难度, 有挑战性我们对该程序做进一步优化:
- private static class SingletonFactory{
- private static Singleton instance = new Singleton();
- }
- public static Singleton getInstance(){
- return SingletonFactory.instance;
- }
实际情况是, 单例模式使用内部类来维护单例的实现, JVM 内部的机制能够保证当一个类被加载的时候, 这个类的加载过程是线程互斥的这样当我们第一次调用 getInstance 的时候, JVM 能够帮我们保证 instance 只被创建一次, 并且会保证把赋值给 instance 的内存初始化完毕, 这样我们就不用担心上面的问题同时该方法也只会在第一次调用的时候使用互斥机制, 这样就解决了低性能问题这样我们暂时总结一个完美的单例模式:
- 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 SingletonTest {
- private static SingletonTest instance = null;
- private SingletonTest() {
- }
- private static synchronized void syncInit() {
- if (instance == null) {
- instance = new SingletonTest();
- }
- }
- public static SingletonTest getInstance() {
- if (instance == null) {
- syncInit();
- }
- return instance;
- }
- }
通过单例模式的学习告诉我们:
1 单例模式理解起来简单, 但是具体实现起来还是有一定的难度
2synchronized 关键字锁定的是对象, 在用的时候, 一定要在恰当的地方使用(注意需要使用锁的对象和过程, 可能有的时候并不是整个对象及整个过程都需要锁)
到这儿, 单例模式基本已经讲完了, 结尾处, 笔者突然想到另一个问题, 就是采用类的静态方法, 实现单例模式的效果, 也是可行的, 此处二者有什么不同?
首先, 静态类不能实现接口(从类的角度说是可以的, 但是那样就破坏了静态了因为接口中不允许有 static 修饰的方法, 所以即使实现了也是非静态的)
其次, 单例可以被延迟初始化, 静态类一般在第一次加载是初始化之所以延迟加载, 是因为有些类比较庞大, 所以延迟加载有助于提升性能
再次, 单例类可以被继承, 他的方法可以被覆写但是静态类内部方法都是 static, 无法被覆写
最后一点, 单例类比较灵活, 毕竟从实现上只是一个普通的 Java 类, 只要满足单例的基本需求, 你可以在里面随心所欲的实现一些其它功能, 但是静态类不行从上面这些概括中, 基本可以看出二者的区别, 但是, 从另一方面讲, 我们上面最后实现的那个单例模式, 内部就是用一个静态类来实现的, 所以, 二者有很大的关联, 只是我们考虑问题的层面不同罢了两种思想的结合, 才能造就出完美的解决方案, 就像 HashMap 采用数组 + 链表来实现一样, 其实生活中很多事情都是这样, 单用不同的方法来处理问题, 总是有优点也有缺点, 最完美的方法是, 结合各个方法的优点, 才能最好的解决问题!
来源: http://www.bubuko.com/infodetail-2523992.html