设计模式学习

设计模式学习

一、面向对象设计原则(7原则)

我们在进行软件开发时,不仅需要将基本的业务给完成,还要考虑整个项目的可维护性和可复用性,我们开发的项目不单单需要我们自己来维护,也需要其他的开发者一起来进行共同维护,因此我们在编写代码时,应该尽可能的规范。如果我们在编写代码时不注重这些问题,随着项目的不断扩大,整体结构只会越来越糟。为了避免这种情况的发生,我们就可以使用设计模式

1、单一职责原则

一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//一个人类
public class People {

public void coding(){
System.out.println("人类会编程");
}

/**
* 工厂打螺丝也会
*/
public void work(){
System.out.println("工厂打螺丝也会");
}

/**
* 送外卖也会
*/
public void ride(){
System.out.println("送外卖也会");
}
}

这个People类啥都会,显然这个People太过臃肿,根据单一职责原则,我们需要进行更明确的划分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Coder{

public void coding(){
System.out.println("程序员会编程");
}
}

class Worker{

public void work(){
System.out.println("工人会打螺丝");
}
}

class Rider {

public void ride(){
System.out.println("骑手会送外卖");
}
}

我们将类的粒度进行近一步划分,以后在设计Mapper、Service、Controller时,根据不同的业务进行划分,都可以采用单一职责原则

2、开闭原则

软件实体应当对扩展开放,对修改关闭

比如:

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 abstract class Coder {

public abstract void coding();

class JavaCoder extends Coder{
@Override
public void coding() {
System.out.println("Java太卷了");
}
}

class PHPCoder extends Coder{
@Override
public void coding() {
System.out.println("PHP是世界上最好的语言");
}
}

class CCoder extends Coder{
@Override
public void coding() {
System.out.println("底层还是得找我");
}
}
}

不同的程序员可以自由地决定他们该如何进行编程,这是扩展开放;具体哪个程序员使用什么语言怎么编程,不需要其他程序员干涉,这是修改关闭

3、里氏替换原则

子类可以扩展父类的功能,但不能改变父类原有的功能

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class Coder {

public void coding() {
System.out.println("我会打代码");
}


class JavaCoder extends Coder{
public void game(){
System.out.println("艾欧尼亚最强王者已上号");
}

/**
* 这里我们对父类的行为进行了重写,现在它不再具备父类原本的能力了
*/
public void coding() {
System.out.println("摆烂了,啊对对对");
System.out.println("emo结束,继续卷");
}
}
}

我们对父类的方法进行了重写,子类已经不具备父类的原本的行为,违背了里氏替换原则

正确的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class People {

public abstract void coding(); //这个行为还是定义出来,但是不实现

class Coder extends People{
@Override
public void coding() {
System.out.println("我会打代码");
}
}


class JavaCoder extends People{
public void game(){
System.out.println("艾欧尼亚最强王者已上号");
}

public void coding() {
System.out.println("摆烂了,啊对对对");
}
}
}

4、依赖倒转原则

高层模块不应该依赖低层模块,二者都应该依赖接口

未使用依赖倒转原则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DependencyReverse {

public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}

class Email {
public String getInfo() {
return "电子邮件信息: hello,world";
}
}

class Person {
public void receive(Email email) {
System.out.println(email.getInfo());
}
}

采用接口实现依赖倒转:

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
public class DependencyReverse {

public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
person.receive(new Wechat());
}
}

interface IReceive {
public String getInfo();
}

class Email implements IReceive{
public String getInfo() {
return "电子邮件信息: hello,world";
}
}
class Wechat implements IReceive{
public String getInfo() {
return "微信信息: hello,world--wechat";
}
}

class Person {
public void receive(IReceive info) {
System.out.println(info.getInfo());
}
}

5、接口隔离原则

对接口的细化

比如:

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
interface Device {
String getCpu();
String getType();
String getMemory();
}

//电脑就是一种电子设备,那么我们就实现此接口
class Computer implements Device {

@Override
public String getCpu() {
return "i9-12900K";
}

@Override
public String getType() {
return "电脑";
}

@Override
public String getMemory() {
return "32G DDR5";
}
}

//电风扇也算是一种电子设备
class Fan implements Device {

@Override
public String getCpu() {
return null; //就一个破风扇,还需要CPU?
}

@Override
public String getType() {
return "风扇";
}

@Override
public String getMemory() {
return null; //风扇也不需要内存吧
}
}

风扇不需要CPU和内存,我们要对其进行更细粒度的划分

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
interface SmartDevice {   //智能设备才有getCpu和getMemory
String getCpu();
String getType();
String getMemory();
}

interface NormalDevice { //普通设备只有getType
String getType();
}

//电脑是一种智能设备
class Computer implements SmartDevice {

@Override
public String getCpu() {
return "i9-12900K";
}

@Override
public String getType() {
return "电脑";
}

@Override
public String getMemory() {
return "32G DDR5";
}
}

//电风扇是一种普通设备
class Fan implements NormalDevice {
@Override
public String getType() {
return "风扇";
}
}

6、合成复用原则

优先使用对象组合,而不是通过继承来达到复用的目的

比如:

1
2
3
4
5
6
7
8
9
10
11
12
class A {
public void connectDatabase(){
System.out.println("我是连接数据库操作!");
}
}

class B extends A{ //直接通过继承的方式,得到A的数据库连接逻辑
public void test(){
System.out.println("我是B的方法,我也需要连接数据库!");
connectDatabase(); //直接调用父类方法就行
}
}

这样的代码耦合度太高,修改为:

1
2
3
4
5
6
7
8
9
10
11
12
class A {
public void connectDatabase(){
System.out.println("我是连接数据库操作!");
}
}

class B { //不进行继承,而是在用的时候给我一个A,当然也可以抽象成一个接口,更加灵活
public void test(A a){
System.out.println("我是B的方法,我也需要连接数据库!");
a.connectDatabase(); //在通过传入的对象A去执行
}
}

7、迪米特法则

一个类对其他类交互越少越好,目的还是降低耦合度

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 8080); //假设我们当前的程序需要进行网络通信
Test test = new Test();
test.test(socket); //现在需要执行test方法来做一些事情
}

static class Test {
/**
* 比如test方法需要得到我们当前Socket连接的本地地址
*/
public void test(Socket socket){
System.out.println("IP地址:"+socket.getLocalAddress());
}
}
}

改进后:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 8080);
Test test = new Test();
test.test(socket.getLocalAddress()); //在外面解析好就行了
}

static class Test {
public void test(String str){ //一个字符串就能搞定,就没必要丢整个对象进来
System.out.println("IP地址:"+str);
}
}
}

二、创建型(5种)

1、工厂方法模式

当我们需要对象时,直接调用工厂类中的工厂方法来为我们生成对象,而不是直接new一个对象。这样,就算类出现了变动,我们也只需要修改工厂中的代码即可,而不是大面积地进行修改

以前:

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Fruit {   //水果抽象类
private final String name;

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

@Override
public String toString() {
return name+"@"+hashCode(); //打印一下当前水果名称,还有对象的hashCode
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Apple extends Fruit{   //苹果,继承自水果

public Apple() {
super("苹果");
}
}

public class Orange extends Fruit{ //橘子,也是继承自水果
public Orange() {
super("橘子");
}
}
1
2
3
4
5
6
public class Main {
public static void main(String[] args) {
Apple apple = new Apple();
System.out.println(apple);
}
}

现在:

1
2
3
public abstract class FruitFactory<T extends Fruit> {   //将水果工厂抽象为抽象类,添加泛型T由子类指定水果类型
public abstract T getFruit(); //不同的水果工厂,通过此方法生产不同的水果
}
1
2
3
4
5
6
public class AppleFactory extends FruitFactory<Apple> {  //苹果工厂,直接返回Apple,一步到位
@Override
public Apple getFruit() {
return new Apple();
}
}
1
2
3
4
5
public class Main {
public static void main(String[] args) {
System.out.println(new AppleFactory()::getFruit);
}
}

2、抽象工厂模式

工厂方法模式只适用于简单对象,当我们需要多个产品族的时候,就需要抽象工厂模式

比如小米产品线上生产小米手机、小米平板、小米路由;华为产品线上生产华为手机、华为平板、华为路由;Apple产品线上生产iPhone、iPad、AirPort

按工厂方法模式,需要创建手机工厂、平板工厂、路由器工厂,然后分别实现三个产品子工厂,共9个工厂

使用抽象工厂模式后:

1
2
3
4
5
public abstract class AbstractFactory {
public abstract Phone getPhone();
public abstract Table getTable();
public abstract Router getRouter();
}

这样只需要创建三个产品族子工厂即可

3、建造者模式

我们通过建造者来不断配置参数或是内容,当我们配置完所有内容后,最后再进行对象的构建

以前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Student {
int id;
int age;
int grade;
String name;
String college;
String profession;
List<String> awards;

public Student(int id, int age, int grade, String name, String college, String profession, List<String> awards) {
this.id = id;
this.age = age;
this.grade = grade;
this.name = name;
this.college = college;
this.profession = profession;
this.awards = awards;
}
}
1
2
3
public static void main(String[] args) {
Student student = new Student(1, 18, 3, "小明", "计算机学院", "计算机科学与技术", Arrays.asList("ICPC-ACM 区域赛 金牌", "LPL 2022春季赛 冠军"));
}

参数太多,我们得一个个对应着去填,不小心可能把参数填到错误的位置

现在:

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
public class Student {
...

//一律使用建造者来创建,不对外直接开放
private Student(int id, int age, int grade, String name, String college, String profession, List<String> awards) {
...
}

public static StudentBuilder builder(){ //通过builder方法直接获取建造者
return new StudentBuilder();
}

public static class StudentBuilder{ //这里就直接创建一个内部类
//Builder也需要将所有的参数都进行暂时保存,所以Student怎么定义的这里就怎么定义
int id;
int age;
int grade;
String name;
String college;
String profession;
List<String> awards;

public StudentBuilder id(int id){ //直接调用建造者对应的方法,为对应的属性赋值
this.id = id;
return this; //为了支持链式调用,这里直接返回建造者本身,下同
}

public StudentBuilder age(int age){
this.age = age;
return this;
}

...

public StudentBuilder awards(String... awards){
this.awards = Arrays.asList(awards);
return this;
}

public Student build(){ //最后我们只需要调用建造者提供的build方法即可根据我们的配置返回一个对象
return new Student(id, age, grade, name, college, profession, awards);
}
}
}
1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
Student student = Student.builder() //获取建造者
.id(1) //逐步配置各个参数
.age(18)
.grade(3)
.name("小明")
.awards("ICPC-ACM 区域赛 金牌", "LPL 2022春季赛 冠军")
.build(); //最后直接建造我们想要的对象
}

这样赋值的方式就比以前优雅多了,lombok提供了@Builder注解,可以直接使用建造者模式

4、单例模式

只有一个实例对象,SpringBoot采用的就是单例模式 singleton

饿汉式,对象在一开始类加载的时候就创建好了

1
2
3
4
5
6
7
8
9
public class Singleton {
private final static Singleton INSTANCE = new Singleton(); //用于引用全局唯一的单例对象,在一开始就创建好

private Singleton() {} //不允许随便new,需要对象直接找getInstance

public static Singleton getInstance(){ //获取全局唯一的单例对象
return INSTANCE;
}
}

懒汉式,当我们需要获取对象时,才进行检查并创建

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
private static Singleton INSTANCE; //在一开始先不进行对象创建

private Singleton() {}

public static Singleton getInstance(){ //将对象的创建延后到需要时再进行
if(INSTANCE == null) { //如果实例为空,那么就进行创建,不为空说明已经创建过了,那么就直接返回
INSTANCE = new Singleton();
}
return INSTANCE;
}
}

在多线程环境下,如果三条线程同时调用 getInstance() 方法,会导致对象被多次创建,因此需要对懒汉式的写法做一些改进,加上锁

1
2
3
4
5
6
7
8
public static Singleton getInstance(){
if(INSTANCE == null) {
synchronized (Singleton.class) { //实际上只需要对赋值这一步进行加锁即可
INSTANCE = new Singleton();
}
}
return INSTANCE;
}

不过这样还不完美,因为这样还是有可能多个线程同时判断为null而进入等锁的状态,这样还是会导致对象被多次创建,所以,我们还得加一层内层判断

1
2
3
4
5
6
7
8
public static Singleton getInstance(){
if(INSTANCE == null) {
synchronized (Singleton.class) {
if(INSTANCE == null) INSTANCE = new Singleton(); //内层还要进行一次检查,双重检查锁定
}
}
return INSTANCE;
}

即使这样,我们还缺少了一项内容,我们要添加一个 volatile 给 INSTANCE,INSTANCE的作用是让对象在线程之间可见,这样其他线程才会拿 INSTANCE 的最新值去判断

5、原型模式

通过原对象拷贝创建新对象

浅拷贝:对于类中基本数据类型,会直接复制值给拷贝对象;对于引用类型,只会复制对象的引用

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
int a = 10;
int b = a; //基本类型浅拷贝
System.out.println(a == b);

Object o = new Object();
Object k = o; //引用类型浅拷贝,拷贝的仅仅是对上面对象的引用
System.out.println(o == k);
}

深拷贝:将原对象拷贝成新的对象,我们可以使用 Cloneable 接口实现深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Student implements Cloneable{

String name;

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

public String getName() {
return name;
}

@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
1
2
3
4
5
public static void main(String[] args) throws CloneNotSupportedException {
Student student0 = new Student("小明");
Student student1 = (Student) student0.clone();
System.out.println(student0.getName() == student1.getName());
}

虽然Student对象成功拷贝,但是其内层对象并没有进行拷贝,依然只是对象引用的复制,所以Java为我们提供的 clone 方法只会进行浅拷贝

1
2
3
4
5
6
@Override
public Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.name = new String(name);
return student; //成员拷贝完成后,再返回
}

这里我们改进一下,对成员变量也进行拷贝,就实现了深拷贝

三、结构型(7种)

1、适配器模式

比如Macbook为了轻薄全是type-c的接口,而我们现在需要使用usb口,这时需要买一个扩展坞,这实际上就是一种适配模式

1
2
3
4
5
6
7
8
9
10
11
public class TestSupplier {   //手机供应商类

public String supply(){
return "iPhone 14 Pro";
}
}

public interface Target { //手机供应商接口

String supply();
}
1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
TestSupplier supplier = new TestSupplier();
test( ? );
}

public static void test(Target target){ //test方法只支持手机供应商接口,但是我们想要传入TestSupplier手机供应商类,使用其中的supply方法
System.out.println("成功得到:"+target.supply());
}
}

这时:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestAdapter implements Target{

TestSupplier supplier;

public TestAdapter(TestSupplier supplier){
this.supplier = supplier;
}

@Override
public String supply() {
return supplier.doSupply();
}
}
1
2
3
4
5
6
7
8
public static void main(String[] args) {
TestAdapter adapter = new TestAdapter();
test(adapter);
}

public static void test(Target target){
System.out.println("成功得到:"+target.supply());
}

2、桥接模式

买奶茶时,我们需要确认大中小杯、什么配料,不同类型的奶茶都要去创建一个类。桥接模式就是,将奶茶的类型作为最基本的抽象类,然后对尺寸、配料等属性进行桥接

以前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface Tea {   //不同奶茶类型
String getType();
}

public interface Size { //分大杯中杯小杯
String getSize();
}

/**
* 大杯芋圆啵啵奶茶
*/
public class LargeKissTea implements Tea, Size{

@Override
public String getSize() {
return "大杯";
}

@Override
public String getType() {
return "芋圆啵啵奶茶";
}
}

现在:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class AbstractTea {

private Size size; //尺寸作为桥接属性存放在类中

public AbstractTea(Size size){ //在构造时需要知道尺寸属性
this.size = size;
}

public abstract String getType(); //奶茶类型由子类决定

public String getSize(){ //添加尺寸维度获取方式
return size.getSize();
}
}

我们需要为Size创建子类:

1
2
3
4
5
6
7
public class Large implements Size{

@Override
public String getSize() {
return "大杯";
}
}

创建芋圆啵啵奶茶子类:

1
2
3
4
5
6
7
8
9
10
public class KissTea extends AbstractTea{   //创建一个芋圆啵啵奶茶的子类
public KissTea(Size size) { //在构造时需要指定具体的大小实现
super(size);
}

@Override
public String getType() {
return "芋圆啵啵奶茶"; //返回奶茶类型
}
}
1
2
3
4
5
public static void main(String[] args) {
KissTea tea = new KissTea(new Large());
System.out.println(tea.getType());
System.out.println(tea.getSize());
}

3、组合模式

使用 Employee 类来创建和打印员工的层次结构,打印所有员工

创建 Employee 类,该类带有 Employee 对象的列表

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
public class Employee {
private String name;
private String dept;
private int salary;
private List<Employee> subordinates;

//构造函数
public Employee(String name,String dept, int sal) {
this.name = name;
this.dept = dept;
this.salary = sal;
subordinates = new ArrayList<Employee>();
}

public void add(Employee e) {
subordinates.add(e);
}

public void remove(Employee e) {
subordinates.remove(e);
}

public List<Employee> getSubordinates(){
return subordinates;
}

public String toString(){
return ("Employee :[ Name : "+ name
+", dept : "+ dept + ", salary :"
+ salary+" ]");
}
}

使用 Employee 类来创建和打印员工的层次结构

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 class CompositePatternDemo {
public static void main(String[] args) {
Employee CEO = new Employee("John","CEO", 30000);

Employee headSales = new Employee("Robert","Head Sales", 20000);

Employee headMarketing = new Employee("Michel","Head Marketing", 20000);

Employee clerk1 = new Employee("Laura","Marketing", 10000);
Employee clerk2 = new Employee("Bob","Marketing", 10000);

Employee salesExecutive1 = new Employee("Richard","Sales", 10000);
Employee salesExecutive2 = new Employee("Rob","Sales", 10000);

CEO.add(headSales);
CEO.add(headMarketing);

headSales.add(salesExecutive1);
headSales.add(salesExecutive2);

headMarketing.add(clerk1);
headMarketing.add(clerk2);

//打印该组织的所有员工
System.out.println(CEO);
for (Employee headEmployee : CEO.getSubordinates()) {
System.out.println(headEmployee);
for (Employee employee : headEmployee.getSubordinates()) {
System.out.println(employee);
}
}
}
}

执行结果:

1
2
3
4
5
6
7
Employee :[ Name : John, dept : CEO, salary :30000 ]
Employee :[ Name : Robert, dept : Head Sales, salary :20000 ]
Employee :[ Name : Richard, dept : Sales, salary :10000 ]
Employee :[ Name : Rob, dept : Sales, salary :10000 ]
Employee :[ Name : Michel, dept : Head Marketing, salary :20000 ]
Employee :[ Name : Laura, dept : Marketing, salary :10000 ]
Employee :[ Name : Bob, dept : Marketing, salary :10000 ]

4、装饰模式

对现有的类进行装饰

1
2
3
public abstract class Base {   //顶层抽象类,定义了一个test方法执行业务
public abstract void test();
}
1
2
3
4
5
6
public class BaseImpl extends Base{
@Override
public void test() {
System.out.println("我是业务方法"); //具体的业务方法
}
}

现在的实现类太单调了,我们加一点装饰上去:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Decorator extends Base{   //装饰者需要将装饰目标组合到类中

protected Base base;

public Decorator(Base base) {
this.base = base;
}

@Override
public void test() {
base.test(); //这里还是使用原本方法实现
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class DecoratorImpl extends Decorator{   //装饰实现

public DecoratorImpl(Base base) {
super(base);
}

@Override
public void test() { //对原本的方法进行装饰,我们可以在前后都去添加额外操作
System.out.println("装饰方法:我是操作前逻辑");
super.test();
System.out.println("装饰方法:我是操作后逻辑");
}
}

实现装饰模式:

1
2
3
4
5
6
public static void main(String[] args) {
Base base = new BaseImpl();
Decorator decorator = new DecoratorImpl(base); //将Base对象进行装饰

decorator.test();
}

5、代理模式

对现有的类进行代理

实现代理模式:

1
2
3
public abstract class Subject {
public abstract void test();
}
1
2
3
4
5
6
7
public class SubjectImpl extends Subject{  //此类无法直接使用,需要我们进行代理

@Override
public void test() {
System.out.println("我是测试方法!");
}
}

代理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Proxy extends Subject{   //为了保证和Subject操作方式一样,保证透明性,也得继承

Subject target; //被代理的对象(甚至可以多重代理)

public Proxy(Subject subject){
this.target = subject;
}

@Override
public void test() { //由代理去执行被代理对象的方法,并且我们还可以在前后添油加醋
System.out.println("代理前绕方法");
target.test();
System.out.println("代理后绕方法");
}
}

代理模式和装饰模式很像,不同的在于思想。装饰模式强调增强自身;代理模式强调让别人帮你做事情,比如记录日志、设置缓存等

6、外观模式

比如我们需要吃饭,需要做买菜、煮菜、吃饭的准备,但是现在我们通过门面统一来完成,这就是外观模式

1
2
3
4
5
public class SubSystemA {
public void test1(){
System.out.println("买菜");
}
}
1
2
3
4
5
public class SubSystemB {
public void test2(){
System.out.println("煮菜");
}
}
1
2
3
4
5
public class SubSystemC {
public void test3(){
System.out.println("吃饭");
}
}

我们添加一个门面:

1
2
3
4
5
6
7
8
9
10
11
12
public class Facade {

SubSystemA a = new SubSystemA();
SubSystemB b = new SubSystemB();
SubSystemC c = new SubSystemC();

public void havaLunch(){ //吃饭一条龙服务
a.test1();
b.test2();
c.test3();
}
}
1
2
3
4
public static void main(String[] args) {
Facade facade = new Facade();
facade.havaLunch();
}

使用外观模式简化了流程

7、享元模式

将重复出现的内容作为共享部分取出。比如现在我们有两个服务,但是他们都需要使用数据库工具类来操作,实际上这个工具类没必要创建多个,我们这时就可以使用享元模式,让数据库工具类作为享元类

1
2
3
4
5
public class DBUtil {
public void selectDB(){
System.out.println("我是数据库操作...");
}
}
1
2
3
4
5
6
7
public class DBUtilFactory {
private static final DBUtil UTIL = new DBUtil(); //享元对象被存放在工厂中

public static DBUtil getFlyweight(){ //获取享元对象
return UTIL;
}
}

我们想要使用数据库,直接找享元工厂

1
2
3
4
5
6
7
public class UserService {   //用户服务

public void service(){
DBUtil util = DBUtilFactory.getFlyweight(); //通过享元工厂拿到DBUtil对象
util.selectDB(); //数据库操作
}
}

四、行为型(11种)

1、解释器模式

该设计模式比较少使用,就是对我们的语言进行解释

比如我们输入:1+2*3,最后得到结果7,这就是解释器模式

2、模板方法模式

在程序中,某些操作是固定的,而有些操作需要视情况而定,由不同的子类实现,这时要用到模板方法模式

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 抽象诊断方法,因为现在只知道挂号和看医生是固定模式,剩下的开处方和拿药都是不确定的
*/
public abstract class AbstractDiagnosis {

public void test(){
System.out.println("今天头好晕,不想起床,开摆,先跟公司请个假");
System.out.println("去医院看病了~");
System.out.println("1 >> 先挂号");
System.out.println("2 >> 等待叫号");
//由于现在不知道该开什么处方,所以只能先定义一下行为,然后具体由子类实现
//大致的流程先定义好就行
this.prescribe();
this.medicine(); //开药同理
}

public abstract void prescribe(); //开处方操作根据具体病症决定了

public abstract void medicine(); //拿药也是根据具体的处方去拿
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 感冒相关的具体实现子类
*/
public class ColdDiagnosis extends AbstractDiagnosis{
@Override
public void prescribe() {
System.out.println("3 >> 没有感冒");
}

@Override
public void medicine() {
System.out.println("4 >> 开点头孢回去吃吧");
}
}
1
2
3
4
public static void main(String[] args) {
AbstractDiagnosis diagnosis = new ColdDiagnosis();
diagnosis.test();
}

看病的逻辑已经由父类定义好了,所以子类只需要实现需要实现的部分即可,这样我们就实现了简单的模板方法模式

3、责任链模式

使用责任链模式来模拟一个简单的面试过程,按一面二面三面这样走的流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class Handler {

protected Handler successor; //这里我们就设计责任链以单链表形式存在,这里存放后继节点

public Handler connect(Handler successor){ //拼接后续节点
this.successor = successor;
return successor; //这里返回后继节点,方便我们一会链式调用
}

public void handle(){
this.doHandle(); //由不同的子类实现具体处理过程
Optional
.ofNullable(successor)
.ifPresent(Handler::handle); //责任链上如果还有后继节点,就继续向下传递
}

public abstract void doHandle(); //结合上节课学习的模板方法,交给子类实现
}
1
2
3
4
5
6
7
8
9
public class FirstHandler extends Handler{   //用于一面的处理器
@Override
public void doHandle() {
System.out.println("============= 一面 ==========");
System.out.println("1. 谈谈你对static关键字的理解?");
System.out.println("2. 内部类可以调用外部的数据吗?如果是静态的呢?");
System.out.println("3. hashCode()方法是所有的类都有吗?默认返回的是什么呢?");
}
}
1
2
3
4
5
6
7
8
9
10
public class SecondHandler extends Handler{  //二面
@Override
public void doHandle() {
System.out.println("============= 二面 ==========");
System.out.println("1. 如果我们自己创建一个java.lang包并且编写一个String类,能否实现覆盖JDK默认的?");
System.out.println("2. HashMap的负载因子有什么作用?变化规律是什么?");
System.out.println("3. 线程池的运作机制是什么?");
System.out.println("4. ReentrantLock公平锁和非公平锁的区别是什么?");
}
}
1
2
3
4
5
6
7
8
9
10
public class ThirdHandler extends Handler{  //三面
@Override
public void doHandle() {
System.out.println("============= 三面 ==========");
System.out.println("1. synchronized关键字了解吗?如何使用?底层是如何实现的?");
System.out.println("2. IO和NIO的区别在哪里?NIO三大核心组件?");
System.out.println("3. TCP握手和挥手流程?少一次握手可以吗?为什么?");
System.out.println("4. 操作系统中PCB是做什么的?运行机制是什么?");
}
}

编写好每一轮的面试流程,我们就可以构建一个责任链

1
2
3
4
5
6
7
public static void main(String[] args) {
Handler handler = new FirstHandler(); //一面首当其冲
handler
.connect(new SecondHandler()) //继续连接二面和三面
.connect(new ThirdHandler());
handler.handle(); //开始面试
}

4、命令模式

比如现在我们有很多的类,彩电、冰箱、空调等,我们要通过一个遥控器去控制他们,就需要将控制这些电器的指令都给设计好,而且还不能有太强的关联性

所有的电器都是接收者:

1
2
3
public interface Receiver {
void action(); //具体行为
}

我们要控制这些电器,那么肯定需要一个指令才能控制:

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Command {   //指令抽象,不同的电器有指令

private final Receiver receiver;

protected Command(Receiver receiver){ //指定此命令对应的电器(接受者)
this.receiver = receiver;
}

public void execute() {
receiver.action(); //执行命令,实际上就是让接收者开始干活
}
}

创建一个遥控器:

1
2
3
4
5
public class Controller {   //遥控器只需要把我们的指令发出去就行了
public static void call(Command command){
command.execute();
}
}

创建一个空调:

1
2
3
4
5
6
public class AirConditioner implements Receiver{
@Override
public void action() {
System.out.println("空调已开启,呼呼呼");
}
}

创建一个开启空调的命令:

1
2
3
4
5
public class OpenCommand extends Command {
public OpenCommand(AirConditioner airConditioner) {
super(airConditioner);
}
}

通过遥控器开启空调:

1
2
3
4
public static void main(String[] args) {
AirConditioner airConditioner = new AirConditioner(); //先创建一个空调
Controller.call(new OpenCommand(airConditioner)); //直接通过遥控器来发送空调开启命令
}

5、迭代器模式

通过迭代器进行集合的遍历

1
2
3
4
5
6
public static void main(String[] args) {
List<String> list = Arrays.asList("AAA", "BBB", "CCC");
for (String s : list) { //使用foreach语法进行迭代,依次获取每一个元素
System.out.println(s); //打印一下
}
}

编译后使用的是迭代器实现

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
List<String> list = Arrays.asList("AAA", "BBB", "CCC");
Iterator var2 = list.iterator(); //实际上这里本质是通过List生成的迭代器来遍历我们每个元素的

while(var2.hasNext()) { //判断是否还有元素可以迭代,没有就false
String s = (String)var2.next(); //通过next方法得到下一个元素,每调用一次,迭代器会向后移动一位
System.out.println(s); //打印一下
}
}

6、中介者模式

程序中可能会出现很多的对象,这些对象之间的调用关系错综复杂,可能一个对象要做上面事情就得联系好几个对象。这时如果设置一个中间人,只需要联系中间人即可

比如:

1
2
3
4
5
6
7
8
9
10
11
public class Mediator {   //房产中介
private final Map<String, User> userMap = new HashMap<>(); //在出售的房子需要存储一下

public void register(String address, User user){ //出租房屋的人,需要告诉中介他的房屋在哪里
userMap.put(address, user);
}

public User find(String address){ //找房的人通过此方法来看看有没有对应的房源
return userMap.get(address);
}
}

用户有两种角色,一种是租房,一种是出租

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class User {   //用户可以是出售房屋的一方,也可以是寻找房屋的一方
String name;
String tel;

public User(String name, String tel) {
this.name = name;
this.tel = tel;
}

public User find(String address, Mediator mediator){ //找房子的话,需要一个中介和你具体想找的地方
return mediator.find(address);
}

@Override
public String toString() {
return name+" (电话:"+tel+")";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
User user0 = new User("刘女士", "10086"); //出租人
User user1 = new User("李先生", "10010"); //找房人
Mediator mediator = new Mediator(); //我是中介

mediator.register("成都市武侯区天府五街白马程序员", user0); //先把房子给中介挂上去

User user = user1.find("成都市武侯区天府五街下硅谷", mediator); //开始找房子
if(user == null) System.out.println("没有找到对应的房源");

user = user1.find("成都市武侯区天府五街白马程序员", mediator); //开始找房子
System.out.println(user); //成功找到对应房源
}

中介者模式优化了原有的复杂多对多关系,而是将其简化为一对多的关系

7、备忘录模式

为我们的软件提供了一个可回溯的时间节点,可能我们程序在运行过程中某一步出现了错误,这时我们就可以回到之前某个节点重新来过

学生对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student {
private String currentWork; //当前正在做的事情
private int percentage; //当前的工作完成百分比

public void work(String currentWork) {
this.currentWork = currentWork;
this.percentage = new Random().nextInt(100);
}

@Override
public String toString() {
return "我现在正在做:"+currentWork+" (进度:"+percentage+"%)";
}
}

状态保存类:

1
2
3
4
5
6
7
8
9
public class State {
final String currentWork;
final int percentage;

State(String currentWork, int percentage) { //仅开放给同一个包下的Student类使用
this.currentWork = currentWork;
this.percentage = percentage;
}
}

实现状态的保存和恢复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student {
...

public State save(){
return new State(currentWork, percentage);
}

public void restore(State state){
this.currentWork = state.currentWork;
this.percentage = state.percentage;
}

...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
Student student = new Student();
student.work("学Java"); //开始学Java
System.out.println(student);

State savedState = student.save(); //保存一下当前的状态

student.work("打游戏"); //学一半,然后开始打游戏
System.out.println(student);

student.restore(savedState); //两级反转!回到上一个保存的状态
System.out.println(student); //回到学Java的状态
}

8、观察者模式

当对象发生改变时,观察者能够立刻观察到并进行一些联动操作

观察者接口:

1
2
3
public interface Observer {   //观察者接口
void update(); //当对象有更新时,会回调此方法
}
1
2
3
4
5
6
7
8
9
10
11
public class Subject {
private final Set<Observer> observerSet = new HashSet<>();

public void observe(Observer observer) { //添加观察者
observerSet.add(observer);
}

public void modify() { //模拟对象进行修改
observerSet.forEach(Observer::update); //当对象发生修改时,会通知所有的观察者,并进行方法回调
}
}
1
2
3
4
5
6
7
public static void main(String[] args) {
Subject subject = new Subject();
//当对象发生修改时,会通知所有的观察者,进行方法回调
subject.observe(() -> System.out.println("我是一号观察者!"));
subject.observe(() -> System.out.println("我是二号观察者!"));
subject.modify();
}
1
2
我是一号观察者!
我是二号观察者!

9、状态模式

水在不同的温度下呈现不同的状态,这就是状态模式

我们来设计一个学生类,然后学生的学习方法会根据状态不同而发生改变,我们先设计一个状态枚举:

1
2
3
public enum State {   //状态直接使用枚举定义
NORMAL, REST
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Student {

public class Student {

private State state; //使用一个成员来存储状态

public void setState(State state) {
this.state = state;
}

public void study(){
switch (state) { //根据不同的状态,学习方法会有不同的结果
case REST:
System.out.println("学累了,我去休息一会儿");
break;
case NORMAL:
System.out.println("好好学习,天天向上");
break;
}
}
}
1
2
3
4
5
6
7
8
public static void main(String[] args) {
Student student = new Student();
student.setState(State.NORMAL); //先正常模式
student.study();

student.setState(State.LAZY); //开启休息模式
student.study();
}

10、策略模式

为对象设定一种策略,之后对象的行为就按这个策略执行。跟刚刚说的状态模式有点像,区别在于策略模式由我们指定,状态模式是运行过程中自动切换

策略模式其实我们以前也遇到过,比如线程池的各种策略,拒绝策略、抛弃策略、抛弃最老策略等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 10,
TimeUnit.SECONDS, new SynchronousQueue<>(), //这里不给排队
new ThreadPoolExecutor.AbortPolicy()); //这里指定线程池为拒绝策略

Runnable runnable = () -> {
try {
TimeUnit.SECONDS.sleep(60);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
};

executor.execute(runnable); //连续提交两次任务,肯定塞不下,这时就得走拒绝了
executor.execute(runnable);
}

11、访问者模式

不同的访问者对某一件事务的处理可能不同

比如我们日以继夜地努力,终于在某某比赛赢得了冠军,而不同的访问者对这份荣誉有不同的反应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Prize {   //奖
String name; //比赛名称
String level; //等级

public Prize(String name, String level) {
this.name = name;
this.level = level;
}

public String getName() {
return name;
}

public String getLevel() {
return level;
}
}

访问者接口:

1
2
3
public interface Visitor {
void visit(Prize prize); //visit方法来访问我们的奖项
}
1
2
3
4
5
6
7
public class Teacher implements Visitor {   //指导老师作为一个访问者
@Override
public void visit(Prize prize) { //老师关心你得了什么奖以及是几等奖
System.out.println("你得了什么奖?"+prize.name);
System.out.println("你得了几等奖?"+prize.level);
}
}
1
2
3
4
5
6
7
public class Family implements Visitor{    //家人作为一个访问者
@Override
public void visit(Prize prize) { //你的家人并不最先关心你得了什么奖,而是先关心你自己然后才是奖项
System.out.println("孩子,辛苦了,有没有好好照顾自己啊");
System.out.println("你得了什么奖啊?"+prize.name+",很不错,要继续加油啊!");
}
}

访问者模式将奖项本身的属性和对于奖项的不同操作进行了分离

小傅哥设计模式

创建型模式

1、工厂方法模式

2、结构型模式

3、建造者模式

4、原型模式

5、单例模式

懒汉、饿汉、线程是否安全、静态类、内部类、加锁、串行化

结构型模式

6、适配器模式

7、桥接模式

8、组合模式

9、装饰器模式

10、外观模式

11、享元模式

12、代理模式

行为模式

13、责任链模式

14、命令模式

15、迭代器模式

16、中介者模式

17、备忘录模式

18、策略模式

19、模板模式

20、访问者模式

2024.1.28已完结!