目录

前置概念

🔘什么是设计模式?
设计模式就是把一些重复的问题进行统一的处理,提升代码的可靠性

🔘设计模式的要求

①面向接口编程而不是对实现编程

②优先使用对象组合而不是继承

🔘设计模式的六大原则

①开闭原则:对扩展开放,对修改关闭。即允许对现有的功能扩展但不允许修改之前的内容

②里氏代换原则:任何父类可以出现的地方都允许其子类出现

③依赖倒转原则:针对接口编程,依赖于抽象接口而不依赖于具体

④接口隔离原则:要求多个接口之间保持隔离,是为了降低类之间的耦合度

⑤最少知道原则:一个实体应当减少与其他实体之间发生的相互作用,让多功能模块保持相对独立

⑥合成复用原则:尽量使用合成/聚合的方式,而不是使用继承,换句话来说就是少用继承


设计模式

创建型模式

1
🔘什么是创建型模式(Creational Patterns)

这种类型的模式提供了一种在创建对象的同时隐藏创建对象的过程,而不是使用new运算符直接实例化对象。


🔘工厂模式

🔘什么是工厂模式(Factory Pattern)

创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象.

1
🔘目的和使用场景

在明确的计划下,需要在不同的条件下创建不同的对象

1
🔘优缺点

优点:①调用者只需要提供条件 ②扩展性极高 ③屏蔽产品的具体实现,开发者只需关心产品的接口

缺点:①每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,

在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。

1
🔘个人理解

定义一个接口,让所有的类去继承,定义一个工厂类,根据不同的条件创建不同的实现类对象,

从而实现创建对象的过程不再依赖某个具体的实现类而是依赖于工厂类的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//绘制图形接口
public interface Shape {
void draw();
}
//圆形
public class Circle implements Shape{
@Override
public void draw() {
System.out.println("绘制了圆形");
}
}
//图形生产工厂
public class ShapeFactory {
public Shape getShape(String target){
if (target == null){
return null;
}else if ("circle".equalsIgnoreCase(target)){
return new Circle();
}else if ("square".equalsIgnoreCase(target)){
return new Square();
}else if ("rectangle".equalsIgnoreCase(target)){
return new Rectangle();
}
return null;
}
}


抽象工厂模式

1
🔘什么是抽象工厂模式(Abstract Factory Pattern)

该模式围绕一个超级工厂创建其他工厂,这个超级工厂又称为工厂的工厂。

接口是负责创建一个工厂的标准约束,不再指向某个具体实现类。

1
🔘使用场景

当某个系统的产品中有多个产品族,而在某个时刻只需要使用其中一个产品

产品族:一个品牌下面的所有产品;例如华为的手机,路由器,电脑 统称为华为的产品族;

1
🔘优缺点

优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

缺点:产品族扩展非常困难,要增加一个系列的某一产品,就违背了开闭原则,既要修改工厂抽象类里加代码,

又修改具体的实现类里面加代码;

img


单例模式

1
🔘什么是单例模式(Singleton Pattern)

单例模式对单一的类提供了一种创建自己对象的方式,同时确保了只有单个对象被创建。

且这个类还有提供了一张方式去直接访问其唯一的对象,不需要实例化该类的对象。

🔘单例模式的要求

​ ①只能有一个实例

​ ②必须是自己创建自己的唯一实例,即将构造函数私有化

​ ③必须有给其他对象提供访问唯一实例的方式

单例模式的目的

避免一个全局使用的类频繁创建和销毁,保证此类仅有一个实例,并且提供一个访问它的方式

优点是在内存中只有一个实例,减少了内存开销,避免了重复资源的创建

缺点是没有接口,不能被继承,与单一职责原则冲突(一个类应该只关系内部逻辑,而不是外部如何实例化)


1
🔘单例模式之饿汉式

是否为懒加载:否

是否多线程安全:是(在类加载时就会自动加载到内存中,在执行业务之前此对象就被创建了)

优点:没有加锁,执行效率高

缺点:类加载时就完成初始化,占用内存空间(无法保证初始化后使用,如果一直不使用,就会浪费内存)

1
2
3
4
5
6
7
8
9
10
11
public class HungrySingleton {
//提前先创建好单例对象
private static HungrySingleton instance = new HungrySingleton();
//私有化类的构造器,防止外部new对象
private HungrySingleton(){
}
//提供公共的方法访问此单例对象
public static HungrySingleton getSingleInstance(){
return instance;
}
}

🔘单例模式之懒汉式

是否为懒加载:是

是否多线程安全:否(在多个线程访问时,有可能创建多个对象)

优点:什么时候调用什么时候创建,节省内存空间

缺点:线程不安全,需要通过加锁synchronized来保证单例,但加锁后会影响效率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LazySingleton {
//创建好单例对象但暂时不进行初始化
private static LazySingleton instance;
//私有化类的构造器,防止外部new对象
private LazySingleton(){
}
//提供公共的方法访问此单例对象
public static synchronized LazySingleton getSingleInstance(){
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
}

🔘单例模式之双重校验锁

是否为懒加载:是

是否多线程安全:是(通过加锁保证了多线程安全)

优点:双重锁机制,在保证多线程安全的同时保持相对较高的效率,比懒汉式效率更快,缩小了锁的范围

缺点:添加了锁,不可避免效率受到一定的影响

一般通过这种方式用于取代懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DCLSingleton {
//创建好单例对象但暂时不进行初始化
private volatile static DCLSingleton instance;
//私有化类的构造器,防止外部new对象
private DCLSingleton(){
}
//提供公共的方法访问此单例对象
public static DCLSingleton getSingleInstance(){
if (instance == null){
synchronized (DCLSingleton.class){
if (instance == null){
instance = new DCLSingleton();
}
}
}
return instance;
}
}

🔘单例模式之登记式(静态内部类)

是否为懒加载:是

因为是在静态内部类中声明了单例对象,在加载一个外部类时,其内部类不会被同时加载。

只有在外部类显式调用或获取内部类属性和方法时,内部类的静态变量,静态方法才会被加载

**(但只是加载,不一定是实例化)**因此间接实现了延迟加载

是否多线程安全:是

通过classloader机制保证了初始化单例对象instance时只有一个线程,因此保证了线程安全问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RegisterSingleton {
/*1、在单例类中声明一个静态内部类
2、在静态内部类中声明单例对象并初始化这个单例对象
*/
private static class SingletonHolder{
private static final RegisterSingleton INSTANCE = new RegisterSingleton();
}

private RegisterSingleton(){};

public static RegisterSingleton getInstance(){
return SingletonHolder.INSTANCE;
}
}

🔘单例模式之枚举式

是否为懒加载:否(枚举类加载完成该单例对象已存在)

是否多线程安全:是

枚举类最终会被编译为被 final 修饰的普通类,它的所有属性也都会被 static 和 final 关键字修饰,

枚举类在启动时就会被 JVM 加载并初始化,而这个执行过程是线程安全的,所以枚举类也是线程安全的类。

优点:自动支持序列化机制,同时防止了多次实例化

缺点:jdk1.5后才引入enum,因此使用较少

1
2
3
4
5
6
7
8
9
10
11
public enum EnumSingleton {
/* 1、枚举类型中定义的变量只能是常量
2、枚举类型中定义方法(和class类中定义的方法是一样的)
枚举常量可以作为枚举内部定义方法的访问点,即可以通过INSTANCE.test()来调用
*/
INSTANCE;

public void test(){
System.out.println("枚举式单例对象获取成功,调用方法成功!");
}
}

建造者模式

🔘什么是建造者模式(Builder Pattern)

使用多个简单的对象一步步构成一个复杂的对象。一个Builder类会一步步构造最终对象,该类是独立于其他对象的

🔘建造者模式的目的

将一个复杂的构建与其最终表示相分离,使得同样的构建过程可以创建不同的表示

🔘建造者模式的优缺点

优点:

①建造者独立,可以很容易扩展新的功能

②便于控制细节风险

缺点:

①产品必须有共同点,范围有所限制

②如果内部变化复杂,就会有很多的建造类

img


原型模式

🔘什么是原型模式(Prototype Pattern)

该模式用于需要反复创造重复的对象,同时又要保证性能。

该模式是实现了一个原型接口(Cloneable),该接口用于克隆对象

🔘原型模式的优缺点

优点:

①性能得到提高(不需要再反复new对象,而是在指定对象的基础上进行复制)

②避免了构造函数的约束(因为也是在指定对象的基础上进行复制,不需要通过构造函数创建的)

缺点:

①配备克隆方法需要对类的功能进行通盘考虑,对全新的类来说很简单,但对已存在的类就不一定,

特别是当一个类引用不支持序列化的间接对象或者引用含有循环结构的时候。

②必须实现Cloneable接口

🔘使用场景

①类的初始化需要耗费大量资源

②new生成一个对象需要非常繁琐的数据准备和访问权限就可以考察使用原型模式

③一个对象多个修改者时或者一个对象多个修改者且需要保存一份原始数据时

**PS:**与通过对一个类进行实例化来构造新对象不同的是原型模式是通过拷贝一个现有对象生成新的对象。

浅拷贝实现Cloneable接口,重写clone()方法;深拷贝是通过实现Serializable接口读取二进制流。

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* @description: 图形的父类(也是个抽象类)
* Cloneable接口:
* 1.功能:表示可以通过克隆的技术实现对象的快速创建
* 2.实现:直接对类的实现Cloneable接口
* 3.方法:编写一个clone()方法,用来表示具体的克隆操作
*/
public abstract class Shape implements Cloneable{
private String id;
//类型需要在子类中确定(需要在子类中访问父类中的成员)
protected String type;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getType() {
return type;
}

abstract void draw();

public Object clone(){
Object clone = null;
try {
//可以根据当前类的模板自动克隆出一个一模一样的对象
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}

public class ShapeCache {
//用于存储三个需要被复制的原始对象
private static Hashtable<String,Shape> hashtable = new Hashtable<>();

//创建三个原始对象并放入map中
public static void load(){
Circle circle = new Circle();
circle.setId("1");
Square square = new Square();
square.setId("2");
Rectangle rectangle = new Rectangle();
rectangle.setId("3");
hashtable.put(circle.getId(),circle);
hashtable.put(square.getId(), square);
hashtable.put(rectangle.getId(),rectangle);
}

//克隆对象,需要调用clone()方法来完成
public static Shape cloneByCache(String id){
//根据传递id获取被克隆的原始对象
Shape shape = hashtable.get(id);
//由于clone()方法返回的是object对象,因此需要将其向下转型
return (Shape) shape.clone();
}
}

结构型模式


🔘什么是结构型模式(Structural Patterns)

这种类型模式关注类和对象的组合,继承的概念被用来组合接口和定义组合对象获得新功能的方式


适配器模式

🔘什么是适配器模式(Adapter Pattern)

该模式作为两个不兼容的接口之间的桥梁,起到转换和兼容的作用。

该模式涉及到一个单一的适配器类,该类负责加入独立的或不兼容的接口功能。

🔘优点

①让两个互不关联的类形成一定的关系

②提升了类的复用和透明度

③灵活性得到提升

🔘缺点

①引入过多的类,系统复杂度更高了

②由于JAVA至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

🔘适配器模式的实现

①类适配器模式(适配器继承被适配的类)

②对象适配器(适配器持有被适配类的实例对象)

③接口适配器(通过抽象适配器实现某个接口,再通过自定义适配器继承抽象适配器,自定义实现接口的某个方法即可)

🔘个人的理解

把一个类的接口变成客户端所期待的另一种接口,使原本接口不兼容的两个类,可以一起工作。

一般逻辑是将自己不兼容的接口,包裹在一个类里面,而该类就是适配器。

简单来说,就是将源角色的api通过适配器,适配为目标角色的api。

适配器模式的角色有三种,分别是被适配接口角色,适配器角色以及目标接口角色。

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class AudioPlayer implements MediaPlayer{
private MediaAdapter mediaAdapter; //使用适配器类
@Override
public void play(String audioType, String filename) {
if (audioType.equalsIgnoreCase("mp3")){
System.out.println("正在播放MP3,文件名是:" + filename); //使用自己能播放的类型
}else if (audioType.equalsIgnoreCase("vlc") ||
audioType.equalsIgnoreCase("mp4") ){
//创建当前适配器类的对象,不需要关注具体创建的是什么对象
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType,filename);
}else {
System.out.println("播放类型不对");
}
}
}

/*
这里的实现方式是对象适配器
MediaAdapter implements MediaPlayer已经实现了MediaPlayer和AdvanceMediaPlayer的协同
可以通过直接new MediaAdapter的方式调用AdvanceMediaPlayer接口的方法
但是这里是通过AudioPlayer作为要使用的类,因此AudioPlayer会持有MediaAdapter的实例
在AudioPlayer的实现方法中通过MediaAdapter的实例的实例间接使用了AdvanceMediaPlayer接口的方法
*/
public class MediaAdapter implements MediaPlayer {
private AdvanceMediaPlayer advanceMediaPlayer; //要适配的目标接口
public MediaAdapter(String audioType){
if ("vlc".equalsIgnoreCase(audioType)){
advanceMediaPlayer = new VlcPlayer();
}else if ("mp4".equalsIgnoreCase(audioType)){
advanceMediaPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String filename) {
if ("vlc".equalsIgnoreCase(audioType)){
advanceMediaPlayer.playVlc(filename);
}else if ("mp4".equalsIgnoreCase(audioType)){
advanceMediaPlayer.playMp4(filename);
}
}
}

public class AdapterPatternDemo {
public static void main(String[] args) {
//媒体播放类只需要执行播放方法,不需关注其他东西,不支持的类型由适配器类进行适配
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3","test");
}
}

桥接模式

🔘什么是桥接模式(Bridge Pattern)

用于把抽象化与实现化解耦,使得二者可以独立变化。

这种模式涉及到一个作为桥接的接口,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

🔘如何理解桥接模式

桥接模式是有方向性的,桥绑定的一方是被调用者,属于被动方,抽象方属于主动方。通过组合的方式建立

两个类之间联系,而不是继承。通过桥接(接口)将抽象部分和实现部分解耦,桥接是一个接口,实现方实现

这个接口,抽象方在抽象类中调用接口中的方法指向实现方。这样实现方通过实现桥接口进行单方面扩展,而

抽象方通过继承抽象类进行单方面扩展,两者通过桥接口调用,而接口不受双方扩展的影响。

🔘优缺点

优点:

①抽象化和实现化的分离 ②可扩展性强 ③实现细节对客户透明

缺点:

①桥接模式的引入会增加系统的理解与设计难度,由于聚联关联关系建立在抽象层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//桥接的接口
public interface DrawAPI {
void drawCircle(int radius,int x,int y);
}
//实现方
public class GreenCircle implements DrawAPI{
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("填充绿色:" + radius + x + y);
}
}
//实现方
public class RedCircle implements DrawAPI{}

//抽象类
public abstract class Shape {
//抽象类中依赖了具体的实现,具体的实现剥离出去进行了独立的定义
//这一步可以视为抽象和具体的解耦,桥接模式的关键步骤
public DrawAPI drawAPI; //表示颜色填充的属性

public Shape(DrawAPI drawAPI){
this.drawAPI = drawAPI;
}
public abstract void draw(); //具体绘制动作实现交给子类来完成
}
//抽象方
public class Circle extends Shape{
private int radius;
private int x;
private int y;

public Circle(DrawAPI drawAPI, int radius, int x, int y) {
super(drawAPI);
this.radius = radius;
this.x = x;
this.y = y;
}

@Override
public void draw() {
//表示具体的绘制动作
//调用具体的实现来完成图形的绘制
drawAPI.drawCircle(radius,x,y);
}
}

img


过滤器模式

🔘什么是过滤器模式(Filter || Criteria Pattern)

过滤器模式也被称为标准模式,它可以结合了多个标准来获得单一标准。

这种模式允许使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。

🔘优缺点

优点:可以从多个对象中将满足对象保留,不满足对象过滤掉

缺点:冗余代码较多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//过滤器接口(标准接口)
public interface Criteria {
/**
* Description : 指定过滤条件的方法
* @date 2023/10/7
* @param list 存储了多个对象的list集合
**/
List<Person> meetCriteria(List<Person> list);
}

//多个自定义的过滤条件
public class OrCriteria implements Criteria{
private Criteria criteria;
private Criteria otherCriteria;

public OrCriteria(Criteria criteria, Criteria otherCriteria) {
this.criteria = criteria;
this.otherCriteria = otherCriteria;
}

@Override
public List<Person> meetCriteria(List<Person> list) {
List<Person> first = new ArrayList<>();
List<Person> second = new ArrayList<>();
//调用自己的过滤方法获取满足条件的对象
first = criteria.meetCriteria(list);
second = otherCriteria.meetCriteria(list);

for (Person person : second) {
//合并两个集合中的数据
if (!first.contains(person)){
first.add(person);
}
}
return first;
}
}
//符合男性的过滤条件
public class CriteriaMale implements Criteria{
@Override
public List<Person> meetCriteria(List<Person> list) {
List<Person> people = new ArrayList<>();
for (Person person : list) {
if (person.getGender().equalsIgnoreCase("male")){
people.add(person);
}
}
return people;
}
}

img


组合模式


🔘什么是组合模式(Composite Pattern)

组合模式又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。

组合模式依据树形结构来组合对象,用来表示部分以及整体层次。

这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。

🔘使用场景

当某些需要实现树形结构显示时可以考虑使用,例如文件夹遍历操作,前端下拉列表显示不同级别菜单

🔘优缺点

优点:

①高层模块调用简单

②节点添加自由(例如这个案例中,某个员工被提拔为领导,只需要在其list中添加员工即可,整体结构不受影响)

缺点:

①其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。

🔘个人理解

组合模式,就是在一个对象中包含其他对象,这些被包含的对象可能是终点对象(不再包含别的对象),

也有可能是非终点对象(其内部还包含其他对象,或叫组对象),我们将对象称为节点,即一个根节点

包含许多子节点,这些子节点有的不再包含子节点,而有的仍然包含子节点,以此类推。很明显,这是

树形结构,终结点叫叶子节点,非终节点(组节点)叫树枝节点,第一个节点叫根节点。同时也类似于

文件目录的结构形式:文件可称之为终节点,目录可称之为非终节点(组节点)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Employee {
private String name;//姓名
private String dept;//部门
private int salary;//收入
private ArrayList<Employee> subordinate;//当前员工的下属

public Employee(String name, String dept, int salary) {
this.name = name;
this.dept = dept;
this.salary = salary;
//用于维护当前员工是否由下属的一个属性
this.subordinate = new ArrayList<>();
}

public void add(Employee employee){
this.subordinate.add(employee);
}

public void remove(Employee employee){
this.subordinate.remove(employee);
}

public List<Employee> getSubordinate(){
return this.subordinate;
}

@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", dept='" + dept + '\'' +
", salary=" + salary +
", subordinate=" + subordinate +
'}';
}
}

装饰器模式

🔘什么是装饰器模式(Decorator Pattern)

即向一个现有对象添加新的功能,同时不能改变该对象的结构

该模式通过创建一个装饰类,用于包装原来的类,并在保持类方法签名完整性的前提下提供新的功能。

关于装饰者模式的辅助理解 –> 装饰者模式

🔘使用场景

当为了扩展某一个类经常需要使用继承方式实现,或者动态增加功能,动态撤销。则可以考虑使用装饰器模式

🔘优缺点:

优点:

①装饰类和被装饰类可以独立发展,不会相互耦合

②装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:

①多层装饰比较复杂。

装饰器模式与桥接模式的区别
①装饰器模式中抽象类是实现了接口并持有接口实例,桥接模式中抽象类以组合的方式持有接口实例,但没实现接口。

②桥接模式是为了实现两个没有关联的维度的东西的自由组合,这里没有关联是指各自拥有自己的属性和方法,

没有相同点(使用聚合或者组合)

③装饰者模式使用了继承必然是两个种类具有相同的一些属性和方法,它不是为了实现两个维度的自由组合,

是为了实现对对象的一层一层又一层包装,调用方法时,每一层包装递归的调用上一层的包装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//绘制图形接口
public interface Shape {
void draw();
}
public class Circle implements Shape{
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
//装饰者类
public abstract class ShapeDecorator implements Shape{
private Shape shape; //装饰的目标对象

//传递过来的对象就需要初始化
public ShapeDecorator(Shape shape) {
this.shape = shape;
}

@Override
public void draw() {
//第一步,调用目标的draw方法
shape.draw();
/*
第二步,扩展新功能(起到装饰效果),
如果在此处进行扩展新功能则对扩展性大打折扣,
因此新功能的扩展可以交由抽象类的子类的draw方法来实现
*/
}
}
//装饰者类的子类
public class RedShapeDecorator extends ShapeDecorator{

public RedShapeDecorator(Shape shape) {
super(shape);
}

@Override
public void draw() {
//1.通过super关键字调用目标对象的方法
super.draw();
//2.扩展新功能(就是装饰器的核心代码)
setRedColor();
}

private void setRedColor(){
System.out.println("图形被填充为红色");
}
}

img


外观模式

🔘什么是外观模式(Facade Pattern)

该模式用于隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。

这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。

🔘使用场景:

①为复杂的模块或子系统提供外界访问的模块。②子系统相对独立。 ③预防低水平人员带来的风险。

🔘优缺点

优点:①减少系统相互依赖。 ②提高灵活性。 ③提高了安全性。

缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

🔘外观模式与工厂模式的区别

外观模式侧重于对外部调用者提供简化的方法。工厂模式侧重于隐藏对象创建的过程。

虽然两种模式下都是在一个额外的类中去创建子系统的对象。不同之处在于简单工厂模式的output是一个子系统对象。

而外观模式没有对外返回子系统对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//绘制图形接口
public interface Shape {
void draw();
}
//圆形
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("绘制了圆形");
}
}

// 外观类(用于生成制造目标对象)
public class ShapeMaker {
private Shape circle;
private Shape rectangle;
private Shape square;

public ShapeMaker() {
this.circle = new Circle();
this.rectangle = new Rectangle();
this.square = new Square();
}

public void drawCircle(){
circle.draw();
}

public void drawRectangle(){
rectangle.draw();
}

public void drawSquare(){
square.draw();
}
}

img


享元模式

🔘什么是享元模式(Flyweight Pattern)

该模式主要用于减少创建对象的数量,以减少内存占用和提高性能,并改善应用所需的对象结构的方式。

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。

🔘使用场景

系统中有大量的重复对象、重复对象的创建消耗大量内存

🔘优缺点

优点:减少创建对象的数量,降低系统对内存的使用,使得效率提高

缺点:同时也提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,

不应该随着内部状态的变化而变化,否则会造成系统的混乱。

🔘享元模式和原型模式的区别

享元模式是为了在创建新对象的过程中减少已存在对象的创建同时提高性能的目的;

原型模式是为了在已存在的对象基础上创建大量重复对象的同时保持高性能

例如:在有些场景下,我们需要重复创建多个实例,例如在循环体中赋值一个对象,此时我们

就可以采用原型模式来优化对象的创建过程;而在有些场景下,我们则可以避免重复创建多个实例,

通过采用享元模式在内存中共享对象就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 绘制图形接口
public interface Shape {
void draw();
}

//创建20圆形,只有5种颜色(要共享的内容)
public class Circle implements Shape{
private int x;
private int y;
private int radius;
private String color;
//因为只有五种颜色是固定的,是20个对象共享的,其他变量都是动态的,
//因此只需要将color属性用于检查现有的 Circle 对象。
public Circle(String color) {
this.color = color;
}
}

//这个类主要是用于判断要创建的对象是否需要创建还是直接返回
public class ShapeFactory {
/*这一个map对象很关键,整个享元模式是根据这个对象中的信息来判断是否需要创建对象的
key 表示颜色的值
value 表示Shape接口类型的对象
*/
private static HashMap<String, Shape> list = new HashMap<>();

//获取圆的对象
public static Shape getShape(String color) {
//通过颜色值来判断list中是否由此颜色对象
Circle circle = (Circle) list.get(color);
if (circle != null){ //有,直接返回此对象
return circle;
}
//没有,创造对象并放入list中.
Circle newCircle = new Circle(color);
System.out.println("创建[" + color + "]色圆形成功");
list.put(color,newCircle);
return newCircle;
}
}

img


代理模式

🔘什么是代理模式(Proxy Pattern)

该模式通过一个类代表另一个类的功能,为其他对象提供一种代理以控制对这个对象的访问

🔘使用场景

想在访问一个类时做一些控制

🔘优缺点

优点:①职责明确 ②扩展性较高

缺点:①增加了中间对象,执行效率有所降低 ②实现代理模式需要额外的工作,有些代理模式较为复杂

代理模式、适配器模式、装饰器模式的区别
①和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。

②和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//规范接口
public interface Image {
void display();
}
//真正加载图片和显示图片的执行类
public class RealImage implements Image{
private String fileName;//文件的路径
public RealImage(String fileName){
this.fileName = fileName;
LoadFromDisk(this.fileName);
}

@Override
public void display() {
System.out.println("正在显示图片");
}

private void LoadFromDisk(String fileName){
System.out.println("从硬盘中加载文件成功!" + fileName);
}
}
//代理类
public class ProxyImage implements Image{
private RealImage realImage;
private String fileName;

public ProxyImage(String fileName) {
this.fileName = fileName;
}

@Override
public void display() {
//借助真正加载图片的类完成图片加载的操作
if (realImage == null){ //判断realImage是否为空
//初始化加载图片的类对象
this.realImage = new RealImage(this.fileName);
}
//调用当前加载图片的类对象的display的方法
realImage.display();
}
}

img


行为型模式

🔘什么是行为型模式(Behavioral Patterns)

这种类型的模式特别关注对象之间的通信


命令模式

🔘什么是命令模式(Command Pattern)

它是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。

调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

🔘目的

将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

🔘优缺点

优点:①降低了系统耦合度 ②新的命令可以很容易添加到系统中去

缺点:使用命令模式可能会导致某些系统有过多的具体命令类

🔘个人理解

命令模式把一个请求或者操作封装到命令对象中,这些请求或者操作的内容包括接收者的信息,然后将

该命令对象交由执行者执行,执行者不需要关心命令的接收人或者命令的具体内容,因为这些信息均被

封装到命令对象中。

img


空对象模式

🔘什么是空对象模式(Null Object Pattern)

通过一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。

在空对象模式中,我们创建一个指定各种要执行的操作的抽象类和扩展该类的实体类,还创建一个未对该类

做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//抽象类
public abstract class AbstractCustomer {
public String name;

//判断是否为空值
public abstract Boolean isNull();

public String getName() {
return name;
}
}
//实际有值的类
public class RealCustomer extends AbstractCustomer{
private String name;

public RealCustomer(String name) {
this.name = name;
}

@Override
public Boolean isNull() {
return false;
}

@Override
public String getName() {
return name;
}
}
//代替空指针的空对象
public class NullCustomer extends AbstractCustomer{
private String name;

public NullCustomer(String name) {
this.name = name;
}

@Override
public Boolean isNull() {
return true;
}

@Override
public String getName() {
return "没有定义当前对象" + name;
}
}
//创建AbstractCustomer的辅助类
public class CustomerFactory {
private static final String[] names = {"tom","jack","mark"};
/**
* Description :根据用户传递的名称判断当前是否存在此用户
* @param name 用户传入的名字
**/
public static AbstractCustomer getCustomer(String name){
for (int i = 0; i < names.length; i++) {
if (names[i].equalsIgnoreCase(name)){ //表示存在,返回真实对象
return new RealCustomer(name);
}
}
//不存在,返回指定的空对象
return new NullCustomer(name);
}
}

img


策略模式

🔘什么是策略模式(Strategy Pattern)

该模式表示一个类的行为或其算法可以在运行时更改。在策略模式中,我们创建表示各种策略的对象

和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

🔘使用场景

一个系统有许多许多类,而区分它们的只是他们直接的行为。

🔘优缺点

优点:①策略的算法可以自由切换 ②避免多个多重条件判断 ③扩展性很高

缺点:①策略类会增多 ②所有的策略类都需要对外暴露

🔘策略模式、装饰器模式、桥接模式的区别

①与装饰器模式的区别:两者都持有了接口的实例,但是对接口的处理,装饰器模式是实现了接口,

策略模式是以聚合、组合的方法。装饰器模式为了动态增强功能而出现,策略模式是通过策略对象的

改变从而影响算法的不同执行方法

②与桥接模式的区别:桥接模式的抽象化和实现化部分都可以自由变化,桥接接口仅仅是提供两者之间

的联系;而在策略模式中,并不考虑Context的变化,只考虑作为策略接口的实例变化对Context的影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//策略接口,提供一种规范
public interface Strategy {
int doOperation(int num1,int num2); //算术运算的操作方法
}
//加法运算(策略类)
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
//表示使用哪种策略对应的类,并不关注细节,仅仅关注以什么样的策略实现目的
public class Context {
//依赖于策略接口(不能依赖实现类)
private Strategy strategy;

public Context(Strategy strategy) {
this.strategy = strategy;
}
//执行策略业务的逻辑
public int executeStrategy(int num1,int num2){
return strategy.doOperation(num1,num2);//调用目标方法
}
}

img


模板模式

🔘什么是模板模式(Template Pattern)

该模式用一个抽象类公开定义了执行它的方法的方式/模板。

它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。

🔘使用场景

①有多个子类共有的方法,且逻辑相同。 ②重要的、复杂的方法,可以考虑作为模板方法。

🔘优缺点

优点:①封装不变部分,扩展可变部分 ②提取公共代码,便于维护 ③行为由父类控制,子类实现

缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

🔘个人理解

将方法作为模板,但是由于作为模板的方法不应该有具体的实现逻辑,不然就成为了固定结构,

也就不能称之为模板,因此需要将模板方法内的具体实现置为多个抽象方法,让其子类来定义具体的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//游戏的模板类(抽象类),方法由具体的子类完成
public abstract class Game {
//游戏初始化
public abstract void initialize();
//开始游戏
public abstract void startPlay();
//结束游戏
public abstract void endPlay();

//定义游戏操作方法(模板方法),通过final确保子类不能修改模板方法
public final void play(){
initialize();
startPlay();
endPlay();
}
}
//足球,如果当前类还想实现其他父类,扩展就会违背单继承的原则
public class FootBall extends Game{
@Override
public void initialize() {
System.out.println("足球游戏初始化");
}

@Override
public void startPlay() {
System.out.println("足球游戏初始化");
}

@Override
public void endPlay() {
System.out.println("足球游戏初始化");
}
}

img

待补充:

责任链模式(Chain of Responsibty Pattern)

解释器模式(Interpreter Pattern)

迭代器模式(Iterator Pattern)

中介者模式(Mediator Pattern)

备忘录模式(Memento Pattern)

观察者模式(Observer Pattern)