Java设计模式

面向对象设计原则

1、单一职责原则

Simple Responsibility Pinciple,SRP,细粒度化,高内聚,低耦合。

一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中,将功能划分得更明确。

2、开闭原则

Open Close Principle。

软件实体应当对扩展开放,对修改关闭。一个软件实体,比如类、模块和函数应该对扩展开放,对修改关闭。其中,对扩展开放是针对提供方来说的,对修改关闭是针对调用方来说的。

比如我们的程序员分为 Java程序员、C#程序员、C++程序员、PHP程序员、前端程序员等,而他们要做的都是去打代码,而具体如何打代码是根据不同语言的程序员来决定的,我们可以将程序员打代码这一个行为抽象成一个统一的接口或是抽象类,这样我们就满足了开闭原则的第一个要求:对扩展开放,不同的程序员可以自由地决定他们该如何进行编程。

而具体哪个程序员使用什么语言怎么编程,是自己在负责,不需要其他程序员干涉,所以满足第二个要求:对修改关闭。

public abstract class Coder {
  // 扩展开放
  public abstract void coding();
​
  class JavaCoder extends Coder{
    // 修改关闭,此类扩展只有一种实现
    @Override
    public void coding() {
      System.out.println("Java太卷了!");
    }
  }
}

3、里氏替换原则

Liskov Substitution Principle,是对子类型的特别定义。

所有引用基类的地方必须能透明地使用其子类的对象。简单的说就是,子类可以扩展父类的功能,但不能改变父类原有的功能:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

  • 子类可以增加自己特有的方法。

  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。

  • 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或与父类一样。

4、依赖倒转原则

Dependence Inversion Principle,最明显的就是我们的Spring框架了。

高层模块不应依赖于底层模块,它们都应该依赖抽象。抽象不应依赖于细节,细节应该依赖于抽象。

在以前,我们没有使用 Spirng 的时候,我们创建 Mapper,Service 中 new Mapper,Controller 中 new Service。然后突然业务改变了,那么 Service 要换了,换成 Service2,那么所有引用 Service 的 Controller 都要进行更改,去重新 new。

即模块之间具有强关联,一个模块指定依赖另一个模块,一旦项目庞大,修改很麻烦。而使用 Spring 之后,我们通过注入获得 Bean,然后进行使用,可以随时替换,Spring 会自动注入。

5、接口隔离原则

Interface Segregation Principle,ISP,实际上是对接口的细化。

客户端不应依赖那些它不需要的接口。即定义接口的时候要注意控制接口的粒度,接口中的方法不可以臃肿,进行接口划分,使得更契合。

比如电器设备接口,划分成智能电器设备,普通电器设备。

6、合成复用原则

Composite Reuse Principle,核心就是委派。

优先使用对象组合,而不是通过继承来达到复用的目的。即在一个新的对象里面使用一些已有的对象,新的对象通过向这些对象的委派达到复用已有功能的目的。

比如:A 有一个复用度很高的方法,如果 B想用,可以让 B extends A,然后 B就可以使用 A 的方法了,但是这样耦合度太高,并且暴露了 A,不安全;应该是通过获得 A 的对象,然后去使用。

7、迪米特法则

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

每一个软件单位对其他单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。简单来说就是,一个类/模块对其他的类/模块有越少的交互越好。即耦合度越低越好。

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

就像你吃完饭,是你去扫码然后付款,而不是把手机给服务员,告诉其支付信息,然后服务员来决定付款。

23种设计模式

创建型

单例模式

简介

单例模式的优点

节省内存,节省计算,保证结果的正确,方便管理

使用场景
  • 无状态工具类 日志工具、字符串工具、

  • 全局信息类 全局计数、环境变量

饿汉式

优缺点

优点:

  • 线程安全,因为 JVM 会保证 static 只会加载一次 缺点:

  • 可能会浪费内存,因为一上来就创建了对象,假如对象里面有很多东西,但是你暂时用不上此对象,那么就产生了占了茅坑不拉屎的现象

普通饿汉式
public class HungryMan {
  private static final HungryMan HUNGRYMAN = new HungryMan();
  private HungryMan() {}
  public static HungryMan getInstance() {
    return HUNGRYMAN;
  }
  /* 创建很多数组啊什么的,总之就是在对象里面占内存的东西,那么饿汉式就会占内存 */
}
静态代码块饿汉式
private static LazyMan lazyMan;
static {
  lazyMan = new LazyMan();
}
private LazyMan() {}
​
public static LazyMan getInstance() {
  return lazyMan;
}

懒汉式

懒汉式一定是不安全的,因为反序列化、反射机制可以破坏普通类,只要知道名字,就能破坏,只要反射时不调用 getInstance(),并且知道了这个标志位的名字,直接去调用构造器创建就可以了,在构造器 new 之前将标志位重置就好了。

优点:

  • 按需加载,不会浪费内存 缺点:

  • 会被反射破坏

  • 多线程下不安全

普通懒汉式
private static LazyMan lazyMan;
private LazyMan() {}
​
public static LazyMan getInstance() {
  if(lazyMan == null){
    lazyMan = new LazyMan();
  }
  return lazyMan;
}
synchronized锁方法懒汉式

但是效率太低了,每次想获取实例的时候调用 getInstance()方法,不管是不是第一次 new对象都要进行同步。

private static LazyMan lazyMan;
private LazyMan() {}
​
public static synchronized LazyMan getInstance() {
  if(lazyMan == null){
    lazyMan = new LazyMan();
  }
  return lazyMan;
}
synchronized锁代码懒汉式

相比于锁方法来说,效率提高了不少,缩小了同步范围,但是 if(lazyMan == null) 这句话不是线程同步的,多线程下可能不安全。

private static LazyMan lazyMan;
private LazyMan() {}
​
public static LazyMan getInstance() {
  if(lazyMan == null){
    synchronized(LazyMan.class){
      lazyMan = new LazyMan(); 
    }
  }
  return lazyMan;
}
DCL懒汉式(懒汉式最优)

double check lock。

public class DCLLazyMan {
  private volatile static DCLLazyMan dcllazyMan;
  private DCLLazyMan() {}
  public static DCLLazyMan getInstance() {
    if (dcllazyMan == null) {
      synchronized (DCLLazyMan.class) {
        if (dcllazyMan == null) {
          dcllazyMan = new DCLLazyMan();
        }
      }
    }
    return dcllazyMan;
  }
}
静态内部类懒汉式

这种方式避免了线程不安全,并且是懒加载,使用了类装载的机制,保证初始化实例的时候只有一个线程。

public class Singleton {
    private Singleton() {}
    public static class SingletonInstance {
        private static final Singleton singleton = new Singleton();
    }
    public Singleton getInstance() {
        return SingletonInstance.singleton;
    }
}

使用DCL懒汉式的原因

new对象的过程细分

lazyMan = new LazyMan(); 这个语句不是原子性操作,这句话会分为三步:

  1. 分配内存空间,

  2. 执行构造方法,初始化对象,

  3. 把对象指向内存空间 而 synchronized 只能保证原子性和可见性,并不能保证有序性,那么就有可能发生这种情况:

  4. 线程 A分配到内存空间后直接跳过 2,直接去把对象指向空间,

  5. 但是此时还没有初始化对象,然后线程B 来了,发现对象不为 null,

  6. 那么线程B 就会去 return了,但是其实此时内存空间的对象并没有完成构造,接下来的调用就会出错。 所以要给 lazyMan 这个对象加一个 volatile关键字,防止指令重排序,要么就 new 成功,要么就没 new。

并且就算线程A 拿到锁之后突然死掉了,也是只在 第二层 if 判断前,线程B 还可以来 new对象,然后线程A 判断的时候就不会重新 new了

为什么要 double check

第一个 if check 使得不是所有的线程都是串行执行,就不需要都去获得锁了,减少了冲突。并且加入多线程下都进入了第一个 if,那就都拿锁然后进入下一个 if。

第二个 if check 用来保证多线程下可能 new 两个实例的情况。然后 volatile 保证了第一个拿到锁资源的线程 new 对象的时候要么就 new 成功了,就么就没 new。

枚举式(单例模式最优)

反射、反序列化机制无法破坏枚举类,因为枚举对象自带单例模式,并且构造器私有。

public enum EnumSingleton {
  INSTANCE;
  public EnumSingleton getInstance() {
      return INSTANCE;
  }
}

1.2、工厂方法模式

1.2.1、简介

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

1.2.2、使用工厂方法模式的原因

如果所有的对象我们都通过 new 的方式去创建,那么当我们的程序中大量使用此对象时,突然有一天这个对象的构造方法或是类名发生了修改,那我们岂不是得挨个去进行修改?

根据迪米特法则,我们应该尽可能地少与其他类进行交互,所以我们可以将那些需要频繁出现的对象创建,封装到一个工厂类中,当我们需要对象时,直接调用工厂类中的工厂方法来为我们生成对象,这样,就算类出现了变动,我们也只需要修改工厂中的代码即可,而不是大面积地进行修改。

同时,可能某些对象的创建并不只是一个 new 就可以搞定,可能还需要更多的步骤来准备构造方法需要的参数,可以一并在工厂中进行完成。

1.2.3、使用工厂前后对比

1.2.3.1、使用前
public abstract class Fruit {   //水果抽象类
    private final String name;
    
    public Fruit(String name){
        this.name = name;
    }
​
    @Override
    public String toString() {
        return name+"@"+hashCode();   //打印一下当前水果名称,还有对象的hashCode
    }
}
​
public class Apple extends Fruit{   //苹果,继承自水果
​
    public Apple() {
        super("苹果");
    }
}
​
public class Orange extends Fruit{  //橘子,也是继承自水果
    public Orange() {
        super("橘子");
    }
}
1.2.3.2、使用后
// 将水果工厂抽象为抽象类,添加泛型T由子类指定水果类型
public abstract class FruitFactory<T extends Fruit> {
    // 不同的水果工厂,通过此方法生产不同的水果
    public abstract T getFruit();
}
​
public class AppleFactory extends FruitFactory<Apple> {
    @Override
    public Apple getFruit() {
        return new Apple();
    }
}

1.3、抽象工厂模式

1.3.1、简介

当工厂很多时,需要创建很多工厂,那么你调用时需要提前了解有哪些工厂,我们可以将多个产品,都放在一个工厂中进行生成,按不同的产品族进行划分。

抽象工厂明显比工厂更好,但是一旦工厂有新的产品,那么每个继承者都需要进行改动,违背了开闭原则。

1.3.2、简单使用

1.3.2.1、实例1
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();
}

1.4、建造者模式

1.4.1、简介

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

实际上我们是通过建造者来不断配置参数或是内容,当我们配置完所有内容后,最后再进行对象的构建。比直接去 new 一个新的对象,建造者模式的重心更加关注在如何完成每一步的配置,同时如果一个类的构造方法参数过多,我们通过建造者模式来创建这个对象,会更加优雅。

1.4.2、简单使用

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

1.5、原型模式

1.5.1、简介

原型模式实际上与对象的拷贝息息相关,原型模式使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。也就是说,原型对象作为模板,通过克隆操作,来产生更多的对象,就像细胞的复制一样。就像深拷贝一样。

在 Java 中,我们可以 implements Cloneable,重写 clone()方法来实现拷贝,但是是浅拷贝,需要在重写方法中加入代码实现深拷贝。

2、结构型

结构型设计模式关注如何将现有的类或对象组织在一起形成更加强大的结构。并且根据我们前面学习的合成复用原则,我们该如何尽可能地使用关联关系来代替继承关系。

2.1、适配器模式

2.1.1、简介

扩展坞就相当于适配器,接口适配,充电器也是适配器,

适配器模式分为类适配器和对象适配器

2.1.2、简单使用

2.1.2.1、类适配器
public class TestSupplier {   // 手机供应商
    public String doSupply(){
        return "iPhone 14 Pro";
    }
}
public interface Target {// test 能联系到的手机供应商
    String supply();
}
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();
    System.out.println("成功得到:"+target.supply());
}
2.1.2.2、对象适配器

类适配器模式需要占用一个继承坑位,如果此时 Target 不是接口而是抽像类的话,由于 Java 不支持多继承,那么就无法实现了。同时根据合成复用原则,我们应该更多的通过合成的方式去实现功能。

public class TestAdapter implements Target{   //现在不再继承TestSupplier,仅实现Target
    TestSupplier supplier;
    
    public TestAdapter(TestSupplier supplier){
        this.supplier = supplier;
    }
    
    @Override
    public String supply() {
        return supplier.doSupply();
    }
}

2.2、桥接模式

2.2.1、简介

不同的方式(接口interface)进行组合可以产生指数式的品种(类class),即此时是类去实现接口,然后产生固定组合的类。

桥接模式下是不去实现,而是把他们作为属性,产生一个抽象类,然后具体的实现通过继承抽象类,进行自定义扩展。然后方式的扩展只需要去扩展实现即可,然后由抽象类置入想要的。

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

比如:奶茶抽象类,其有类型和大小两个实现接口,然后大小接口又有大杯和小杯两个接口实现类,然后创建奶茶的时候,传入类型,选择对应杯型传入对象,然后创建。

2.3、组合模式

2.3.1、简介

组合模式实际上就是将多个组件进行组合,让用户可以对它们进行一致性处理。

2.3.2、简单使用

2.3.2.1、实例1

就比如文件夹

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();   //执行对应的业务方法,比如修改文件名称
}
​
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方法执行业务
    }
}
​
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);   //具体的名称修改操作
    }
}

2.4、装饰模式

2.4.1、简介

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

2.4.2、简单使用

2.4.2.1、实例1
public abstract class Base {   //顶层抽象类,定义了一个test方法执行业务
    public abstract void test();
}
public class BaseImpl extends Base{
    @Override
    public void test() {
        System.out.println("我是业务方法");   //具体的业务方法
    }
}
​
public class Decorator extends Base{   //装饰者需要将装饰目标组合到类中
    protected Base base;
    public Decorator(Base base) {this.base = base;}
​
    @Override
    public void test() {base.test();    //这里暂时还是使用目标的原本方法实现}
}
public class DecoratorImpl extends Decorator{   //装饰实现
    public DecoratorImpl(Base base) {super(base);}
​
    @Override
    public void test() {    //对原本的方法进行装饰,我们可以在前后都去添加额外操作
        System.out.println("装饰方法:我是操作前逻辑");
        super.test();
        System.out.println("装饰方法:我是操作后逻辑");
    }
}
​
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();
}

2.5、代理模式

2.5.1、简介

代理模式和装饰模式很像,初学者很容易搞混。首先请记住,当无法直接访问某个对象或访问某个对象存在困难时,我们就可以通过一个代理对象来间接访问。

代理类需要保证客户端使用的透明性,也就是说操作起来需要与原本的真实对象相同,比如我们访问 Github。只需要输入网址即可访问,而添加代理(使用梯子)之后,也是使用同样的方式去访问 Github,所以操作起来是一样的。包括 Spring框架其实也是依靠代理模式去实现的 AOP记录日志等。

装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能,增强后你还是你,只不过被强化了而已;代理模式强调要让别人帮你去做事情,以及添加一些本身与你业务没有太多关系的事情(记录日志、设置缓存等)重点在于让别人帮你做。

  • 主题类角色:可以是接口,也可以是抽象类,需要让目标对象和代理对象去实现或者继承,目的是为了让目标对象和代理对象具有相同的方法

  • 目标类角色:它的对象就是目标对象,核心业务逻辑的具体执行者

  • 代理类角色:它的对象就是代理对象,内部含有对真实目标对象的引用,负责对目标对象方法的调用,并且在调用前后进行预处理

2.5.1.1、静态代理

静态代理是代理模式的实现方式之一,在程序运行前,手动创建代理类,从而实现对目标类中的方法进行增强。目标类与代理类一一对应,在运行之前写好的代理类,这就是静态代理。但是代理是强关联的,一旦发生修改就需要修改所有相关类。

2.5.1.2、动态代理

动态代理则是在程序运行期间,采用字节码技术,动态生成的一个代理对象,从而实现目标对象中方法的增强。比如 MyBatis 的 getMapper ,

相比静态代理,动态代理可以很方便地对目标类的相关方法进行统一增强处理,动态代理有两种方式:JDK动态代理、CGLIB动态代理。

  • JDK动态代理: 只支持接口,代理的对象必须实现某个接口,

JDK动态代理主要是借助 java.lang.reflect.Proxy 生成代理对象,动态生成 $Proxy数字 这个对象,

使用 JDK动态代理,有一个前提条件,就是目标对象必须实现了一个或多个接口,并且传给 Proxy 的对象必须是父类引用的子类对象,那么产生的代理对象就是用来代理这个接口中的方法。但是如果目标对象没有实现任何接口,那么就不能使用 JDK动态代理,这时候可以使用 CGLIB的动态代理

  • CGLIB动态代理: 如果目标对象没有实现任何接口,那么就不能使用 JDK动态代理,这时候可以使用 CGLIB的动态代理,

CGLIB 通过 ASM动态操作指令,生成了被代理类的子类,如果目标类是 final修饰的,则不能够被代理。

CGLIB 生成的代理对象,其实是目标对象的子类型对象,并重写了目标类中所有的非 private、非 final的方法,从而完成这些方法的代理工作。

2.5.2、简单使用

2.5.2.1、JDK动态代理实例1

被代理的类必须实现一个接口,然后 JDK动态代理会去代理接口的方法。

// 接口
public interface Subject {void test();}
// 要代理的类,接口的实现类
public class SubjectImpl implements Subject {
    @Override
    public void test() {System.out.println("我是接口的实现方法!");}
}
​
public class ProxyTest implements InvocationHandler {
  // 保存一下被代理的对象,下面需要用到
  private final Object object;
  public ProxyTest(Object object) {this.object = object;}
  
  // 调用代理对象的对应方法时会自动调用 invoke方法
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // proxy 就是生成的代理对象,method 就是调用的代理对象的哪一个方法,args 是实参数组
    System.out.println("前置代理,代理的对象:" + proxy.getClass());
    // 在前置代理执行完后执行原本的业务,然后再执行后置代理
    Object result = method.invoke(object, args);
    System.out.println("后置代理,方法代理完成,返回值为:" + result);
    return result;
  }
}
​
public static void main(String[] args) {
  // 被代理的对象
  SubjectImpl subject = new SubjectImpl();
  // 创建代理
  InvocationHandler handler = new ProxyTest(subject);
  Subject proxy = (Subject) Proxy.newProxyInstance(
        // 被代理类的类加载器
        subject.getClass().getClassLoader(),
        // 被代理的类的接口列表
        subject.getClass().getInterfaces(),
        // 代理,InvocationHandler接口的实现类对象
        handler);
  proxy.test();
}
2.5.2.2、CGlib动态代理实例1

由于 CGlib 底层使用 ASM框架,进行字节码编辑。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.1</version>
</dependency>
​
// 要代理的类
public class SubjectImpl {
    public void test() {System.out.println("我是要代理的方法!");}
}
​
public class ProxyTest implements MethodInterceptor {
  // 保存一下被代理的对象,下面需要用到
  private final Object target;
  public ProxyTest(Object object) {this.target = object;}
  @Override
  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    System.out.println("前置代理,生成的代理对象:" + o.getClass());
    Object result = method.invoke(target, objects);
    System.out.println("后置代理,方法代理完成!");
    return result;
  }
}
​
public static void main(String[] args) {
    // 增强器,需要依靠增强器来为我们生成动态代理对象
    Enhancer enhancer = new Enhancer();
    // 直接选择我们需要代理的类型,不需要必须有接口或是抽象类
    enhancer.setSuperclass(SubjectImpl.class);
    // 被代理的对象
    SubjectImpl subject = new SubjectImpl();
    // 设定代理
    enhancer.setCallback(new ProxyTest(subject));
    // 直接创建动态代理对象
    SubjectImpl proxy = (SubjectImpl) enhancer.create();
    proxy.test();
}

2.6、外观模式

2.6.1、简介

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

这样的好处是,首先肯定方便了代码的编写,统一找门面就行,不需要去详细了解子系统,并且,当子系统需要修改时,也只需要修改门面中的逻辑,不需要大面积的变动,遵循迪米特法则尽可能少的交互。

就相当于创建了一个中台,我们不在乎中台是如何与其他子系统交互的,我们只关心如何通过中台的一个方法就可以获取多个子系统的数据。

2.7、享元模式

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

就比如数据库连接工具类,就可以把原本每个类中的数据库连接语句提取出来,进行复用。

3、行为型

行为型设计模式关注系统中对象之间的交互,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责。

3.1、解释器模式

3.1.1、简介

解释器顾名思义,就是对我们的语言进行解释,根据不同的语义来做不同的事情,比如我们在 SE 中学习的双栈计算器,正是根据我们输入的算式,去进行解析,并根据不同的运算符来不断进行计算。

3.2、模板方法模式

3.2.1、简介

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

3.2.2、简单使用

3.2.2.1、实例1
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();   //拿药也是根据具体的处方去拿
}
​
AbstractDiagnosis diagnosis = new ColdDiagnosis();
diagnosis.test();

3.3、责任链模式

3.3.1、简介

比如我们的钉钉审批,实际上就是一条流水线一样的操作,由你发起申请,然后经过多个部门主管审批,最后才能通过,所以你的申请表相当于是在一条责任链上传递。当然除了这样的直线型责任链之外,还有环形、树形等。

3.4、命令模式

3.4.1、简介

比如智能家居,我们只需要在一个终端上进行操作,就可以随便控制家里的电器。

3.4.2、简单使用

3.4.2.1、实例1
public interface Receiver {void action();// 接收者收到命令后的具体行为}
public class AirConditioner implements Receiver{
    @Override
    public void action() {System.out.println("空调已开启,呼呼呼");}
}
​
// 指令抽象,不同的电器有相同指令
public abstract class Command {
  private final Receiver receiver;
  // 指定此命令对应的电器(接受者)
  protected Command(Receiver receiver){this.receiver = receiver;}
​
  public void execute() {receiver.action();}
}
public class OpenCommand extends Command {
  public OpenCommand(Receiver receiver) {super(receiver);}
}
​
public class Controller {   // 遥控器只需要把我们的指令发出去就行了
    public static void call(Command command){command.execute();}
}
​
Receiver receiver = new AirConditioner();
Controller.call(new OpenCommand(receiver));  

3.5、迭代器模式

3.5.1、简介

迭代器我们使用得很多,foreach、iterator,当然,foreach 的底层就是 iterator。

3.5.2、简单使用

3.5.2.1、设计一个迭代器
public class ArrayCollection<T> implements Iterable<T>{
  private final T[] array; // 底层使用一个数组来存放数据
  private ArrayCollection(T[] array){this.array = array; // 私有构造器}
​
  // 开个静态方法把数组转换成 ArrayCollection
  public static <T> ArrayCollection<T> of(T[] array){
      return new ArrayCollection<>(array);
  }
​
  // 此方法会返回一个迭代器,用于迭代我们集合中的元素
  @Override
  public Iterator<T> 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++];}
  }
}

3.6、中介者模式

3.6.1、简介

中介者模式和很多模式很像,但是中介者模式更像服务中心,知晓所有的服务,然后允许任意两服务间的互相调用,而不是说一定是哪一方通过中介去调用哪一方。

3.6.2、简单使用

3.6.2.1、实例1
// 房产中介
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);
  }
}
​
// 用户可以是租客,也可以是房东
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);
  }
}

3.7、备忘录模式

3.7.1、简介

备忘录模式,就为我们的软件提供了一个可回溯的时间节点,可能我们程序在运行过程中某一步出现了错误,这时我们就可以回到之前的某个被保存的节点上重新来过,比如 Ctrl + Z。

其本质就是数据冗余,将对应节点的数据进行备份。

3.8、观察者模式

3.8.1、简介

在 Java 中,一个对象的状态发生改变,可能就会影响到其他的对象,与之相关的对象可能也会联动的进行改变。还有我们之前遇到过的监听器机制,当具体的事件触发时,我们在一开始创建的监听器就可以执行相关的逻辑。

我们可以使用观察者模式来实现这样的功能,当对象发生改变时,观察者能够立即观察到并进行一些联动操作。java.util 包下提供的观察者抽象类 Observable。

3.8.2、简单使用

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

3.9、状态模式

3.9.1、简介

在标准大气压下,水在0度时会结冰变成固态,在0-100度之间时,会呈现液态,100度以上会变成气态,水这种物质在不同的温度下呈现出不同的状态,而我们的对象,可能也会像这样存在很多种状态,甚至在不同的状态下会有不同的行为,我们就可以通过状态模式来实现。

3.10、策略模式

3.10.1、简介

看起来和前面的状态模式很像,但是,它与状态模式的区别在于,这种转换是“主动”的,是由我们去指定,而状态模式,可能是在运行过程中自动切换的。

比如线程池的拒绝策略,是一开始就指定的策略,然后当满足对应条件时启动策略。

3.11、访问者模式

3.11.1、简介

即相同的对象,对于不同的访问者,会产生不同的对应结果。

就比如对于一个比赛奖项来说,老师关心得了什么奖,是几等奖,有什么奖励;同学关心得了什么奖,谁得的奖,下次不许再得了;不同人访问相同的奖杯,会有不同的反应结果。

###

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值