目录
声明
此文只针对我个人的目前的理解程度来记录的,有的我知道的或者觉得没有必要写的内容我就会省略掉。并且主要针对面试,介绍的可能不是很全面,甚至有的地方不是很正确,请包涵。
主要参考文章
设计模式的目的
- ++可重用性++ (即:相同功能的代码,不用多次编写)
- ++可扩展性++ (即:当需要增加新的功能时,非常的方便,称为可维护)
- ++可靠性++ (即:当我们增加新的功能后,对原来的功能没有影响)
- ++可读性++ (即:编程规范性, 便于其他程序员的阅读和理解)
- 使程序呈现++高内聚,低耦合++的特性。
设计模式的七大原则
固定记忆:单 开 里 依 接 合 迪。
设计模式的三大分类及关键点
1、创建型模式(++用于解耦对象的实例化过程++)
单例模式:某个类只能有一个实例,提供一个全局的访问点。
工厂模式:一个工厂类根据传入的参量决定创建出哪一种产品类的实例。
抽象工厂模式:创建相关或依赖对象的家族,而无需明确指定具体类。
建造者模式:封装一个复杂对象的创建过程,并可以按步骤构造。
原型模式:通过复制现有的实例来创建新的实例。
2、结构型模式
装饰器模式:动态的给对象添加新的功能。
代理模式:不修改原始对象,通过代理对象来间接访问原始对象,并在访问前后执行额外的操作。
桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。
适配器模式:将一个类的方法接口转换成客户希望的另一个接口。
组合模式:将对象组合成树形结构以表示"部分-整体"的层次结构。
外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
享元模式:通过共享技术来有效的支持大量细粒度的对象。
3、行为型模式
策略模式:定义一系列算法,把他们封装起来,并且使它们可以相互替换。
模板模式:抽象父类定义一个算法,子类在不改变该算法结构的情况下重定义该算法的某些步骤。
命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。
观察者模式:对象间的一对多的依赖关系。
仲裁者模式:用一个中介对象来封装一系列的对象交互。
备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。
建造者模式:允许一个对象在其对象内部状态改变时改变它的行为。
责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
访问者模式:不改变数据结构的前提下,增加作用于一组对象元素的新功能。
23种设计模式(乱序--现学现写,不全面--应付面试为主)
单例模式--创建型
某个类只能有一个实例,提供一个全局的访问点。
单例模式要素:
- 私有构造方法;
- 私有静态引用指向自己实例 ;
- 以自己实例为返回值的公有静态方法;
单例模式好处:
- 单例模式只允许创建一个对象,因此节省内存;
- 避免了频繁的对象创建,可以加快对象访问速度。
单例模式场景:
- 需要频繁实例化然后销毁的对象;
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象;
- 有状态的工具类对象;
单例模式的实现方式分类:
饿汉式单例:
java//线程安全的 public class Singleton { //单例模式,构造器要私有化。 private Singleton() {} //私有的静态的常量--私有静态引用指向自己实例。 //【因为是static修饰的属性,所以是在类加载的时候就被创建, //后期不会再改变,所以线程是安全的!】 private static final Singleton single = new Singleton(); //以自己实例为返回值的公有静态方法。 public static Singleton getInstance() { return single; } }
懒汉式单例:
java//有线程安全的问题,不推荐使用 //就算是使用了[加锁+双重判断]的解救办法,性能还是被损耗了。 public class SingletonTest { public static SingletonTest singleton = null; public static SingletonTest getInstance(){ if(singleton == null){ singleton = new SingletonTest(); } return singleton; } //单例模式,构造器要私有化 private SingletonTest { } }
DCL(Double Check双重判断 + Lock加锁)懒汉式单例但线程安全,推荐使用:
java//线程安全的 public class Singleton { private volatile static Singleton singleton; public static Singleton getSingleton(){ //双重判断之一 if(singleton==null){ //加锁 synchronized (Singleton.class) { //双重判断之二 if (singleton == null) { singleton = new Singleton(); } } } return singleton; } //构造函数私有化 private Singleton(){ } }
静态内部类(懒汉式单例但线程安全):
java//线程安全的 //外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。 //第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类且初始化INSTANCE。 //在创建时是否有并发问题? => 没有没有,类加载时jvm会保证线程安全性! public class Singleton { //静态内部类 private static class SingleTonHoler{ private static Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingleTonHoler.INSTANCE; } //单例模式,构造器要私有化 private Singleton { } }
枚举类(饿汉式):
java//枚举单例属于懒汉式还是饿汉式:饿汉式,内部枚举类相当于静态成员变量, //类加载时就会创建,因此也是线程安全的。 public class SingletonObject7 { private SingletonObject7(){ } /** * 枚举类型是线程安全的,并且只会装载一次 */ private enum Singleton{ INSTANCE; private final SingletonObject7 instance; Singleton(){ instance = new SingletonObject7(); } private SingletonObject7 getInstance(){ return instance; } } public static SingletonObject7 getInstance(){ //内部枚举类相当于静态成员变量 return Singleton.INSTANCE.getInstance(); } }
破坏单例模式的场景及解决办法:
1、反射是通过调用构造方法生成新对象的,++除枚举方式外,其他方式都会被反射破坏单例++,所以如果我们想要阻止单例破坏,可以在构造方法中进行判断,若已有实例,则阻止生成新的实例,解决办法如下。
javaprivate SingletonObject(){ if (instance != null) { throw new RuntimeException("实例已经存在,请通过 getInstance()方法获取"); } }
2、如果单例类实现了序列化接口Serializable, 就可以通过反序列化破坏单例(反序列化不会调用构造函数--反序列化会用到反射但不是通过反射调用构造函数而是ObjectInputStream 类的私有的++readObject()方法++ --使用ObjectInputStream.readObject()读取进来之后,如果是多次读取,就会创建多个实例),所以我们可以不实现序列化接口,如果非得实现序列化接口,可以在单例类中定义反序列化方法readResolve(),在反序列化时直接返回单例对象。
javaimport java.io.Serializable; // Singleton Box: class Box implements Serializable { private static Box instance = new Box("TEST"); public static Box getInstance() { return Box.instance; } private Box(String name) { this.name = name; } private String name; @Override public String toString() { return "Box " + name; } //【readResolve()方法】,如果不写此方法反序列化时会破坏单例 private Object readResolve() { return Box.getInstance(); } }
javaimport java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class Foo { public static void main(String[] args) { Box box = Box.getInstance(); System.out.println(box.toString()); try { ObjectOutputStream o = new ObjectOutputStream( new FileOutputStream("e:/box.out")); o.writeObject(box); o.close(); } catch(Exception e) { e.printStackTrace(); } Box box1 = null, box2 = null; try { ObjectInputStream in =new ObjectInputStream( new FileInputStream("e:/box.out")); box1 = (Box)in.readObject(); in.close(); } catch(Exception e) { e.printStackTrace(); } try { ObjectInputStream in =new ObjectInputStream( new FileInputStream("e:/box.out")); box2 = (Box)in.readObject(); in.close(); } catch(Exception e) { e.printStackTrace(); } System.out.println("box1.equals(box2) : " + box1.equals(box2)); System.out.println(box1); System.out.println(box2); } }
模板模式--行为型
抽象父类定义一个算法,子类在不改变该算法结构的情况下重定义该算法的某些步骤。
算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中,提高了代码复用性。
自定义lamabda表达式也符合此设计模式。
案例:炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。
java
/**
* @ClassName: AbstractClass
* @Description: 抽象类(定义模板方法和基本方法)
* @Author: Sevenyear
*/
public abstract class AbstractClass {
//模板方法定义
//【为防止恶意操作,模板方法一般都加上 final 关键词】
public final void cookProcess() {
pourOil();
heatOil();
pourVegetable();
pourSauce();
fry();
}
public void pourOil() {
System.out.println("倒油");
}
//第二步:热油是一样的,所以直接实现
public void heatOil() {
System.out.println("热油");
}
//第三步:倒蔬菜是不一样的(一个下包菜,一个是下菜心)
public abstract void pourVegetable();
//第四步:倒调味料是不一样
public abstract void pourSauce();
//第五步:翻炒是一样的,所以直接实现
public void fry(){
System.out.println("炒啊炒啊炒到熟啊");
}
}
java
/**
* @ClassName: ConcreteClass_BaoCai
* @Description: 炒包菜类
* @Author: Sevenyear
*/
public class ConcreteClass_BaoCai extends AbstractClass {
@Override
public void pourVegetable() {
System.out.println("下锅的蔬菜是包菜");
}
@Override
public void pourSauce() {
System.out.println("下锅的酱料是辣椒");
}
}
java
/**
* @ClassName: ConcreteClass_BaoCai
* @Description: 炒菜心类
* @Author: Sevenyear
*/
public class ConcreteClass_CaiXin extends AbstractClass {
@Override
public void pourVegetable() {
System.out.println("下锅的蔬菜是菜心");
}
@Override
public void pourSauce() {
System.out.println("下锅的酱料是蒜蓉");
}
}
java
public class Client {
public static void main(String[] args) {
//炒包菜
//创建对象
ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
//调用炒菜的功能
baoCai.cookProcess();
}
}
jdk案例:++InputStream抽象父类++ 中已经定义好了读取字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个空闲索引位置,循环读取len个字节数据。++具体如何读取一个字节数据?是由子类实现的++。
java
public abstract class InputStream implements Closeable {
//抽象方法,要求子类必须重写
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read(); //调用了无参的read方法,该方法是每次读取一个字节数据
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
}
在InputStream类中定义了多个 read() 方法,从上面代码可以看到,++无参的 read() 方法是抽象方法,要求子类必须实现++。而 read(byte b[]) 方法调用了 read(byte b[], int off, int len) 方法,所以在此处重点看的方法是带三个参数的方法。 在该方法中可以看到调用了无参的抽象的 read() 方法。
享元模式--结构型
java常量池(字符串常量池、Integer常量池)的实现其实是基于享元模式(通过共享技术来有效的支持大量细粒度的对象)的思想,可以节省创建的时间,并且节省空间。
++字符串常量池在java1.8中是在Java堆中的++ 。在编译期就存在的字符串将会直接存入这个池中,在不同代码地方的++字面量形式的相同的字符串++将会直接引用同一个字符串,为什么能这样引用是因为字符串的不可变性。
策略模式--行为型
comparable和comparator