0%

复习笔记 - 设计模式

Java设计模式

设计模式是在大量的实践中总结和理论化之后的优选的代码结构、编程风格、以及解决问题的思考方式。

单例设计模式

就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得对象实例的方法。

饿汉式

在加载类的时候就会创建对象,可能会造成对象创建了但是没有使用,造成资源浪费

实现方法

  1. 构造方法私有化。这一步的目的是为了不让外部可以创建对象。
  2. 类内部创建对象,创建的对象是static
  3. 向外暴露一个静态公共方法,getInstance()。该静态的公共方法返回创建的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Animal {
private String name;

private static Animal animal = new Animal("大白"); // 2.类内部创建对象,用static修饰

private Aniaml(String name) { // 1.构造器私有化
this.name = name;
}

public static Animal getInstance() { // 3.向外暴露一个方法用来获取创建的对象。static
return animal;
}
}

public class Main {
public static void main(String[] args) {
Animal animal = Animal.getInstance(); // 获取对象。
}
}

懒汉式

在使用时创建对象,只有在用户使用getInstance()方法时才返回对象,后面再次调用时会返回上次创建的对象,从而保证了单例。

实现方法

  1. 构造器私有化。
  2. 定义一个static静态属性对象。
  3. 定义一个publicstatic方法可以返回对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal {
private String name;

private static Animal animal; // 2.类内部创建对象,用static修饰

private Aniaml(String name) { // 1.构造器私有化
this.name = name;
}

public static Animal getInstance() { // 3.向外暴露一个方法用来获取创建的对象。static
if(animal == null)
animal = new Animal("大白");
return animal;
}
}

public class Main {
public static void main(String[] args) {
Animal animal = Animal.getInstance(); // 获取对象。
}
}

饿汉式和懒汉式的区别

  • 最主要的区别是创建对象的时机不同,饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建
  • 饿汉式不存在线程安全问题,懒汉式存在线程安全问题
  • 饿汉式存在浪费资源的问题,懒汉式则不存在这个问题。和对象的创建时机有很大的关系
  • javaSE标准类中,java.lang.Runtime 就是经典的单例模式

观察者模式

定义了对象之间的一对多的依赖,这样一来,当一个对象改变时,它的所有依赖者都会收到通知并自动更新。

实例解释

以微信服务号为背景,介绍观察者模式:

img

如上图所示:服务号就是主题,使用者就是观察者,现在我们明确一下功能。

  • 服务号就是主题,业务就是推送消息
  • 观察者只需要订阅主题,只要有新的消息就会收到。
  • 当不想此主题消息时,取消订阅。
  • 只要服务号还在,就会一直有人订阅。

实例实现

  • 首先实现一个观察者接口。
1
2
3
4
5
6
7
8
9
10
11
package com.xzt.observerpattern;

/**
* @author xzt
* @version 1.0
* 所有的观察者需要实现此接口
*/
public interface Observer {
void update(String msg);
}

  • 实现一个主题接口,里面声明三个函数
    • registerObserver:用来添加观察者。
    • removeObserver:用来删除观察者。
    • notifyObservers:用来将更新的信息响应给所有的观察者。
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
package com.xzt.observerpattern;

/**
* @author xzt
* @version 1.0
* 主题接口,所有的公众号必须实现此接口
*/
public interface Subject {
/**
* 加入一个观察者
* @param observer
*/
void registerObserver(Observer observer);

/**
* 移除一个观察者
* @param observer
*/
void removeObserver(Observer observer);

/**
* 向所有观察者响应
*/
void notifyObservers();
}
  • 实现一个类,这个类实现主题接口
    • 在这个类中需要定义一个List变量,用来存储所有订阅的观察者。
    • 定义一个msg,用来保存需要更新的消息。
    • 实现set函数,更新msg,同时调用notifyObservers来向所有的观察者广播更新后的消息。
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
package com.xzt.observerpattern.impl;

import com.xzt.observerpattern.Observer;
import com.xzt.observerpattern.Subject;

import java.util.ArrayList;
import java.util.List;

/**
* @author xzt
* @version 1.0
* 3D彩票的公众号
*/
public class ObjectFor3D implements Subject {
private List<Observer> observers = new ArrayList<>();

private String msg; // 彩票号码
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}

@Override
public void removeObserver(Observer observer) {
int index = observers.indexOf(observer);
if(index >= 0) {
observers.remove(index);
}
}

@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(msg);
}
}

/**
* 主题更新消息
* @param msg
*/
public void setMsg(String msg) {
this.msg = msg;
notifyObservers();
}
}
  • 实现两观察者类。需要实现Observer接口。实现update函数。这里就输出更新的信息即可。
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
package com.xzt.observerpattern.impl;

import com.xzt.observerpattern.Observer;
import com.xzt.observerpattern.Subject;

/**
* @author xzt
* @version 1.0
*/
public class Observer1 implements Observer {


public void registerObserver(Subject subject) {
subject.registerObserver(this);
}

public void removeObserver(Subject subject) {
subject.removeObserver(this);
}
@Override
public void update(String msg) {
System.out.println("observer1 得到 彩票号码 ---> " + msg + ", 我要记下来");
}
}

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
package com.xzt.observerpattern.impl;

import com.xzt.observerpattern.Observer;
import com.xzt.observerpattern.Subject;

/**
* @author xzt
* @version 1.0
*/
public class Observer2 implements Observer {

/**
* 这里使用了多态Subject
*/
public void registerObserver(Subject subject) {
subject.registerObserver(this);
}

public void removeObserver(Subject subject) {
subject.removeObserver(this);
}
@Override
public void update(String msg) {
System.out.println("observer2 得到 彩票号码 ---> " + msg + ", 我要告诉舍友们");
}
}
  • 编写测试类:
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
package com.xzt.observerpattern.test;

import com.xzt.observerpattern.Observer;
import com.xzt.observerpattern.impl.ObjectFor3D;
import com.xzt.observerpattern.impl.Observer1;
import com.xzt.observerpattern.impl.Observer2;
import com.xzt.observerpattern.innerclass.SubjectFor3d;

/**
* @author xzt
* @version 1.0
*/
public class Test {
public static void main(String[] args) {
// 模拟一个公众号
ObjectFor3D objectFor3D = new ObjectFor3D();

Observer1 observer1 = new Observer1();
observer1.registerObserver(objectFor3D);
Observer2 observer2 = new Observer2();
observer2.registerObserver(objectFor3D);

}
}

总结

上面的实例是自己定义了接口模拟了观察者模型的过程,在java中也提供了相应的库。java.util.Observablejava.util.Observer来分别表示服务者和观察者。

不一样的地方是java库中提供的是两个类,我们自己实现的是两个接口。

这个的好处体现在:当有多个主题时,每个主题的addObserver, removeObserver, notifyObservers代码基本都是相同的。如果使用接口的话就没法实现代码复用,而且也没有办法使用组合的模式实现这三个方法的复用。

代理模式

Proxy Pattern 也叫做委托模式,是一个使用率非常高的模式。

定义:为其他对象提供一种代理以控制这个对象的访问。

简单理解

代理模式将原类进行封装,客户端不能直接找到原类,必须通过代理角色。即代理是原类的一个替身,客户端要找到原类,统统找代理就可以搞定。明星和经纪人就是一种代理模式。

通用类

image-20230412095815342

  • Subject抽向主题角色:

    抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。

  • RealSubject具体主题角色:

    也叫被委托角色,被代理角色。他才是冤大头,是业务逻辑的具体执行者。

  • Proxy代理主题角色

    也叫做委托类,代理类。她负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色的实现,并且在真实主题角色处理完毕后做预处理和善后处理工作。

通用源代码

1
2
3
4
5
//抽象主题类
public interface Subject {
//定义一个方法
public void request();
}

在接口中定义了一个request来作为方法的代表。RealSubject对它进行实现。

1
2
3
4
5
6
7
// 真实主题类
public class RealSubject implements Subject {
// 实现方法
public void request() {
// 业务逻辑处理
}
}

RealSubject是一个正常的业务实现类,代理模式的核心就在代理类上。

代理类如下:

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
public class Proxy implements Subject {
//要代理哪个实现类
private Subject subject = null;
//默认被代理者
public Proxy(){
this.subject = new Proxy();
}
//通过构造函数传递代理者
public Proxy(Object...objects ) {
}
//实现接口中定义的方法
public void request() {
this.before();
this.subject.request();
this.after();
}
//预处理
private void before(){
//do something
}
//善后处理
private void after(){
//do something
}
}

代理模式优点

  • 职责清晰
  • 高扩展性
  • 只能化

案例分析

追星族想要明星签名,必须找其经纪人搞定。

  • 首先定义一个明星接口,所有的明星都会签字
1
2
3
4
5
6
7
8
9
10
11
package com.xzt.proxypattern.cases;

/**
* @author xzt
* @version 1.0
* 明星接口
*/
public interface IStar {
// 明星都会签字
public void sign();
}
  • 定义一个歌手类,需要实现这个接口,并且实现签字函数,每个明星的签字函数不一样。明星可以有很多,例如歌手,演员,这里以歌手来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.xzt.proxypattern.cases;

/**
* @author xzt
* @version 1.0
* 明星
*/
public class Singer implements IStar{
@Override
public void sign() {
System.out.println("明星签字:我是鸡你太美,你干嘛!!!");
}
}
  • 定义一个经纪人类,需要通过构造函数指明代理的是哪个明星。并调用该明星进行签字。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.xzt.proxypattern.cases;

/**
* @author xzt
* @version 1.0
* 经纪人
*/
public class Agent implements IStar{
// 定义是谁的经纪人
private IStar star;

// 构造函数传递明星
public Agent(IStar star) {
this.star = star;
}

// 经纪人不会签字的,会找代理的明星进行签字。
@Override
public void sign() {
star.sign();
}
}
  • 定义一个追星族类。首先找到歌手,再找到代理歌手的经纪人,然后找经纪人要签名。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.xzt.proxypattern.cases;

/**
* @author xzt
* @version 1.0
* 追星族
*/
public class Idolater {
public static void main(String[] args) {
// 崇拜的明星是谁
IStar star = new Singer();
// 找到明星的经纪人
IStar agent = new Agent(star);
System.out.println("追星族:我是你的崇拜者,请签名!");
// 让经纪人找明星签字
agent.sign();
}
}

代理模式的扩展

  • 普通代理:要求客服端只能访问代理角色,不能访问真实角色

  • 强制代理:客户端必须通过真实角色找到代理角色,否则你不能访问。

  • 动态代理:在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。相对来说,自己写代理类的方式就是静态代理。
正在加载今日诗词....