23-design-pattern


Java设计模式

设计模式:是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

面向对象设计原则

都是为了高内聚低耦合原则。编程时基本都要遵守

单一职责原则

分类原则:一种人只干一种事。

举例:(比较简单就不代码了)

人可以干的事情有很多:敲代码、唱歌、跳舞、打篮球....以人设置成一个类,里面的方法就太多太杂了。所以可以有多个类:程序员(敲代码)、音乐人(唱歌)、爱豆(跳舞)、NBA球员(打篮球)。这样类就具体化了,可以干的事情也就具体了,一旦需要用哪个方法就知道从哪个类里调用了。

开闭原则

开:提供方提供 抽象类/接口/方法 等,实现类可以决定行为。闭:调用方调用时,尽量不需要修改代码。

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

举例:

copy
//接口 public interface AccountService { //实现注册账户 void createAccount(String username,String password,String email); } //实现类 public class AccountServiceImpl implements AccountService { public void createAccount(String username,String password,String email) { .... } }

里氏替换原则

对子类的特别定义:父类方法非抽象方法,子类不可以重载(覆盖)。但如果父类有抽象方法则子类必须实现父类的抽象方法,子类也可以编写自己的方法

里氏替换原则(Liskov Substitution Principle)是对子类型的特别定义。所有引用基类的地方必须能透明地使用其子类的对象。

白话:子类可以扩展父类的功能,但不能改变父类原有的功能。有以下四原则:(重点在一二)

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

如:子类继承了父类,但没有修改父类的非抽象方法

copy
public abstract class Coder { public abstract void eat(); //这个行为还是定义出来,但是不实现 public void coding() { System.out.println("我会打代码"); } class JavaCoder extends Coder{ public void game(){ //子类自己的额外的方法 System.out.println("艾欧尼亚最强王者已上号"); } public void eat(){ //子类实现父类的抽象方法(必须) System.out.println("干啥啥不行,干饭第一名!") } } }

依赖倒转原则

使用Spring注解 注入接口,这样需求更改后实现类可以自由编写,不会影响到controller层(将每一层都分隔开来降低耦合性)

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

传统:没有接口而是类与类之间的对象创建。一旦需求变化,类就需要重写,这样其他类也需要修改

copy
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(); //只能修改成新的了 //业务代码.... } }

Spring框架:使用注解注入接口bean,这样实现类可随便改,只要最后的实现类实现了该接口即可

copy
//controller public class LoginApiController { @Autowired //Spring注解注入接口bean private VerifyService verifyService; @GetMapping("/verify-code") public RestBean<Void> verifyCode(@RequestParam("email") String email) { try { verifyService.sendVerifyCode(email); return new RestBean<>(200, "邮箱发送成功!"); } catch (Exception e) { return new RestBean<>(500, "邮箱发送失败!"); } } }

接口隔离原则

对接口进行细分,避免接口中定义的方法,在实现类中用不上。

举例:定义一个接口,有方法:设备芯片、设备名称、设备内存。这样的接口只有电脑、手机等实现类才可以实现,而对于风扇、台灯等普通设备实现类而言确只有设备名称才是有效的方法。于是就需要把接口进行细化成两个接口。

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

合成复用原则

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

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

情况:如果A类里写了想要的方法,为了不在B类不重复编写代码,可以在B类中设置一个方法:将A类的对象作为参数并在设置的方法里通过对象获取到A类中想要的方法。【此时不建议使用继承,因为容易引起安全隐患,如:A中有一下信息(密码字段)不方便传递】

举例:

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

迪米特法则

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

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

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

白话:在设计方法参数的时候,保证不多给方法多余的参数。例如:方法只需要一个用户的ip地址就可以执行,方法参数就不要写成需要输入用户对象,然后在方法里面通过对象再去调用其ip出来;而是在调用方法前就把用户对象的ip取出来,然后作为参数来调用方法。

举例:

正面教材

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

反面教材

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

创建型设计模式

针对对象/类创建时的优化

工厂方法模式(了解)

通过定义顶层抽象工厂类,通过继承的方式,针对于每一个产品都提供一个工厂类用于创建。

情况:只适用于简单对象,当我们需要生产许多个产品族的时候,这种模式就有点乏力了

创建对象不再使用传统的new,而是创建一个工厂类,作为all实体类创建对象的一个封装类。(避免了更改类名、构造方法时,需要修改大量的代码)

简单工厂模式:(不灵活,不建议)

优点:简单明了

缺点:不符合开闭原则。如果输入没有提前写好的水果则就需要再添加每个类里的代码

copy
//水果抽象类 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("橘子"); } } //水果工厂 public class FruitFactory { /** * 这里就直接来一个静态方法根据指定类型进行创建 * @param type 水果类型 * @return 对应的水果对象 */ public static Fruit getFruit(String type) { switch (type) { case "苹果": return new Apple(); case "橘子": return new Orange(); default: return null; } } } //主方法 public class Main { public static void main(String[] args) { Fruit fruit = FruitFactory.getFruit("橘子"); //直接问工厂要,而不是我们自己去创建 System.out.println(fruit); } }

工厂方法模式:通过范型灵活实现

优点:如果新增了水果类型,直接创建一个新的工厂类就行,不需要修改之前已经编写好的内容。

缺点:一种水果就有一种新的工厂类,太多工厂类了

copy
//水果抽象类 public abstract class Fruit { private final String name; public Fruit(String name){ this.name = name; } @Override public String toString() { return name+"@"+hashCode(); //打印一下当前水果名称,还有对象的hashCode } } //水果工厂 public abstract class FruitFactory<T extends Fruit> { //将水果工厂抽象为抽象类,添加泛型T由子类指定水果类型 public abstract T getFruit(); //不同的水果工厂,通过此方法生产不同的水果 } //Apple工厂 public class AppleFactory extends FruitFactory<Apple> { //苹果工厂,直接返回Apple,一步到位 @Override public Apple getFruit() { return new Apple(); } } //主方法 public class Main { public static void main(String[] args) { test(new AppleFactory()::getFruit); //比如我们现在要吃一个苹果,那么就直接通过苹果工厂来获取苹果 } //此方法模拟吃掉一个水果 private static void test(Supplier<Fruit> supplier){ System.out.println(supplier.get()+" 被吃掉了,真好吃。"); } }

抽象工厂模式

情况:适用于有一系列产品的公司。

优点:只跟接口打交道,不用去管具体的实现类。只用知道几个产品等级 几个产品族即可

缺点:容易违背开闭原则。一旦增加了一种产品,此时就必须去修改抽象工厂的接口,这就涉及到抽象工厂类的以及所有子类的改变

举例:

image

一个品牌的产品叫产品族,一个产品种类叫产品等级

实际上这些产品都是成族出现的,比如小米的产品线上有小米12,小米平板等,华为的产品线上也有华为手机、华为平板,但是如果按照我们之前工厂方法模式来进行设计,那就需要单独设计9个工厂来生产上面这些产品,显然这样就比较浪费时间的。

我们就可以使用抽象工厂模式,我们可以将多个产品,都放在一个工厂中进行生成,按不同的产品族进行划分,比如小米,那么我就可以安排一个小米工厂,而这个工厂里面就可以生产整条产品线上的内容,包括小米手机、小米平板、小米路由等。

类图:

image

代码实现:

copy
/*以华为和小米分别可以生产自家的手机和路由器为例 【以下接口是重点】*/ /*一个产品族由一个品牌工厂实现*/ //抽象产品的抽象工厂(抽象工厂生产抽象产品) public interface IProductFactory { //生成路由器 IRouterProduct routerProduct(); //返回值是一个接口 //生产手机 IPhoneProduct phoneProduct(); } //小米工厂生产小米族 public class XiaomiFactory implements IProductFactory{ @Override public IRouterProduct routerProduct() {return new XiaomiRouter();} //需要返回接口,但由于返回的实体类实现了接口,所以最终返回的可以看作成接口 @Override public IPhoneProduct phoneProduct() {return new XiaomiPhone();} } //华为工厂生成华为族 public class HuaweiFactory implements IProductFactory{ @Override public IRouterProduct routerProduct() {return new HuaweiRouter();} @Override public IPhoneProduct phoneProduct() {return new HuaweiPhone();} } /*一个产品等级就由一个产品接口*/ //手机产品接口 public interface IPhoneProduct { void start(); void shutdown(); void wechat(); void email(); } //路由器产品接口 public interface IRouterProduct { void start(); void shutdown(); void openWifi(); void stopWifi(); } //小米实现手机接口 public class XiaomiPhone implements IPhoneProduct { @Override public void start() {System.out.println("启动小米手机");} @Override public void shutdown() {System.out.println("关闭小米手机");} @Override public void wechat() {System.out.println("使用小米手机聊天");} @Override public void email() {System.out.println("用小米手机发短信");} } //华为实现手机接口 public class HuaweiPhone implements IPhoneProduct{ @Override public void start() {System.out.println("启动华为手机");} @Override public void shutdown() {System.out.println("关闭华为手机");} @Override public void wechat() {System.out.println("使用华为手机聊天");} @Override public void email() {System.out.println("用华为手机发短信");} } //小米实现路由器接口 public class XiaomiRouter implements IRouterProduct{ @Override public void start() {System.out.println("启动小米路由器");} @Override public void shutdown() {System.out.println("关闭小米路由器");} @Override public void openWifi() {System.out.println("打开小米路由器的wifi");} @Override public void stopWifi() {System.out.println("关闭小米路由器的wifi");} } //华为实现路由器接口 public class HuaweiRouter implements IRouterProduct{ @Override public void start() {System.out.println("启动华为路由器");} @Override public void shutdown() {System.out.println("关闭华为路由器");} @Override public void openWifi() {System.out.println("打开华为路由器的wifi");} @Override public void stopWifi() {System.out.println("关闭华为路由器的wifi");} }

建造者模式

当构造对象时参数较多,可以通过建造者模式使用链式方法创建对象,保证参数填写正确。

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

建造者模式创建对象其实和StringBuilder一样:实际上我们是通过建造者来不断配置参数或是内容,当我们配置完所有内容后,最后再进行对象的构建。

copy
public static void main(String[] args) { StringBuilder builder = new StringBuilder(); //创建一个StringBuilder来逐步构建一个字符串 builder.append(666); //拼接一个数字 builder.append("老铁"); //拼接一个字符串 builder.insert(2, '?'); //在第三个位置插入一个字符 System.out.println(builder.toString()); //差不多成形了,最后转换为字符串 }

举例:

copy
//实体类的编写 public class Student { int id; int age; int grade; String name; String college; String profession; List<String> awards; //一律使用建造者来创建,不对外直接开放 private Student(int id, int age, int grade, String name, String college, String profession, List<String> awards) { this.id = id; this.age = age; this.grade = grade; this.name = name; this.college = college; this.profession = profession; this.awards = awards; } public static StudentBuilder builder(){ //通过builder方法直接获取建造者 return new StudentBuilder(); } public static class StudentBuilder{ //这里就直接创建一个内部类 //Builder也需要将所有的参数都进行暂时保存,所以Student怎么定义的这里就怎么定义 int id; int age; int grade; String name; String college; String profession; List<String> awards; public StudentBuilder id(int id){ //直接调用建造者对应的方法,为对应的属性赋值 this.id = id; return this; //为了支持链式调用,这里直接返回建造者本身,下同 } public StudentBuilder age(int age){ this.age = age; return this; } ... public StudentBuilder awards(String... awards){ this.awards = Arrays.asList(awards); return this; } public Student build(){ //最后我们只需要调用建造者提供的build方法即可根据我们的配置返回一个对象 return new Student(id, age, grade, name, college, profession, awards); } } }
copy
//主方法 public static void main(String[] args) { Student student = Student.builder() //获取建造者 .id(1) //逐步配置各个参数 .age(18) .grade(3) .name("小明") .awards("ICPC-ACM 区域赛 金牌", "LPL 2022春季赛 冠军") .build(); //最后直接建造我们想要的对象 }

单例模式

单例模式:在计算机进程中,同一个类始终只会有一个对象来进行操作。

多例模式:在计算机进程中,对一个实体类创建一次对象就是对当个对象操作,若是创建多个对象则是分别对对应的对象操作。

单例模式的三种写法:

  1. 饿汉式单例(不建议)

    在最开始就创建了对象(太饥渴了,一开始就需要对象)

    copy
    public class Singleton { private final static Singleton INSTANCE = new Singleton(); //用于引用全局唯一的单例对象,在一开始就创建好 private Singleton() {} //禁用了构造方法Singleton()来创建对象。不允许随便new,需要对象直接找getInstance public static Singleton getInstance(){ //获取全局唯一的单例对象 return INSTANCE; } }
  2. 加锁的懒汉式单例(不建议,没有第三种方法好)

    懒汉:在要用的时候才创建对象。但又得防多线程就上了锁

    copy
    public class Singleton { private static volatile Singleton INSTANCE; //在一开始先不进行对象创建。volatile关键字是多线程的时候,这个变量更改了,别的线程可以立马检测到 private Singleton() {} //禁用了构造方法Singleton()来创建对象。不允许随便new,需要对象直接找getInstance public static Singleton getInstance(){ if(INSTANCE == null) { //这层判断是便于第一次外访问时不用在走锁 synchronized (Singleton.class) { //加锁是为了防止多线程创建了多个对象 if(INSTANCE == null) INSTANCE = new Singleton(); //由于加了锁,所以当一个进程进来创建了对象,其他线程需要再判断一次有没有人已经创建了这个类对象,有就不创建了。内层还要进行一次检查,双重检查锁定 } } return INSTANCE; } }
  3. 静态内部类的半懒、半饿式单例(建议)

    静态内部类刚开始不会加载,需要的时候才会加载,由于这个类一加载就会创建对象。

    所以实现了懒汉的资源不滥用,饿汉的防止多线程

    copy
    public class Singleton { private Singleton() {}//禁用了构造方法Singleton()来创建对象。不允许随便new,需要对象直接找getInstance private static class Holder { //由静态内部类持有单例对象,但是根据类加载特性,我们仅使用Singleton类时,不会对静态内部类进行初始化。一旦类初始化之后值将不会改变,有点饿汉式的味道。 private final static Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ //只有真正使用内部类时,才会进行类初始化 return Holder.INSTANCE; // } }

原型模式

定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。(说白了就是复制)

  • 浅拷贝:①对于类中基本数据类型,会直接复制值给拷贝对象;②对于引用类型(对象类型),只会复制对象的地址,而实际上指向的还是原来的那个对象,拷贝个寂寞。
copy
public static void main(String[] args) { int a = 10; int b = a; //基本类型浅拷贝 System.out.println(a == b); //true Object o = new Object(); Object k = o; //引用类型浅拷贝,拷贝的仅仅是对上面对象的引用 System.out.println(o == k); //true }
  • 深拷贝:无论是基本类型还是引用类型,深拷贝会将引用类型的所有内容,全部拷贝为一个新的对象,包括对象内部的所有成员变量,也会进行拷贝。

使用Cloneable接口提供的拷贝机制,来实现原型模式:操作完会发现Object的clone默认还是浅复制

copy
protected class Student implements Cloneable{ //注意需要实现Cloneable接口 ... //Cloneable中的方法,下面代码复制Object的clone源码 @Override public Object clone() throws CloneNotSupportedException { //提升clone方法的访问权限 return super.clone(); } } //主方法 public static void main(String[] args) throws CloneNotSupportedException { Student student0 = new Student(); Student student1 = (Student) student0.clone(); System.out.println(student0); System.out.println(student1); //两个结果不同,就是地址不同 Student student0 = new Student("小明"); Student student1 = (Student) student0.clone(); System.out.println(student0.getName() == student1.getName()); //true }

深拷贝:在student实现接口Cloneable后重写clone方法

copy
@Override public Object clone() throws CloneNotSupportedException { //这里我们改进一下,针对成员变量也进行拷贝 Student student = (Student) super.clone(); student.name = new String(name); return student; //成员拷贝完成后,再返回 }

结构性设计模式

针对类与对象的组织结构。(白话:类与对象之间的交互的多种模式

类/对象适配器模式

当需要传入一个A类型参数,但只有B类型类时,就需要一个A类型的适配器装入B类的数据,来将B数据转成A类型,然后作为参数传入

适配器,在生活中又称转换器。现在的手机基本都割去了3.5mm的耳机接口,此时只有有线耳机,要听歌就需要一个转换器将3.5mm接口转成手机有的type-c的接口

类适配器(不建议)

继承需要转变的类,实现需要转成的类型接口

copy
//主方法 public class Main { public static void main(String[] args) { TestSupplier supplier = new TestSupplier(); test( ? ); //我们没有35MM类型的手机接口,只有type-c的手机接口,那这里该填个type-c。所以需要一个转接口将35MM转为type-c接口 } public static void test(typeC typec){ //现在我们需要调用test方法,但是test方法需要类型是typeC System.out.println("成功得到:"+typec.listen()); } } //接口 public interface typeC { //typeC接口也想听歌 String listen(); } //父类 public class 35MM{ public String listenMusic(){ return "有线耳机听歌!" //因为只有有线耳机,所以只有35MM才能听歌 } } //子类作适配器 继承35MM,实现type-C接口 public class Adapter extends 35MM implements typeC{ @Override public String listen() { //现在不再继承35MM,仅实现typeC接口 return super.listenMusic(); } }

对象适配器

实现需要转成的类型接口,将需要转变的类实例化,并用作与适配器类的构造方法

因为类适配器会占用一个继承位,而java又是单继承的。如果typeC不是接口而是抽象类的话就用不了了。所以提出对象适配器:

copy
//主方法 public class Main { public static void main(String[] args) { TestSupplier supplier = new TestSupplier(); test( ? ); //我们没有35MM类型的手机接口,只有type-c的手机接口,那这里该填个type-c。所以需要一个转接口将35MM转为type-c接口 } public static void test(typeC typec){ //现在我们需要调用test方法,但是test方法需要类型是typeC System.out.println("成功得到:"+typec.listen()); } } //接口 public interface typeC { //typeC接口也想听歌 String listen(); } //父类 public class 35MM{ public String listenMusic(){ return "有线耳机听歌!" //因为只有有线耳机,所以只有35MM才能听歌 } } //子类作适配器 继承35MM,实现type-C接口 public class Adapter implements typeC{ //现在不再继承35MM,仅实现typeC接口 35MM 35mm; //实例化需要转变的类 public Adapter(35MM 35mm){ //将实例化的对象用于构造对象 this.35mm = 35mm; } @Override public String listen() { //接着实现listen方法,直接使用typeC提供的实现 return 35mm.listenMusic(); } }

桥接模式

选择不同的配置(零件)组成一个东西(先组一个再组一个,一层一层的基础来达到组零件的目的)

同一种产品有着不同的配置,就像手机有:运行内存 4 6 8g,存储内存:64 128 256g,芯片:骁龙 A系列 麒麟 联发科 猎户座。不能每一种配置都写一个类就太麻烦了,所以有了桥接模式,可以通过多个类桥接成一个产品类。

优势:可以通过多个维度来自由设定配置

这里以华为手机举例:(小知识——华为手机是用自家的麒麟芯片)

copy
//第一层类:继承该类可以自定义芯片类型 public abstract class AbstractPhone { private Size size; //这里是描述存储内存。由于举例简单点方便看得懂就不写运行内存了 public AbstractPhone(Size size){ this.size = size; } public abstract String getType(); //这里的类型是指芯片类型 } //接口及实现类 public interface Size{ String getSize(); } public class 256G implements Size{ @Override public String getSize() { return "256g内存"; } } //第二层类:继承该类可以自定义芯片类型和存储内存的尺度大小 public abstract class RefinedAbstractPhone extends AbstractPhone{ protected RefinedAbstractPhone(Size size) { super(size); } public String getSize(){ //添加尺寸维度获取方式 return size.getSize(); } } //产品类:继承第二层类,然后自定义存储内存大小和芯片种类 public class HUAWEI extends RefinedAbstractPhone{ protected HUAWEI(Size size){ //构造方法指定具体存储内存大小 super(size); } @Override public String getType() { return "华为手机"; //返回手机品牌类型 } } //主方法 public static void main(String[] args) { HUAWEI huawei = new HUAWEI(new 256G()); System.out.println(huawei.getType()); System.out.println(huawei.getSize()); }

组合模式

对多个组件进行统一一样的操作

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

它就像是一个树形结构一样,有分支有叶子,而组合模式则是可以对整个树形结构上的所有节点进行递归处理,比如我们现在希望将所有文件夹中的文件的名称前面都添加一个前缀,那么就可以使用组合模式。

image

组合模式的示例如下,这里我们就用文件和文件夹的例子来讲解:

copy
/** * 首先创建一个组件抽象,组件可以包含组件,组件有自己的业务方法 */ 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(); //执行对应的业务方法,比如修改文件名称 }

接着我们来编写两种实现类:文件夹实现类,文件实现类

copy
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方法执行业务 } }
copy
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); //具体的名称修改操作 } }

最后,我们来测试一下:可以看到我们对最外层目录进行操作后,会递归向下处理当前目录和子目录中所有的文件

copy
public static void main(String[] args) { Directory outer = new Directory(); //新建一个外层目录 Directory inner = new Directory(); //新建一个内层目录 outer.addComponent(inner); outer.addComponent(new File()); //在内层目录和外层目录都添加点文件,注意别导错包了 inner.addComponent(new File()); inner.addComponent(new File()); outer.test(); //开始执行文件名称修改操作 }

装饰模式

通过B类 实现对A类方法执行前后,分别多执行一些操作。类似于AOP

image

Base是抽象类/接口,有一个实现类实现其里面具体的业务方法。Decorator可以理解为是一个给装饰者们的一个抽象类,然后不同的装饰者再去具体继承Decorator并在业务方法前后进行修饰。

适用:业务功能前后实现一些操作。如:在支付前提醒是否需要支付xxx元。

copy
//顶层抽象类 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(); //装饰两次:装饰前——装饰前——业务方法——装饰后——装饰后 }

代理模式

和装饰模式代码一模一样,但核心是思想不同

image

代理模式是讲DecoratorImpl给别人代理了,装饰模式是DecoratorImpl自己增强。

装饰模式和代理模式:

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

代理模式一般代码:

copy
//顶层抽象类 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(); //代理两次:代理前——代理前——业务方法——代理后——代理后 }

实现代理模式除了和装饰模式一样的代码情况外还有两种实现方式:【因为都是动态代理所以生成的代理类是看不到的】

  1. JDK提供的动态代理:我们不再需要手动编写继承关系创建代理类,它能够在运行时通过反射机制为我们自动生成代理类:【只能代理接口】

    copy
    //接口 public interface Subject { //JDK提供的动态代理只支持接口 void test(); } //接口实现类 public class SubjectImpl implements Subject{ @Override public void test() { System.out.println("我是测试方法!"); } } //创建动态代理的处理逻辑(就是执行业务前后的方法编写在里面) public class TestProxy implements InvocationHandler { //代理类,需要实现InvocationHandler接口 private final Object object; //这里需要保存一下被代理的对象,下面需要用到 public TestProxy(Object object) { this.object = object; } @Override //此方法就是调用代理对象的对应方法时会进入,这里我们就需要编写如何进行代理了 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //method就是调用的代理对象的哪一个方法,args是实参数组 System.out.println("代理的对象:"+proxy.getClass()); //proxy就是生成的代理对象了,我们看看是什么类型的 Object res = method.invoke(object, args); //在代理中调用被代理对象原本的方法,因为你是代理,还是得执行一下别人的业务,当然也可以不执行,但是这样就失去代理的意义了,注意要用上面的object System.out.println("方法调用完成,返回值为:"+res); //看看返回值是什么 return res; //返回返回值 } }
  2. Spring在使用的CGLib框架代理。

    maven依赖:

    copy
    <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.1</version> </dependency>

    代码实现:

    copy
    //接口 public interface Subject { //JDK提供的动态代理只支持接口 void test(); } //接口实现类 public class SubjectImpl implements Subject{ @Override public void test() { System.out.println("我是测试方法!"); } } //创建动态代理的处理逻辑(就是执行业务前后的方法编写在里面) public class TestProxy implements MethodInterceptor { //首先还是编写我们的代理逻辑 private final Object target; //这些和之前JDK动态代理写法是一样的 public TestProxy(Object target) { this.target = target; } @Override //我们也是需要在这里去编写我们的代理逻辑 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("现在是由CGLib进行代理操作!"+o.getClass()); return method.invoke(target, objects); //也是直接调用代理对象的方法即可 } } //主方法 public static void main(String[] args) { SubjectImpl subject = new SubjectImpl(); Enhancer enhancer = new Enhancer(); //增强器,一会就需要依靠增强器来为我们生成动态代理对象 enhancer.setSuperclass(SubjectImpl.class); //直接选择我们需要代理的类型,直接不需要接口或是抽象类,SuperClass作为代理类的父类存在,这样我们就可以按照指定类型的方式去操作代理类了 enhancer.setCallback(new TestProxy(subject)); //设定我们刚刚编写好的代理逻辑 SubjectImpl proxy = (SubjectImpl) enhancer.create(); //直接创建代理类 proxy.test(); //调用代理类的test方法 }

外观模式

可以理解为门面模式,将需要通过操作多个类实现的一个功能封装到一个类中,便于使用

当每个功能是一个系统,完成一个业务需要多个功能时就需要分别调用多个系统,此时就可以将一个业务需要使用的多个系统封装成一个门面系统,只要调用该门面系统即可完成该业务。

image

举例:比如现在我们设计了三个子系统,分别是排队、结婚、领证,正常情况下我们是需要分别去找这三个部门去完成的,但是现在我们通过门面统一来完成

copy
//系统一 public class SubSystemA { public void test1(){ System.out.println("排队"); } } //系统二 public class SubSystemB { public void test2(){ System.out.println("结婚"); } } //系统三 public class SubSystemC { public void test3(){ System.out.println("领证"); } } //门面 public class Facade { SubSystemA a = new SubSystemA(); SubSystemB b = new SubSystemB(); SubSystemC c = new SubSystemC(); public void marry(){ //红白喜事一条龙服务 a.test1(); b.test2(); c.test3(); } } //主方法 public static void main(String[] args) { Facade facade = new Facade(); facade.marry(); }

享元模式

核心是共享。当A类方法里写了一个方法,B类中需要同样的方法就可以直接创建A类对象来调用方法或者通过一个方法工厂类收集各个方法,然后B类通过工厂类调用A类方法

举例:通过享元工厂类实现共享方法

copy
//A类 public class DBUtil { public void selectDB(){ System.out.println("我是数据库操作..."); } } //享元工厂 public class DBUtilFactory { private static final DBUtil UTIL = new DBUtil(); //享元对象被存放在工厂中 public static DBUtil getFlyweight(){ //获取享元对象 return UTIL; } } //B类 public class UserService { //用户服务 public void service(){ DBUtil util = DBUtilFactory.getFlyweight(); //通过享元工厂拿到DBUtil对象 util.selectDB(); //该干嘛干嘛 } }

行为型设计模式

针对对象之间的交互

解释器模式

java中用的很多。JVM编译的时候就是对我们写的代码进行了解释操作;数据库SQL语句亦是如此

解释器:对语言进行解释,根据不同语义来做不同的事情。

举例:双栈计算器

copy
public class 双栈实现计算器 { //设置两栈 private static Deque<Character> opr = new LinkedList<>(); private static Deque<Double> number = new LinkedList<>(); public static void main(String[] args) { //接收一串字符串并转字符数组 Scanner scanner = new Scanner(System.in); String str = scanner.nextLine(); char [] arrC = str.toCharArray(); for (int i = 0; i < arrC.length;) { char c = arrC[i]; //是+-/*字符时 if (isChar(c)){ ///如果栈内有字符,则需要判断优先级,入栈字符小于等于栈内字符则需要先计算栈内字符 Character peek = opr.peek(); //细节!!! 这里必须先取 while (peek!=null && isLowIn(c,peek)){ cal(); peek=opr.peek(); //细节!!这里也必须更新peek } //入栈 opr.push(c); i++; } //字符是数字时 else { double sum=0; //接收整数和 double sum2=0; //接收小数和 int times = 1; //记录当前小数位数 boolean flag=false; //是否开启小数检测模式 //判断下一个是不是+-*/,不是的话就继续判断直到非数字 while (i<=arrC.length-1 && !isChar(arrC[i])){ //细节:括号内两者顺序不能改变 //遇到小数的情况 if (arrC[i]=='.'){ flag=true; }else { //小数情况 if (flag){ double val=arrC[i]-'0'; for (int j = 0; j < times; j++) { //细节!用times缩小值 val/=10.0; } times++; sum2+=val; } //正数情况 else { sum=sum*10+arrC[i]-'0'; //获取多位数字的关键!!! } } i++; } number.push(sum+sum2); } } //字符都获取完了后栈内还有数字和字符的话,就计算完栈内的数据并输出最终结果 while (!opr.isEmpty()) cal(); System.out.println(number.peek()); } //判断是否为字符 public static boolean isChar(char c) { return c=='+'||c=='-'||c=='*'||c=='/'; } //判断优先级是否是栈外字符小于等于栈内字符 public static boolean isLowIn(char out,char in){ return (out=='+'||out=='-')||(in=='*'||in=='/'); } public static void cal(){ //从栈内取出两个数组和一个字符 double a = number.pop(); double b = number.pop(); char c = opr.poll(); //根据字符c进行不同的运算 switch (c){ case '+': number.push(a+b); break; case '-': number.push(b-a); break; case '*': number.push(b*a); break; case '/': number.push(b/a); break; default: System.out.println("字符输入有误"); } } }

模板方法模式

在执行一类业务时前面有很多步骤都是相同时,就可以写一个模板抽象类,留出一个方法去给子类定义业务最后的操作。

该模式在源码中大量的被应用。这样写会给后期维护提供非常清晰的思路

举例:去医院看病,挂号和看医生是固定模式,但后面要不要开处方药和拿药是不一定的

copy
//模板抽象类 /** * 抽象诊断方法,因为现在只知道挂号和看医生是固定模式,剩下的开处方和拿药都是不确定的 */ 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(); //拿药也是根据具体的处方去拿 } //实现具体业务的子类 /** * 感冒相关的具体实现子类 */ public class ColdDiagnosis extends AbstractDiagnosis{ @Override public void prescribe() { System.out.println("3 >> 一眼丁真,鉴定为假,你这不是感冒,纯粹是想摆烂"); } @Override public void medicine() { System.out.println("4 >> 开点头孢回去吃吧"); } } //主方法 public static void main(String[] args) { AbstractDiagnosis diagnosis = new ColdDiagnosis(); diagnosis.test(); }

责任链模式

就像闯关,一层接一层的往下进行。可以理解为报销的时候需要一层一层审批

比如JavaWeb中学习的Filter过滤器,正是采用的责任链模式,通过将请求一级一级不断向下传递,来对我们所需要的请求进行过滤和处理。

image

举例:这里就使用责任链模式来模拟一个简单的面试过程,面试也是一面二面三面这样走的流程,这里先设计一下责任链上的各个处理器

copy
//设计模板抽象方法,并在此基础上写层层往下的责任链 public abstract class Handler { protected Handler successor; //这里我们就设计责任链以单链表形式存在,这里存放后继节点 public Handler connect(Handler successor){ //拼接后续节点 this.successor = successor; return successor; //这里返回后继节点,方便我们一会链式调用 } public void handle(){ this.doHandle(); //由不同的子类实现具体处理过程 Optional .ofNullable(successor) //设置可以为null .ifPresent(Handler::handle); //责任链上如果还有后继节点,就继续向下传递 } public abstract void doHandle(); //结合上节课学习的模板方法,交给子类实现 } //一面子类 public class FirstHandler extends Handler{ //用于一面的处理器 @Override public void doHandle() { System.out.println("============= 白马程序员一面 =========="); System.out.println("1. 谈谈你对static关键字的理解?"); System.out.println("2. 内部类可以调用外部的数据吗?如果是静态的呢?"); System.out.println("3. hashCode()方法是所有的类都有吗?默认返回的是什么呢?"); System.out.println("以上问题会的,可以依次打在评论区"); } } //二面子类 public class SecondHandler extends Handler{ //二面 @Override public void doHandle() { System.out.println("============= 白马程序员二面 =========="); System.out.println("1. 如果我们自己创建一个java.lang包并且编写一个String类,能否实现覆盖JDK默认的?"); System.out.println("2. HashMap的负载因子有什么作用?变化规律是什么?"); System.out.println("3. 线程池的运作机制是什么?"); System.out.println("4. ReentrantLock公平锁和非公平锁的区别是什么?"); System.out.println("以上问题会的,可以依次打在评论区"); } } //三面子类 public class ThirdHandler extends Handler{ @Override public void doHandle() { System.out.println("============= 白马程序员三面 =========="); System.out.println("1. synchronized关键字了解吗?如何使用?底层是如何实现的?"); System.out.println("2. IO和NIO的区别在哪里?NIO三大核心组件?"); System.out.println("3. TCP握手和挥手流程?少一次握手可以吗?为什么?"); System.out.println("4. 操作系统中PCB是做什么的?运行机制是什么?"); System.out.println("以上问题会的,可以依次打在评论区"); } } //主方法 public static void main(String[] args) { Handler handler = new FirstHandler(); //一面首当其冲 handler .connect(new SecondHandler()) //继续连接二面和三面 .connect(new ThirdHandler()); handler.handle(); //开始面试 }

命令模式

命令模式,此时会有三个顶层行为:遥控器、命令、接收器。

话不多说,直接搬例。小米家具就是典型的命令模式。只需要在手机(遥控器)上通过红外线、蓝牙等按下一些命令,家中的家具(接收器)就会执行命令。

举例:设置三个顶层的 接口/抽象类 ,遥控器、命令、接收器

copy
//遥控器 public class Controller { //遥控器只需要把我们的指令发出去就行了 public static void call(Command command){ command.execute(); } } //命令 public abstract class Command { //指令抽象,不同的电器有指令 private final Receiver receiver; protected Command(Receiver receiver){ //指定此命令对应的电器(接受者) this.receiver = receiver; } public void execute() { receiver.action(); //执行命令,实际上就是让接收者开始干活 } } //接收器 public interface Receiver { void action(); //具体行为,这里就写一个算了 } //具体接收器 public class AirConditioner implements Receiver{ @Override public void action() { System.out.println("空调已开启,呼呼呼"); } } //具体命令 public class OpenCommand extends Command { public OpenCommand(AirConditioner airConditioner) { super(airConditioner); } } //可以创建具体控制器(手机),也可以不创建直接遥控,因为控制器一般只有一个。所以这里就不创建了 //主方法 public static void main(String[] args) { AirConditioner airConditioner = new AirConditioner(); //先创建一个空调 Controller.call(new OpenCommand(airConditioner)); //直接通过遥控器来发送命令开启空调 }

迭代器模式

每个集合类都有相应的迭代器。很少自定义,大都是用jdk定义好的迭代器

迭代器最直接的例子就是foreach语法糖。

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

上述代码编译后的样子:

copy
public static void main(String[] args) { List<String> list = Arrays.asList("AAA", "BBB", "CCC"); Iterator var2 = list.iterator(); //实际上这里本质是通过List生成的迭代器来遍历我们每个元素的 while(var2.hasNext()) { //判断是否还有元素可以迭代,没有就false String s = (String)var2.next(); //通过next方法得到下一个元素,每调用一次,迭代器会向后移动一位 System.out.println(s); //打印一下 } }

由此可知迭代器原理:使用迭代器对List进行遍历时,实际上就像一个指向列表头部的指针,我们通过不断向后移动指针来依次获取所指向的元素:

image

image

拓展:自定义迭代器

(由于每个迭代器需要根据不同的集合类特点来设计,所以自定义迭代器前需要自定义一个集合类)

copy
//自定义集合类 public class ArrayCollection<T> { //首先设计一个简单的数组集合,一会我们就迭代此集合内的元素 private final T[] array; //底层使用一个数组来存放数据 private ArrayCollection(T[] array){ //private掉,自己用 this.array = array; } public static <T> ArrayCollection<T> of(T[] array){ //开个静态方法直接把数组转换成ArrayCollection,其实和直接new一样,但是这样写好看一点 return new ArrayCollection<>(array); } } //自定义迭代器 public class ArrayCollection<T> implements Iterable<T>{ //实现Iterable接口表示此类是支持迭代的 ... @Override public Iterator<T> iterator() { //需要实现iterator方法,此方法会返回一个迭代器,用于迭代我们集合中的元素 return new ArrayIterator(); } public class ArrayIterator implements Iterator<T> { //这里实现一个,注意别用静态,需要使用对象中存放的数组 private int cur = 0; //这里我们通过一个指针表示当前的迭代位置 @Override public boolean hasNext() { //判断是否还有下一个元素 return cur < array.length; //如果指针大于或等于数组最大长度,就不能再继续了 } @Override public T next() { //返回当前指针位置的元素并向后移动一位 return array[cur++]; //正常返回对应位置的元素,并将指针自增 } } } //主方法 public static void main(String[] args) { String[] arr = new String[]{"AAA", "BBB", "CCC", "DDD"}; ArrayCollection<String> collection = ArrayCollection.of(arr); for (String s : collection) { //可以直接使用foreach语法糖,当然最后还是会变成迭代器调用 System.out.println(s); } } //编译主方法后的样子 public static void main(String[] args) { String[] arr = new String[]{"AAA", "BBB", "CCC", "DDD"}; ArrayCollection<String> collection = ArrayCollection.of(arr); Iterator var3 = collection.iterator(); //首先获取迭代器,实际上就是调用我们实现的iterator方法 while(var3.hasNext()) { String s = (String)var3.next(); //直接使用next()方法不断向下获取 System.out.println(s); } }

中介者模式

将多对多的复杂关系,变成一对多的简单明了关系

话不多说,直接上例子。中介,第一个想到的就是房子的中介。当一堆人需要出租房屋、另一堆人又需要租房,如果没有中介,那再好的房子很难遇上租客也租不出去,此时就需要一个中介将双方的需要进行匹配从而实现房子出租的目的。

不找中介:很乱,还不一定能够遇上需求相同的对方

image

找中介:中介将双方整理好,进行需求匹配

image

举例:中介、出租者、租房者

copy
//中介 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); } @Override public String toString() { return name+" (电话:"+tel+")"; } } //主方法 public static void main(String[] args) { User user0 = new User("刘女士", "10086"); //出租者 User user1 = new User("李先生", "10010"); //租房者 Mediator mediator = new Mediator(); //我是中介 mediator.register("广州市天河区白马程序员", user0); //出租人先把房子给中介挂上去 User user = user1.find("广州市天河区非马程序员", mediator); //租房者向指定中介找房子 if(user == null) System.out.println("没有找到对应的房源"); System.out.println(user); //成功找到对应房源 }

备忘录模式

我也称其为时光回溯模式。比较少用,大都是底层代码才用

这个备忘录不是我们平时用于记录容易忘记的ddl,而是保存曾经某个时刻的状态,后面有需要就恢复到该时刻的状态

举例:保存对象的状态

copy
//对象实体 public class Student { private String currentWork; //当前正在做的事情 private int percentage; //当前的工作完成百分比 public void work(String currentWork) { this.currentWork = currentWork; this.percentage = new Random().nextInt(100); } @Override public String toString() { return "我现在正在做:"+currentWork+" (进度:"+percentage+"%)"; } public State save(){ return new State(currentWork, percentage); } public void restore(State state){ this.currentWork = state.currentWork; this.percentage = state.percentage; } } //状态保存类 public class State { final String currentWork; final int percentage; State(String currentWork, int percentage) { //仅开放给同一个包下的Student类使用 this.currentWork = currentWork; this.percentage = percentage; } } //主方法 public static void main(String[] args) { Student student = new Student(); student.work("学Java"); //开始学Java System.out.println(student); State savedState = student.save(); //保存一下当前的状态 student.work("打电动"); //刚打开B站播放视频,学一半开始摆烂了 System.out.println(student); student.restore(savedState); //后悔浪费时间,回到上一个保存的状态 System.out.println(student); //回到学Java的状态 }

观察者模式

观察者模式可以实现监听器机制,当对象发生改变时,观察者能够立即察觉到并进行一些联动操作。很少用,大都是直接用监听器

举例:自定义观察者

copy
//观察者接口 public interface Observer { //观察者接口 void update(); //当对象有更新时,会回调此方法 } //被观察者的实体 public class Subject { private final Set<Observer> observerSet = new HashSet<>(); public void observe(Observer observer) { //添加观察者 observerSet.add(observer); } public void modify() { //模拟对象进行修改 observerSet.forEach(Observer::update); //当对象发生修改时,会通知所有的观察者,并进行方法回调 } } //主方法 public static void main(String[] args) { Subject subject = new Subject(); subject.observe(() -> System.out.println("我是一号观察者!")); //这里是实现接口里的抽象方法 update() subject.observe(() -> System.out.println("我是二号观察者!")); subject.modify();//修改了会调用前面实现的抽象方法 update() }

JDK也提供了是实现观察者模式的相关接口:

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

状态模式

根据不同的状态执行不同的行为

水在不同的温度状态会随之改变,程序也可以达到某种状态后就执行不同的行为

copy
//枚举状态 public enum State { //状态直接使用枚举定义 NORMAL, LAZY } //实体类 public class Student { private State state; //使用一个成员来存储状态 public void setState(State state) { this.state = state; } public void study(){ switch (state) { //根据不同的状态,学习方法会有不同的结果 case LAZY: System.out.println("只要我不努力,老板就别想过上想要的生活,开摆!"); break; case NORMAL: System.out.println("拼搏百天,我要上清华大学!"); break; } } } //主方法 public static void main(String[] args) { Student student = new Student(); student.setState(State.NORMAL); //先正常模式 student.study(); student.setState(State.LAZY); //开启摆烂模式 student.study(); }

策略模式

和状态模式代码一样。但状态模式思想是:状态是先天的设定,就像水不同温度状态不同。而策略模式思想是:策略需要根据不同情况制定的。

举例:简单的数组排列

copy
//策略接口(模板方法模式) public interface Strategy { //策略接口,不同的策略实现也不同 Strategy SINGLE = Arrays::sort; //单线程排序方案 Strategy PARALLEL = Arrays::parallelSort; //并行排序方案 void sort(int[] array); } //排序类 public class Sorter { private Strategy strategy; //策略 public void setStrategy(Strategy strategy) { this.strategy = strategy; } public void sort(int[] array){ strategy.sort(array); } } //主方法 public static void main(String[] args) { Sorter sorter = new Sorter(); sorter.setStrategy(Strategy.PARALLEL); //指定为并行排序方案 sorter.sort(new int[]{9, 2, 4, 5, 1, 0, 3, 7}); }

访问者模式

一件事情,一个访问接口。访问者实现该接口,但不同访问者关心的事情着重点不同

举例:假如你获奖了还是国家级一等奖

copy
//奖实体类 public class Prize { //奖 String name; //比赛名称 String level; //等级 public Prize(String name, String level) { this.name = name; this.level = level; } public String getName() { return name; } public String getLevel() { return level; } } //访问者接口 public interface Visitor { void visit(Prize prize); //visit方法来访问我们的奖项 } //不同访问者 public class Teacher implements Visitor { //指导老师作为一个访问者 @Override public void visit(Prize prize) { //它只关心你得了什么奖以及是几等奖,这也关乎老师的荣誉 System.out.println("你得奖是什么奖?"+prize.name); System.out.println("你得了几等奖?"+prize.level); } } public class Boss implements Visitor{ //你的公司老板作为一个访问者 @Override public void visit(Prize prize) { //你的老板只关心这些能不能为公司带来什么效益,奖本身并不重要 System.out.println("你的奖项大么,能够为公司带来什么效益么?"); System.out.println("还不如老老实实加班给我多干干,别去搞这些没用的"); } } //主方法 public static void main(String[] args) { Prize prize = new Prize("ACM国际大学生程序设计竞赛","一等价"); Teacher teacher = new Teacher(); Boss boss = new Boss(); teacher.visit(prize); boss.visit(prize); }