- 开闭原则:对修改封闭,对扩展开放。
- 单一职责原则:一个类只做一件事,一个类应该只有一个引起它修改的原因。
- 里氏替换原则:子类应该可以完全替换父类。也就是说在使用继承时,只扩展新功能,而不要破坏父类原有的功能。
- 依赖倒置原则:细节应该依赖于抽象,抽象不应依赖于细节。把抽象层放在程序设计的高层,并保持稳定,程序的细节变化由低层的实现层来完成。
- 变量不可以持有具体类的引用
- 不要让类派生自具体类
- 不要覆盖基类中已实现的方法
- 迪米特法则:又名“最少知道原则”,一个类不应知道自己操作的类的细节,换言之,只和朋友谈话,不和朋友的朋友谈话。
- 接口隔离原则:客户端不应依赖它不需要的接口。如果一个接口在实现时,部分方法由于冗余被客户端空实现,则应该将接口拆分,让实现类只需依赖自己需要的接口方法。
接口:接口是一个概念,是超类型,比如抽象类和接口。这样我们声明的时候不需要管以后执行时的真实对象类型。
- 找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起
- 针对接口编程,而不是针对实现编程
- 多用组合,少用继承
- 类应该对修改封闭,对扩展开放
- 要依赖抽象,不要依赖具体类
创建型、结构型、行为型
默认使用饿汉式
饿汉式:线程安全;但是对象加载时间长
懒汉式:线程不安全;但是可以延迟对象的创建
枚举类:
- 反编译后只生成了一个 static 静态变量。静态变量初始化是在类加载的时候完成的,是线程安全的
- 无法被反射、反序列化破坏单例
- 属于饿汉式
变量在声明时便初始化(也可以把实例化放到静态代码块里面)
// 加final 是防止继承,防止子类覆盖父类的方法
// 如果实现了序列化接口,如何防止反序列化破坏单例(添加readResovle方法,返回自己写的单例对象)
public final class Singleton implements Serializable{
// static 对象在类加载的时候完成,可以保证线程安全
private static final Singleton instance = new Singleton();
// 设置私有不能防止反射破坏单例
private Singleton() {}
// 为什么提供静态方法,而不是直接把变量设为 public
// 1.封装性
public static Singleton getInstance() {
return instance;
}
public Object readResovle(){
return instance;
}
}
先声明一个空变量,需要用时才初始化。例如:
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
// 线程安全,但是加锁范围太大了
public static synchronized Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
// 效率较低。如果已经实例化过了,其实直接拿着就走了,不需要再加锁了
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
}
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
// 效率稍高
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
JVM 底层为了优化程序运行效率,可能会对我们的代码进行指令重排序,在一些特殊情况下会导致出现空指针,为了防止这个问题,更进一步的优化是给 instance 变量加上 volatile 关键字。
问:双检锁单例模式中,volatile 主要用来防止哪几条指令重排序?如果发生了重排序,会导致什么样的错误?
答案:
instance = new Singleton();
这一行代码中,执行了三条重要的指令:
- 分配对象的内存空间
- 初始化对象
- 将变量 instance 指向刚分配的内存空间
在这个过程中,如果第二条指令和第三条指令发生了重排序,可能导致 instance 还未初始化时,其他线程提前通过双检锁外层的 null 检查,获取到“不为 null,但还没有执行初始化”的 instance 对象,发生空指针异常。
public class Singleton {
// 懒汉式
private static class SingletonHolder {
public static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
定义一个类,类里面有个 create
静态方法,根据传递的参数不同来创建不同的对象
public class FruitFactory {
public static Fruit create(String type){
switch (type){
case "苹果": return new Apple();
case "梨子": return new Pear();
default: throw new IllegalArgumentException("暂时没有这种水果");
}
}
}
定义:工厂方法定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
让子类决定该创建什么样的对象,以此达到将对象的创建过程封装的目的
public abstract class FruitFactory {
abstract Fruit create(String type);
}
// 具体的工厂
public class AppleFactory extends FruitFactory {
Fruit create(String type);
}
// 其实,水果本身也是抽象的
public abstract class Fruit{
// 一些代码
}
使用继承
工厂方法声明为抽象的,让子类去实现
抽象工厂提供一个接口,用于创建相关或依赖对象的家族,而不需要指明具体的类
抽象工厂的方法经常以工厂方法的方式实现。
原型模式:复制现有的实例来创建新的实例
Java 里重写 clone 方法即可,或者反序列化
Java 自带的 clone 方法是浅拷贝的。就是说只有基本类型的参数会被拷贝一份,非基本类型的对象不会被拷贝一份,而是继续使用传递引用的方式。如果需要实现深拷贝,必须要自己手动修改 clone 方法才行。
优点:
- 客户不需要知道对象内部的复杂性
- 部分时候,复制比创建一个新对象更优秀(但有时也很复杂)
适配器模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
代理模式:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
不修改底层代码,赋予对象新的职责。
特点:
- 装饰者和被装饰者有相同的超类型
- 可以有多个装饰者
- 装饰者可以有自己的行为
- 对象可以在任何时候被装饰
- 把被装饰的对象作为参数传递给装饰者
实现:一般使用抽象类继承的方式,但是也可以用 Java 的接口
新建颜值接口:
public interface IBeauty {
int getBeautyValue();
}
新建 Me 类,实现颜值接口:
public class Me implements IBeauty {
@Override
public int getBeautyValue() {
return 100;
}
}
戒指装饰类,将 Me 包装起来:
public class RingDecorator implements IBeauty {
private final IBeauty me;
public RingDecorator(IBeauty me) {
this.me = me;
}
@Override
public int getBeautyValue() {
return me.getBeautyValue() + 20;
}
}
客户端测试:
public class Client {
@Test
public void show() {
IBeauty me = new Me();
System.out.println("我原本的颜值:" + me.getBeautyValue());
IBeauty meWithRing = new RingDecorator(me);
System.out.println("戴上了戒指后,我的颜值:" + meWithRing.getBeautyValue());
}
}
运行程序,输出如下:
我原本的颜值:100 戴上了戒指后,我的颜值:120 这就是最简单的增强功能的装饰模式。以后我们可以添加更多的装饰类,比如:
耳环装饰类:
public class EarringDecorator implements IBeauty {
private final IBeauty me;
public EarringDecorator(IBeauty me) {
this.me = me;
}
@Override
public int getBeautyValue() {
return me.getBeautyValue() + 50;
}
}
项链装饰类:
public class NecklaceDecorator implements IBeauty {
private final IBeauty me;
public NecklaceDecorator(IBeauty me) {
this.me = me;
}
@Override
public int getBeautyValue() {
return me.getBeautyValue() + 80;
}
}
客户端测试:
public class Client {
@Test
public void show() {
IBeauty me = new Me();
System.out.println("我原本的颜值:" + me.getBeautyValue());
// 随意挑选装饰
IBeauty meWithNecklace = new NecklaceDecorator(me);
System.out.println("戴上了项链后,我的颜值:" + meWithNecklace.getBeautyValue());
// 多次装饰
IBeauty meWithManyDecorators = new NecklaceDecorator(new RingDecorator(new EarringDecorator(me)));
System.out.println("戴上耳环、戒指、项链后,我的颜值:" + meWithManyDecorators.getBeautyValue());
// 任意搭配装饰
IBeauty meWithNecklaceAndRing = new NecklaceDecorator(new RingDecorator(me));
System.out.println("戴上戒指、项链后,我的颜值:" + meWithNecklaceAndRing.getBeautyValue());
}
} 运行程序,输出如下:
我原本的颜值:100 戴上了项链后,我的颜值:180 戴上耳环、戒指、项链后,我的颜值:250 戴上戒指、项链后,我的颜值:200
作者:力扣 (LeetCode) 链接:https://leetcode-cn.com/leetbook/read/design-patterns/99j7re/ 来源:力扣(LeetCode)
策略模式(Strategy Pattern):定义了一系列算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
模板方法模式(Template Method Pattern):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。