面向对象设计原则

单一职责原则

单一职责原则(Simple Responsibility Principle,SRP)是最简单的面向对象设计原则,它用于控制类的粒度大小。

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

 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
class Coder{
    /**
     * 程序员会编程
     */
    public void coding(){
        System.out.println("int main() {");
        System.out.println("   printf(\"Hello World!\")");
        System.out.println("}");
        System.out.println("啊嘞,怎么运行不起?明明照着老师敲的啊");
    }
}

class Worker{
    /**
     * 工人会打螺丝
     */
    public void work(){
        System.out.println("真开心,能进到富土康打螺丝");
        System.out.println("诶,怎么工友都提桶跑路了");
    }
}

class Rider {
    /**
     * 骑手会送外卖
     */
    public void ride(){
        System.out.println("今天终于通过美团最终面,加入了梦寐以求的大厂");
        System.out.println("感觉面试挺简单的,就是不知道为啥我同学是现场做一道力扣接雨水,我是现场问会不会骑车");
        System.out.println("(迫不及待穿上外卖服装)");
    }
}

将类的粒度进行更近一步的划分,这样就很清晰了,包括我们以后在设计Mapper、Service、Controller等等,根据不同的业务进行划分,都可以采用单一职责原则,以它作为我们实现高内聚低耦合的指导方针。实际上我们的微服务也是参考了单一职责原则,每个微服务只应担负一个职责。

开闭原则

开闭原则(Open Close Principle)也是重要的面向对象设计原则。

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

 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太卷了T_T,快去学Go吧!");
        }
    }

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

    class C艹Coder extends Coder{
        @Override
        public void coding() {
            System.out.println("笑死,Java再牛逼底层不还得找我?");
        }
    }
}

通过提供一个Coder抽象类,定义出编程的行为,但是不进行实现,而是开放给其他具体类型的程序员来实现,这样就可以根据不同的业务进行灵活扩展了,具有较好的延续性。

里氏替换原则

里氏替换原则(Liskov Substitution Principle)是对子类型的特别定义。

所有引用基类的地方必须能透明地使用其子类的对象。

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

  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  2. 子类可以增加自己特有的方法。
  3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。
  4. 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或与父类一样。
 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("摆烂了,啊对对对");
        }
    }
}

里氏替换也是实现开闭原则的重要方式之一。

依赖倒转原则

依赖倒转原则(Dependence Inversion Principle)也是我们一直在使用的,最明显的就是我们的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
29
30
31
32
public class Main {

    public static void main(String[] args) {
        UserController controller = new UserController();
    }

    interface UserMapper {
        //接口中只做CRUD方法定义
    }

    static class UserMapperImpl implements UserMapper {
        //实现类完成CRUD具体实现
    }

    interface UserService {
        //业务代码定义....
    }

    static class UserServiceImpl implements UserService {
        @Resource   //现在由Spring来为我们选择一个指定的实现类,然后注入,而不是由我们在类中硬编码进行指定
        UserMapper mapper;
        
        //业务代码具体实现
    }

    static class UserController {
        @Resource
        UserService service;   //直接使用接口,就算你改实现,我也不需要再修改代码了

        //业务代码....
    }
}

通过使用接口,我们就可以将原有的强关联给弱化,我们只需要知道接口中定义了什么方法然后去使用即可,而具体的操作由接口的实现类来完成,并由Spring来为我们注入,而不是我们通过硬编码的方式去指定。

接口隔离原则

接口隔离原则(Interface Segregation Principle, ISP)实际上是对接口的细化。

客户端不应依赖那些它不需要的接口。

 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 "风扇";
    }
}

这样,我们就将接口进行了细粒度的划分,不同类型的电子设备就可以根据划分去实现不同的接口了。当然,也不能划分得太小,还是要根据实际情况来进行决定。

合成复用原则

合成复用原则(Composite Reuse Principle)的核心就是委派。

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

 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去执行
    }
}

迪米特法则

迪米特法则(Law of Demeter)又称最少知识原则,是对程序内部数据交互的限制。

每一个软件单位对其他单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。

简单来说就是,一个类/模块对其他的类/模块有越少的交互越好。当一个类发生改动,那么,与其相关的类(比如用到此类啥方法的类)需要尽可能少的受影响(比如修改了方法名、字段名等,可能其他用到这些方法或是字段的类也需要跟着修改)这样我们在维护项目的时候会更加轻松一些。

 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().getHostAddress());  //在外面解析好就行了
    }

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

设计模式(创建型)

工厂方法模式

工厂方法模式,我们知道,如果需要创建一个对象,那么最简单的方式就是直接new一个即可。而工厂方法模式代替了传统的直接new的形式,那么为什么要替代传统的new形式呢?

所有的对象我们都通过new的方式去创建,那么当我们的程序中大量使用此对象时,直接调用工厂类中的工厂方法来为我们生成对象,这样,就算类出现了变动,我们也只需要修改工厂中的代码即可,而不是大面积地进行修改。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public abstract class FruitFactory<T extends Fruit> {   //将水果工厂抽象为抽象类,添加泛型T由子类指定水果类型
    public abstract T getFruit();  //不同的水果工厂,通过此方法生产不同的水果
}

public class AppleFactory extends FruitFactory<Apple> {  //苹果工厂,直接返回Apple,一步到位
    @Override
    public Apple getFruit() {
        return new Apple();
    }
}

抽象工厂模式

当我们需要生产许多个产品族的时候,比如小米的产品线上有小米12,小米平板等,华为的产品线上也有华为手机、华为平板,但是如果按照我们之前工厂方法模式来进行设计,那就需要单独设计9个工厂来生产上面这些产品,显然这样就比较浪费时间的。我们就可以使用抽象工厂模式,我们可以将多个产品,都放在一个工厂中进行生成,按不同的产品族进行划分,比如小米,那么我就可以安排一个小米工厂,而这个工厂里面就可以生产整条产品线上的内容,包括小米手机、小米平板、小米路由等。

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

public class Table {
}

public class Phone {
}

public abstract class AbstractFactory {
    public abstract Phone getPhone();
    public abstract Table getTable();
    public abstract Router getRouter();
}

一个工厂可以生产同一个产品族的所有产品,这样按族进行分类,显然比之前的工厂方法模式更好。

建造者模式

建造者模式也是非常常见的一种设计模式,我们经常看到有很多的框架都为我们提供了形如XXXBuilder的类型,我们一般也是使用这些类来创建我们需要的对象。

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

 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();   //最后直接建造我们想要的对象
}

单例模式

饿汉式单例模式

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
public static void main(String[] args) {
    Singleton singleton = Singleton.getInstance();
}

懒汉式单例模式

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

    private Singleton() {}   //不允许new构造

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

在多线程环境下,如果三条线程同时调用getInstance()方法,会同时进行INSTANCE == null的判断,那么此时由于确实还没有进行任何实例化,所以导致三条线程全部判断为true

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

但是双重检查会带来性能问题,我们添加一个volatile给INSTANCE,我们还需要保证INSTANCE在线程之间的可见性,这样当其他线程进入之后才会拿INSTANCE由其他线程更新的最新值去判断

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Singleton {
    private static volatile Singleton INSTANCE;   //在一开始先不进行对象创建,并通过volatile实现线程间的可见性

    private Singleton() {}   //不允许new构造

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

但是加锁会导致资源浪费,所以使用静态内部类,这是最好的实现方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Singleton {
    private Singleton() {}

    private static class Holder {   //由静态内部类持有单例对象,但是根据类加载特性,我们仅使用Singleton类时,不会对静态内部类进行初始化
        private final static Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){   //只有真正使用内部类时,才会进行类初始化
        return Holder.INSTANCE;   //直接获取内部类中的
    }
}

原型模式

浅拷贝

对于类中基本数据类型,会直接复制值给拷贝对象;对于引用类型,只会复制对象的地址,而实际上指向的还是原来的那个对象

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);
}

深拷贝

基本类型还是引用类型,深拷贝会将引用类型的所有内容,全部拷贝为一个新的对象,包括对象内部的所有成员变量,也会进行拷贝

 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
public class Student implements Cloneable{   //注意需要实现Cloneable接口
    @Override
    public Object clone() throws CloneNotSupportedException {   //提升clone方法的访问权限
        return super.clone();
    }
}
public static void main(String[] args) throws CloneNotSupportedException {
    Student student0 = new Student();
    Student student1 = (Student) student0.clone();
    System.out.println(student0);
    System.out.println(student1);
}
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();
    }
}
public static void main(String[] args) throws CloneNotSupportedException {
    Student student0 = new Student("小明");
    Student student1 = (Student) student0.clone();
    System.out.println(student0.getName() == student1.getName());
}

通过clone()方法克隆的对象并不是原来的对象,但是其内层对象并没有进行拷贝,依然只是对象引用的复制,所以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;   //成员拷贝完成后,再返回
}

这样,我们就实现了深拷贝(原型模式)

设计模式(结构型)

类/对象适配器模式

现在需要解决以下问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class TestSupplier {   //手机供应商
    public String doSupply(){
        return "iPhone 14 Pro";
    }
}

public class Main {
    public static void main(String[] args) {
        TestSupplier supplier = new TestSupplier();
      	// test( ? );   我们没有Target类型的手机供应商,只有其他的,那这里该填个啥
    }

    public static void test(Target target){   //现在我们需要调用test方法,但是test方法需要Target类型的手机供应商
        System.out.println("成功得到:"+target.supply());
    }
}

public interface Target {    //现在的手机供应商,并不是test方法所需要的那种类型
    String supply();
}

类适配器模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class TestAdapter extends TestSupplier implements Target {  
  //让我们的适配器继承TestSupplier并且实现Target接口
    @Override
    public String supply() {   //接着实现supply方法,直接使用TestSupplier提供的实现
        return super.doSupply();
    }
}

public static void main(String[] args) {
    TestAdapter adapter = new TestAdapter();
    test(adapter);
}

对象适配器模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class TestAdapter implements Target{   //现在不再继承TestSupplier,仅实现Target

    TestSupplier supplier;
    
    public TestAdapter(TestSupplier supplier){
        this.supplier = supplier;
    }
    
    @Override
    public String supply() {
        return supplier.doSupply();
    }
}

public static void main(String[] args) {
    TestSupplier supplier = new TestSupplier();
    TestAdapter adapter = new TestAdapter(supplier);
    test(adapter);
}

桥接模式

通过桥接模式,使得抽象和实现可以沿着各自的维度来进行变化,不再是固定的绑定关系。

1
2
3
public interface Size {   //分大杯小杯中杯
    String getSize();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public abstract class AbstractTea {
    
    protected Size size;   //尺寸作为桥接属性存放在类中
    
    protected AbstractTea(Size size){   //在构造时需要知道尺寸属性
        this.size = size;
    }
    
    public abstract String getType();   //具体类型依然是由子类决定
}
1
2
3
4
5
6
7
8
9
public abstract class RefinedAbstractTea extends AbstractTea{
    protected RefinedAbstractTea(Size size) {
        super(size);  //调用父类的构造器,传递尺寸参数
    }
    
    public String getSize(){   //添加尺寸维度获取方式
        return size.getSize();
    }
}
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 RefinedAbstractTea{   //创建一个啵啵芋圆奶茶的子类
    protected 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());
}

组合模式

组合模式实际上就是将多个组件进行组合,让用户可以对它们进行一致性处理。比如我们的文件夹,一个文件夹中可以有很多个子文件夹或是文件

1
2
3
4
5
6
7
8
9
/**
 * 首先创建一个组件抽象,组件可以包含组件,组件有自己的业务方法
 */
public abstract class Component {
    public abstract void addComponent(Component component);    //添加子组件
    public abstract void removeComponent(Component component);   //删除子组件
    public abstract Component getChild(int index);   //获取子组件
    public abstract void test();   //执行对应的业务方法,比如修改文件名称
}
 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 class Directory extends Component{   //目录可以包含多个文件或目录

    List<Component> child = new ArrayList<>();   //这里我们使用List来存放目录中的子组件

    @Override
    public void addComponent(Component component) {
        child.add(component);
    }

    @Override
    public void removeComponent(Component component) {
        child.remove(component);
    }

    @Override
    public Component getChild(int index) {
        return child.get(index);
    }

    @Override
    public void test() {
        child.forEach(Component::test);   //将继续调用所有子组件的test方法执行业务
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class File extends Component{   //文件就相当于是树叶,无法再继续添加子组件了

    @Override
    public void addComponent(Component component) {
        throw new UnsupportedOperationException();   //不支持这些操作了
    }

    @Override
    public void removeComponent(Component component) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Component getChild(int index) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void test() {
        System.out.println("文件名称修改成功!"+this);   //具体的名称修改操作
    }
}
1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
    Directory outer = new Directory();   //新建一个外层目录
    Directory inner = new Directory();   //新建一个内层目录
    outer.addComponent(inner);
    outer.addComponent(new File());   //在内层目录和外层目录都添加点文件,注意别导错包了
    inner.addComponent(new File());
    inner.addComponent(new File());
    outer.test();    //开始执行文件名称修改操作
}

装饰模式

装饰模式的核心就在于不改变一个对象本身功能的基础上,给对象添加额外的行为,并且它是通过组合的形式完成的,而不是传统的继承关系。

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
7
8
9
public static void main(String[] args) {
    Base base = new BaseImpl();
    Decorator decorator = new DecoratorImpl(base);  //将Base实现装饰一下
    Decorator outer = new DecoratorImpl(decorator);  //装饰者还可以嵌套

    decorator.test();

    outer.test();
}

代理模式

静态代理

 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 abstract class Subject {
    public abstract void test();
}

public class SubjectImpl extends Subject{  //此类无法直接使用,需要我们进行代理

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

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("代理后绕方法");
    }
}

装饰器模式来说,装饰者和被装饰者都实现同一个接口/抽象类。对代理模式来说,代理类和被代理的类都实现同一个接口/抽象类,在结构上确实没有啥区别。但是他们的作用不同,装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能,增强后你还是你,只不过被强化了而已;代理模式强调要让别人帮你去做事情,以及添加一些本身与你业务没有太多关系的事情(记录日志、设置缓存等)重点在于让别人帮你做。

装饰模式和代理模式的不同之处在于思想。

动态代理

接口动态代理(JDK)

1
2
3
public interface Subject {  //JDK提供的动态代理只支持接口
    void test();
}
1
2
3
4
5
6
7
public class SubjectImpl implements Subject{

    @Override
    public void test() {
        System.out.println("我是测试方法!");
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class TestProxy implements InvocationHandler {    //代理类,需要实现InvocationHandler接口

    private final Object object;   //这里需要保存一下被代理的对象,下面需要用到

    public TestProxy(Object object) {
        this.object = object;
    }

    @Override   //此方法就是调用代理对象的对应方法时会进入,这里我们就需要编写如何进行代理了
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     		//method就是调用的代理对象的哪一个方法,args是实参数组
        System.out.println("代理的对象:"+proxy.getClass());   //proxy就是生成的代理对象了,我们看看是什么类型的
        Object res = method.invoke(object, args);   //在代理中调用被代理对象原本的方法,因为你是代理,还是得执行一下别人的业务,当然也可以不执行,但是这样就失去代理的意义了,注意要用上面的object
        System.out.println("方法调用完成,返回值为:"+res);   //看看返回值是什么
        return res;   //返回返回值
    }
}
1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
    SubjectImpl subject = new SubjectImpl();   //被代理的大冤种
    InvocationHandler handler = new TestProxy(subject);
    Subject proxy = (Subject) Proxy.newProxyInstance(
            subject.getClass().getClassLoader(),    //需要传入被代理的类的类加载器
            subject.getClass().getInterfaces(),    //需要传入被代理的类的接口列表
            handler);    //最后传入我们实现的代理处理逻辑实现类
    proxy.test();    //比如现在我们调用代理类的test方法,那么就会进入到我们上面TestProxy中invoke方法,走我们的代理逻辑
}

抽象类动态代理(cglib)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class TestProxy implements MethodInterceptor {  //首先还是编写我们的代理逻辑

    private final Object target;   //这些和之前JDK动态代理写法是一样的

    public TestProxy(Object target) {
        this.target = target;
    }

    @Override   //我们也是需要在这里去编写我们的代理逻辑
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("现在是由CGLib进行代理操作!"+o.getClass());
        return method.invoke(target, objects);   //也是直接调用代理对象的方法即可
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public static void main(String[] args) {
    SubjectImpl subject = new SubjectImpl();

    Enhancer enhancer = new Enhancer();   //增强器,一会就需要依靠增强器来为我们生成动态代理对象
    enhancer.setSuperclass(SubjectImpl.class);    //直接选择我们需要代理的类型,直接不需要接口或是抽象类,SuperClass作为代理类的父类存在,这样我们就可以按照指定类型的方式去操作代理类了
    enhancer.setCallback(new TestProxy(subject));  //设定我们刚刚编写好的代理逻辑

    SubjectImpl proxy = (SubjectImpl) enhancer.create();   //直接创建代理类

    proxy.test();   //调用代理类的test方法
}

外观模式

观模式充分体现了迪米特法则。可能我们的整个项目有很多个子系统,但是我们可以在这些子系统的上面加一个门面(Facade)当我们外部需要与各个子系统交互时,无需再去直接使用各个子系统,而是与门面进行交互,再由门面与后面的各个子系统操作

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 marry(){   //红白喜事一条龙服务
        a.test1();
        b.test2();
        c.test3();
    }
}
1
2
3
4
public static void main(String[] args) {
    Facade facade = new Facade();
    facade.marry();
}

享元模式

将那些重复出现的内容作为共享部分取出,这样当我们拥有大量对象时,我们把其中共同的部分抽取出来,由于提取的部分是多个对象共享只有一份,那么就可以减轻内存的压力

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();    //该干嘛干嘛
    }
}

设计模式(行为型)

解释器模式

这种模式的使用场景较少,很少使用的一种设计模式

核心思想是:将一个复杂的语言(或表达式)解析成一个抽象语法树,然后再根据需要对该树进行遍历,并根据节点执行相应的操作。通过这种方式,可以将与语言相关的操作与语言本身分离开来,从而简化程序的设计和实现。

模板方法模式

在我们的程序中可能某些操作是固定的,我们就可以直接在类中对应方法进行编写,但是可能某些操作需要视情况而定,由不同的子类实现来决定,就需要用到模板方法模式让这些操作由子类来延迟实现了

 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();
}

责任链模式

 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
10
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()方法是所有的类都有吗?默认返回的是什么呢?");
        System.out.println("以上问题会的,可以依次打在评论区");
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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公平锁和非公平锁的区别是什么?");
        System.out.println("以上问题会的,可以依次打在评论区");
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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是做什么的?运行机制是什么?");
        System.out.println("以上问题会的,可以依次打在评论区");
    }
}
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();   //开始面试
}

命令模式

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));   //直接通过遥控器来发送空调开启命令
}

迭代器模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class ArrayCollection<T> {    //首先设计一个简单的数组集合,一会我们就迭代此集合内的元素

    private final T[] array;   //底层使用一个数组来存放数据

    private ArrayCollection(T[] array){   //private掉,自己用
        this.array = array;
    }

    public static <T> ArrayCollection<T> of(T[] array){   //开个静态方法直接吧数组转换成ArrayCollection,其实和直接new一样,但是这样写好看一点
        return new ArrayCollection<>(array);
    }
}
1
2
3
4
public static void main(String[] args) {
    String[] arr = new String[]{"AAA", "BBB", "CCC", "DDD"};
    ArrayCollection<String> collection = ArrayCollection.of(arr);
}
 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
public class ArrayCollection<T> implements Iterable<T>{   //实现Iterable接口表示此类是支持迭代的

    private final T[] array;   //底层使用一个数组来存放数据

    private ArrayCollection(T[] array){   //private掉,自己用
        this.array = array;
    }

    public static <T> ArrayCollection<T> of(T[] array){   //开个静态方法直接吧数组转换成ArrayCollection,其实和直接new一样,但是这样写好看一点
        return new ArrayCollection<>(array);
    }
    
    @Override
    public Iterator<T> iterator() {    //需要实现iterator方法,此方法会返回一个迭代器,用于迭代我们集合中的元素
        return new ArrayIterator();
    }

    public class ArrayIterator implements Iterator<T> {   //这里实现一个,注意别用静态,需要使用对象中存放的数组
        private int cur = 0;   //这里我们通过一个指针表示当前的迭代位置

        @Override
        public boolean hasNext() {     //判断是否还有下一个元素
            return cur < array.length;   //如果指针大于或等于数组最大长度,就不能再继续了
        }

        @Override
        public T next() {   //返回当前指针位置的元素并向后移动一位
            return array[cur++];   //正常返回对应位置的元素,并将指针自增
        }
    }
}
1
2
3
4
5
6
7
public static void main(String[] args) {
    String[] arr = new String[]{"AAA", "BBB", "CCC", "DDD"};
    ArrayCollection<String> collection = ArrayCollection.of(arr);
    for (String s : collection) {    //可以直接使用foreach语法糖,当然最后还是会变成迭代器调用
        System.out.println(s);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public static void main(String[] args) {
    String[] arr = new String[]{"AAA", "BBB", "CCC", "DDD"};
    ArrayCollection<String> collection = ArrayCollection.of(arr);
    Iterator var3 = collection.iterator();   //首先获取迭代器,实际上就是调用我们实现的iterator方法

    while(var3.hasNext()) {
        String s = (String)var3.next();   //直接使用next()方法不断向下获取
        System.out.println(s);
    }
}

中介者模式

 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);   //成功找到对应房源
}

备忘录模式

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
15
16
17
18
19
20
21
22
23
24
25
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+"%)";
    }
    
    public State save(){
        return new State(currentWork, percentage);
    }

    public void restore(State state){
        this.currentWork = state.currentWork;
        this.percentage = state.percentage;
    }
    
//    set方法,get方法
}
 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("打电动");   //刚打开B站播放视频,学一半开始摆烂了
    System.out.println(student);

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

观察者模式

自定义实现

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
public static void main(String[] args) {
    Subject subject = new Subject();
    subject.observe(() -> System.out.println("我是一号观察者!"));
    subject.observe(() -> System.out.println("我是二号观察者!"));
    subject.modify();
}

JDK实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import java.util.Observable;    //java.util包下提供的观察者抽象类

public class Subject extends Observable {   //继承此抽象类表示支持观察者

    public void modify(){
        System.out.println("对对象进行修改!");
        this.setChanged();    //当对对象修改后,需要setChanged来设定为已修改状态
        this.notifyObservers(new Date());   //使用notifyObservers方法来通知所有的观察者
      	//注意只有已修改状态下通知观察者才会有效,并且可以给观察者传递参数,这里传递了一个时间对象
    }
}
1
2
3
4
5
6
public static void main(String[] args) {
    Subject subject = new Subject();
    subject.addObserver((o, arg) -> System.out.println("监听到变化,并得到参数:"+arg));  
  	//注意这里的Observer是java.util包下提供的
    subject.modify();   //进行修改操作
}

状态模式

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

    public class Student {

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

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

        public void study() {
            switch (state) {   //根据不同的状态,学习方法会有不同的结果
                case LAZY:
                    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();
}

策略模式

1
2
3
4
5
6
7
public interface Strategy {   //策略接口,不同的策略实现也不同

    Strategy SINGLE = Arrays::sort;   //单线程排序方案
    Strategy PARALLEL = Arrays::parallelSort;   //并行排序方案

    void sort(int[] array);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Sorter {

    private Strategy strategy;   //策略

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void sort(int[] array){
        strategy.sort(array);
    }
}
1
2
3
4
5
6
public static void main(String[] args) {
    Sorter sorter = new Sorter();
    sorter.setStrategy(Strategy.PARALLEL);    //指定为并行排序方案
    
    sorter.sort(new int[]{9, 2, 4, 5, 1, 0, 3, 7});
}
 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 Boss implements Visitor{    //你的公司老板作为一个访问者
    @Override
    public void visit(Prize prize) {   //你的老板只关心这些能不能为公司带来什么效益,奖本身并不重要
        System.out.println("你的奖项大么,能够为公司带来什么效益么?");
        System.out.println("还不如老老实实加班给我多干干,别去搞这些没用的");
    }
}
1
2
3
4
5
6
7
public class Classmate implements Visitor{   //你的同学也可以作为一个访问者
    @Override
    public void visit(Prize prize) {   //你的同学也关心你得了什么奖,不过是因为你是他的奖学金竞争对手,他其实并不希望你得奖
        System.out.println("你得了"+prize.name+"奖啊,还可以");
        System.out.println("不过这个奖没什么含金量,下次别去了");
    }
}