设计模式
一、设计模式六大原则
- 单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。
- 开放封闭原则:类、模块、函数等应该是可以拓展的,但是不可修改。
- 里氏替换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
- 尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法。
- 运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码
- 增加新的功能可以通过增加一个新的子类来实现
- 里氏替换原则是开放封闭原则的具体实现手段之一
- 依赖倒置原则:
- 定义:高层模块不应该依赖低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
- 抽象指接口或者抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或者继 承抽象类而产生的就是细节,也就是可以加上一个关键字new产生的对象。
- 高层模块就是调用端,低层模块就是具体实现类
- 依赖倒置原则在Java中的表现就是,模块间的依赖通过抽象发生,实现类之间不发生直接依赖关系,其依赖关系是通过接口或者抽象类产生的。如果类与类直接依赖细节,那么就会直接耦合。
- 迪米特原则:一个软件实体应当尽可能少地与其他实体发生相互作用。
- 最少知识原则
- 在类的划分上,应当尽量创建松耦合的类
- 在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限。
- 在对其他类的引用上,一个对象对其他对象的引用应当降到最低
- 接口隔离原则:一个类对另一个类的依赖应该建立在最小的接口上
- 接口尽量小,但是要有限度。
- 依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来
- 提高内聚,减少对外交互。接口方法尽量少用public修饰。
二、设计模式分类
-
创建型设计模式,共5种:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
-
结构型设计模式,共7种:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
-
行为型设计模式,共11种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
三、创建型设计模式(5)
创建型设计模式,顾名思义就是与对象创建有关,它包括单例模式、工厂方法模式、抽象工厂模式、
建造者模式、原型模式。
1. 单例模式
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点
(1)饿汉模式
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
- 在类加载时就完成初始化,所以类加载较慢,但获取对象的速度快
- 在类加载时就完成初始化,避免了多线程的问题
- 在类加载时就完成实例化,没有达到懒加载效果
- 如果从始至终都没有使用过这个实例,就会造成内存的浪费
(2)懒汉模式(线程不安全)
public class Singleton{
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
(3)懒汉模式(线程安全)
public class Singleton{
private static Singleton instance;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
- 线程安全
- 但每次调用getInstance方法时都需要进行同步(造成不必要的同步开销,而且很多时候我们是用不到同步的)
- 不建议使用
(4)双重检查模式(DCL)
public class Singleton{
private static volatile Singleton instance;
private Singleton(){
}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- static的用法
- 三种用法
- static变量存在哪?
- 加载的时机
- volatile的用法
- 可见性
- 禁止指令重排序:在该代码中是禁止哪条指令重排序
- 在getSingleton方法中对Singleton进行了两次判空
- 第一次是为了不必要的同步
- 第二次是在 Singleton等于null的情况下才创建实例
- 优点:资源利用率高
(4)静态内部类单例模式
public class Singleton{
private Singleton(){
}
public static Singleton getInstance getInstance() {
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private staic final Singleton sInstance = new Singleton();
}
}
第一次加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance方法时虚拟机加载 SingletonHolder 并初始化 sInstance。这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
所以,推荐使用静态内部类单例模式。
(6)枚举单例
public enum Singleton {
INSTANCE;
public void doSomeThing() {
}
}
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例。
在上面讲的几种单例模式实现中,有一种情况下其会重新创建对象,那就是反序列化:将一个单例实例对象写到磁盘再读回来,从而获得了一个实例。反序列化操作提供了readResolve方法,这个方法可以让开发人员控制对象的反序列化。在上述几个方法示例中,如果要杜绝单例对象被反序列化时重新生成对象,就必须加入如下方法:
private Object readResolve() throws ObjectStreamException {
return singleton;
}
(7)使用场景
在一个系统中,要求一个类有且仅有一个对象,它的具体使用场景如下:
• 整个项目需要一个共享访问点或共享数据。
• 创建一个对象需要耗费的资源过多,比如访问I/O或者数据库等资源。
• 工具类对象。
2. 简单工厂模式
简单工厂模式(又叫作静态工厂方法模式),其属于创建型设计模式,但是并不属于 23种GoF设计模式之一。
定义:简单工厂模式属于创建型模式,其又被称为静态工厂方法模式,这是由一个工厂对象决定创建出哪一种产品类的实例。
-
Factory:工厂类,这是简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
-
IProduct:抽象产品类,这是简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
-
Product:具体产品类,这是简单工厂模式的创建目标。
3. 工厂方法模式
4. 建造者模式
- 适用于复杂对象
四、结构型设计模式
结构型设计模式是从程序的结构上解决模块之间的耦合问题,它包括适配器模式、代理模式、装饰模式、外观模式、桥接模式、组合模式和享元模式。
1. 代理模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H3WYyJcq-1628430944333)(C:/Users/wei/AppData/Roaming/Typora/typora-user-images/image-20210501135307568.png)]
2. 装饰模式
3. 外观模式
4. 享元模式
五、行为型设计模式
行为型模式主要处理类或对象如何交互及如何分配职责。它共有11种模式:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式和解释器模式。
1. 策略模式
2. 模板方法模式
在软件开发中,有时会遇到类似的情况:某个方法的实现需要多个步骤,其中有些步骤是固定的;而有些步骤并不固定,存在可变性。为了提高代码的复用性和系统的灵活性,可以使用模板方法模式来应对这类情况。
定义:定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类不改变一个算法的结构即可重定义算法的某些特定步骤。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LCyHNxZF-1628430944343)(C:/Users/wei/AppData/Roaming/Typora/typora-user-images/image-20210501143659054.png)]
使用场景:
-
多个子类有共有的方法,并且逻辑基本相同时。
-
面对重要、复杂的算法,可以把核心算法设计为模板方法,周边相关细节功能则由各个子类实现。
-
需要通过子类来决定父类算法中的某个步骤是否执行,实现子类对父类的反向控制。
• 优点:
- 模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
- 子类实现算法的某些细节,有助于算法的扩展。
• 缺点:
每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象
3. 观察者模式
观察者模式又被称为发布-订阅模式,属于行为型设计模式的一种,是一个在项目中经常使用的模式。 它的定义如下。
**定义:**定义对象间一种一对多的依赖关系,每当一个对象改变状态时,则所有依赖于它的对象都会 得到通知并被自动更新。
- Subject:抽象主题(抽象被观察者)。抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者)。该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者的抽象类。它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
• 使用场景:
- 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
- 事件多级触发场景。
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
• 优点:
- 观察者和被观察者之间是抽象耦合,容易扩展。
- 方便建立一套触发机制。
• 缺点:
在应用观察者模式时需要考虑一下开发效率和运行效率的问题。程序中包括一个被观察者、多 个观察者,开发、调试等内容会比较复杂,而且在 Java 中消息的通知一般是顺序执行的,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步方式。
class Solution {
public String addStrings(String num1, String num2) {
StringBuilder res = new StringBuilder("");
int i = nums1.length() -1, j = nums2.length() - 1;
int carry = 0;
while(i >= 0 || j >= 0) {
int n1 = i >= 0 ? num1.charAt(i) - '0' : 0;
int n2 = j >= 0 ? num2.charAt(j) - '0' : 0;
int temp = n1 + n2 + carry;
carry = temp / 10;
res.append(temp % 10);
i--;
j--;
}
if(carry == 1) res.append(1);
return res.reverse().toString();
}
}
int n2 = j >= 0 ? num2.charAt(j) - '0' : 0;
int temp = n1 + n2 + carry;
carry = temp / 10;
res.append(temp % 10);
i--;
j--;
}
if(carry == 1) res.append(1);
return res.reverse().toString();
}
}