设计模式
设计模式
概念:是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。、
分类:设计模式可分为创建型(Creational),结构型(Structural)和行为型(Behavioral)
- 创建型模式:用于描述如何 创建对象————五种
其中包括:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
- 结构型模式:用于描述如何 实现类或对象的组合————七种
其中包括:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式:用于描述类或对象 怎样交互以及怎样分配职责————十一种
其中包括:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
面向对象设计思想
单一职责原则——SRP
- 概念:一个类只负责一个功能。
单一职责原则是实现高内聚、低耦合的指导方针
开闭原则——OCP
- 概念:一个软件实体应当对扩展开放,对修改关闭。
里氏代换原则——LSP
- 概念:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
例如:父类Animal————子类Dog
依赖倒转原则——DIP
- 概念:抽象不应该依赖于细节,细节应当依赖于抽象。就是要针对接口编程,而不是针对实现编程。
开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段
接口隔离原则——ISP
- 概念:使用多个专门的接口,而不使用单一的总接口。
合成复用原则——CRP
- 概念:尽量使用对象组合,而不是继承来达到复用的目的。
迪米特法则——LOD
- 概念:一个软件实体应当尽可能少地与其他实体发生相互作用。
应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。
单例模式
- 概念:保证对某个类只存在一个对象实例,并且仅提供一个生成该类的实例的静态方法。
- 使用场景
- 需要频繁创建或销毁的对象。
- 创建对象耗费资源过多
- 工具类对象、数据源对象、文件对象
饿汉式————静态常量
class SingleMan{
// 1. 私有构造器
private SingleMan() {}
// 2. 私有静态变量(也可以说是常量,因为仅一个,且不变)
private static final SingleMan singleMan = new SingleMan();
// 3. 公有静态方法
public static SingleMan getInstance(){
return singleMan;
}
}
- 优点:在类装载时完成实例化,避免线程同步问题获取多个对象实例。
- 缺点:提前加载好,若实例未使用,浪费内存空间,没有达到懒加载的效果。
饿汉式————静态代码块
其他步骤不变,第二步,将创建对象的操作放在静态代码块中
// 2. 私有静态变量(也可以说是常量,因为仅一个,且不变)
private static final SingleMan singleMan ;
// 2. 将创建对象的步骤放在静态代码块中
static {
singleMan = new SingleMan();
总结:可以使用,但是会造成资源浪费。
懒汉式————线程不安全
在使用getInstance()方法时才会创建对象实例
class LazyMan{
// 1. 私有构造器
private LazyMan() {}
// 2. 私有静态变量
private static LazyMan lazyMan ;
// 3. 公有静态方法
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
- 优点:达到了懒加载的效果。
- 缺点:线程不安全。当出现多个线程同时进入getInance()方法时,会产生多个实例。
懒汉式————线程安全(同步方法)
其他步骤不变,第三步,在方法上加synchronized同步锁,保证线程安全。
public static synchronized LazyMan getInstance(){
if (lazyMan==null){
lazyMan = new LazyMan();
}
return lazyMan;
}
- 优点:解决了线程不安全的问题。
- 缺点:效率低。每次获取实例时都会调用getInstance()方法,每次都会进入同步锁。
双重检查
第一步:在变量上添加volatile关键字。
- volatile目的:多线程时,一个线程改变volatile修饰的变量,其他线程能立马看到。
第三步:在getInstance()方法中添加一个同步代码块,里面再次判断对象是否为空。
双重检查锁原理
条件:多线程情况下,未创建对象。
执行步骤
- 线程A、B均进入getInstance()方法,因为对象实例为空,均在同步代码块处等待。
- 此时线程A先进入同步代码块,再次判断确定对象为空,创建对象后退出。
- 此时线程B进入同步代码块,再次判断时,因为实例对象上有volatile修饰,B看到A已创建对象,此时对象不为空,则直接返回A创建好的对象实例。
class SingleTon{
// 1. 私有构造器
private SingleTon() {}
// 2. 私有静态变量
private static volatile SingleTon singleTon ;
// 3. 公有静态方法————双重检查
public static SingleTon getInstance(){
if (singleTon==null){
synchronized (SingleTon.class){
if (singleTon==null){
singleTon = new SingleTon();
}
}
}
return singleTon;
}
}
总结:解决了线程不安全问题,双重判断也使程序效率提升。不用每次都进入同步锁中进行判断,减少了消耗。推荐使用!!!
静态内部类
静态内部类的特点:
- 主类被加载时,静态内部类不会被加载
- 静态内部类只会加载一次
class SingleOne{
// 1. 私有构造器
private SingleOne() {}
// 2. 静态内部类
private static class SingleManInner{
private static final SingleOne INSTANCE = new SingleOne();
}
// 3. 公有静态方法————调用静态内部类
public static SingleOne getInstance(){
return SingleManInner.INSTANCE;
}
}
总结:既解决了线程不安全问题,又满足了懒加载的需求。推荐使用!!!
枚举
public class EnumDemo {
public static void main(String[] args) {
Single instance01 = Single.INSTANCE;
Single instance02 = Single.INSTANCE;
System.out.println(instance01==instance02);
System.out.println(instance01.hashCode());
System.out.println(instance02.hashCode());
}
}
enum Single{
INSTANCE;
public void say(){
System.out.println("你好啊");
}
}
阻绝了反射和反序列化,简单方便。