成为架构师的必经之路-掌握软件设计模式(一)
为什么要学设计模式
学习设计模式的目的是什么?一定要学吗?相信很多同学都对设计模式这个概念有疑惑,那就简单的来说说学习设计模式会有什么用处吧。
1、公司的项目需要进行重构,但是不知道怎么下手,做到高内聚低耦合
2、为了成为武林高手、在面试时对面试官的各种刁难问题
3、为了写一些高大上的代码,让新手看不懂,或者是为了看高手写的代码
4、为了提升自己,更好的理解框架源码的设计思想,封装中间件
5、让代码更好地重用、可读、可靠、可维护、可拓展
设计模式的六大原则你了解多少
我们知道,设计模式是在设计原则的基础之上设计的,所以在学习设计模式之前,有必要对设计原则来做一些了解。
软件设计开发的原则:为了让代码有更好的重用性、可读性、可靠性、可维护性。所以就诞生出了很多的软件设计原则,这六大设计原则就是我们要掌握的。我们将六大原则的英文首字母拼在一起就是SOLID,所以也被成为SOLID原则。
「六大设计原则」
单一原则:一个类只负责一个功能领域相应的职责,就一个类来说,应该只有一个引起它发生变化的原因。单一原则是实现高内聚、低耦合的指导方针。高内聚就是尽可能类的每个成员方法只完成一件事,实现最大限度的聚合。模块内部里的代码,它们之间相互联系越强的话,内聚就越高,模块的独立性就越好。低耦合就是减少类的内部,一个成员方法调用另一个成员方法,不要一有牵动就发动全身。
开闭原则:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。
里氏替换原则LSP:任务基类可以出现的地方,子类就一定可以出现,在程序中尽量使用基类类型来对对象进行定义,而在运行的时候再确定其子类类型,用子类对象来替换父类的对象。controller->service->dao
依赖倒转原则:是开闭原则的基础,针对的是接口编程,依赖于抽象而不依赖于具体,高层的模块不应该依赖低层的模块,二者都应该依赖其抽象。
接口隔离原则:客户端不应该依赖哪些它不需要的接口,使用多个隔离的接口,比使用单个接口要好,降低类之间耦合度。
迪米特法则:这个应该是最少知道的原则,一个实体应该尽量少的与其他实体之间发生相互的作用,使得系统的功能模块相对独立,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改的话,不会对关联的类造成太大的波及。通过引入一个合理的第三者来降低现有对象之间的耦合度。
常见的三大设计模式分类
「创建型模式」
这种模式提供了一种在创建对象时同时隐藏创建逻辑的方式,使得程序在判断针对某一个给定的实例需要创建哪些对象时更加的灵活。常用的有:工厂模式、抽象工厂模式、单例模式、建造者模式;不常用:原型模式
「结构型模式」
关注类和对象的组合,继承的概念被用来组合接口和定义组合对象获得新功能的方式。常用的有:适配器模式、桥接模式、装饰器模式、代理模式;不常用的有:组合模式、享元模式
「行为型模式」
这种模式特别关注对象之间的通信,常用的有:责任链模式、迭代器模式、观察者模式、状态模式、策略模式、模板模式;不常用的有:备忘录模式、命令模式;几乎用不上的有:访问者模式、中介者模式、解释器模式
设计模式-单例设计模式
「单例设计模式:」
这个是最简单的设计模式,也是面试重点考察。单例的意思只包含一个对象被称为单例的特殊类,通过单例可以保证系统中,应用该模式的类只有一个对象实例。
使用场景:业务系统全局只需要一个对象实例,比如说像发号器、redis连接对象等等。SpringIOC容器中的bean默认就是单例模式的。springboot里的controller、service、dao层中通过@autowire的依赖注入对象默认的话都是单例的。
「单例设计模式又分为懒汉式和饿汉式」
懒汉式:就是所谓的懒加载,延迟的创建对象
饿汉式:与懒汉相反,提前创建对象
「饿汉式单例模式」
首先看看最原始的单例模式方式
/**
* 饿汉单例模式
*/
public class Singleton {
//直接声明一个静态变量并进行实例化
private static final Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
//返回已经初始化的实例
return instance;
}
}
上面的实现方式是饿汉式的单例模式标准写法,可以看到一般的单例模式的构造方法会进行私有化,这是为了防止new的操作创建多余的对象,其实就是提供一种全局访问的方法,饿汉式的单例实现有几个特点
第一种,饿汉式的单例写法用static关键字声明后直接new对象,这样就是在类的加载中进行对象的初始化。
第二种,因为是在类加载的时候就进行了对象初始化类,所以它绝对是线程安全的,因为它没有在线程出现之前就已经实例化好了,所以不会存在线程的安全问题。
仔细想想这种写法会有问题吗?答案是必须有问题的,我们都知道计算器的内存资源是很珍贵的,但是这种在类加载前就进行了实例化,如果系统里有大量的这种饿汉式的写法时,会造成了对内存的浪费。针对这种问题,就出现了懒汉式单例模式。
//private static SingletonLazy instance;
/**
* 构造函数私有化
*/
private SingletonLazy(){}
/**
* 单例对象的方法
*/
public void process(){
System.out.println("方法调用成功");
}
/**
* 第一种方式
* 对外暴露一个方法获取类的对象
*
* 线程不安全,多线程下存在安全问题
*
*/
// public static SingletonLazy getInstance(){
// if(instance == null){
// instance = new SingletonLazy();
// }
// return instance;
// }
从上面代码可以看出在调用getinstance方法的时候会进行一次判断,只有当单例的对象为null的时候才进行实例化的创建。然而并非这么简单,如果是由于多线程访问getinstance方法导致系统出现多余的单例对象,线程不安全问题。针对这个问题,又有一种单例写法出现。
/**
* 第二种实现方式
* 通过加锁 synchronized 保证单例
*
* 采用synchronized 对方法加锁有很大的性能开销
*
* 解决办法:锁粒度不要这么大
*
* @return
*/
// public static synchronized SingletonLazy getInstance(){
// if(instance == null){
// instance = new SingletonLazy();
// }
// return instance;
// }
就这样,懒汉式的单例写法二出现了,这种方式最主要的就是线程安全的。这样的方式还有没有问题呢?答案还是有问题的!如果你有考虑到性能方面的话就会发现问题所在了,当线程并发很激烈的时候,大家都想要得到单例对象,这个时候又全部进行排队获取,会导致大量的线程阻塞,导致程序的性能大幅度的下降。这个时候又一种写法出现了哈哈哈哈
/**
* 第三种实现方式
*
* DCL 双重检查锁定 (Double-Checked-Locking),在多线程情况下保持高性能
*
* 这是否安全,instance = new SingletonLazy(); 并不是原子性操作
* 1、分配空间给对象
* 2、在空间内创建对象
* 3、将对象赋值给引用instance
*
* 假如线程 1-》3-》2顺序,会把值写会主内存,其他线程就会读取到instance最新的值,但是这个是不完全的对象
* (指令重排)
* @return
*/
// public static SingletonLazy getInstance(){
// if(instance == null){
// // A、B
// synchronized (SingletonLazy.class){
// if(instance == null) {
// instance = new SingletonLazy();
// }
// }
// }
// return instance;
// }
双重检查锁的模式是一种近乎于完美的单例实现模式,它解决了上几个没有解决的性能、线程安全的问题。近乎完美也就是还是有缺点的,对没错,在多线程的情况下会出现空指针的问题,出现这个问题的原因就是JVM在实例化对象的时候会进行优化与指令的重排,要解决这个异常问题,只需要使用valatile关键字来保证可见性和有序性来修饰instance实例对象
- 点赞
- 收藏
- 关注作者
评论(0)