23种设计模式
七大原则
设计模式七大原则
设计模式六大原则分别为:
- 单一职责原则(SRP): 一个类只应该有一个引起它变化的原因,即一个类只负责一项职责。(有点DDD那味儿)
-
高内聚:避免大而全,避免不相关功能的耦合
-
低耦合:减少所需要依赖和被依赖的类
-
- 开放封闭原则(OCP):对扩展开放,对修改关闭。当需要改变一个程序的功能或给它增加新功能时,可以通过增加代码来实现,而不是修改已有的代码。
- 测试简单
- 可复用性变强
- 稳定性变高
- 里氏替换原则(LSP):子类可以替换其父类并且仍然产生正确的结果。这表明在使用继承时需要非常小心,子类和父类之间的关系应该是is-a。
- 接口隔离原则(ISP):客户端不应该依赖那些它不需要使用的接口。一个类对另一个类的依赖性应该是建立在最小的接口上。
- 依赖倒置原则(DIP):高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。这个原则的目的是为了减少类之间的耦合。
- 迪米特法则(LoD):也称最少知道原则,即一个对象应该对其他对象有最少的了解。一个类应该对自己需要耦合或调用的类知道得最少,这样可以降低类之间的耦合度,提高系统的可维护性和可扩展性。缺点是有可能存在大量的中介类,增加系统的复杂度
- 合成复用原则:尽量使用对象聚合/组合,而不是继承来实现软件复用的目的。
创建型模式
创建型模式
- 帮助我们创建类或者对象
- 核心思想:把对象的创建和使用分离,使两者能相对独立地变换
单例模式
-
概念:保证一个类只有一个实例。Spring中用的很多。
-
示例:分为懒汉式和饿汉式
-
懒汉式:用的时候再实例化,需要注意线程安全问题
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/**
* 苹果
*/
public class Apple {
/**
* 这是亚当和夏娃吃的那个苹果,独一无二
* volatile防止指令重排
*/
private static volatile Apple INSTANCE;
/**
* 私有化构造器,避免被实例化,但是反射还是可以做到。
*/
private Apple(){}
public static Apple getInstance(){
if (INSTANCE == null){
synchronized (Apple.class){
//防止后续线程在INSTANCE 实例化后,将要return时,
//其他线程抢到了sync的锁,又new一次
if (INSTANCE == null){
INSTANCE = new Apple();
}
}
}
return INSTANCE;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class Apple1 {
/**
* 私有化构造器,避免被实例化,但是反射还是可以做到。
*/
private Apple1(){}
private static Apple1 INSTANCE;
public static Apple1 getInstance(){
return AppleHolder.APPLE;
}
/**
* 完美写法,私有内部类在使用时才加载,所以Apple1在使用时才实例化,
* 同时JVM底层做了线程安全
*/
private static class AppleHolder {
public static final Apple1 APPLE = new Apple1();
}
} -
饿汉式:类加载完就实例化了
1
2
3
4
5
6
7public class Apple2 {
private static final Apple2 INSTANCE = new Apple2();
private Apple2(){};
public static Apple2 getInstance(){
return INSTANCE;
}
}1
2
3
4
5
6
7
8
9
10/**
* 写法有点怪,但是确实能保证只有一个实例
* 并且线程安全,同时可以防止被反序列化,
* 因为枚举类没有构造器。
* 在枚举类型中,实例字段被final修饰,并且是一个在类加载时就被初始化的静态常量。
* 因此,枚举实现的单例模式是一种饿汉式的变体,也可以看作是一种预先实例化的单例模式。
*/
public enum Apple3 {
INSTANCE;
}
-
工厂模式
-
概念:定义了一个用于创建对象的接口,但让子类决定实例化哪个类
-
示例:下面是一个简单的工厂模式示例代码,实现了在不同的国家/地区生成不同格式的日期
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
34public interface DateFormat {
String format(LocalDate date);
}
public class ChinaDateFormat implements DateFormat {
public String format(LocalDate date) {
return date.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
}
}
public class USDateFormat implements DateFormat {
public String format(LocalDate date) {
return date.format(DateTimeFormatter.ofPattern("MM/dd/yyyy"));
}
}
public class DateFormatFactory {
public enum Country {
CHINA, US
}
public static DateFormat getFormatter(Country country) {
switch (country) {
case CHINA:
return new ChinaDateFormat();
case US:
return new USDateFormat();
default:
throw new IllegalArgumentException("Unsupported country: " + country);
}
}
}笔者觉得跟策略模式很像……只是这个会返回实例,策略模式是根据选择执行代码。
抽象工厂模式
-
概念:一种创建型设计模式,它提供了一种创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
有点拗口,举个例子。小说有魔法,武侠两种类型,他们都有攻击手段,能量两种要素
- 魔法:魔咒,魔力(金木水火土等)
- 武侠:武功,内力(至阳至刚,至阴至柔等)
那么抽象工厂就可以分为
- 魔法工厂,可以创建魔咒和魔力
- 武侠工厂,可以创建武功和内力
其实就是对工厂分类,一种工厂只能生产同一对象族的东西
-
代码实现自己去写了,上面都说得这么明白了
建造者模式
-
概念:主要目的是将一个复杂对象的构建与表示分离,从而可以使用相同的构建过程来创建不同的表示。
-
示例:
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
65//地址类
public class Location {
private String street;
private String roomNo;
}
//学生类
public class Student {
private String name;
private Integer age;
private Location loc;
/**
* 建造者,可以使用loc方法给Location对象设置值,
* 也可以分开通过roomNo和street方法设置值
* 做到复杂对象可以简单的构建
*/
public static class StudentBuilder {
private String name;
private Integer age;
private String street;
private String roomNo;
public StudentBuilder name(String name) {
this.name = name;
return this;
}
public StudentBuilder age(Integer age) {
this.age = age;
return this;
}
public StudentBuilder roomNo(String roomNo) {
this.roomNo = roomNo;
return this;
}
public StudentBuilder street(String street) {
this.street = street;
return this;
}
public StudentBuilder loc(String street, String roomNo) {
this.roomNo = roomNo;
this.street = street;
return this;
}
public Student build() {
Student student = new Student();
if (this.street != null || this.roomNo != null) {
Location location = new Location();
location.setRoomNo(this.roomNo);
location.setStreet(this.street);
student.loc = location;
}
student.name = this.name;
student.age = this.age;
return student;
}
}
}
使用建造者模式时,客户端关注的是产品的组装过程,而不是产品的创建细节,因此更加灵活,适用于需要灵活组装、创建复杂对象的场景。同时也方便扩展和修改已有的构建流程,提高了代码的可维护性和可读性。
原型模式
没啥好说的,就是java的clonable
接口,浅拷贝和深拷贝的问题。深拷贝就是里面的对象,比方说上面的Student.loc
,要实现深拷贝,那Location
也要实现clonable
接口
结构型模式
结构性模式
- 涉及如何组合各种对象以便获得更好、更灵活的结构
- 更多地通过组合与运行期的动态结合来实现更灵活的功能
适配器模式
-
概念:将两个不兼容的接口之间进行桥接,以使它们可以一起工作。适配器模式通过创建一个中间层,将客户端和目标接口分离,从而达到复用已有的代码的目的。
-
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13public class Main {
public static void main(String[] args) throws IOException {
InputStream is = new FileInputStream("/home/temp/test.txt");
//isr就是适配器,让br适配is
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
}
桥接模式
面试里不常问到。有点像套壳贴牌。
-
概念:通过将抽象部分与实现部分分离开来,使它们可以独立地变化。桥接模式通过将抽象和实现解耦,从而可以减少系统中类的数量,并简化类的继承关系。
-
示例:
定义实现部分接口:
1
2
3
4public interface DrawingAPI {
void drawCircle(double x, double y, double radius);
void drawRectangle(double x1, double y1, double x2, double y2);
}定义具体实现部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class DrawingAPI1 implements DrawingAPI {
public void drawCircle(double x, double y, double radius) {
System.out.printf("API1.circle at %f:%f radius %f%n", x, y, radius);
}
public void drawRectangle(double x1, double y1, double x2, double y2) {
System.out.printf("API1.rectangle at %f:%f to %f:%f%n", x1, y1, x2, y2);
}
}
public class DrawingAPI2 implements DrawingAPI {
public void drawCircle(double x, double y, double radius) {
System.out.printf("API2.circle at %f:%f radius %f%n", x, y, radius);
}
public void drawRectangle(double x1, double y1, double x2, double y2) {
System.out.printf("API2.rectangle at %f:%f to %f:%f%n", x1, y1, x2, y2);
}
}定义抽象部分:
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
42public abstract class Shape {
protected DrawingAPI drawingAPI;
public Shape(DrawingAPI drawingAPI) {
this.drawingAPI = drawingAPI;
}
public abstract void draw();
}
public class Circle extends Shape {
private double x, y, radius;
public Circle(double x, double y, double radius, DrawingAPI drawingAPI) {
super(drawingAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
public void draw() {
drawingAPI.drawCircle(x, y, radius);
}
}
public class Rectangle extends Shape {
private double x1, y1, x2, y2;
public Rectangle(double x1, double y1, double x2, double y2, DrawingAPI drawingAPI) {
super(drawingAPI);
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
public void draw() {
drawingAPI.drawRectangle(x1, y1, x2, y2);
}
}在上述代码中,我们定义了实现部分接口
DrawingAPI
和具体实现部分DrawingAPI1
和DrawingAPI2
。在抽象部分中,我们定义了抽象类Shape
和具体形状类Circle
和Rectangle
。可以看到,抽象部分持有一个实现部分对象的引用,并将抽象部分的操作转发给实现部分对象,从而实现了抽象部分与实现部分的解耦。使用桥接模式,我们可以将系统中的抽象部分和实现部分解耦开来,减少类的数量,并简化类的继承关系。同时,它还能够提高系统的可扩展性和可维护性,使系统更加灵活,易于修改和扩展。
组合模式
-
概念:将对象组织成树形结构,以表示部分和整体之间的层次关系,并且使得用户对单个对象和组合对象的使用具有一致性。组合模式通过递归组合来实现对整个树形结构的操作,使得用户在使用时无需知道具体处理的是单个对象还是组合对象。
- Component:抽象构件,声明了组合中所有对象共有的接口和默认行为。
- Leaf:叶子构件,表示组合中的叶子对象,叶子对象没有子节点。
- Composite:容器构件,表示组合中的容器对象,容器对象可以包含其他组合对象和叶子对象,并且提供了管理其子部件的方法。
-
示例:
以下是一个简单的组合模式示例代码,用于展示不同部门和员工的层次关系:
定义抽象构件:
1
2
3
4
5
6
7
8
9
10
11public abstract class Department {
protected String name;
public Department(String name) {
this.name = name;
}
public abstract void add(Department department);
public abstract void remove(Department department);
public abstract void display(int depth);
}定义叶子构件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class Employee extends Department {
public Employee(String name) {
super(name);
}
public void add(Department department) {
// 叶子节点无法添加子节点
}
public void remove(Department department) {
// 叶子节点无法移除子节点
}
public void display(int depth) {
System.out.println("-".repeat(depth) + name);
}
}定义容器构件:
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
26public class DepartmentComposite extends Department {
private List<Department> subDepartments = new ArrayList<>();
public DepartmentComposite(String name) {
super(name);
}
public void add(Department department) {
subDepartments.add(department);
}
public void remove(Department department) {
subDepartments.remove(department);
}
public void display(int depth) {
System.out.println("-".repeat(depth) + name);
for (Department department : subDepartments) {
department.display(depth + 2);
}
}
}在上述代码中,我们定义了抽象构件
Department
,并通过叶子构件Employee
和容器构件DepartmentComposite
来实现其具体子类。容器构件中包含了一个子部件列表,可以添加和移除子部件,并在展示时递归显示其所有子部件。通过使用组合模式,我们可以将部分和整体都看作是“部门”,并且可以统一对它们进行操作,使得客户端代码更加简洁并易于扩展。
装饰器模式
-
概念:动态地给一个对象增加一些额外的职责,同时又不改变该对象的结构。
-
示例:装饰器模式通过将对象包装在装饰器对象中,从而使得装饰器对象与被包装的对象拥有相同的接口,以此来扩展对象的功能。
1
2
3
4
5
6
7
8
9
10
11
12/**
* 饮料,不用抽象类也可以。抽象类更符合这个场景,
* 比如可以把茶替换椰奶等
*/
public interface Drink {
/**
* 口味
* @return
*/
String taste();
}1
2
3
4
5
6
7
8
9/**
* 茶
*/
public class Tea implements Drink{
public String taste(){
return "茶";
}
}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/**
* 加柠檬,或者定义其他的,只要是类似结构
*/
public class LemonDecorator implements Drink {
private Drink drink;
public LemonDecorator(Drink drink) {
this.drink = drink;
}
/**
* 装饰器对原有方法加强
* @return
*/
public String taste() {
return "柠檬" + drink.taste();
}
/**
* 装饰器自有增强方法
* @param tenth
* @return
*/
public String sugar(int tenth) {
return tenth + "分糖";
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Main {
public static void main(String[] args) {
Drink drink = new Tea();
LemonDecorator lemonDecorator = new LemonDecorator(drink);
//对原有方法增强
//柠檬茶
System.out.println(lemonDecorator.taste());
//装饰器自己的方法
//甜度
System.out.println(lemonDecorator.sugar(5));
//装饰器也可以变成Drink,实现多态,但是没有甜度的方法
drink = lemonDecorator;
//还是柠檬茶
System.out.println(drink.taste());
}
}这个也是套壳,但是像手机套壳,内里还是手机。桥接模式是换内里,然后内里有相同的方法,不同的实现逻辑
门面模式(Facade)
-
概念:提供了一个简化的接口,来为复杂的子系统提供一个统一的界面。外观模式的目的是简化客户端与子系统之间的交互,减少客户端代码的复杂度。
-
示例:下面是一个使用外观模式的示例,假设我们有一家咖啡店,客户可以在该咖啡店订购不同类型的咖啡和添加不同的调料。为简化客户端订购咖啡的流程,我们可以使用外观模式,创建一个咖啡外观类作为客户端与咖啡店后台的接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class CoffeeFacade {
private Americano americano;
private Cappuccino cappuccino;
private Latte latte;
public CoffeeFacade() {
this.americano = new Americano();
this.cappuccino = new Cappuccino();
this.latte = new Latte();
}
public void orderAmericano() {
americano.makeCoffee();
}
public void orderCappuccino() {
cappuccino.makeCoffee();
}
public void orderLatte() {
latte.makeCoffee();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public abstract class Coffee {
public abstract void makeCoffee();
}
public class Americano extends Coffee {
public void makeCoffee() {
System.out.println("Making Americano...");
}
}
public class Cappuccino extends Coffee {
public void makeCoffee() {
System.out.println("Making Cappuccino...");
}
}
public class Latte extends Coffee {
public void makeCoffee() {
System.out.println("Making Latte...");
}
}在上述代码中,我们创建了一个咖啡外观类
CoffeeFacade
,该类持有各种类型的咖啡对象,客户端只需要调用相应的方法便可以订购对应类型的咖啡。同时我们也创建了Coffee
抽象类和其具体子类Americano
、Cappuccino
和Latte
作为子系统,实现了各自特定类型咖啡的制作过程。通过使用外观模式,我们将复杂的咖啡店后台与客户端逻辑解耦,并提供了一个简单、易于使用的接口,使得客户端订购咖啡的流程更加简单、直观。
享元模式
- 概念:通过共享对象来减少内存消耗和提高性能。享元模式的核心思想是将多个相似对象的共同部分抽象出来,作为一个共享对象来使用,而将不同的部分作为外部状态。比如 线程池
- 示例:看下线程池吧
代理模式
面试考的最多的,Spring AOP用的最多的
-
概念:通过代理控制对象的访问,可以在这个对象调用方法之前、调用方法之后去处理/添加新的功能。
-
常见的代理模式有静态代理,动态代理,cglib代理
-
静态代理:由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。(笔者觉得这是装饰器模式……)
-
动态代理:JDK提供的API,基于反射生成新的接口实现类(生成的字节码直接在内存中),在调用每个方法时,会调用invoke,走invocationHandler里的逻辑。不用关心代理类,只需要在运行阶段才指定代理哪一个对象,但是只能面向接口类。
动态代理字节码输出
通过java.lang.System#setProperty方法设置系统属性
sun.misc.ProxyGenerator.saveGeneratedFiles=true
jdk.proxy.ProxyGenerator.saveGeneratedFiles=true
上述属性二者择其一,具体设置哪个需要依照JDK版本进行选择,老版本的前者,新版本的后者
-
cglib代理:也是利用的asm,不同的是它可以代理非接口类,因为它是通过生成目标类的子类来完成的代理。
-
-
代码示例:
我就问,这个装饰器模式有啥区别……
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class Tea implements Drink{
public String taste() {
System.out.println("获取饮料类型");
return "茶";
}
}
public class TeaProxy extends Tea{
private Tea tea;
public TeaProxy(Tea drink){
this.tea = drink;
}
public String taste() {
System.out.println("--before--");
String taste = this.tea.taste();
System.out.println("--after--");
return taste;
}
}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
29public interface Drink {
String taste();
}
public class Tea implements Drink{
public String taste() {
System.out.println("获取饮料类型");
return "茶";
}
}
public class JdkProxyMain {
public static void main(String[] args) {
//输出代理类
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
Drink o = (Drink) Proxy.newProxyInstance(Tea.class.getClassLoader()
, Tea.class.getInterfaces()
, (proxy, method, args1) -> {
System.out.println("--before--");
Object invoke = method.invoke(Tea.class.getConstructor().newInstance());
System.out.println("--after--");
return invoke;
});
//代理对象调用方法
System.out.println(o.taste());
}
}看下反编译之后的代理类
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/**
* 喜茶
*/
public class HeaTea {
public String fruit(){
System.out.println("执行fruit方法");
return "芒果";
}
public String sugar(int tenth){
System.out.println("执行sugar方法");
return tenth + "分糖";
}
}
public class CglibProxyMain {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(HeaTea.class);
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
System.out.println("--before--");
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("--after--");
return o1;
});
HeaTea o = (HeaTea) enhancer.create();
//输出代理出来的子类 com.example.designmodel.proxy.HeaTea$$EnhancerByCGLIB$$3fe21093
System.out.println(o.getClass().getName());
System.out.println(o.sugar(5)+o.fruit());
}
}
行为型模式
行为型模式
- 用于描述类或者对象是怎样交互和如何分配职责的。
- 涉及到算法和对象间的职责分配,描述一组对象应该如何协作来完成一个整体的任务
责任链模式
- 概念:可以将多个对象组成一条链,并依次处理请求,直到其中一个对象能够处理该请求为止。在责任链模式中,每个对象都拥有处理请求的机会,但是如果自己不能够处理该请求,则会将请求转发给下一个对象进行处理。这样的设计可以大大降低对象之间的耦合度,使系统更加灵活和可扩展。
- 示例:Spring的Filter链就是。
命令模式
- 概念:它将请求封装成一个对象,使得可以将不同的请求进行参数化和操作,从而实现请求的解耦和队列化。核心思想是将请求封装成一个命令对象,该命令对象包含了请求的相关信息,例如请求的接收者、请求的操作等。同时,还可以使用回调函数来处理请求的结果,从而实现请求与响应之间的解耦。
- 示例:命令模式常被用于实现撤销、重做、事务等功能,也可以用于实现多级菜单、快捷键等用户界面交互功能。
解释器模式
- 概念:用于定义一个语言或规则的语法,并提供一个解释器来解释语言中的句子或表达式。
- 示例:脚本或底层开发才会用到,可以不用了解
迭代器模式
- 概念:提供了一种访问容器(如列表、集合等)元素的方式,不必暴露容器的内部结构。迭代器模式将遍历与容器的实现分离开来,并为容器提供了一个统一的接口,使得不同类型的容器都可以使用相同的遍历方式。
- 示例:看下
Iterator
的源码吧。通常包括两个核心角色:迭代器 (Iterator) 和容器 (Container)。
中介者模式(Mediator)
-
概念:通过引入一个中介者对象,可以让对象间不直接交互,而是通过中介者来实现相互通信和协作。
-
示例:在中介者模式中,通常包括三个角色:中介者 (Mediator)、具体中介者 (Concrete Mediator) 和同事类 (Colleague)。在中介者模式中,同事类之间不直接交互,而是通过中介者来传递消息和协作,因此可以降低对象间的耦合性,使得系统更加易于维护和扩展。
1
2
3
4
5public interface Mediator {
void sendMessage(String message, Colleague colleague);
void register(Colleague colleague);
void remove(Colleague colleague);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class ConcreteMediator implements Mediator {
private List<Colleague> colleagues;
public ConcreteMediator() {
colleagues = new ArrayList<>();
}
public void sendMessage(String message, Colleague colleague) {
for (Colleague c : colleagues) {
if (!c.equals(colleague)) {
c.receiveMessage(message);
}
}
}
public void register(Colleague colleague) {
colleagues.add(colleague);
colleague.setMediator(this);
}
public void remove(Colleague colleague) {
colleagues.remove(colleague);
}
}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
26public interface Colleague {
void sendMessage(String message);
void receiveMessage(String message);
void setMediator(Mediator mediator);
Mediator getMediator();
}
public class ConcreteColleague implements Colleague {
private Mediator mediator;
public void sendMessage(String message) {
mediator.sendMessage(message, this);
}
public void receiveMessage(String message) {
System.out.println("收到消息:" + message);
}
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
public Mediator getMediator() {
return mediator;
}
}1
2
3
4
5
6
7
8
9
10
11public class Client {
public static void main(String[] args) {
Mediator mediator = new ConcreteMediator();
Colleague colleague1 = new ConcreteColleague();
Colleague colleague2 = new ConcreteColleague();
mediator.register(colleague1);
mediator.register(colleague2);
colleague1.sendMessage("Hello, colleague2!");
colleague2.sendMessage("Hi, colleague1!");
}
}
备忘录模式/快照模式
- 概念:用于在不破坏封装性的前提下,保存和恢复对象的内部状态
- 示例:备忘录模式通过一个备忘录角色来存储对象的状态,并且可以在需要时恢复对象的状态,从而实现撤销和重做等功能。通常和命令模式结合使用。但是,在使用备忘录模式时,需要注意备忘录对象的存储和管理,避免占用过多的内存空间。
观察者模式/发布-订阅模式
-
概念:定义了一个一对多的依赖关系,当被依赖的对象状态发生改变时,它的所有依赖者都会收到通知并自动更新。这种模式在应用程序中广泛使用,例如处理事件、用户界面设计,消息中间件和监听器(Listener)等。观察者模式的优点是遵循开闭原则,可以在不修改代码的情况下增加新的主题和观察者;同时可以实现一种松耦合的关系,让主题和观察者之间的依赖减小,提高了系统的灵活性和可维护性。但是,使用观察者模式也有缺点,例如可能会引起内存泄漏问题等。
-
示例:
1
2
3
4
5
6
7
8
9/**
* 定义发布的事件,通常携带源数据信息,参考下js的监听事件
*/
public class MyEvent {
private String content;
private String title;
private String author;
}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
32public interface Observer {
void receive(MyEvent event);
}
/**
* 主题
*/
public class Subject {
private List<Observer> observers = new ArrayList<>();
public void registerObserver(Observer observer) {
this.observers.add(observer);
}
public void publish() {
MyEvent myEvent = new MyEvent();
myEvent.setTitle("静夜思");
myEvent.setAuthor("李白");
myEvent.setContent("床前明月光,疑是地上霜。举头望明月,低头思故乡。");
for (Observer observer : observers) {
observer.receive(myEvent);
}
}
public static void main(String[] args) {
Subject subject = new Subject();
subject.registerObserver(event -> System.out.println("提取作者:"+event.getAuthor()));
subject.registerObserver(event -> System.out.println("提取题目:"+event.getTitle()));
subject.registerObserver(event -> System.out.println("诗句数量:"+event.getContent().split("。").length));
subject.publish();
}
}
状态模式
-
概念:它允许对象在不同的状态下有不同的行为。
-
示例:状态模式-掘金
反正我是不会用这个东西的,给代码挖坑。什么玩意儿……
策略模式
这个应该会经常问到
-
概念:定义了一系列算法族,将每个算法封装到具有公共接口的独立类中,并且这些算法之间可以相互替换。
-
示例:
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
48import com.alibaba.fastjson.JSON;
import lombok.Data;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Resp<T> {
private T data;
/**
* 转换List,生成新的类
*
* @param origData 原始数据
* @param func 转换的自定义策略,示例直接用了lambda函数,也可以定义成类
* @param <T> 泛型,原始数据类型
* @param <R> 泛型,返回数据类型
* @return
*/
public static <T, R> Resp<R> toList(List<T> origData, Function<List<T>, R> func) {
Resp<R> tResp = new Resp<>();
R apply = func.apply(origData);
tResp.setData(apply);
return tResp;
}
public static void main(String[] args) {
List<String> strings = Arrays.asList("1", "2", "3");
//转换成整型
Resp<List<Integer>> listResp = Resp.toList(strings, origList -> origList
.stream()
.map(Integer::valueOf)
.collect(Collectors.toList())
);
//转换成浮点
Resp<List<Double>> listResp2 = Resp.toList(strings, origList -> origList
.stream()
.map(Double::valueOf)
.collect(Collectors.toList())
);
System.out.println(JSON.toJSONString(listResp,true));
System.out.println(JSON.toJSONString(listResp2,true));
}
}
模板方法模式
-
概念:定义了一个算法的框架,并在一个方法中定义了该算法的各个步骤,但是把一些步骤的具体实现留给子类去完成。
-
示例:
1
2
3
4
5
6
7
8
9
10
11public abstract class AbstractClass {
public void templateMethod() {
operation1();
operation2();
operation3();
}
abstract void operation1();
abstract void operation2();
abstract void operation3();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class ConcreteClass extends AbstractClass {
void operation1() {
System.out.println("ConcreteClass: operation1");
}
void operation2() {
System.out.println("ConcreteClass: operation2");
}
void operation3() {
System.out.println("ConcreteClass: operation3");
}
}1
2
3
4
5
6
7public class Client {
public static void main(String[] args) {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();//固定执行了子类的operation1,2,3
}
}
访问者模式
- 概念:将算法和对象结构分离开来,使得可以在不修改对象结构的情况下,增加新的操作和算法。
- 示例:访问者模式的核心在于同一个事物不同视角下的访问信息不同,比如看一场篮球比赛,外行人关注的是是否进球,内行人看的是球员的技术,球队的配合等。
- 思考:这跟策略模式有啥区别?
策略模式侧重于主体结构的变化,而访问者模式更强调主体结构不变,变化的是传入的访问者。
相同点是,本质上来讲,都是面向接口的编程。