设计模式讲解

设计原则

单一职责原则

> 一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中 >
//一个人类
public class People {/*** 人类会编程*/public void coding(){System.out.println("int mian() {");System.out.println("   printf(\"Holle Wrold!\");");System.out.println("}");System.out.println("啊嘞,怎么运行不起?明明照着老师敲的啊");}/*** 工厂打螺丝也会*/public void work(){System.out.println("真开心,能进到富土康打螺丝");System.out.println("诶,怎么工友都提桶跑路了");}/*** 送外卖也会*/public void ride(){System.out.println("今天终于通过美团最终面,加入了梦寐以求的大厂了");System.out.println("感觉面试挺简单的,就是不知道为啥我同学是现场做一道力扣接雨水,而我是现场问会不会骑车");System.out.println("(迫不及待穿上外卖服装)");}
}

上面的people十八般武艺样样精通,臃肿,如果要修改一个类就需要修改people类

class Coder{/*** 程序员会编程*/public void coding(){System.out.println("int mian() {");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("(迫不及待穿上外卖服装)");}
}

开闭原则

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

对扩展开放,不同的程序员可以自由地决定他们该如何进行编程。而具体哪个程序员使用什么语言怎么编程,是自己在负责,不需要其他程序员干涉,所以满足第二个要求:对修改关闭。

程序员可分为c++,GO,Python程序员

public abstract class Coder {public abstract void coding();class GoCoder extends Coder {@Overridepublic void coding() {System.out.println("GoCoder coding...");}}class JavaCoder extends Coder {@Overridepublic void coding() {System.out.println("JavaCoder coding...");}}class PythonCoder extends Coder {@Overridepublic void coding() {System.out.println("PythonCoder coding...");}}
}

定义一个抽象类,定义编程的行为,但是不进行实现,交由其他的类进行实现拓展

里氏替换原则

> 所有引用基类的地方必须能透明地使用其子类的对象 >
  • ProxyFactory是代理类吗?
  1. 子类可以增加父类的功能,不能改变父类原有的功能
  2. 子类能实现父类的抽象方法,但不能覆盖父类的非抽象方法
  3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。
  4. 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或与父类一样。
public abstract class Coder {public void coding() {System.out.println("我会打代码");}class JavaCoder extends Coder{/*** 子类除了会打代码之外,还会打游戏*/public void game(){System.out.println("艾欧尼亚最强王者已上号");}}
}

继承自Coder,但是并没有对父类方法进行重写,并且还在父类的基础上进行额外扩展

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("摆烂了,啊对对对");  }}
}

对父类的方法进行了重写,父类方法被覆盖,不符合里氏替换原则

public abstract class People {public abstract void coding();   //这个行为还是定义出来,但是不实现class Coder extends People{@Overridepublic void coding() {System.out.println("我会打代码");}}class JavaCoder extends People{public void game(){System.out.println("艾欧尼亚最强王者已上号");}public void coding() {System.out.println("摆烂了,啊对对对");}}
}

依赖倒转原则

> 高层模块不应依赖于底层模块,它们都应该依赖抽象 > > 抽象不应依赖于细节,细节应该依赖于抽象 >
public class Main {public static void main(String[] args) {UserController controller = new UserController();//该怎么用就这么用}static class UserMapper {//CRUD...}static class UserService {UserMapper mapper = new UserMapper();//业务代码....}static class UserController {UserService service = new UserService();//业务代码....}
}
public class Main {public static void main(String[] args) {UserController controller = new UserController();}static class UserMapper {//CRUD...}static class UserServiceNew {   //由于UserServiceNew发生变化,会直接影响到其他高层模块UserMapper mapper = new UserMapper();//业务代码....}static class UserController {   //焯,干嘛改底层啊,我这又得重写了UserService service = new UserService();   //哦豁,原来的不能用了UserServiceNew serviceNew = new UserServiceNew();   //只能修改成新的了//业务代码....}
}

底层模块一边动,上层的都需要更改

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 {@ResourceUserService service;   //直接使用接口,就算你改实现,我也不需要再修改代码了//业务代码....}
}

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

接口依赖

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

定义接口的时候,要注意接口的粒度

interface Device {String getCpu();String getType();String getMemory();
}//电脑就是一种电子设备,那么我们就实现此接口
class Computer implements Device {@Overridepublic String getCpu() {return "i9-12900K";}@Overridepublic String getType() {return "电脑";}@Overridepublic String getMemory() {return "32G DDR5";}
}//电风扇也算是一种电子设备
class Fan implements Device {@Overridepublic String getCpu() {return null;   //就一个破风扇,还需要CPU?}@Overridepublic String getType() {return "风扇";}@Overridepublic String getMemory() {return null;   //风扇也不需要内存吧}
}

风扇不需要CPU和内存,所以接口的粒度需要再分

interface SmartDevice {   //智能设备才有getCpu和getMemoryString getCpu();String getType();String getMemory();
}interface NormalDevice {   //普通设备只有getTypeString getType();
}//电脑就是一种电子设备,那么我们就继承此接口
class Computer implements SmartDevice {@Overridepublic String getCpu() {return "i9-12900K";}@Overridepublic String getType() {return "电脑";}@Overridepublic String getMemory() {return "32G DDR5";}
}//电风扇也算是一种电子设备
class Fan implements NormalDevice {@Overridepublic String getType() {return "风扇";}
}

合成复用原则

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

在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分,新的对象通过向这些对象的委派达到复用已有功能的目的

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

如果不用A链接数据库了,改用C,那就需要修改A全部子类的代码

:::info
继承的缺点

  1. 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
  2. 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
  3. 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。

:::

:::info
合成复用的优点

  1. 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
  2. 对象间的耦合度低。可以在类的成员位置声明抽象。
  3. 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。

:::

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

通过对象之间的组合,我们就大大降低了类之间的耦合度,并且A的实现细节我们也不会直接得到了

迪米特法则

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

一个类/模块对其他的类/模块有越少的交互越好。当一个类发生改动,那么,与其相关的类修改的就越少

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

test方法只需要传递一个字符串就可以了,而不用传递整个对象

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

创建型设计模式

提供了创建对象的机制, 能够提升已有代码的灵活性和可复用性

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

单例 Singleton

> 保证一个类只有一个实例,并提供一个它的全局访问点 >

饿汉式

> 饿汉式:类加载就会导致该单实例对象被创建 > > 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建 >
public class Singleton {//私有构造方法private Singleton() {}//在成员位置创建该类的对象private static Singleton instance = new Singleton();//对外提供静态方法获取该对象public static Singleton getInstance() {return instance;}
}// main里
public static void main(String[] args){Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance == instance2);// 结果为true
}
public class Singleton {private Singleton () {}private static Singleton instancce;static {instancce = new Singleton();}// 对外提供获取对象的方法public static Singleton getInstance() {return instancce;}
}public class Client {public static void main(String[] args){Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance == instance2);}// 结果为true
}

懒汉式

```java public class Singleton { private Singleton(){}
private static Singleton INSTANCE;public static Singleton getInstance(){if (INSTANCE == null) {INSTANCE = new Singleton();   }return  INSTANCE;
}

}

public class Client {
public static void main(String[] args){
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
}
}


![](https://cdn.nlark.com/yuque/0/2024/png/44045791/1729152481252-feb56078-ce95-47ed-917f-8fd64fbcfae0.png)<font style="color:rgb(93, 93, 93);">如果三条线程同时调用</font>`getInstance()`<font style="color:rgb(93, 93, 93);">方法,会同时进行</font>`INSTANCE == null`<font style="color:rgb(93, 93, 93);">的判断,那么此时由于确实还没有进行任何实例化,所以导致三条线程全部判断为</font>`true`<font style="color:rgb(93, 93, 93);">(而饿汉式由于在类加载时就创建完成,不会存在这样的问题)此时问题就来了,我们既然要使用单例模式,那么肯定是只希望对象只被初始化一次的,但是现在由于多线程的机制,导致对象被多次创建。</font>```java
public class Singleton {private Singleton(){}private static Singleton instance;public  static synchronized Singleton getInstance(){if (instance == null) {instance = new Singleton();}return  instance;}
}

用synchronized加一把锁,这样同一时间只能有一个线程进入了,但是在高并发的情况下,会导致线程竞争和频繁的上下文切换,影响效率

public class Singleton {private Singleton(){}private static Singleton instance;public static Singleton getInstance() {// 第一次判断. 如果不为空, 不需要抢占锁,直接返回对象if (instance == null) {synchronized (Singleton.class) {if (instance == null) {// 第二次判断. 多线程有可能同时在锁语句那里排队等候了,所以里面还得再检验一次,及时进来了,也不会再实例化。instance = new Singleton();}}}return instance;}
}

但是,idea会提示出双重检查锁定,防止JVM的指令重排序优化, 可能会出现一个线程看到一个半初始化的对象的情况

public class Singleton {private Singleton(){}private static volatile Singleton instance;public static Singleton getInstance() {// 第一次判断. 如果不为空, 不需要抢占锁,直接返回对象if (instance == null) {synchronized (Singleton.class) {if (instance == null) {// 第二次判断. 多线程有可能同时在锁语句那里排队等候了,所以里面还得再检验一次,及时进来了,也不会再实例化。instance = new Singleton();}}}return instance;}
}
public class Singleton {private Singleton() {}// 声明静态内部类private static class SingletonHolder {// 在内部类中声明并初始化外部类的对象赋值给instanceprivate static final Singleton INSTANCE = new Singleton();}// 提供一个公共的访问方式public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
   静态内部类在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
public enum Singleton {INSTANCE;
}public class Client {public static void main(String[] args){Singleton instance1 = Singleton.INSTANCE;Singleton instance2 = Singleton.INSTANCE;System.out.println(instance1 == instance2);}
}
// 结果为true

枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

存在问题

使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射。

枚举方式不会破坏

序列化与反序列化
```java public class Singleton implements Serializable {

//私有构造方法
private Singleton() {}

private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}

//对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

public class Test {
public static void main(String[] args) throws Exception {
//往文件中写对象
//writeObject2File();
//从文件中读取对象
Singleton s1 = readObjectFromFile();
Singleton s2 = readObjectFromFile();

      //判断两个反序列化后的对象是否是同一个对象System.out.println(s1 == s2);}private static Singleton readObjectFromFile() throws Exception {//创建对象输入流对象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));//第一个读取Singleton对象Singleton instance = (Singleton) ois.readObject();System.out.println(instance);return instance;}public static void writeObject2File() throws Exception {//获取Singleton类的对象Singleton instance = Singleton.getInstance();//创建对象输出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));//将instance对象写出到文件中oos.writeObject(instance);}

}

/* 输出

  • com.designpatterns.sigleton.demo7.Singleton@7506e922
    com.designpatterns.sigleton.demo7.Singleton@4ee285c6
    false
  • */

反序列化后的到的是新对象,所以两次反序列化得到的对象不一样**解决方案**在Singleton中添加readResolve方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象```java
public class Singleton implements Serializable {private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}/*** 下面是为了解决序列化反序列化破解单例模式*/private Object readResolve() {return SingletonHolder.INSTANCE;}
}
public final Object readObject() throws IOException, ClassNotFoundException{...// if nested read, passHandle contains handle of enclosing objectint outerHandle = passHandle;try {Object obj = readObject0(false);//重点查看readObject0方法.....
}private Object readObject0(boolean unshared) throws IOException {
...try {switch (tc) {...case TC_OBJECT:return checkResolve(readOrdinaryObject(unshared));//重点查看readOrdinaryObject方法...}} finally {depth--;bin.setBlockDataMode(oldMode);}    
}private Object readOrdinaryObject(boolean unshared) throws IOException {
...
//isInstantiable 返回true,执行 desc.newInstance(),通过反射创建新的单例类,obj = desc.isInstantiable() ? desc.newInstance() : null; ...// 在Singleton类中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为trueif (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {// 通过反射调用 Singleton 类中的 readResolve 方法,将返回值赋值给rep变量// 这样多次调用ObjectInputStream类中的readObject方法,继而就会调用我们定义的readResolve方法,所以返回的是同一个对象。Object rep = desc.invokeReadResolve(obj);...}return obj;
}
反射
```java // Singleton代码同上

public class Client{
public static void main(String[] args)throws Exception{
// 获取Singleton字节码对象
Class clazz = Singleton.class;

    // 获取无参构造对象Constructor constructor = clazz.getDeclaredConstructor();// 取消访问检查constructor.setAccessible(true);// 创建singleton对象Singleton s1 =(Singleton)constructor.newInstance();Singleton s2 =(Singleton)constructor.newInstance();//System.out.println(s1 == s2);
}

}

// 结果false

**解决方法**```java
public class Singleton {//私有构造方法private Singleton() {/*反射破解单例模式需要添加的代码*/if(instance != null) {throw new RuntimeException();}}private static volatile Singleton instance;//对外提供静态方法获取该对象public static Singleton getInstance() {if(instance != null) {return instance;}synchronized (Singleton.class) {if(instance != null) {return instance;}instance = new Singleton();return instance;}}
}

当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此中操作

仿制Runtime类

```java public class RunTimeDemo { public static void main(String[] args) throws IOException { // 获取Runtime类的对象 Runtime runtime = Runtime.getRuntime();
    //返回 Java 虚拟机中的内存总量。System.out.println(runtime.totalMemory());//返回 Java 虚拟机试图使用的最大内存量。System.out.println(runtime.maxMemory());// 调用runtime的方法exec,传入一个命令Process process = runtime.exec("ifconfig");// 调用process对象的获取输入流的方法InputStream is = process.getInputStream();byte[] arr = new byte[1024 * 1024 * 100];// 读取数据int len = is.read(arr);// 将字节数组转换为字符串输出到控制台System.out.println(new String(arr, 0, len, "GBK"));
}

}

<h2 id="An8m9"> 工厂方法 Factory Method</h2>
> 定义一个用于创建对象的接口,让子类决定实例化哪一个类
>
> Factory Method使一个类的实例化延迟到其子类
>```java
public class SimpleCoffeeFactory {public Coffee createCoffee(String type) {Coffee coffee = null;if("americano".equals(type)) {coffee = new AmericanoCoffee();} else if("latte".equals(type)) {coffee = new LatteCoffee();}return coffee;}
}

工厂(factory)处理创建对象的细节,一旦有了SimpleCoffeeFactory,CoffeeStore类中的orderCoffee()就变成此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了和Coffee实现类的耦合,同时又产生了新的耦合,CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合,工厂对象和商品对象的耦合

后期如果再加新品种的咖啡,我们势必要需求修改SimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。

优点

封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。

缺点

增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”

public class SimpleCoffeeFactory {public static Coffee createCoffee(String type) {Coffee coffee = null;if("americano".equals(type)) {coffee = new AmericanoCoffee();} else if("latte".equals(type)) {coffee = new LatteCoffee();}return coffe;}
}

针对以上缺点,工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象,使一个产品的实例化延迟其工厂的子类

常规实现

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
// 抽象工厂
public interface CoffeeFactory {Coffee createCoffee();
}// 具体工厂
public class LatteCoffeeFactory implements CoffeeFactory {public Coffee createCoffee() {return new LatteCoffee();}
}
public class AmericanCoffeeFactory implements CoffeeFactory {public Coffee createCoffee() {return new AmericanCoffee();}
}// 咖啡店类
public class CoffeeStore {private CoffeeFactory factory;public CoffeeStore(CoffeeFactory factory) {this.factory = factory;}public Coffee orderCoffee(String type) {Coffee coffee = factory.createCoffee();coffee.addMilk();coffee.addsugar();return coffee;}
}// 测试
public static void main(String[] args) {// 创建咖啡店对象CoffeeStore store = new CoffeeStore();// 创建对象CoffeeFactory factory = new LatteCoffeeFactory();store.setFactory(factory);// 点咖啡Coffee coffee = store.orderCoffee();System.out.println(coffee.getName());}

优点:

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则

缺点:

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度

抽象工厂 Abstract Factory

> 提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类 >

工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机、培训机构只培养计算机软件专业的学生等

工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品

结构

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
  • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。

实现

public interface DessertFactory {// 生产咖啡的功能Coffee createCoffee();// 生产甜品Dessert createDessert();
}
//美式甜点工厂
public class AmericanDessertFactory implements DessertFactory {public Coffee createCoffee() {return new AmericanCoffee();}public Dessert createDessert() {return new MatchaMousse();}
}
//意大利风味甜点工厂
public class ItalyDessertFactory implements DessertFactory {public Coffee createCoffee() {return new LatteCoffee();}public Dessert createDessert() {return new Tiramisu();}
}// 测试类
public static void main(String[] args){// 意大利风味甜品工厂对象ItalyDessertFactory factory = new ItalyDessertFactory();// 获取拿铁咖啡和提拉米苏甜品Coffee coffee1 =  factory.createCoffee();Dessert dessert1 = factory.createDessert();System.out.println(coffee1.getName());dessert1.show();// 美式风味甜品工厂对象AmericanDessertFactory factory2 = new AmericanDessertFactory();// 获取拿铁咖啡和提拉米苏甜品Coffee coffee2 =  factory.createCoffee();Dessert dessert2 = factory.createDessert();System.out.println(coffee2.getName());dessert2.show();
}
/* 输出
拿铁咖啡
提拉米苏
拿铁咖啡
提拉米苏
*  */

优缺点

优点

一个产品族的产品只需要一个接口或者类

缺点

当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改,搭配是固定的

简单工厂+配置文件 解耦合

american=com.itheima.pattern.factory.config_factory.AmericanCoffee
latte=com.itheima.pattern.factory.config_factory.LatteCoffee
public class CoffeeFactory {// 加载配置文件, 获取配置文件中定义的类名, 并创建该类的对象进行存储// 定义容器对象存储咖啡对象private static HashMap<String, Coffee> map = new HashMap<String, Coffee>();// 加载配置文件, 只需要加载一次static {// 创建properties对象Properties p = new Properties();// 调用p对象中的load方法, 加载配置文件InputStream inputStream =  CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");try{p.load(inputStream);// 从p集合获取全类名并创建对象Set<Object> keys = p.keySet();for (Object key : keys) {// 配置文件等号后面的内容String className = p.getProperty((String)key);// 通过反射技术创建对象Class clazz = Class.forName(className);Coffee coffee = (Coffee)clazz.newInstance();// 将对象和名称存储到容器中map.put((String)key, coffee);}} catch (IOException e) {e.printStackTrace();}}public static Coffee createCoffee(String name) {return map.get(name);}
}// 测试类
public class Client {public static void main(String[] args){Coffee coffee = CoffeeFactory.createCoffee("american");System.out.println(coffee.getName());Coffee coffee2 = CoffeeFactory.createCoffee("latte");System.out.println(coffee2.getName());}
}// 结果
// 美式咖啡
// 拿铁咖啡

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Collection接口是抽象工厂类,ArrayList是具体的工厂类;

Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。

在具体的工厂类中iterator()方法创建具体的商品类的对象。

另:

1,DateForamt类中的getInstance()方法使用的是工厂模式;

2,Calendar类中的getInstance()方法使用的是工厂模式;

原型 Prototype

> 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象 >

结构

  • 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
  • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址

Java中的Object类中提供了 clone() 方法来实现浅克隆

Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类

浅克隆

```java public class RealizeType implements Cloneable{ public RealizeType(){ System.out.println("具体对象创建成功"); }
@Override
public RealizeType clone() throws CloneNotSupportedException {System.out.println("具体原型复制成功");return (RealizeType)super.clone();
}

}

// 测试
public static void main(String[] args) throws CloneNotSupportedException{
// 创建一个原型类对象
RealizeType r1 = new RealizeType();

// 调用RealizeType类中的clone方法进行对象的克隆
RealizeType clone = r1.clone();System.out.println("原型对象和克隆对象是否是同一个对象" + (r1 == clone));

}

/* 具体对象创建成功
具体原型复制成功
原型对象和克隆对象是否是同一个对象false
*/

同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后在修改奖状上的名字即可```java
public class Citation implements Cloneable {// 三好学生的姓名private String name;public String getName() {return this.name;}public void setName(String name) {this.name = name;}@Overrideprotected Citation clone() throws CloneNotSupportedException {return (Citation)super.clone();}public void show() {System.out.println(name + "同学:在2024学年第一学期中表现优秀,被评为三好学生。");}
}//测试访问类
public static void main(String[] args) throws CloneNotSupportedException{// 创建原型对象Citation citation = new Citation();// 克隆奖状对象Citation clone = citation.clone();citation.setName("哈哈");clone.setName("张三");citation.show();clone.show();
}

使用场景

  • 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
  • 性能和安全要求比较高。

深克隆

public class Citation implements Cloneable {// Student 只有一个属性String nameprivate Student stu;public Student getStu() {return stu;}public void setStu(Student stu) {this.stu = stu;}@Overrideprotected Citation clone() throws CloneNotSupportedException {return (Citation)super.clone();}public void show() {System.out.println(stu.getName() + "同学:在2024学年第一学期中表现优秀,被评为三好学生。");}
}// 测试
public static void main(String[] args) throws CloneNotSupportedException{// 创建原型对象Citation citation = new Citation();// 创建张三对象Student stu = new Student();stu.setName("张三");citation.setStu(stu);// 克隆奖状对象Citation clone1 = citation.clone();clone1.getStu().setName("哈哈");citation.show();clone1.show();
}
/* 哈哈同学:在2024学年第一学期中表现优秀,被评为三好学生。
哈哈同学:在2024学年第一学期中表现优秀,被评为三好学生。*/

stu对象和stu1对象是同一个对象,就会产生将stu1对象中name属性值改为“李四”,两个Citation(奖状)对象中显示的都是李四。这就是浅克隆的效果,对具体原型类(Citation)中的引用类型的属性进行引用的复制。

这种情况需要使用深克隆,而进行深克隆需要使用对象流,进行序列化操作

Citation类和Student类必须实现Serializable接口,否则会抛NotSerializableException异常

public class Client {public static void main(String[] args) throws Exception{// 创建原型对象Citation c1 = new Citation();// 创建张三对象Student stu1 = new Student();stu1.setName("张三");c1.setStu(stu1);// 创建对象输出流对象ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));oos.writeObject(c1);oos.close();// 创建对象输入流对象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));Citation c2 = (Citation) ois.readObject();// ois.close();Student stu2 = c2.getStu();stu2.setName("哈哈");System.out.println(stu1 == stu2);c1.show();c2.show();}
}
/* false
张三同学:在2024学年第一学期中表现优秀,被评为三好学生。
哈哈同学:在2024学年第一学期中表现优秀,被评为三好学生。*/

建造者 Builder

> 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 >
  • 实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
  • 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

结构

  • 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
  • 具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
  • 产品类(Product):要创建的复杂对象。
  • 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

常规用法

@Data
public class Bike {// 车架private String frame;// 车座private String seat;
}public abstract class Builder {// 声明bike类型的变量并赋值protected Bike bike = new Bike();public abstract void buildFrame();public abstract void buildSeat();// 构建自行车public Bike createBike() {return bike;}
}// 具体的构建者,用来构建哈罗单车对象
public class HelloBuilder extends Builder{@Overridepublic void buildFrame() {bike.setFrame("哈罗车架");}@Overridepublic void buildSeat() {bike.setSeat("哈罗车座");}@Overridepublic Bike createBike() {return super.createBike();}
}
public class OfoBuilder extends Builder{@Overridepublic void buildFrame() {bike.setFrame("OFO车架");}@Overridepublic void buildSeat() {bike.setSeat("OFO车座");}@Overridepublic Bike createBike() {return super.createBike();}
}// 指挥者类, 指挥建造顺序
public class Director {// 声明builder类型的变量private Builder builder;public Director(Builder builder) {this.builder = builder;}// 组装自行车的方法public Bike construct() {builder.buildFrame();builder.buildSeat();return builder.createBike();}
}// 测试
public static void main(String[] args){// 创建指挥者对象Director director = new Director(new HelloBuilder());// 指挥者去指挥装配自行车Bike bike =  director.construct();System.out.println(bike.toString());
}// 输出
// Bike(frame=哈罗车架, seat=哈罗车座)

指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合

public abstract class Builder {protected Bike mBike = new Bike();public abstract void buildFrame();public abstract void buildSeat();public abstract Bike createBike();public Bike construct() {this.buildFrame();this.BuildSeat();return this.createBike();}
}

简化了系统结构,但同时也加重了抽象建造者类的职责,也不是太符合单一职责原则,如果construct() 过于复杂,建议还是封装到 Director 中

优缺点

优点

  • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
  • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
  • 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。

缺点

造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制

使用场景

  • 建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
  • 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
  • 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

扩展

当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构

public class Phone {private String cpu;private String screen;private String memory;private String mainboard;public Phone(String cpu, String screen, String memory, String mainboard) {this.cpu = cpu;this.screen = screen;this.memory = memory;this.mainboard = mainboard;}public String getCpu() {return cpu;}public void setCpu(String cpu) {this.cpu = cpu;}public String getScreen() {return screen;}public void setScreen(String screen) {this.screen = screen;}public String getMemory() {return memory;}public void setMemory(String memory) {this.memory = memory;}public String getMainboard() {return mainboard;}public void setMainboard(String mainboard) {this.mainboard = mainboard;}@Overridepublic String toString() {return "Phone{" +"cpu='" + cpu + '\'' +", screen='" + screen + '\'' +", memory='" + memory + '\'' +", mainboard='" + mainboard + '\'' +'}';}
}public class Client {public static void main(String[] args) {//构建Phone对象Phone phone = new Phone("intel","三星屏幕","金士顿","华硕");System.out.println(phone);}
}

上面在客户端代码中构建Phone对象,传递了四个参数,如果参数更多的话代码的可读性及使用的成本就是比较高

@ToString
public class Phone {private String cpu;private String screen;private String memory;private String mainBoard;// 私有构造方法private Phone(Builder builder) {this.cpu = builder.cpu;this.screen = builder.screen;this.memory = builder.memory;this.mainBoard = builder.mainBoard;}public static class Builder {private String cpu;private String screen;private String memory;private String mainBoard;public Builder cpu(String cpu) {this.cpu = cpu;return this;}public Builder screen(String screen) {this.screen = screen;return this;}public Builder memory(String memory) {this.memory = memory;return this;}public Builder mainBoard(String mainBoard) {this.mainBoard = mainBoard;return this;}// 使用构建者创建Phone对象public Phone build() {return new Phone(this);}}}// 测试
public class Client {public static void main(String[] args){// 创建手机对象, 通过构建者对象获取手机对象Phone phone = new Phone.Builder().cpu("Intel").mainBoard("华硕").screen("三星").memory("金士顿").build();System.out.println(phone);}
}
/* 输出
* Phone(cpu=Intel, screen=三星, memory=金士顿, mainBoard=华硕)*/

重构后的代码在使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。

对比

工厂模式:侧重于对象的构建和分离,建造者:侧重于构建复杂对象的过程

工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。

我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。

建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

结构型模式

代理 Proxy

> 为其他对象提供一种代理以控制对这个对象的访问 >

结构

  • 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能

静态代理

```java //卖票接口 public interface SellTickets { void sell(); }

//火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {

public void sell() {System.out.println("火车站卖票");
}

}

//代售点
public class ProxyPoint implements SellTickets {

private TrainStation station = new TrainStation();public void sell() {System.out.println("代理点收取一些服务费用");station.sell();
}

}

//测试类
public class Client {
public static void main(String[] args) {
ProxyPoint pp = new ProxyPoint();
pp.sell();
}
}


测试类直接访问的是ProxyPoint类对象,ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强(代理点收取一些服务费用)<h3 id="ZWPgk">JDK动态代理</h3>
Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象```java
//卖票接口
public interface SellTickets {void sell();
}//火车站  火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {public void sell() {System.out.println("火车站卖票");}
}//代理工厂,用来创建代理对象
public class ProxyFactory {private TrainStation station = new TrainStation();public SellTickets getProxyObject() {//使用Proxy获取代理对象/*newProxyInstance()方法参数说明:ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口InvocationHandler h : 代理对象的调用处理程序*/SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),station.getClass().getInterfaces(),new InvocationHandler() {/*InvocationHandler中invoke方法参数说明:proxy : 代理对象method : 对应于在代理对象上调用的接口方法的 Method 实例args : 代理对象调用接口方法时传递的实际参数*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理点收取一些服务费用(JDK动态代理方式)");//执行真实对象Object result = method.invoke(station, args);return result;}});return sellTickets;}
}//测试类
public class Client {public static void main(String[] args) {//获取代理对象ProxyFactory factory = new ProxyFactory();SellTickets proxyObject = factory.getProxyObject();proxyObject.sell();}
}

问题

  • ProxyFactory是代理类吗?

ProxyFactory不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类

package com.sun.proxy;import com.itheima.proxy.dynamic.jdk.SellTickets;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements SellTickets {private static Method m1;private static Method m2;private static Method m3;private static Method m0;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}public final boolean equals(Object object) {try {return (Boolean)this.h.invoke(this, m1, new Object[]{object});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String toString() {try {return (String)this.h.invoke(this, m2, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final int hashCode() {try {return (Integer)this.h.invoke(this, m0, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final void sell() {try {this.h.invoke(this, m3, null);return;}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}
- 代理类($Proxy0)实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口。
- 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。
  • 动态代理的执行流程是什么样?

    1. 在测试类中通过代理对象调用sell()方法
    2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
    3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
    4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法
 //程序运行过程中动态生成的代理类
public final class $Proxy0 extends Proxy implements SellTickets {private static Method m3;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);}public final void sell() {this.h.invoke(this, m3, null);}
}//Java提供的动态代理相关类
public class Proxy implements java.io.Serializable {protected InvocationHandler h;protected Proxy(InvocationHandler h) {this.h = h;}
}//代理工厂类
public class ProxyFactory {private TrainStation station = new TrainStation();public SellTickets getProxyObject() {SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),station.getClass().getInterfaces(),new InvocationHandler() {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理点收取一些服务费用(JDK动态代理方式)");Object result = method.invoke(station, args);return result;}});return sellTickets;}
}//测试访问类
public class Client {public static void main(String[] args) {//获取代理对象ProxyFactory factory = new ProxyFactory();SellTickets proxyObject = factory.getProxyObject();proxyObject.sell();}
}

CGLIB动态代理

如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>2.2.2</version>
</dependency>
//火车站
public class TrainStation {public void sell() {System.out.println("火车站卖票");}
}//代理工厂
public class ProxyFactory implements MethodInterceptor {private TrainStation target = new TrainStation();public TrainStation getProxyObject() {//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数Enhancer enhancer =new Enhancer();//设置父类的字节码对象enhancer.setSuperclass(target.getClass());//设置回调函数enhancer.setCallback(this);//创建代理对象TrainStation obj = (TrainStation) enhancer.create();return obj;}/*intercept方法参数说明:o : 代理对象method : 真实对象中的方法的Method实例args : 实际参数methodProxy :代理对象中的方法的method实例*/public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);return result;}
}//测试类
public class Client {public static void main(String[] args) {//创建代理工厂对象ProxyFactory factory = new ProxyFactory();//获取代理对象TrainStation proxyObject = factory.getProxyObject();proxyObject.sell();}
}

和JDK一个是接口一个是继承

对比

+ jdk代理和CGLIB代理

有接口 - JDK

没有接口 - CGLIB

  • 动态代理和静态代理

动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

优缺点

优点

在客户端与目标对象之间起到一个中介作用和保护目标对象的作用

扩展目标对象的佛那个能

解客户端与目标对象耦

缺点

增加了系统复杂度

使用场景

  • 远程(Remote)代理本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能 的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
  • 防火墙(Firewall)代理当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
  • 保护(Protect or Access)代理控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

适配器 Adapter

将一个类的接口转换成客户希望的另外一个接口。

使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

  • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  • 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

类适配器 - 用的少

使用继承方式,耦合度高
//SD卡的接口
public interface SDCard {//读取SD卡方法String readSD();//写入SD卡功能void writeSD(String msg);
}//SD卡实现类
public class SDCardImpl implements SDCard {public String readSD() {String msg = "sd card read a msg :hello word SD";return msg;}public void writeSD(String msg) {System.out.println("sd card write msg : " + msg);}
}//电脑类
public class Computer {public String readSD(SDCard sdCard) {if(sdCard == null) {throw new NullPointerException("sd card null");}return sdCard.readSD();}
}//TF卡接口
public interface TFCard {//读取TF卡方法String readTF();//写入TF卡功能void writeTF(String msg);
}//TF卡实现类
public class TFCardImpl implements TFCard {public String readTF() {String msg ="tf card read msg : hello word tf card";return msg;}public void writeTF(String msg) {System.out.println("tf card write a msg : " + msg);}
}//定义适配器类(SD兼容TF)
public class SDAdapterTF extends TFCardImpl implements SDCard {public String readSD(){System.out.println("adapter read tf card ");return readTF();}public void writeSD(String msg) {System.out.println("adapter write tf card");writeTF(msg);}
}//测试类
public class Client {public static void main(String[] args) {Computer computer = new Computer();SDCard sdCard = new SDCardImpl();System.out.println(computer.readSD(sdCard));System.out.println("------------");SDAdapterTF adapter = new SDAdapterTF();System.out.println(computer.readSD(adapter));}
}

类适配器模式违背了合成复用原则。

类适配器是客户类有一个接口规范的情况下可用,反之不可用。

对象适配器模式

```java // 适配器类 public class SDAdapterTF extends TFCardImpl implements SDCard {
// 声明适配者类
private TFCard tfcard;public SDAdapterTF(TFCard tfcard) {this.tfcard = tfcard;
}@Override
public String readSD() {System.out.println("适配器: 读取tf");return tfcard.readTF();
}@Override
public void writeSD(String msg) {System.out.println("适配器: 写入tf");writeTF(msg);}

}

// 测试类
public class Client {

public static void main(String[] args){// 创建计算机对象Computer c = new Computer();// 读取SD卡数据String s = c.readSD(new SDCardImpl());System.out.println(s);System.out.println("--------------------------------");// 使用该电脑从TFCard读取数据String s2 = c.readSD(new SDAdapterTF(new TFCardImpl()));System.out.println(s2);
}

}

/* 输出

  • SDCard 读取 msg : hello world SD

适配器: 读取tf
TF读取: hello

*/

<h3 id="uaecN">接口适配器模式</h3>
当不希望实现一个接口中所有的方法时,可以创建一个抽象类Adapter ,实现所有方法。而此时我们只需要继承该抽象类即可。**应用场景**+ 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
+ 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。**JDK源码解析**Reader(字符流)、InputStream(字节流)的适配使用的是InputStreamReader。InputStreamReader继承自java.io包中的Reader,对他中的抽象的未实现的方法给出实现。如:```java
public int read() throws IOException {return sd.read();
}public int read(char cbuf[], int offset, int length) throws IOException {return sd.read(cbuf, offset, length);
}

如上代码中的sd(StreamDecoder类对象),在Sun的JDK实现中,实际的方法实现是对sun.nio.cs.StreamDecoder类的同名方法的调用封装。类结构图如下:

继承了Reader类,重写他的read方法,再聚合StreamDecoder类,在read方法中调用StreamDecoder类的read方法

  • InputStreamReader是对同样实现了Reader的StreamDecoder的封装。

  • StreamDecoder不是Java SE API中的内容,是Sun JDK给出的自身实现。但我们知道他们对构造方法中的字节流类(InputStream)进行封装,并通过该类进行了字节流和字符流之间的解码转换。

    从表层来看,InputStreamReader做了InputStream字节流类到Reader字符流之间的转换。而从如上Sun JDK中的实现类关系结构中可以看出,是StreamDecoder的设计实现在实际上采用了适配器模式。

装饰者 Decorator

> 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰者模式相比生成子类更为灵活 >

结构

  • 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
  • 抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  • 具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

实现

//快餐接口
@Data
@NoArgsConstructor
@AllArgsConstructor
public abstract class FastFood {private float price;private String desc;public abstract float cost();  //获取价格
}//炒饭
public class FriedRice extends FastFood {public FriedRice() {super(10, "炒饭");}public float cost() {return getPrice();}
}//炒面
public class FriedNoodles extends FastFood {public FriedNoodles() {super(12, "炒面");}public float cost() {return getPrice();}
}//配料类
public abstract class Garnish extends FastFood {private FastFood fastFood;public FastFood getFastFood() {return fastFood;}public void setFastFood(FastFood fastFood) {this.fastFood = fastFood;}public Garnish(FastFood fastFood, float price, String desc) {super(price,desc);this.fastFood = fastFood;}
}//鸡蛋配料
public class Egg extends Garnish {public Egg(FastFood fastFood) {super(fastFood,1,"鸡蛋");}public float cost() {return getPrice() + getFastFood().getPrice();}@Overridepublic String getDesc() {return super.getDesc() + getFastFood().getDesc();}
}//培根配料
public class Bacon extends Garnish {public Bacon(FastFood fastFood) {super(fastFood,2,"培根");}@Overridepublic float cost() {return getPrice() + getFastFood().getPrice();}@Overridepublic String getDesc() {return super.getDesc() + getFastFood().getDesc();}
}//测试类
public class Client {public static void main(String[] args) {//点一份炒饭FastFood food = new FriedRice();//花费的价格System.out.println(food.getDesc() + " " + food.cost() + "元");System.out.println("========");//点一份加鸡蛋的炒饭FastFood food1 = new FriedRice();food1 = new Egg(food1);//花费的价格System.out.println(food1.getDesc() + " " + food1.cost() + "元");System.out.println("========");//点一份加培根的炒面FastFood food2 = new FriedNoodles();food2 = new Bacon(food2);//花费的价格System.out.println(food2.getDesc() + " " + food2.cost() + "元");}
}

优点


  • 可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
  • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

使用场景

  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:
    • 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
    • 第二类是因为类定义不能继承(如final类)
  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。

JDK源码解析

IO流中的包装类使用到了装饰者模式。BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。

我们以BufferedWriter举例来说明,先看看如何使用BufferedWriter

public class Demo {public static void main(String[] args) throws Exception{//创建BufferedWriter对象//创建FileWriter对象FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");BufferedWriter bw = new BufferedWriter(fw);//写数据bw.write("hello Buffered");bw.close();}
}

代理和装饰者的区别

静态代理和装饰者模式的区别:

  • 相同点:
    • 都要实现与目标类相同的业务接口
    • 在两个类中都要声明目标对象
    • 都可以在不修改目标类的前提下增强目标方法
  • 不同点:
    • 目的不同
      装饰者是为了增强目标对象
      静态代理是为了保护和隐藏目标对象
    • 获取目标对象构建的地方不同
      装饰者是由外界传递进来,可以通过构造方法传递
      静态代理是在代理类内部创建,以此来隐藏目标对象

桥接 Bridge

> 将抽象部分与它的实现部分分离,使他们都可以独立地变化 >

结构

  • 抽象化(Abstraction)角色 :定义抽象类,并包含一个对实现化对象的引用。
  • 扩展抽象化(Refined Abstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  • 实现化(Implementor)角色 :定义实现化角色的接口,供扩展抽象化角色调用。
  • 具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。

实现

//视频文件
public interface VideoFile {void decode(String fileName);
}//avi文件
public class AVIFile implements VideoFile {public void decode(String fileName) {System.out.println("avi视频文件:"+ fileName);}
}//rmvb文件
public class REVBBFile implements VideoFile {public void decode(String fileName) {System.out.println("rmvb文件:" + fileName);}
}//操作系统版本
public abstract class OperatingSystemVersion {protected VideoFile videoFile;public OperatingSystemVersion(VideoFile videoFile) {this.videoFile = videoFile;}public abstract void play(String fileName);
}//Windows版本
public class Windows extends OperatingSystem {public Windows(VideoFile videoFile) {super(videoFile);}public void play(String fileName) {videoFile.decode(fileName);}
}//mac版本
public class Mac extends OperatingSystemVersion {public Mac(VideoFile videoFile) {super(videoFile);}public void play(String fileName) {videoFile.decode(fileName);}
}//测试类
public class Client {public static void main(String[] args) {OperatingSystem os = new Windows(new AVIFile());os.play("战狼3");}
}

好处:

  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。如:如果现在还有一种视频文件类型wmv,我们只需要再定义一个类实现VideoFile接口即可,其他类不需要发生变化。
  • 实现细节对客户透明

外观 Facade

> 为子系统中的一个一组接口提供一个一致的界面,Facade模式定义了一个高层接口,使得这次子系统更容易使用 >

结构

  • 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
  • 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。

实现

//灯类
public class Light {public void on() {System.out.println("打开了灯....");}public void off() {System.out.println("关闭了灯....");}
}//电视类
public class TV {public void on() {System.out.println("打开了电视....");}public void off() {System.out.println("关闭了电视....");}
}//空调类
public class AirCondition {public void on() {System.out.println("打开了空调....");}public void off() {System.out.println("关闭了空调....");}
}//智能音箱
public class SmartAppliancesFacade {private Light light;private TV tv;private AirCondition airCondition;public SmartAppliancesFacade() {light = new Light();tv = new TV();airCondition = new AirCondition();}public void say(String message) {if(message.contains("打开")) {on();} else if(message.contains("关闭")) {off();} else {System.out.println("我还听不懂你说的!!!");}}//起床后一键开电器private void on() {System.out.println("起床了");light.on();tv.on();airCondition.on();}//睡觉一键关电器private void off() {System.out.println("睡觉了");light.off();tv.off();airCondition.off();}
}//测试类
public class Client {public static void main(String[] args) {//创建外观对象SmartAppliancesFacade facade = new SmartAppliancesFacade();//客户端直接与外观对象进行交互facade.say("打开家电");facade.say("关闭家电");}
}
/* 输出
* 打开电视
打开电灯
打开空调
==================
关闭电视
关闭电灯
关闭空调
*  */

优缺点

  • 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
  • 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。

缺点:

  • 不符合开闭原则,修改很麻烦

使用场景

  • 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
  • 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
  • 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。

JDK源码解析

使用tomcat作为web容器时,接收浏览器发送过来的请求,tomcat会将请求信息封装成ServletRequest对象,如下图①处对象。但是大家想想ServletRequest是一个接口,它还有一个子接口HttpServletRequest,而我们知道该request对象肯定是一个HttpServletRequest对象的子实现类对象,到底是哪个类的对象呢?可以通过输出request对象,我们就会发现是一个名为RequestFacade的类的对象。

RequestFacade类就使用了外观模式。结构图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为什么在此处使用外观模式呢?

定义 RequestFacade 类,分别实现 ServletRequest ,同时定义私有成员变量 Request ,并且方法的实现调用 Request  的实现。然后,将 RequestFacade上转为 ServletRequest  传给 servlet 的 service 方法,这样即使在 servlet 中被下转为 RequestFacade ,也不能访问私有成员变量对象中的方法。既用了 Request ,又能防止其中方法被不合理的访问。

组合 Composite

> 将对象组合成树形结构以表示“部分-整体”的层次结构 > > Composite使得用户对单个对象和组合对象的使用具有一致性。 >
  • 抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。
  • 树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
  • 叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

// MenuComponent.java
public abstract class MenuComponent {// 菜单组件名字protected String name;// 组件的层级protected int level;//添加菜单public void add(MenuComponent menuComponent){throw new UnsupportedOperationException();}//移除菜单public void remove(MenuComponent menuComponent){throw new UnsupportedOperationException();}//获取指定的子菜单public MenuComponent getChild(int i){throw new UnsupportedOperationException();}//获取菜单名称public String getName(){return name;}public void print(){throw new UnsupportedOperationException();}
}// Menu.java
public class  Menu extends MenuComponent{// 菜单可以有多个子菜单或子菜单项private List<MenuComponent> menuComponentList;public Menu(String name, int level) {this.name = name;this.level = level;menuComponentList = new ArrayList<MenuComponent>();}@Overridepublic void add(MenuComponent menuComponent) {menuComponentList.add(menuComponent);}@Overridepublic void remove(MenuComponent menuComponent) {menuComponentList.remove(menuComponent);}@Overridepublic MenuComponent getChild(int i) {return menuComponentList.get(i);}@Overridepublic void print() {// 打印菜单名称for (int i = 1; i < level; i++) {System.out.print("--");}System.out.println(name);// 打印子菜单名称or子菜单项for (MenuComponent menuComponent : menuComponentList) {menuComponent.print();}}
}// 菜单项, 叶子节点
public class menuItem extends MenuComponent {public menuItem(String name,int level) {this.name = name;this.level = level;}@Overridepublic void print() {for (int i = 1; i < level; i++) {System.out.print("--");}System.out.println(name);}
}// 测试类
public class Client {public static void main(String[] args){MenuComponent menu1 = new Menu("菜单管理", 2);menu1.add(new menuItem("页面访问", 3));menu1.add(new menuItem("展开菜单", 3));menu1.add(new menuItem("编辑菜单", 3));menu1.add(new menuItem("删除菜单", 3));menu1.add(new menuItem("新增菜单", 3));MenuComponent menu2 = new Menu("权限管理", 2);menu2.add(new menuItem("页面访问", 3));menu2.add(new menuItem("提交保存", 3));MenuComponent menu3 = new Menu("角色管理", 2);menu3.add(new menuItem("新增角色", 3));menu3.add(new menuItem("修改角色", 3));menu3.add(new menuItem("删除角色", 3));// 一级菜单MenuComponent rootMenu = new Menu("系统管理", 1);rootMenu.add(menu1);rootMenu.add(menu2);rootMenu.add(menu3);// 打印菜单,包括子项rootMenu.print();}
}
/* 输出* 系统管理
--菜单管理
----页面访问
----展开菜单
----编辑菜单
----删除菜单
----新增菜单
--权限管理
----页面访问
----提交保存
--角色管理
----新增角色
----修改角色
----删除角色*/
  • 透明组合模式 - 标准实现

透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 MenuComponent 声明了 addremovegetChild 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)

  • 安全组合模式

安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu 类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。

优点

  • 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
  • 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
  • 在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
  • 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

使用场景

树形结构出现的地方

享元 Flyweight

> 运用共享技术支持大量细粒度的对象 >

结构

  1. 内部状态,即不会随着环境的改变而改变的可共享部分。
  2. 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。
  • 抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  • 具体享元(Concrete Flyweight)角色 :它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • 非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
  • 享元工厂(Flyweight Factory)角色 :负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

优点

  • 极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
  • 享元模式中的外部状态相对独立,且不影响内部状态

缺点:

为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂


public abstract class AbstractBox {public abstract String getShape();public void display(String color) {System.out.println("方块形状:" + this.getShape() + " 颜色:" + color);}
}public class IBox extends AbstractBox {@Overridepublic String getShape() {return "I";}
}public class LBox extends AbstractBox {@Overridepublic String getShape() {return "L";}
}public class OBox extends AbstractBox {@Overridepublic String getShape() {return "O";}
}public class BoxFactory {private static HashMap<String, AbstractBox> map;private BoxFactory() {map = new HashMap<String, AbstractBox>();AbstractBox iBox = new IBox();AbstractBox lBox = new LBox();AbstractBox oBox = new OBox();map.put("I", iBox);map.put("L", lBox);map.put("O", oBox);}public static final BoxFactory getInstance() {return SingletonHolder.INSTANCE;}private static class SingletonHolder {private static final BoxFactory INSTANCE = new BoxFactory();}public AbstractBox getBox(String key) {return map.get(key);}
}

使用场景:

  • 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  • 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
public class Demo {public static void main(String[] args) {Integer i1 = 127;Integer i2 = 127;System.out.println("i1和i2对象是否是同一个对象?" + (i1 == i2));Integer i3 = 128;Integer i4 = 128;System.out.println("i3和i4对象是否是同一个对象?" + (i3 == i4));}
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Integer 默认先创建并缓存 -128 ~ 127之间数的 Integer对象,当调用 valueOf时如果参数在 -128 ~ 127之间则计算下标并从缓存中返回,否则创建一个新的 Integer对象

行为型模式

描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉

及算法与对象间职责的分配。

  • 类行为模式: 继承在类间分派行为,只有模板方法和解释器是
  • 对象行为模式: 组合或聚合,更灵活

模板方法 Template Method

> 定义一个操作中的算法的骨架,将一些步骤延迟到子类中 > > 使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤 >

银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。

  • 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
    • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:
      • 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。
      • 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
      • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。
  • 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。
public abstract class AbstractClass {public final void cookProcess() {//第一步:倒油this.pourOil();//第二步:热油this.heatOil();//第三步:倒蔬菜this.pourVegetable();//第四步:倒调味料this.pourSauce();//第五步:翻炒this.fry();}public void pourOil() {System.out.println("倒油");}//第二步:热油是一样的,所以直接实现public void heatOil() {System.out.println("热油");}//第三步:倒蔬菜是不一样的(一个下包菜,一个是下菜心)public abstract void pourVegetable();//第四步:倒调味料是不一样public abstract void pourSauce();//第五步:翻炒是一样的,所以直接实现public void fry(){System.out.println("炒啊炒啊炒到熟啊");}
}public class ConcreteClass_BaoCai extends AbstractClass {@Overridepublic void pourVegetable() {System.out.println("下锅的蔬菜是包菜");}@Overridepublic void pourSauce() {System.out.println("下锅的酱料是辣椒");}
}public class ConcreteClass_CaiXin extends AbstractClass {@Overridepublic void pourVegetable() {System.out.println("下锅的蔬菜是菜心");}@Overridepublic void pourSauce() {System.out.println("下锅的酱料是蒜蓉");}
}public class Client {public static void main(String[] args) {//炒手撕包菜ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();baoCai.cookProcess();//炒蒜蓉菜心ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();caiXin.cookProcess();}
}/*
倒油
热油
下锅的蔬菜是包菜
下锅的酱料是辣椒
炒啊炒啊炒到熟啊
倒油
热油
下锅的蔬菜是菜心
下锅的酱料是蒜蓉
炒啊炒啊炒到熟啊
*/

一般模板方法都加上 final 关键词

优点

  • 提高代码复用性将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
  • 实现了反向控制通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。

缺点

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

适用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

JDK中的InputStream类就用到了

策略 Strategy

> 定义一系列的算法,把他们封装起来,并且使他们可以相互替换 > > 使得算法可以独立于使用它的客户而变化 >

结构

  • 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
  • 环境(Context)类:持有一个策略类的引用,连接上下文,最终给客户端调用。
// 定义所有促销活动的共同接口
public interface Strategy {void show();
}//为春节准备的促销活动A
public class StrategyA implements Strategy {public void show() {System.out.println("春节促销");}
}//为中秋准备的促销活动B
public class StrategyB implements Strategy {public void show() {System.out.println("中秋节促销");}
}//为圣诞准备的促销活动C
public class StrategyC implements Strategy {public void show() {System.out.println("圣诞节促销");}
}// 定义环境角色(Context):用于连接上下文,把促销活动推销给客户,这里可以理解为销售员
@Data
public class Salesman {// 聚合private Strategy strategy;public Salesman(Strategy strategy) {this.strategy = strategy;}public void show() {strategy.show();}
}// 测试类
public class Client {public static void main(String[] args){// 春节到了Salesman salesman = new Salesman(new StrategyA());salesman.show();System.out.println("=============");salesman.setStrategy(new StrategyB());salesman.show();System.out.println("=============");salesman.setStrategy(new StrategyC());salesman.show();}
}
/*
春节促销
=============
中秋节促销
=============
圣诞节促销
*/



优点:

  • 策略类之间可以自由切换由于策略类都实现同一个接口,所以使它们之间可以自由切换。
  • 易于扩展增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
  • 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

使用场景

  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
  • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
  • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

JDK中的Comparator

命令 Command

> 将一个请求封装为一个对象,可用不同的请求对客户进行参数化 > > 对请求排队或记录请求日志,支持可撤销的操作 >

结构

  • 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
  • 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
  • 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
public interface Command {void execute();//只需要定义一个统一的执行方法
}public class OrderCommand implements Command {//持有接受者对象private SeniorChef receiver;private Order order;public OrderCommand(SeniorChef receiver, Order order){this.receiver = receiver;this.order = order;}public void execute()  {System.out.println(order.getDiningTable() + "桌的订单:");Set<String> keys = order.getFoodDic().keySet();for (String key : keys) {receiver.makeFood(order.getFoodDic().get(key),key);}try {Thread.sleep(100);//停顿一下 模拟做饭的过程} catch (InterruptedException e) {e.printStackTrace();}System.out.println(order.getDiningTable() + "桌的饭弄好了");}
}public class Order {// 餐桌号码private int diningTable;// 用来存储餐名并记录份数private Map<String, Integer> foodDic = new HashMap<String, Integer>();public int getDiningTable() {return diningTable;}public void setDiningTable(int diningTable) {this.diningTable = diningTable;}public Map<String, Integer> getFoodDic() {return foodDic;}public void setFoodDic(String name, int num) {foodDic.put(name,num);}
}// 资深大厨类 是命令的Receiver
public class SeniorChef {public void makeFood(int num,String foodName) {System.out.println(num + "份" + foodName);}
}public class Waitor {private ArrayList<Command> commands;//可以持有很多的命令对象public Waitor() {commands = new ArrayList();}public void setCommand(Command cmd){commands.add(cmd);}// 发出命令 喊 订单来了,厨师开始执行public void orderUp() {System.out.println("美女服务员:叮咚,大厨,新订单来了.......");for (int i = 0; i < commands.size(); i++) {Command cmd = commands.get(i);if (cmd != null) {cmd.execute();}}}
}public class Client {public static void main(String[] args) {//创建2个orderOrder order1 = new Order();order1.setDiningTable(1);order1.getFoodDic().put("西红柿鸡蛋面",1);order1.getFoodDic().put("小杯可乐",2);Order order2 = new Order();order2.setDiningTable(3);order2.getFoodDic().put("尖椒肉丝盖饭",1);order2.getFoodDic().put("小杯雪碧",1);//创建接收者SeniorChef receiver=new SeniorChef();//将订单和接收者封装成命令对象OrderCommand cmd1 = new OrderCommand(receiver, order1);OrderCommand cmd2 = new OrderCommand(receiver, order2);//创建调用者 waitorWaitor invoker = new Waitor();invoker.setCommand(cmd1);invoker.setCommand(cmd2);//将订单带到柜台 并向厨师喊 订单来了invoker.orderUp();}
}

优点

  • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
  • 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
  • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  • 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

缺点

  • 使用命令模式可能会导致某些系统有过多的具体命令类。
  • 系统结构更加复杂。

使用场景

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 系统需要在不同的时间指定请求、将请求排队和执行请求。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

JDK源码中

Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法

责任链 ChainOfResponsibility

> 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系, > > 将这些对象连成一条链,沿着这条链传递请求,直到有一个对象处理它为止 >
  • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
/*
请假一天以下的假只需要小组长同意即可;
请假1天到3天的假还需要部门经理同意;
请求3天到7天还需要总经理同意才行。
*///处理者抽象类
@Data
@AllArgsConstructor
@NoArgsConstructor
public abstract class Handler {protected final static int NUM_ONE = 1;protected final static int NUM_THREE = 3;protected final static int NUM_SEVEN = 7;//  该领导处理的请假天数区间private int numStart;private int numEnd;// 声明后继者(上级领导)private Handler nextHandler;// 各级领导处理请求条的方法protected abstract void handLeave(LeaveRequest leaveRequest);// 提交请假条public final void submit(LeaveRequest leave) {// 该领导先进行审批this.handLeave(leave);if (this.nextHandler != null && leave.getNum() > this.numEnd) {// 提交给上级this.nextHandler.submit(leave);} else {System.out.println("流程结束");}}}// 请假条
@Data
@AllArgsConstructor
public class LeaveRequest {// 姓名private String name;// 请假天数private int num;// 请假原因private String content;
}public class GeneralManager extends Handler{public GeneralManager() {super(Handler.NUM_ONE, Handler.NUM_THREE, null);}@Overrideprotected void handLeave(LeaveRequest leave) {System.out.println("总经理审批:" + leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent());}
}
public class Manager extends Handler{public Manager() {super(NUM_ONE, NUM_THREE, null);}@Overrideprotected void handLeave(LeaveRequest leaveRequest) {System.out.println("经理审批:" + leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天," + leaveRequest.getContent());System.out.println("经理审批通过");}
}
public class GroupLeader extends Handler{public GroupLeader() {super(0, Handler.NUM_ONE, null);}@Overrideprotected void handLeave(LeaveRequest leaveRequest) {System.out.println("小组长审批:" + leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天," + leaveRequest.getContent());System.out.println("小组长审批通过");}
}// 测试类
public class Client {public static void main(String[] args){// 请假条对象LeaveRequest leave = new LeaveRequest("小明", 9, "事假");// 各级领导对象GroupLeader groupLeader = new GroupLeader();Manager manager = new Manager();GeneralManager generalManager = new GeneralManager();// 设置处理者链groupLeader.setNextHandler(manager);manager.setNextHandler(generalManager);groupLeader.submit(leave);}
}
/*
小明请假9天事假
小组长审批通过
经理审批:小明请假9天,事假
经理审批通过
总经理审批:小明请假9天,事假
流程结束
*/


优点

  • 降低了对象之间的耦合度该模式降低了请求发送者和接收者的耦合度。
  • 增强了系统的可扩展性可以根据需要增加新的请求处理类,满足开闭原则。
  • 增强了给对象指派职责的灵活性当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
  • 责任链简化了对象之间的连接一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  • 责任分担每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

缺点

  • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

JDK中FilterChain用到了

  • 模拟web请求Request以及web响应Response
public interface Request{}public interface Response{}
  • 模拟web过滤器Filter
 public interface Filter {public void doFilter(Request req,Response res,FilterChain c);}
  • 模拟实现具体过滤器
public class FirstFilter implements Filter {@Overridepublic void doFilter(Request request, Response response, FilterChain chain) {System.out.println("过滤器1 前置处理");// 先执行所有request再倒序执行所有responsechain.doFilter(request, response);System.out.println("过滤器1 后置处理");}
}public class SecondFilter  implements Filter {@Overridepublic void doFilter(Request request, Response response, FilterChain chain) {System.out.println("过滤器2 前置处理");// 先执行所有request再倒序执行所有responsechain.doFilter(request, response);System.out.println("过滤器2 后置处理");}
}
  • 模拟实现过滤器链FilterChain
public class FilterChain {private List<Filter> filters = new ArrayList<Filter>();private int index = 0;// 链式调用public FilterChain addFilter(Filter filter) {this.filters.add(filter);return this;}public void doFilter(Request request, Response response) {if (index == filters.size()) {return;}Filter filter = filters.get(index);index++;filter.doFilter(request, response, this);}
}
  • 测试类
public class Client {public static void main(String[] args) {Request  req = null;Response res = null ;FilterChain filterChain = new FilterChain();filterChain.addFilter(new FirstFilter()).addFilter(new SecondFilter());filterChain.doFilter(req,res);}
}

状态模式 State

> 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类 >
  • 环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
  • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
  • 具体状态(Concrete State)角色:实现抽象状态所对应的行为。
/*
通过按钮来控制一个电梯的状态
一个电梯有开门状态,关门状态,停止状态,运行状态
每一种状态改变,都有可能要根据其他状态来更新处理
例如,如果电梯门现在处于运行时状态,就不能进行开门操作
如果电梯门是停止状态,就可以执行开门操作。
*///抽象状态类
public abstract class LiftState {//定义一个环境角色,也就是封装状态的变化引起的功能变化protected Context context;public void setContext(Context context) {this.context = context;}//电梯开门动作public abstract void open();//电梯关门动作public abstract void close();//电梯运行动作public abstract void run();//电梯停止动作public abstract void stop();
}//开启状态
public class OpenningState extends LiftState {//开启当然可以关闭了,我就想测试一下电梯门开关功能@Overridepublic void open() {System.out.println("电梯门开启...");}@Overridepublic void close() {//状态修改super.context.setLiftState(Context.closeingState);//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作super.context.getLiftState().close();}//电梯门不能开着就跑,这里什么也不做@Overridepublic void run() {//do nothing}//开门状态已经是停止的了@Overridepublic void stop() {//do nothing}
}//运行状态
public class RunningState extends LiftState {//运行的时候开电梯门?你疯了!电梯不会给你开的@Overridepublic void open() {//do nothing}//电梯门关闭?这是肯定了@Overridepublic void close() {//虽然可以关门,但这个动作不归我执行//do nothing}//这是在运行状态下要实现的方法@Overridepublic void run() {System.out.println("电梯正在运行...");}//这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了@Overridepublic void stop() {super.context.setLiftState(Context.stoppingState);super.context.stop();}
}//停止状态
public class StoppingState extends LiftState {//停止状态,开门,那是要的!@Overridepublic void open() {//状态修改super.context.setLiftState(Context.openningState);//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作super.context.getLiftState().open();}@Overridepublic void close() {//虽然可以关门,但这个动作不归我执行//状态修改super.context.setLiftState(Context.closeingState);//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作super.context.getLiftState().close();}//停止状态再跑起来,正常的很@Overridepublic void run() {//状态修改super.context.setLiftState(Context.runningState);//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作super.context.getLiftState().run();}//停止状态是怎么发生的呢?当然是停止方法执行了@Overridepublic void stop() {System.out.println("电梯停止了...");}
}//关闭状态
public class ClosingState extends LiftState {@Override//电梯门关闭,这是关闭状态要实现的动作public void close() {System.out.println("电梯门关闭...");}//电梯门关了再打开,逗你玩呢,那这个允许呀@Overridepublic void open() {super.context.setLiftState(Context.openningState);super.context.open();}//电梯门关了就跑,这是再正常不过了@Overridepublic void run() {super.context.setLiftState(Context.runningState);super.context.run();}//电梯门关着,我就不按楼层@Overridepublic void stop() {super.context.setLiftState(Context.stoppingState);super.context.stop();}
}//环境角色
public class Context {//定义出所有的电梯状态public final static OpenningState openningState = new OpenningState();//开门状态,这时候电梯只能关闭public final static ClosingState closeingState = new ClosingState();//关闭状态,这时候电梯可以运行、停止和开门public final static RunningState runningState = new RunningState();//运行状态,这时候电梯只能停止public final static StoppingState stoppingState = new StoppingState();//停止状态,这时候电梯可以开门、运行//定义一个当前电梯状态private LiftState liftState;public LiftState getLiftState() {return this.liftState;}public void setLiftState(LiftState liftState) {//当前环境改变this.liftState = liftState;//把当前的环境通知到各个实现类中this.liftState.setContext(this);}public void open() {this.liftState.open();}public void close() {this.liftState.close();}public void run() {this.liftState.run();}public void stop() {this.liftState.stop();}
}//测试类
public class Client {public static void main(String[] args) {Context context = new Context();context.setLiftState(new ClosingState());context.open();context.close();context.run();context.stop();}
}

优点

  • 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。

缺点

  • 状态模式的使用必然会增加系统类和对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  • 状态模式对"开闭原则"的支持并不太好。

使用场景

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
  • 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。

观察者 Observer

> 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新 >
  • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

定义抽象观察者类,里面定义一个更新的方法

public interface Observer {void update(String message);
}

定义具体观察者类,微信用户是观察者,里面实现了更新的方法

public class WeixinUser implements Observer {// 微信用户名private String name;public WeixinUser(String name) {this.name = name;}@Overridepublic void update(String message) {System.out.println(name + "-" + message);}
}

定义抽象主题类,提供了attach、detach、notify三个方法

public interface Subject {//增加订阅者public void attach(Observer observer);//删除订阅者public void detach(Observer observer);//通知订阅者更新消息public void notify(String message);
}

微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法

public class SubscriptionSubject implements Subject {//储存订阅公众号的微信用户private List<Observer> weixinUserlist = new ArrayList<Observer>();@Overridepublic void attach(Observer observer) {weixinUserlist.add(observer);}@Overridepublic void detach(Observer observer) {weixinUserlist.remove(observer);}@Overridepublic void notify(String message) {for (Observer observer : weixinUserlist) {observer.update(message);}}
}

客户端程序

public class Client {public static void main(String[] args) {SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();//创建微信用户WeixinUser user1=new WeixinUser("孙悟空");WeixinUser user2=new WeixinUser("猪悟能");WeixinUser user3=new WeixinUser("沙悟净");//订阅公众号mSubscriptionSubject.attach(user1);mSubscriptionSubject.attach(user2);mSubscriptionSubject.attach(user3);//公众号更新发出消息给订阅的微信用户mSubscriptionSubject.notify("传智黑马的专栏更新了");}
}

优点

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】

缺点

  • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
  • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

使用场景

  • 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时。

JDK中的Observer

中介者 Mediator

> 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 >
  • 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  • 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  • 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  • 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。

/* 
现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。*/
//抽象中介者
public abstract class Mediator {//申明一个联络方法public abstract void constact(String message,Person person);
}//抽象同事类
public abstract class Person {protected String name;protected Mediator mediator;public Person(String name,Mediator mediator){this.name = name;this.mediator = mediator;}
}//具体同事类 房屋拥有者
public class HouseOwner extends Person {public HouseOwner(String name, Mediator mediator) {super(name, mediator);}//与中介者联系public void constact(String message){mediator.constact(message, this);}//获取信息public void getMessage(String message){System.out.println("房主" + name +"获取到的信息:" + message);}
}//具体同事类 承租人
public class Tenant extends Person {public Tenant(String name, Mediator mediator) {super(name, mediator);}//与中介者联系public void constact(String message){mediator.constact(message, this);}//获取信息public void getMessage(String message){System.out.println("租房者" + name +"获取到的信息:" + message);}
}//中介机构
public class MediatorStructure extends Mediator {//首先中介结构必须知道所有房主和租房者的信息private HouseOwner houseOwner;private Tenant tenant;public HouseOwner getHouseOwner() {return houseOwner;}public void setHouseOwner(HouseOwner houseOwner) {this.houseOwner = houseOwner;}public Tenant getTenant() {return tenant;}public void setTenant(Tenant tenant) {this.tenant = tenant;}public void constact(String message, Person person) {if (person == houseOwner) {          //如果是房主,则租房者获得信息tenant.getMessage(message);} else {       //反正则是房主获得信息houseOwner.getMessage(message);}}
}//测试类
public class Client {public static void main(String[] args) {//一个房主、一个租房者、一个中介机构MediatorStructure mediator = new MediatorStructure();//房主和租房者只需要知道中介机构即可HouseOwner houseOwner = new HouseOwner("张三", mediator);Tenant tenant = new Tenant("李四", mediator);//中介结构要知道房主和租房者mediator.setHouseOwner(houseOwner);mediator.setTenant(tenant);tenant.constact("需要租三室的房子");houseOwner.constact("我这有三室的房子,你需要租吗?");}
}

优点

  • 松散耦合中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可以独立地变化和复用,而不再像以前那样“牵一处而动全身”了。
  • 集中控制交互多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改。
  • 一对多关联转变为一对一的关联没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现。

缺点

当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。

使用场景

  • 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
  • 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。

迭代器 Iterator

> 提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。 >

结构

  • 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
  • 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
  • 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
  • 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

实现

定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现

定义迭代器接口,声明hasNext、next方法

public interface StudentIterator {boolean hasNext();Student next();
}

定义具体的迭代器类,重写所有的抽象方法

public class StudentIteratorImpl implements StudentIterator {private List<Student> list;private int  position = 0;public StudentIteratorImpl(List<Student> list) {this.list = list;}@Overridepublic boolean hasNext() {return position < list.size();}@Overridepublic Student next() {Student currentStudent = list.get(position);position ++;return currentStudent;}
}

定义抽象容器类,包含添加元素,删除元素,获取迭代器对象的方法

public interface StudentAggregate {void addStudent(Student student);void removeStudent(Student student);StudentIterator getStudentIterator();
}

定义具体的容器类,重写所有的方法

public class StudentAggregateImpl implements StudentAggregate {private List<Student> list = new ArrayList<Student>();  // 学生列表@Overridepublic void addStudent(Student student) {this.list.add(student);}@Overridepublic void removeStudent(Student student) {this.list.remove(student);}@Overridepublic StudentIterator getStudentIterator() {return new StudentIteratorImpl(list);}
}

测试类

public class Client {public static void main(String[] args){// 创建聚合对象StudentAggregateImpl studentAggregate = new StudentAggregateImpl();studentAggregate.addStudent(new Student("张三", "001"));studentAggregate.addStudent(new Student("张三", "001"));studentAggregate.addStudent(new Student("张三", "001"));// 遍历聚合对象// 获取迭代器对象StudentIterator iterator = studentAggregate.getIterator();// 遍历while (iterator.hasNext()) {Student student = iterator.next();System.out.println(student);}}
}/*
Student(name=张三, number=001)
Student(name=张三, number=001)
Student(name=张三, number=001)
*/

优点

  • 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
  • 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
  • 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 “开闭原则” 的要求。

缺点

增加了类的个数,这在一定程度上增加了系统的复杂性。

使用场景

  • 当需要为聚合对象提供多种遍历方式时。
  • 当需要为遍历不同的聚合结构提供一个统一的接口时。
  • 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。

JDK中很多集合类都用到了

访问者 Visitor

> 表示一个作用于某对象结构中的各元素的操作 > > 可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 >

结构

  • 抽象访问者(Visitor)角色:定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
  • 具体访问者(ConcreteVisitor)角色:给出对每一个元素类访问时所产生的具体行为。
  • 抽象元素(Element)角色:定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
  • 具体元素(ConcreteElement)角色: 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
  • 对象结构(Object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。

实现

现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。

  • 访问者角色:给宠物喂食的人
  • 具体访问者角色:主人、其他人
  • 抽象元素角色:动物抽象类
  • 具体元素角色:宠物狗、宠物猫
  • 结构对象角色:主人家

创建抽象访问者接口

public interface Person {void feed(Cat cat);void feed(Dog dog);
}

创建不同的具体访问者角色(主人和其他人),都需要实现 Person接口

public class Owner implements Person {@Overridepublic void feed(Cat cat) {System.out.println("主人喂食猫");}@Overridepublic void feed(Dog dog) {System.out.println("主人喂食狗");}
}public class Someone implements Person {@Overridepublic void feed(Cat cat) {System.out.println("其他人喂食猫");}@Overridepublic void feed(Dog dog) {System.out.println("其他人喂食狗");}
}

定义抽象节点 – 宠物

public interface Animal {void accept(Person person);
}

定义实现Animal接口的 具体节点(元素)

public class Dog implements Animal {@Overridepublic void accept(Person person) {person.feed(this);System.out.println("好好吃,汪汪汪!!!");}
}public class Cat implements Animal {@Overridepublic void accept(Person person) {person.feed(this);System.out.println("好好吃,喵喵喵!!!");}
}

定义对象结构,此案例中就是主人的家

public class Home {private List<Animal> nodeList = new ArrayList<Animal>();public void action(Person person) {for (Animal node : nodeList) {node.accept(person);}}//添加操作public void add(Animal animal) {nodeList.add(animal);}
}

测试类

public class Client {public static void main(String[] args) {Home home = new Home();home.add(new Dog());home.add(new Cat());Owner owner = new Owner();home.action(owner);Someone someone = new Someone();home.action(someone);}
}

优点

  • 扩展性好在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  • 复用性好通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
  • 分离无关行为通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

缺点

  • 对象结构变化很困难在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  • 违反了依赖倒置原则访问者模式依赖了具体类,而没有依赖抽象类。

** 使用场景**

  • 对象结构相对稳定,但其操作算法经常变化的程序。
  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。

双分派

备忘录 Memento

> 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。 > > 这样以后就可将该对象恢复到原先保存的状态。 >
  • 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  • 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  • 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

备忘录有两个等效的接口:

  • 窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。
  • 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。

实现

游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。

要实现上述案例,有两种方式:

  • “白箱”备忘录模式
  • “黑箱”备忘录模式

白箱备忘录

备忘录对象对任何对象都提供一个接口,即宽接口
//游戏角色类
@Data
public class GameRole {private int vit;private int atk;private int def;public void initState() {this.vit = 100;this.atk = 100;this.def = 100;}// 战斗public void fight() {this.vit = 0;this.atk = 0;this.def = 0;}// 保存角色状态功能public RoleStateMemento saveState() {return new RoleStateMemento(vit, atk, def);}// 恢复角色初始化状态public void recoverState(RoleStateMemento memento) {this.vit = memento.getVit();this.atk = memento.getAtk();this.def = memento.getDef();}// 展示状态public void stateDisplay() {System.out.println("角色当前的生命:" + vit);System.out.println("角色当前的攻击:" + atk);System.out.println("角色当前的防御:" + def);}}// 备忘录角色类,存储历史状态
@Data
@AllArgsConstructor
public class RoleStateMemento {private int vit;private int atk;private int def;
}@Data
@AllArgsConstructor
@NoArgsConstructor
public class RoleStateCaretaker {// 备忘录类型的变量private RoleStateMemento roleStateMemento;
}/// 测试
public class Client {public static void main(String[] args){System.out.println("==================大战BOSS前==================");GameRole gameRole = new GameRole();gameRole.initState();gameRole.stateDisplay();// 备份RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();roleStateCaretaker.setRoleStateMemento(gameRole.saveState());System.out.println("==================大战BOSS后==================");gameRole.fight();gameRole.stateDisplay();System.out.println("==================恢复==================");gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());gameRole.stateDisplay();}
}
/* 
==================大战BOSS前==================
角色当前的生命:100
角色当前的攻击:100
角色当前的防御:100
==================大战BOSS后==================
角色当前的生命:0
角色当前的攻击:0
角色当前的防御:0
==================恢复==================
角色当前的生命:100
角色当前的攻击:100
角色当前的防御:100
*  */

黑箱备忘录

备忘录角色对发起人提供一个宽接口,对其他对象提供一个窄接口。实现双重接口的方法是把备忘录类设计成发起人类的内部成员类
// 窄接口`Memento`,这是一个标识接口,因此没有定义出任何的方法
public interface Memento {
}// 定义发起人类 `GameRole`,并在内部定义备忘录内部类 `RoleStateMemento`(该内部类设置为私有的)
//游戏角色类
public class GameRole {private int vit; //生命力private int atk; //攻击力private int def; //防御力//初始化状态public void initState() {this.vit = 100;this.atk = 100;this.def = 100;}//战斗public void fight() {this.vit = 0;this.atk = 0;this.def = 0;}//保存角色状态public Memento saveState() {return new RoleStateMemento(vit, atk, def);}//回复角色状态public void recoverState(Memento memento) {RoleStateMemento roleStateMemento = (RoleStateMemento) memento;this.vit = roleStateMemento.getVit();this.atk = roleStateMemento.getAtk();this.def = roleStateMemento.getDef();}public void stateDisplay() {System.out.println("角色生命力:" + vit);System.out.println("角色攻击力:" + atk);System.out.println("角色防御力:" + def);}// lombokprivate class RoleStateMemento implements Memento {private int vit;private int atk;private int def;}
}// 负责人角色类 `RoleStateCaretaker` 能够得到的备忘录对象是以 `Memento` 为接口的,由于这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容
public class RoleStateCaretaker {private Memento memento;public Memento getMemento() {return memento;}public void setMemento(Memento memento) {this.memento = memento;}
}//客户端测试类
public class Client {public static void main(String[] args) {System.out.println("------------大战Boss前------------");//大战Boss前GameRole gameRole = new GameRole();gameRole.initState();gameRole.stateDisplay();//保存进度RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();roleStateCaretaker.setMemento(gameRole.saveState());System.out.println("------------大战Boss后------------");//大战Boss时,损耗严重gameRole.fight();gameRole.stateDisplay();System.out.println("------------恢复之前状态------------");//恢复之前状态gameRole.recoverState(roleStateCaretaker.getMemento());gameRole.stateDisplay();}
}

优点

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
  • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
  • 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

缺点

  • 资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

使用场景

  • 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
  • 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,idea等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。

解释器 Interpreter

> 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。 >

结构

  • 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
  • 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
  • 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
  • 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
  • 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

Spring

使用

```java public interface UserDao { public void add(); }

public class UserDaoImpl implements UserDao {
public void add() {
System.out.println(“userDaoImpl …”);
}
}

```java
public interface UserService {public void add();
}public class UserServiceImpl implements UserService {private UserDao userDao;public void setUserDao(UserDao userDao) {this.userDao = userDao;}public void add() {System.out.println("userServiceImpl ...");userDao.add();} 
}
public class UserController {public static void main(String[] args) {//创建spring容器对象ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");//从IOC容器中获取UserService对象UserService userService = applicationContext.getBean("userService", UserService.class);//调用UserService对象的add方法userService.add();}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><bean id="userService" class="com.itheima.service.impl.UserServiceImpl"><property name="userDao" ref="userDao"></property></bean><bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean></beans>
  • userService对象是从applicationContext容器对象获取到的,也就是userService对象交由spring进行管理。
  • 上面结果可以看到调用了UserDao对象中的add方法,也就是说UserDao子实现类对象也交由spring管理了。
  • UserService中的userDao变量我们并没有进行赋值,但是可以正常使用,说明spring已经将UserDao对象赋值给了userDao变量。

自定义框架

![](https://img-blog.csdnimg.cn/img_convert/063d98e184edb493f3684d3bdc04eb7d.png)

Spring就是面向bean编程

  • spring 将bean对象交由一个叫IOC容器进行管理。
  • bean对象之间的依赖关系在配置文件中体现,并由spring完成。
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"><property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/883356.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

直播系统源码技术搭建部署流程及配置步骤

系统环境要求 PHP版本&#xff1a;5.6、7.3 Mysql版本&#xff1a;5.6&#xff0c;5.7需要关闭严格模式 Nginx&#xff1a;任何版本 Redis&#xff1a;需要给所有PHP版本安装Redis扩展&#xff0c;不需要设置Redis密码 最好使用面板安装&#xff1a;宝塔面板 - 简单好用的…

Android——事件冲突处理

当我们给列表的item设置了点击事件后&#xff0c;又给item中的按钮设置了点击事件&#xff0c;此时item的点击事件会失效。 解决 给item的布局xml中设置以下属性 android:descendantFocusability"blocksDescendants"<LinearLayout xmlns:android"http://sc…

HT7181 16.8V,14A高效升压转换器

1、特征 输入电压范围:2.7V-16V 输出电压范围:最高16.8V 固定开关频率:360kHz 可编程峰值电流:14A 高转换效率: 94% (VIN 7.2V, VOUT9.3V, IOUT1.5A) 90% (VIN 7.2V, VOUT9.3V, IOUT 7A) 93% (VIN 7.2V, VOUT12V, IOUT 1.5A) 90% (VIN 7.2V, VOUT12V, IOUT 5.5A) 90% (VIN …

220V降12V1A恒流点灯WT5112

220V降12V1A恒流点灯WT5112 芯片特点 高精度恒流输出&#xff1a;WT5112 是一款适用于非隔离降压型恒流 LED 驱动芯片。在 220V 降 12V、1A 恒流点灯应用中&#xff0c;它能够提供高精度的恒流输出。其恒流精度通常可以达到 3% - 5% 左右&#xff0c;这对于 LED 灯的稳定发光非…

安卓基础001

前言 也是好久没有更新博客了,最近实习也是需要学习一些知识哈哈哈哈哈哈为了更好的发展嘛,咱们从客户端开始,过程可能有点像写前端,不喜勿喷,希望在学习的过程中也可以给大家带来一些简单得帮助吧....... tips:这里跳过安卓studio安装,大家可自行寻找教程 写的不详细,只是为了…

从“摸黑”到“透视”:AORO A23热成像防爆手机如何改变工业检测?

在工业检测领域&#xff0c;传统的检测手段常因效率低下、精度不足和潜在的安全风险而受到诟病。随着科技的不断进步&#xff0c;一种新兴的检测技术——红外热成像技术&#xff0c;正逐渐在该领域崭露头角。近期&#xff0c;小编对一款集成红外热成像技术的AORO A23防爆手机进…

君正 T31 型号芯片架构模块介绍

文章目录 1. 核心模块2. 存储模块3. 安全模块4. 图像和视频处理5. 输入输出接口6. 其他支持模块 T31 型号 MCU 结构图&#xff1a; T31 集成了高性能 CPU、多功能图像处理单元、丰富的输入输出接口以及多种安全保护机制&#xff0c;适合用于视频监控、智能家居、工业控制等高性…

改进YOLOv8系列:引入低照度图像增强网络Retinexformer | 优化低光照目标检测那题

改进YOLOv8系列:引入低照度图像增强网络Retinexformer | 优化低光照目标检测那题 🚀论文研究概括🚀加入到网络中的理论研究🚀需要修改的代码1 🍀🍀Retinexformer 代码2🍀🍀tasks里引用🚀创建yaml文件🚀测试是否创建成功前言:这篇论文提出了一种用于低光图像…

设计模式06-结构型模式1(适配器/桥接/组合模式/Java)

#1024程序员节&#xff5c;征文# 4.1 适配器模式 结构型模式&#xff08;Structural Pattern&#xff09;的主要目的就是将不同的类和对象组合在一起&#xff0c;形成更大或者更复杂的结构体。结构性模式的分类&#xff1a; ​ 类结构型模式关心类的组合&#xff0c;由多个类…

项目部署 —— 前端、后端

一、 前端 ● 二号标题 在命令框里输入 npm run build 打包成功&#xff1a; 项目就会出现一个 dist 文件夹 将Linux的nginx文件夹中&#xff0c;重命名为 news 二、 后端 ● 通过maven打包后端程序 最终会在项目中生成一个 target 文件夹&#xff0c;将 news-1.0-SNAPSHOT.…

Python爬虫,初识xpath(1)

xpath解析 抓取主页面当中所有壁纸的链接地址 xpath是专门针对xml而创建的表达式语言&#xff0c;可以直接从xml中提取表达式数据&#xff1b;也可以取html取数据&#xff1b;html是xml的子集。 1.按照lxml安装包 在python终端输入 pip install lxml from lxml import etre…

【element-tiptap】如何实现查找替换功能?

这是一个稍微复杂的功能了&#xff0c;因为 element-tiptap 中没有查找替换功能&#xff0c;需要从零开始开发。但是&#xff0c;在万能的github上有一个开源的库&#xff0c;我们可以借用一下 tiptap-search-and-replace 不过这个库是没有UI的&#xff0c;只有一个扩展的方法。…

【Linux】线程池详解及其基本架构与单例模式实现

目录 1.关于线程池的基本理论 1.1.线程池是什么&#xff1f; 1.2.线程池的应用场景&#xff1a; 2.线程池的基本架构 2.1.线程容器 2.2.任务队列 2.3.线程函数&#xff08;HandlerTask&#xff09; 2.4.线程唤醒机制 3.添加单例模式 3.1.单例模式是什么&…

【 thinkphp8 】00006 启动 内、外置服务器

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【 t…

Linux文件类型和根目录结构

Linux文件类型和根目录结构 1.文件类型 字符文件类型说明~普通文件类似于Windows的记事本d目录文件类似于windows文件夹c字符设备文件串行端口设备&#xff0c;顺序读写&#xff0c;键盘b块设备文件可供存储的接口设备&#xff0c;随机读写&#xff0c;硬盘p管道文件用于进程…

jmeter中请求参数:Parameters、Body Data的区别

使用jmeter发送请求&#xff0c;常常要伴随传递参数。有两种请求参数: Parameters, Body Data, 它们的使用方式有很大不同。 先看下get和post请求的区别。 get请求&#xff1a;顾名思义是从服务器获取资源。 post请求&#xff1a;顾名思义是往服务器提交要处理的数据。 直观…

【算法刷题指南】双指针

&#x1f308;个人主页&#xff1a; 南桥几晴秋 &#x1f308;C专栏&#xff1a; 南桥谈C &#x1f308;C语言专栏&#xff1a; C语言学习系列 &#x1f308;Linux学习专栏&#xff1a; 南桥谈Linux &#x1f308;数据结构学习专栏&#xff1a; 数据结构杂谈 &#x1f308;数据…

JavaSE要点 1】Java基础

目录 一、编译和运行 二、JDK,JRE和JVM 三、Java中的基本类型 1. 基本类型的大小 四、JVM内存模型 1. 内存模型 五、JVM虚拟机的组成 1. 虚拟机的5个组成部分 2. 虚拟机栈 3. 栈帧 六、值传递和引用传递 七、变量的默认值 八、String的不可变 九. 包装类和常量池&#xff08;-…

基于SSM的网上购物系统的设计与实现

技术介绍 本系统运用了JSP技术、SSM框架、B/S架构和myspl数据库 MySQL 介绍 MySQL是一种关系型的数据库管理系统&#xff0c;属于Oracle旗下的产品。MySQL的语言是非结构化的&#xff0c;使用的用户可以在数据上进行工作。这个数据库管理系统一经问世就受到了社会的广泛关注…

【WebGis开发 - Cesium】三维可视化项目教程---图层管理拓展图层顺序调整功能

目录 引言一、为什么要开发图层顺序调整功能二、开发思路整理1. 拖拽库方案选择2. cesium图层api查询 三、代码编写1. 编写拖拽组件代码2. 修改原有图层管理代码2.1 图层加载移除的调整2.2 图层顺序与拖拽列表的矛盾 3. 编写图层移动代码 四、总结 引言 本教程主要是围绕Cesium…