本篇文章是学习设计模式过程中做的笔记(主要是每种设计模式的代码示例),部分代码和内容来自掘金社区。

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

为什么要学设计模式?

  1. 设计模式来源众多专家的经验和智慧,它们是从许多优秀的软件系统中总结出的成功的、能够实现可维护性、复用的设计方案,使用这些方案将可以让我们避免做一些重复性的工作。
  2. 大部分设计模式都兼顾了系统的可重用性和可扩展性,这使得我们可以更好地重用一些已有的设计方案、功能模块甚至一个完整的软件系统,避免我们经常做一些重复的设计、编写一些重复的代码
  3. 合理使用设计模式并对设计模式的使用情况进行文档化,将有助于别人更快地理解系统
  4. 学习设计模式有助于更加深入地理解面向对象思想,能够踏出架构的第一步。

设计模式种类

设计模式一共分为三种类型:

  1. 创造型设计模式:单例模式、简单工厂模式、抽象工厂模式、建造者模式、原型模式
  2. 结构型设计模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  3. 行为型设计模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

遵循的七大原则

设计模式遵循六大原则:

  1. 单一职责原则

    每个类应该实现单一的职责。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。想要避免这种现象的发生,就要尽可能的遵守单一职责原则。

    单一职责原则的核心就是解耦和增强内聚性。

  2. 里氏替换原则

    里式替换原则是用来帮助我们在继承关系中进行父子类的设计。

    应用程序中任何父类对象出现的地方,我们都可以用其子类的对象来替换,并且可以保证原有程序的逻辑行为和正确性。

    不符合里氏替换原则的最常见的情况是,父类和子类都是可实例化的非抽象类,且父类的方法被子类重新定义,这一类的实现继承会造成父类和子类间的强耦合,也就是实际上并不相关的属性和方法牵强附会在一起,不利于程序扩展和维护。

  3. 依赖倒置原则

    1. 高层模块不应该依赖低层模块,两者都应该依赖其抽象. 不可分割的原子逻辑就是低层模式,原子逻辑组装成的就是高层模块。
    2. 抽象不应该依赖细节 java 中,抽象 -> 接口或抽象类;细节 -> 实现类
    3. 细节应该依赖抽象

    依赖倒转原则就是要针对接口编程,不要针对实现编程。这就是说,应当使用接口或者抽象类进行变量的类型声明参数的类型声明方法的返回类型说明以及数据类型的转换等。

    在java中体现为多态的使用

  4. 接口隔离原则

    每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。

  5. 迪米特法则

    也称为最少知识原则: 一个对象应该对其他对象有最少的了解。对类的低耦合提出了明确的要求

    只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。

  6. 开闭原则

    对扩展开放,对修改封闭

    开放封闭原则是所有面向对象原则的核心。软件设计本身所追求的目标就是封装变化、降低耦合,而开放封闭原则正是对这一目标的最直接体现。其他的设计原则,很多时候是为实现这一目标服务的.

  7. 合成复用原则

    原则是尽量使用合成/聚合的方式,而不是使用继承。

创造型模式

单例模式

单例模式主要是为了避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例模式能够保证整个应用中有且只有一个实例

单例在于只有一个实例,所以必须私有化构造函数再向外暴露提供唯一实例的方法。

  1. 饿汉式

    public class HungrySingleton {
    private HungrySingleton() {}

    private static final HungrySingleton instance = new HungrySingleton();

    static HungrySingleton getInstance() {
    return instance;
    }

    public void doSomething() {
    System.out.println("饿汉式单例工作中.......");
    }

    }

    好处:使用类加载机制避开线程安全问题

    缺点:类加载的时候就创建实例,内存利用率低。

  2. 懒汉式

    public class LazySingleton {
    private LazySingleton() {}

    private static LazySingleton instance;

    static synchronized LazySingleton getInstance() {
    if (instance == null) {
    instance = new LazySingleton();
    }
    return instance;
    }

    public void doSomething() {
    System.out.println("懒汉式单例工作中.......");
    }
    }

    好处:懒加载,提高内存利用率,synchronized保证线程安全

    缺点:synchronized作用的是整个getInstance方法,每次都要经过判断锁,等待锁,释放锁这些过程。

  3. 静态内部类单例(推荐)

    public class InnerSingleton {
    private InnerSingleton() {}

    static InnerSingleton getInstance() {
    return Holder.instance;
    }

    void doSomething() {
    System.out.println("静态内部类单例工作中.......");
    }

    private static class Holder {
    private static final InnerSingleton instance = new InnerSingleton();
    }
    }

    与饿汉式不同的是静态内部类利用内部类达到了懒加载的效果,类加载保证线程安全。

  4. 双重校验锁(推荐)

    public class TwoIfSingleton {
    private TwoIfSingleton() {}

    private static volatile TwoIfSingleton instance;

    static TwoIfSingleton getInstance() {
    if (instance == null) {
    synchronized (TwoIfSingleton.class) {
    if (instance == null) {
    instance = new TwoIfSingleton();
    }
    }
    }
    return instance;
    }

    void doSomething() {
    System.out.println("双重校验锁单例类工作中.......");
    }
    }

    双重校验指的就是两次if判断,第一次是避免不必要的锁等待,第二次是避免重复new 实例。而new TwoIfSingleton并不是原子操作,不是原子操作就可能发生指令重排序,volatile关键字的一个作用是禁止指令重排,把instance声明为volatile之后,对它的写操作就会有一个内存屏障,这样,在它的赋值完成之前,就不用会调用读操作。关于volatile指令重排因为篇幅原因这里不再赘述,大家可自行查阅。

  5. 枚举类实现单例

public enum EnumSingleton {
instance;

void doSomething() {
System.out.println("枚举类单例工作中.......");
}
}

利用java枚举类的特性实现私有化构造函数,反序列化创建对象。但没有懒加载的效果。

工厂模式

工厂模式其实就是产品和客户之间的解耦,客户通过工厂去获取相应的产品。

工厂模式包含了简单工厂模式,工厂方法模式和抽象工厂模式。

简单工厂模式

我们从一个例子入手。假设我们现在需要生产不同类型的形状,包括圆,三角形,四边形,平行四边形等等。

首先定义一个Shape接口,有一个draw方法。然后定义几个图形产品:

interface Shape {
void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
public class Triangle implements Shape{
@Override
public void draw() {
System.out.println("绘制三角形");
}
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
public class Oval implements Shape{
@Override
public void draw() {
System.out.println("绘制椭圆形");
}
}

再定义一个生产产品的工厂。

public class ShapeFactory {
static Shape createShape(String type){
Shape shape;
switch (type){
case "三角形":
shape = new Triangle();
break;
case "矩形":
shape = new Rectangle();
break;
case "椭圆形":
shape = new Oval();
break;
case "圆形":
shape = new Circle();
break;
default:
shape = null;
break;
}
return shape;
}
}

最后模拟一下客户获取产品的过程:

public class ShapeFactoryDemo {
public static void main(String[] args) {
Shape circle = ShapeFactory.createShape("圆形");
circle.draw();
Shape rectangle = ShapeFactory.createShape("矩形");
rectangle.draw();
Shape oval = ShapeFactory.createShape("椭圆形");
oval.draw();
Shape triangle = ShapeFactory.createShape("三角形");
triangle.draw();
}
}

结果达到了我们的预期:

绘制圆形
绘制矩形
绘制椭圆形
绘制三角形

Process finished with exit code 0

工厂方法模式

但是在上面的 的简单工程模式中你们有没有发现它解耦了但是没完全解耦,因为我每增加一个产品然后就要在工厂的switch里面去添加一个分支,耦合度似乎有点高。而工厂方法模式就将生产产品本身交给一个新的工厂,这样客户想要一个产品只需要知道产品的名字就行了。

我们产品还是不变:

interface Shape {
void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
public class Triangle implements Shape{
@Override
public void draw() {
System.out.println("绘制三角形");
}
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
public class Oval implements Shape{
@Override
public void draw() {
System.out.println("绘制椭圆形");
}
}

工厂的定义改变一下

public interface IFactory {
Shape createShape();
}
public class CircleFactory implements IFactory{
@Override
public Shape createShape() {
return new Circle();
}
}
public class RectangleFactory implements IFactory{
@Override
public Shape createShape() {
return new Rectangle();
}
}
public class TriangleFactory implements IFactory{
@Override
public Shape createShape() {
return new Triangle();
}
}
public class OvalFactory implements IFactory{
@Override
public Shape createShape() {
return new Oval();
}
}

然后是客户使用:

public class ShapeFactoryDemo {
public static void main(String[] args) {
IFactory circleFactory = new CircleFactory();
Shape circle = circleFactory.createShape();
circle.draw();
IFactory ovalFactory = new OvalFactory();
Shape oval = ovalFactory.createShape();
oval.draw();
IFactory rectangleFactory = new RectangleFactory();
Shape rectangle = rectangleFactory.createShape();
rectangle.draw();
IFactory triangleFactory = new TriangleFactory();
Shape triangle = triangleFactory.createShape();
triangle.draw();
}
}

可以看到工厂和产品达到了一定程度的解耦,客户只需要知道产品的名字就可以了。但是每件产品都对应一个工厂,上千上万件产品那工厂的数量不起飞了。所以就有了抽象工厂模式:

抽象工厂模式

抽象工厂和工厂方法的不同在于,工厂方法一个工厂只能生产一个具体的产品。而抽象工厂一个工厂能生产一组类型相同的产品。只有新增类型的产品时才会增加工厂。

这里我们再举一个例子,有手机,平板,电脑等几系列产品,然后有小米,苹果,华为等几个生产这些产品的工厂。用工厂方法模式肯定不行,工厂太多了。

我们来看看抽象工厂,先定义一系列的产品:

public interface IPhone {
void playPhone();
}
public interface IPad {
void playPad();
}
public interface IComputer {
void playComputer();
}
public class ApplePhone implements IPhone{
@Override
public void playPhone() {
System.out.println("苹果手机欢迎您~");
}
}
public class ApplePad implements IPad{
@Override
public void playPad() {
System.out.println("苹果Pad欢迎您~");
}
}
public class AppleComputer implements IComputer{
@Override
public void playComputer() {
System.out.println("苹果电脑欢迎您~");
}
}
public class HuaweiPhone implements IPhone{
@Override
public void playPhone() {
System.out.println("华为手机欢迎您~");
}
}
public class HuaweiPad implements IPad{
@Override
public void playPad() {
System.out.println("华为Pad欢迎您~");
}
}
public class HuaweiComputer implements IComputer{
@Override
public void playComputer() {
System.out.println("华为电脑欢迎您~");
}
}
public class XiaomiPhone implements IPhone{
@Override
public void playPhone() {
System.out.println("小米手机欢迎您~");
}
}
public class XiaomiPad implements IPad{
@Override
public void playPad() {
System.out.println("小米Pad欢迎您~");
}
}
public class XiaomiComputer implements IComputer{
@Override
public void playComputer() {
System.out.println("小米电脑欢迎您~");
}
}

然后定义工厂:

public interface IFactory {
IPhone createPhone();
IPad createPad();
IComputer createComputer();
}
public class AppleFactory implements IFactory{
@Override
public IPhone createPhone() {
return new ApplePhone();
}

@Override
public IPad createPad() {
return new ApplePad();
}

@Override
public IComputer createComputer() {
return new AppleComputer();
}
}
public class HuaweiFactory implements IFactory {
@Override
public IPhone createPhone() {
return new HuaweiPhone();
}

@Override
public IPad createPad() {
return new HuaweiPad();
}

@Override
public IComputer createComputer() {
return new HuaweiComputer();
}
}
public class XiaomiFactory implements IFactory{
@Override
public IPhone createPhone() {
return new XiaomiPhone();
}

@Override
public IPad createPad() {
return new XiaomiPad();
}

@Override
public IComputer createComputer() {
return new XiaomiComputer();
}
}

最后模拟客户:

public class AbstractFactoryDemo {
public static void main(String[] args) {
IFactory huaweiFactory = new HuaweiFactory();
IPhone huaweiPhone = huaweiFactory.createPhone();
IPad huaweiPad = huaweiFactory.createPad();
IComputer huaweiComputer = huaweiFactory.createComputer();
huaweiPhone.playPhone();
huaweiPad.playPad();
huaweiComputer.playComputer();
IFactory appleFactory = new AppleFactory();
IPhone applePhone = appleFactory.createPhone();
IPad applePad = appleFactory.createPad();
IComputer appleComputer = appleFactory.createComputer();
applePhone.playPhone();
applePad.playPad();
appleComputer.playComputer();
IFactory xiaomiFactory = new XiaomiFactory();
IPhone xiaomiPhone = xiaomiFactory.createPhone();
IPad xiaomiPad = xiaomiFactory.createPad();
IComputer xiaomiComputer = xiaomiFactory.createComputer();
xiaomiPhone.playPhone();
xiaomiPad.playPad();
xiaomiComputer.playComputer();
}
}

输出:

华为手机欢迎您~
华为Pad欢迎您~
华为电脑欢迎您~
苹果手机欢迎您~
苹果Pad欢迎您~
苹果电脑欢迎您~
小米手机欢迎您~
小米Pad欢迎您~
小米电脑欢迎您~

Process finished with exit code 0

可以看到这里有9个产品,但只需要三个工厂。抽象工厂的精髓就在于一个工厂负责生产一系列的产品,而不是一个工厂生产一种产品从而减少工厂类也成功解耦了产品和客户。

建造者模式

建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方 法可以构造出不同表现(属性)的对象。

需要用建造者模式的场景

  1. 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量。
  2. 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
  3. 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入指挥者类将创建过程封装在指挥者类中,而不再建造者类和客户类中。
  4. 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

角色

Builder(抽象建造者):它为创建一个产品Product对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是buildPartX(),它们用于创建复杂对象的各个部件;另一类方法是getResult(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口。

ConcreteBuilder(具体建造者):它实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。

Product(产品角色):它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义它的装配过程。

Director(指挥者):指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制),然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。

我们举个例子,比如造手机:

首先定义产品Product:

public class Phone {
private String cpu;
private String gpu;
private String battery;
private String screen;
private String camera;

public String getCpu() {
return cpu;
}

public void setCpu(String cpu) {
this.cpu = cpu;
}

public String getGpu() {
return gpu;
}

public void setGpu(String gpu) {
this.gpu = gpu;
}

public String getBattery() {
return battery;
}

public void setBattery(String battery) {
this.battery = battery;
}

public String getScreen() {
return screen;
}

public void setScreen(String screen) {
this.screen = screen;
}

public String getCamera() {
return camera;
}

public void setCamera(String camera) {
this.camera = camera;
}

@Override
public String toString() {
return "CPU:" + cpu + "\n" + "GPU:" + gpu + "\n" + "Camera:" + camera + "\n" + "Screen:" + screen + "\n" + "Battery:" + battery;
}
}

抽象建造者:

public abstract class Builder {
protected Phone phone = new Phone();

abstract void setScreen(String screen);

abstract void setCpu(String cpu);

abstract void setBattery(String battery);

abstract void setCamera(String camera);

abstract void setGPU(String gpu);

Phone build() {
return phone;
}
}

创建ConcreteBuilder具体建造者:

public class PhoneBuilder extends Builder {

@Override
public void setScreen(String screen) {
phone.setScreen(screen);
}

@Override
public void setCpu(String cpu) {
phone.setCpu(cpu);
}

@Override
public void setBattery(String battery) {
phone.setBattery(battery);
}

@Override
public void setCamera(String camera) {
phone.setCamera(camera);
}

@Override
public void setGPU(String gpu) {
phone.setGpu(gpu);
}
}

构建Director(指挥者):

public class Director {
Phone direct(Builder builder) {
builder.setScreen("6.7英寸 120hz");
builder.setCpu("A16");
builder.setGPU("5核");
builder.setBattery("20w 3279mh");
builder.setCamera("4800万像素");
return builder.build();
}
}

模拟客户端:

public class Client {
public static void main(String[] args) {
Director director = new Director();
Phone phone = director.direct(new PhoneBuilder());
System.out.println(phone);
}
}

输出:

CPU:A16
GPU:5核
Camera:4800万像素
Screen:6.7英寸 120hz
Battery:20w 3279mh

Process finished with exit code 0

这就是建造者模式的全部步骤。

在日常使用中,如果不需要多类型的Builder,Builder更多是作为产品的内部类,还是拿上面的例子,改一下Product,将具体的Builder作为内部类:

public class Phone {
private String cpu;
private String gpu;
private String battery;
private String screen;
private String camera;

public String getCpu() {
return cpu;
}

public void setCpu(String cpu) {
this.cpu = cpu;
}

public String getGpu() {
return gpu;
}

public void setGpu(String gpu) {
this.gpu = gpu;
}

public String getBattery() {
return battery;
}

public void setBattery(String battery) {
this.battery = battery;
}

public String getScreen() {
return screen;
}

public void setScreen(String screen) {
this.screen = screen;
}

public String getCamera() {
return camera;
}

public void setCamera(String camera) {
this.camera = camera;
}

static class Builder{
private final Phone phone = new Phone();
public Builder setScreen(String screen) {
phone.setScreen(screen);
return this;
}


public Builder setCpu(String cpu) {
phone.setCpu(cpu);
return this;
}


public Builder setBattery(String battery) {
phone.setBattery(battery);
return this;
}


public Builder setCamera(String camera) {
phone.setCamera(camera);
return this;
}


public Builder setGPU(String gpu) {
phone.setGpu(gpu);
return this;
}
Phone build() {
return phone;
}
}

@Override
public String toString() {
return "CPU:" + cpu + "\n" + "GPU:" + gpu + "\n" + "Camera:" + camera + "\n" + "Screen:" + screen + "\n" + "Battery:" + battery;
}
}

然后客户端就直接优雅地链式调用:

public class Client {
public static void main(String[] args) {
Phone phone = new Phone.Builder()
.setScreen("6.7英寸 120hz")
.setCpu("A16")
.setGPU("5核")
.setBattery("20w 3279mh")
.setCamera("4800万像素")
.build();
System.out.println(phone);
}
}
CPU:A16
GPU:5核
Camera:4800万像素
Screen:6.7英寸 120hz
Battery:20w 3279mh

Process finished with exit code 0

与工厂模式不同的是,建造者模式更关注产品的内部构造,工厂模式更关注的是生产。

原型模式

定义:原型模式(Prototype)是指使用原型实例指定创建对象的种类,并且通过拷贝这个模型,创建新的对象

java中自带了克隆的方法,类实现Clonable接口,重写clone方法即可。但是java的clone方法对于基本的数据类型是深拷贝,对于引用数据类型的变量是浅拷贝,如果要实现深拷贝就必须一层一层去重写clone方法。

例:

public class User implements Cloneable{
//基本数据类型变量
String name = "王二";
int age = 13;
//引用类型变量
Test test = new Test();

public static void main(String[] args) throws CloneNotSupportedException {
User user = new User();
User clone = (User) user.clone();
clone.name = "张三";
clone.age = 14;
System.out.println("-----基本数据类型是否相等--------");
System.out.println("user.name="+user.name+","+"clone.name="+clone.name);
System.out.println("user.age="+user.age+","+"clone.age="+clone.age);
System.out.println("-----引用类型是否相等--------");
System.out.println("user.test="+user.test+","+"clone.test="+clone.test);
}
}

输出:

-----基本数据类型是否相等--------
user.name=王二,clone.name=张三
user.age=13,clone.age=14
-----引用类型是否相等--------
user.test=prototype.Test@3feba861,clone.test=prototype.Test@3feba861

Process finished with exit code 0

可以看到,基本数据类型是深拷贝,引用类型是浅拷贝,地址没变。假如我改变了clone的test属性那原型的test属性肯定也会改变,这不是原型模式。

而Test类要实现深拷贝就必须实现Cloneable接口,然后User类重写clone方法将test类克隆:

public class Test implements Cloneable{
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class User implements Cloneable{
//基本数据类型变量
String name = "王二";
int age = 13;
//引用类型变量
Test test = new Test();

@Override
protected Object clone() throws CloneNotSupportedException {
User clone = (User) super.clone();
clone.test = (Test) test.clone();
return clone;
}

public static void main(String[] args) throws CloneNotSupportedException {
User user = new User();
User clone = (User) user.clone();
clone.name = "张三";
clone.age = 14;
System.out.println("-----基本数据类型是否相等--------");
System.out.println("user.name="+user.name+","+"clone.name="+clone.name);
System.out.println("user.age="+user.age+","+"clone.age="+clone.age);
System.out.println("-----引用类型是否相等--------");
System.out.println("user.test="+user.test+","+"clone.test="+clone.test);
}
}

输出:

-----基本数据类型是否相等--------
user.name=王二,clone.name=张三
user.age=13,clone.age=14
-----引用类型是否相等--------
user.test=prototype.Test@3feba861,clone.test=prototype.Test@5b480cf9

Process finished with exit code 0

可以看到,test变量地址变了,不是指向的同一个对象。这就实现了标准的克隆。

但是这里就有一个问题了,假如我有很多个引用类型的变量,引用类型变量里面还有引用类型变量,那我要实现深拷贝就得一个一个类地去修改,麻烦不说还容易漏掉。而且每次都去修改原型类侵入性有点大,不符合”开闭原则“。

那么怎么办呢,最简单的一种方式就是序列化和反序列化,怎么做?往下看:

我们只用重写克隆体的clone方法,然后每个引用类型的类要实现Serializable接口才能序列化。然后序列化将其写入文件,再序列化读出文件,这样他们肯定不是指向同一个地址。

public class Test implements  Serializable {

}
public class User implements Cloneable,Serializable {
//基本数据类型变量
String name = "王二";
int age = 13;
//引用类型变量
Test test = new Test();

@Override
protected Object clone() throws CloneNotSupportedException {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}

public static void main(String[] args) throws CloneNotSupportedException {
User user = new User();
User clone = (User) user.clone();
clone.name = "张三";
clone.age = 14;
System.out.println("-----基本数据类型是否相等--------");
System.out.println("user.name=" + user.name + "," + "clone.name=" + clone.name);
System.out.println("user.age=" + user.age + "," + "clone.age=" + clone.age);
System.out.println("-----引用类型是否相等--------");
System.out.println("user.test=" + user.test + "," + "clone.test=" + clone.test);
}
}

输出:

-----基本数据类型是否相等--------
user.name=王二,clone.name=张三
user.age=13,clone.age=14
-----引用类型是否相等--------
user.test=prototype.Test@4bf558aa,clone.test=prototype.Test@7dc5e7b4

Process finished with exit code 0

同样实现了深拷贝,相比第一种方法,这种方法代码侵入性更小,更推荐。

结构型模式

适配器模式

定义:将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

角色

Target目标角色:该角色定义把其他类转化为何种接口

Adaptee源角色:想把谁转化为目标角色,谁就是源角色,是已经存在的,运行良好的接口。

Adaptor适配器角色:适配器模式的核心角色,其他两个角色都是已经存在的,只有这个角色需要新创建,将源角色转化为目标角色。通过类继承或类关联的方式。

举个例子,原本A类有过滤词汇里面脏话的功能,现在新添加了一个新地B类有过滤词汇里面政治敏感的功能,现在要两个功能一起用:

//老的类,就是adaptee
public class ASensitive {
void filterDirty(){
System.out.println("处理脏话词汇......");
}
}
//新的类
public class BSensitive implements Target{

@Override
public void filter() {
System.out.println("处理政治敏感词汇.....");
}
}

然后构建适配器:

//抽象公共的方法
public interface Target {
void filter();
}
//适配老的功能
public class Adapter implements Target{
private ASensitive adaptee = new ASensitive();
public Adapter(ASensitive adaptee) {
this.adaptee = adaptee;
}

@Override
public void filter() {
adaptee.filterDirty();
}
}

客户端测试一起使用:

public class Client {
public static void main(String[] args) {
//添加到一个集合里面
ArrayList<Target> targets = new ArrayList<>();
Target oldFeature = new Adapter(new ASensitive());
Target newFeature = new BSensitive();
targets.add(oldFeature);
targets.add(newFeature);
//这里就模拟了老的功能和新功能一起使用的情况
for (Target target : targets) {
target.filter();
}

}
}

输出:

处理脏话词汇......
处理政治敏感词汇.....

Process finished with exit code 0

上面是适配对象,适配类的话,直接继承即可:

public class Adapter extends ASensitive implements Target{

@Override
public void filter() {
super.filterDirty();
}
}

然后客户端测试,区别就在于少了一个参数:

public class Client {
public static void main(String[] args) {
//添加到一个集合里面
ArrayList<Target> targets = new ArrayList<>();
Target oldFeature = new Adapter();
Target newFeature = new BSensitive();
targets.add(oldFeature);
targets.add(newFeature);
//这里就模拟了老的功能和新功能一起使用的情况
for (Target target : targets) {
target.filter();
}

}
}

输出:

处理脏话词汇......
处理政治敏感词汇.....

Process finished with exit code 0

装饰模式

定义:装饰模式就是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象地功能。他是通过创建一个包装对象,也就是装饰来包裹真实的对象。

角色

  1. 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
  2. 具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。
  3. 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并实现一个与抽象构件接口一致的接口。
  4. 具体装饰(Concrete Decorator)角色:负责给构件对象添加上附加的责任。

这里举个例子,就拿图形来说吧。

定义抽象构件(Component)角色,Shape:

public interface Shape {
void draw();
}

具体构件(Concrete Component)角色:

public class Circle implements Shape{
@Override
public void draw() {
System.out.println("圆形");
}
}
public class Oval implements Shape{
@Override
public void draw() {
System.out.println("椭圆形");
}
}

装饰(Decorator)角色:

public class ShapeDecorator implements Shape {
private Shape shape;

public ShapeDecorator(Shape shape) {
this.shape = shape;
}

@Override
public void draw() {
shape.draw();
}
}

具体装饰(Concrete Decorator)角色:

public class Black extends ShapeDecorator{
public Black(Shape shape) {
super(shape);
}

@Override
public void draw() {
super.draw();
System.out.println("黑色");
}
}
public class Blue extends ShapeDecorator{
public Blue(Shape shape) {
super(shape);
}

@Override
public void draw() {
super.draw();
System.out.println("蓝色");
}
}
public class Stroke extends ShapeDecorator{
public Stroke(Shape shape) {
super(shape);
}

@Override
public void draw() {
super.draw();
System.out.println("描边");
}
}

客户端测试:

public class Client {
public static void main(String[] args) {
//黑色有描边的圆形
Shape circle = new Circle();
circle = new Black(circle);
circle = new Stroke(circle);
circle.draw();
System.out.println("------------------");
//蓝色的椭圆
Shape oval = new Oval();
oval = new Blue(oval);
oval.draw();
}
}

就是一层一层去套娃加功能装饰,输出:

圆形
黑色
描边
------------------
椭圆形
蓝色

Process finished with exit code 0

到这里可能会有人会疑问,这装饰模式怎么和适配器有点像啊?

确实,二者实现结构上有点相似,但是各自实现的效果不同。适配器模式是将原有接口转变成另外一个接口,达成新接口适配老接口从而新老接口都能以一种方式使用也就是复用的目的。而装饰模式相反不改变原有接口而是保留原有接口增强原有接口的功能。

代理模式

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

代理意在客户和对象之间做中介的作用。生活中也不乏这些例子。比如客户和火车站之间通过中介买票。租房子通过中介联系房东等等。

代理模式总共就三个角色,包括抽象主题,委托者和代理者

静态代理

代理者在编译时就能确定的称为静态代理。

举例,客户找中介租房子的例子。

抽象主题

interface Mediator {
void rentHouse();
}

委托者,也就是被代理者房东:

public class Landlord implements Mediator {
@Override
public void rentHouse() {
System.out.println("房东租出房子,1000元/月。");
}
}

代理者,也就是中介:

public class ProxyMediator implements Mediator{
//代理者拥有委托者的引用
private Mediator mediator = new Landlord();

@Override
public void rentHouse() {
System.out.println("代理开始");
mediator.rentHouse();
System.out.println("代理结束");
}
}

客户端测试:

public class Client {
public static void main(String[] args) {
//创建代理者
Mediator proxy = new ProxyMediator();
//通过代理者找房子
proxy.rentHouse();
}
}

输出

中介代理开始找房子.....
房东租出房子,1000元/月。
代理结束。

Process finished with exit code 0

到这里可能又有人会有疑问,我直接调用委托者的方法不行吗?意思就是我直接去找这个房东不行吗?

你知道有这个房东的存在你当然可以直接去找他呀,还省了一笔中介费。但是现在是你不知道这个房东的联系方式,你也不想到处去找,去跑,你直接把你的要求告诉了中介,中介替你去找。这就是代理的作用。客户不需要关注委托者是谁,只需关注需求和功能。

动态代理

代理者在运行时才能确定的称为动态代理。

Java为开发者提供了InvocationHandler,实现该接口重写其invoke 方法即可实现动态代理。

还是拿刚刚的例子,我们现在不知道ProxyMediator的存在。

//抽象主题
interface Mediator {
void rentHouse();
}
//委托者(房东)
public class Landlord implements Mediator {
@Override
public void rentHouse() {
System.out.println("房东租出房子,1000元/月。");
}
}
//客户
public class Client {
public static void main(String[] args) {
//动态创建代理者(反射)
Mediator proxy = (Mediator) Proxy.newProxyInstance(Mediator.class.getClassLoader(), new Class[]{Mediator.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("动态代理开始");
Object invoke = method.invoke(new Landlord());
System.out.println("动态代理结束");
return invoke;
}
});
//代理开始
proxy.rentHouse();
}
}

输出:

动态代理开始
房东租出房子,1000元/月。
动态代理结束

Process finished with exit code 0

当我们不知道代理者的时候就可以使用动态代理。

外观模式

又叫门面模式,提供了一个统一的接口,用来访问子系统中的一群接口。定义了一个高层接口,让子系统更容易使用。

说白了其实就是对子系统的一个统一。比如拍照,假如我们没有手机这个系统,我们拍照要先启动照相机系统,按下快门调用照相机系统拍照,然后调用存储系统存储照片文件等等等等,涉及的子系统很多调用很麻烦。手机就相当于一个统一,将调子系统的功能都封装起来了,你只需按下拍照按钮即可。

现在我们就以这个为例子来写一下代码:

我们有两个子系统,分别是相机和存储:

public class Camera {
void open(){
System.out.println("打开摄像头....");
}
void close(){
System.out.println("关闭摄像头....");
}
void focus(){
System.out.println("聚焦...");
}
void getHead(){
System.out.println("检测人脸...");
}
String picture(){
System.out.println("拍出照片");
return "RQ527的帅照";
}
}
public class Storage {
boolean askPermission(){
System.out.println("申请权限...");
return true;
}
void storePicture(String picture){
System.out.println("存储照片:"+picture);
}
}

使用外观模式提供统一接口拍照简化客户端的调用:

public class Phone {
private Camera camera = new Camera();
private Storage storage = new Storage();

void picture(){
camera.open();
camera.focus();
camera.getHead();
String picture = camera.picture();
boolean b = storage.askPermission();
if (b){
storage.storePicture(picture);
}else {
System.out.println("照片存储失败");
}
camera.close();
}
}

客户端:

public class Client {
public static void main(String[] args) {
Phone phone = new Phone();
phone.picture();
}
}

输出:

打开摄像头....
聚焦...
检测人脸...
拍出照片
申请权限...
存储照片:RQ527的帅照
关闭摄像头....

Process finished with exit code 0

可以看到外观模式统一了之后客户端调用是非常方便的。

桥接模式

桥接模式(Bridge)能将抽象与实现分离,使二者可以各自单独变化而不受对方约束,使用时再将它们组合起来,就像架设桥梁一样连接它们的功能,如此降低了抽象与实现这两个可变维度的耦合度,以保证系统的可扩展性。

桥接模式的核心是将抽象和实现分离。在实现过程中,我们可以通过抽象类或者接口来定义抽象部分,通过另一个抽象类或者接口来定义实现部分。然后,我们可以通过组合的方式将两个部分组合起来,使得它们可以独立地变化。

我们举个例子,假如我们现在设计一个西游记地游戏。

首先定义接口武器,统一方法attack:

public interface Weapon {
void attack();
}

定义另外一个抽象类角色,拥有实现类武器:

public abstract class Role {
protected Weapon weapon;

public Role(Weapon weapon) {
this.weapon = weapon;
}
abstract void attack();
}

然后是两个武器的实现类金箍棒和钉耙:

public class JinGuBang implements Weapon{
@Override
public void attack() {
System.out.println("吃俺老孙一棒!!!");
}
}
public class DingPa implements Weapon{
@Override
public void attack() {
System.out.println("吃俺老猪一耙!!!");
}
}

然后是角色的实现类,悟空和八戒:

public class WuKong extends Role{
public WuKong(Weapon weapon) {
super(weapon);
}
@Override
void attack() {
weapon.attack();
}
}
public class BaJie extends Role{

public BaJie(Weapon weapon) {
super(weapon);
}

@Override
void attack() {
weapon.attack();
}
}

测试:

public class Test {
public static void main(String[] args) {
Role wuKong = new WuKong(new JinGuBang());
wuKong.attack();

Role baJie = new BaJie(new DingPa());
baJie.attack();
}
}

输出:

吃俺老孙一棒!!!
吃俺老猪一耙!!!

Process finished with exit code 0

其实可以看到两个抽象类Role和Weapon,通过组合的方式Role拥有一个Weapon的实例,然后二者的实现类互不影响。这就是桥接模式的实现。

组合模式

组合模式(Composite Design Pattern),将对象组合成树形结构表示整体-部分的层次结构,使得用户对每个单个对象和组合对象的使用具有一致性。

组合模式跟面向对象设计中的组合关系(通过组合来组装两个类),不同。这里讲的“组合模式”,主要是用来处理树形结构数据。这里的“数据”,你可以简单理解为一组对象集合。

角色

  1. Component 抽象根节点,为组合中的对象声明接口
  2. Composite 定义有子节点的那些枝干节点的行为,存储子节点,在 Component 接口中实现与子节点有关的操作
  3. Leaf 在组合中表示叶子节点对象,叶子节点没有子节点,在组合中定义节点对象的行为

举一个例子:请你设计表示一个学校的所有学院,所有学院下面所有的专业。

这属于树形结构,我们就可以使用组合模式。

首先定义Component 抽象根节点,根节点定义了添加删除结点和打印的方法:

public abstract class Component {
protected String name;

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

abstract void add(Component component);
abstract void remove(Component component);
abstract void print();
}

Composite枝干结点定义,枝干结点分别有University(大学)、College(学院),叶子结点(专业):

//大学
public class University extends Component{

private List<Component> colleges = new ArrayList<>();

public University(String name) {
super(name);
}

@Override
void add(Component component) {
colleges.add(component);
}

@Override
void remove(Component component) {
colleges.remove(component);
}

@Override
void print() {
System.out.println("--------"+name+"------------");
for (Component component: colleges){
component.print();
}
}
}
//学院
public class College extends Component {
private List<Component> professions = new ArrayList<>();

public College(String name) {
super(name);
}

@Override
void add(Component component) {
professions.add(component);
}

@Override
void remove(Component component) {
professions.remove(component);
}

@Override
void print() {
System.out.println("--------"+name+"------------");
for (Component component: professions){
component.print();
}
}
}
//专业
public class Profession extends Component{

public Profession(String name) {
super(name);
}

@Override
void add(Component component) {
throw new RuntimeException("叶子结点不支持插入操作!");
}

@Override
void remove(Component component) {
throw new RuntimeException("叶子结点不支持删除操作!");
}

@Override
void print() {
System.out.println(name);
}
}

客户端测试:

public class Test {
public static void main(String[] args) {
University university = new University("重庆邮电大学");

College college = new College("计算机学院");
college.add(new Profession("计算机科学与技术专业"));
college.add(new Profession("人工智能专业"));
college.add(new Profession("网络工程专业"));
university.add(college);

College college1 = new College("软件工程学院");
college1.add(new Profession("信息安全专业"));
college1.add(new Profession("物联网工程专业"));
college1.add(new Profession("软件设计与开发专业"));
university.add(college1);

university.print();
}
}

输出:

--------重庆邮电大学------------
--------计算机学院------------
计算机科学与技术专业
人工智能专业
网络工程专业
--------软件工程学院------------
信息安全专业
物联网工程专业
软件设计与开发专业

Process finished with exit code 0

可以看到,组合模式很好地描述了树形结构中部分和整体的关系,统一处理了一个结点中地多个对象。因为其数据结构要求是树形结构导致了他在日常开发中不是那么常用。

享元模式

定义:使用共享对象可有效地支持大量地细粒度对象。

“享”即共享,”元“即对象,就是共享对象地意思嘛。

适用于存在大量重复对象,但是重复创建销毁对象非常消耗资源的场景,通过共享池共享对象,达到对象共享,避免创建过多对象。享元对象中的字段可分为部分:内部状态(不随环境变化,可共享);外部状态(随环境变化,不可共享)。

角色

  1. 抽象享元(FlyWeight):给出抽象接口,规定所有具体享元角色需要实现的方法
  2. 具体享元(ConcreteFlyWeight):实现抽象享元所规定出的接口;存储内部状态
  3. 不共享具体享元(UnsharedConcreteFlyweight):Flyweight接口使共享成为可能,但它并不强制共享。在Flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点。
  4. 享元工厂(FlyWeightFactory):创建和管理享元角色,保证享元对象可被共享;主要通过HashMap来实现。

我们还是拿图形的那个例子,这次用享元模式优化对象地获取。

首先是FlyWeight,也就是我们抽象出的Shape:

public abstract class Shape {
/**
* 内部状态
*/
protected String name;

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

/**
*
* @param color 外部状态
*/
abstract void draw(String color);
}

这里的name其实就是内部状态不随外部环境变化,被创建时就确定了。color其实就是外部状态,随方法的调用而改变。

然后定义ConcreteFlyWeight,也就是Shape的实现类:

public class Triangle extends Shape{
public Triangle(String name) {
super(name);
}

@Override
public void draw(String color) {
String msg = """
我叫%s。我是%s的三角形。
""";
System.out.printf(msg,name,color);
}
}
public class Oval extends Shape{
public Oval(String name) {
super(name);
}

@Override
public void draw(String color) {
String msg = """
我叫%s。我是%s的椭圆形。
""";
System.out.printf(msg,name,color);
}
}
public class Rectangle extends Shape{

public Rectangle(String name) {
super(name);
}

@Override
public void draw(String color) {
String msg = """
我叫%s。我是%s的矩形;
""";
System.out.printf(msg,name,color);
}
}

然后是FlyWeightFactory享元工厂,就是ShapeFactory:

public class ShapeFactory {
private static Map<String,Shape> shapes = new HashMap<>();
static Shape getShape(String type,String name){
Shape shape = shapes.get(name);
if (shape==null){
switch (type){
case "椭圆形":
Oval oval = new Oval(name);
shapes.put(name,oval);
return oval;
case "三角形":
Triangle triangle = new Triangle(name);
shapes.put(name,triangle);
return triangle;
case "矩形":
Rectangle rectangle = new Rectangle(name);
shapes.put(name,rectangle);
return rectangle;
}
}
return shape;
}
}

这里的Factory可以写成单例模式,我们用Map模拟享元池,每次获取对象先判断享元池中是否存在,存在直接取出,不存在再创建。

客户端测试:

public class Client {
public static void main(String[] args) {
Shape shape = ShapeFactory.getShape("三角形", "阿三");
Shape shape1 = ShapeFactory.getShape("三角形", "阿三");
shape.draw("红色");
shape1.draw("蓝色");
System.out.println("两次取出的对象是否相等:"+(shape==shape1));

Shape shape2 = ShapeFactory.getShape("椭圆形", "阿圆");
Shape shape3 = ShapeFactory.getShape("椭圆形", "阿圆");
shape2.draw("黑色");
shape3.draw("白色");
System.out.println("两次取出的对象是否相等:"+(shape2==shape3));

Shape shape4 = ShapeFactory.getShape("矩形", "阿矩");
Shape shape5 = ShapeFactory.getShape("矩形", "阿正");
shape4.draw("粉色");
shape5.draw("紫色");
System.out.println("两次取出的对象是否相等:"+(shape4==shape5));
}
}

输出:

我叫阿三。我是红色的三角形。
我叫阿三。我是蓝色的三角形。
两次取出的对象是否相等:true
我叫阿圆。我是黑色的椭圆形。
我叫阿圆。我是白色的椭圆形。
两次取出的对象是否相等:true
我叫阿矩。我是粉色的矩形;
我叫阿正。我是紫色的矩形;
两次取出的对象是否相等:false

Process finished with exit code 0

可以看到,固有属性相同的对象只有第一次获取的时候才被创建。color外部状态随外界变化而变化,name内部状态一直不变。这就达到了对象复用的效果。当然实际使用中内部状态不止name,外部状态也不止color,具体根据需求而定。

享元模式被广泛使用,诸如线程池,Integer对象地创建,在-127到128之间的对象已经创建好了,在这之外再重新new 的对象。

行为型设计模式

策略模式

定义:定义一组算法,将每个算法都封装起来,并且他们之间可以互换。

角色

  1. 上下文角色(context):用来操作策略的上下文环境,屏蔽客户端或者高层模块对策略算法的直接访问,封装可能出现的变化。
  2. 抽象策略角色(Strategy):规定策略的算法或者行为
  3. 具体策略角色(ConcreteStrategy):具体策略的算法或者行为的实现

我们以支付为例,支付的方式有支付宝支付,微信支付,银行卡支付。

抽象策略角色(Strategy),这里用Payment接口抽象支付行为:

public interface Payment {
void pay(int amount);
}

具体策略角色(ConcreteStrategy):

public class WechatPay implements Payment {
@Override
public void pay(int amount) {
System.out.println("微信支出" + amount + "元。");
}
}
public class BankPay implements Payment {
@Override
public void pay(int amount) {
System.out.println("银行卡支出" + amount + "元。");
}
}
public class AliPay implements Payment {
@Override
public void pay(int amount) {
System.out.println("支付宝支出" + amount + "一万元。");
}
}

上下文角色(context):

public class PaymentStrategy {
private Payment payment;

public PaymentStrategy(Payment payment) {
this.payment = payment;
}

void execute(int amount){
payment.pay(amount);
}
}

客户端测试:

public class Client {
public static void main(String[] args) {
PaymentStrategy alipay = new PaymentStrategy(new AliPay());
alipay.execute(100000);

PaymentStrategy WxPay = new PaymentStrategy(new WechatPay());
WxPay.execute(99999999);

PaymentStrategy bankPay = new PaymentStrategy(new BankPay());
bankPay.execute(123456789);
}
}
支付宝支出100000一万元。
微信支出99999999元。
银行卡支出123456789元。

Process finished with exit code 0

策略模式适合用于优化方法中大量的if-else语句,而且策略模式对算法和行为封装性更好更便于维护,安全性更高。

模板方法模式

定义:一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。

模板在java中很好实现,就是抽象类和继承的运用。

我们这里用讲PPT作为例子。

首先抽象模板:

public abstract class Presenter {
protected abstract String getSubject();
protected abstract String getName();
protected abstract void talkEachOther();
protected boolean isNeedTalk(){
return true;
};
final public void speech(){
String msg = """
我叫%s,今天我要讲的主题是%s。
""";
System.out.printf(msg,getName(),getSubject());
if (isNeedTalk()){
talkEachOther();
}
}
}

定义了一个模板方法speech,内部调用其他抽象方法作为模板调用,子类无需关心模板的实现,只需实现相应的步骤即可。还定义了一个子类可以选择是否重写的方法isNeedTalk用来决定是否调用talkEachOther方法,我们称这个函数为钩子函数。

具体的演讲者:

public class ZhangSan extends Presenter{
private boolean isNeedTalk;

public ZhangSan(boolean isNeedTalk) {
this.isNeedTalk = isNeedTalk;
}

@Override
protected String getSubject() {
return "科技改变生活";
}

@Override
protected String getName() {
return "张三";
}

@Override
protected void talkEachOther() {
System.out.println("同学们激烈当今前沿科技.....");
}

@Override
protected boolean isNeedTalk() {
return isNeedTalk;
}
}
public class LiSi extends Presenter{

@Override
protected String getSubject() {
return "人工智能";
}

@Override
protected String getName() {
return "李四";
}

@Override
protected void talkEachOther() {
System.out.println("同学们激烈讨论人工智能是否改变生活......");
}
}

测试:

public class Client {
public static void main(String[] args) {
ZhangSan zhangSan = new ZhangSan(false);
zhangSan.speech();
System.out.println("-------------------------------");
LiSi liSi = new LiSi();
liSi.speech();
}
}
我叫张三,今天我要讲的主题是科技改变生活。
-------------------------------
我叫李四,今天我要讲的主题是人工智能。
同学们激烈讨论人工智能是否改变生活......

Process finished with exit code 0

在java的特性上实现模板方法模式是比较简单的,模板方法在日常开发中很常用,在一定程度上能减少代码量,提高代码复用性。

观察者模式

定义:观察者模式定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。观察者模式主要有两个对象,主题(被观察者)和订阅者(观察者),这是一个一对多的关系,同一个主题可以有多个订阅者,当主题发生改变时,每个订阅者都能收到消息通知并执行对应的逻辑。

对于主题(被观察者)而言,需要提供注册、去注册、通知观察者的功能。

对于订阅者(观察者)而言,需要提供统一的接口让主题在通知时调用。

我们拿天气订阅来举例子。

首先抽象观察者:

public interface Observer {
void onChanged();
}

具体的观察者:

public class WangEr implements Observer{
@Override
public void onChanged() {
System.out.println("王二收到了天气更新");
}
}
public class WangWu implements Observer{
@Override
public void onChanged() {
System.out.println("王五收到了天气更新");
}
}

抽象被观察者:

public interface Subject {
void addObserver(Observer observer);
boolean removeObserver(Observer observer);
void removeAllObservers();
void notifyObserver(int i);
void notifyAllObserver();
}

被观察者提供添加、删除观察者的方法,通知观察者的方法。

天气主题(被观察者):

public class Weather implements Subject{
private List<Observer> observers = new ArrayList<>();
@Override
public void addObserver(Observer observer) {
observers.add(observer);
}

@Override
public boolean removeObserver(Observer observer) {
return observers.remove(observer);
}

@Override
public void removeAllObservers() {
observers.clear();
}

@Override
public void notifyObserver(int i) {
Observer observer = observers.get(i);
if (observer!=null){
observer.onChanged();
}
}

@Override
public void notifyAllObserver() {
for (Observer observer:observers){
observer.onChanged();
}
}

public void updateWeather(){
System.out.println("天气更新,开始通知观察者....");
notifyAllObserver();
System.out.println("通知完毕。");
}
}

被观察者(天气)提供更新天气的方法,内部调用通知观察者的逻辑。

客户端测试:

public class Client {
public static void main(String[] args) {
Weather weather = new Weather();
weather.addObserver(new WangWu());
weather.addObserver(new WangEr());
weather.addObserver(() -> System.out.println("匿名用户收到天气更新。"));
weather.updateWeather();
}
}
天气更新,开始通知观察者....
王五收到了天气更新
王二收到了天气更新
匿名用户收到天气更新。
通知完毕。

Process finished with exit code 0

观察者模式降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。目标与观察者之间建立了一套触发机制。观察者模式精髓在于理解接口回调。

迭代子模式

定义:迭代子模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象。我们常见的集合有很多种类,其顶层数据存储和组织方式的不同导致了我们在对数据进行遍历的时候存在一些差异,迭代器模式就是通过实现某种统一的方式来实现对不同的集合的遍历,同时又不暴露出其底层的数据存储和组织方式。

常规的for循遍历对于集合来说耦合度太大,不便于维护,假如集合被修改,for循环也需要更改。

角色

  • 迭代器角色(Iterator): 负责定义访问和遍历元素的接口。
  • 具体迭代器角色(Concrete Iterator):实现迭代器接口,并要记录遍历中的当前位置。
  • 容器角色(Container): 负责提供创建具体迭代器角色的接口。
  • 具体容器角色(Concrete Container):实现创建具体迭代器角色的接口, 这个具体迭代器角色与该容器的结构相关。

下面我们来写一写我们自己的迭代器。

抽象迭代器(Iterator):

public interface Iterator<T> {
boolean hasNext();
T first();
T previous();
T next();
}

迭代器有四个方法,判断是否有下一个元素,获取第一个元素、下一个元素、前一个元素。

具体的迭代器:

public class MyIterator<T> implements Iterator<T>{
private List<T> list;
private int index = 0;

public MyIterator(List<T> list) {
this.list = list;
}

@Override
public boolean hasNext() {
return index != list.size() - 1;
}

@Override
public T first() {
if (!list.isEmpty()) return list.get(0);
return null;
}

@Override
public T previous() {
if (index!=0) return list.get(index-1);
return null;
}

@Override
public T next() {
if (index<list.size()) return list.get(++index);
return null;
}
}

对四个方法具体实现,内部维护一个list和index,对外统一获取元素的方法。

抽象容器:

public abstract class Container<T> {
public abstract Iterator<T> iterator();
abstract void add(T data);
abstract boolean remove(T data);
}

三个方法,添加、删除元素,获取迭代器。

具体容器:

public class MyList<T> extends Container<T>{
private List<T> list = new ArrayList<>();
@Override
public Iterator<T> iterator() {
return new MyIterator<T>(list);
}

@Override
void add(T data) {
list.add(data);
}

@Override
boolean remove(T data) {
return list.remove(data);
}
}

客户端测试:

public class Client {
public static void main(String[] args) {
MyList<Integer> list = new MyList<Integer>();
list.add(1);
list.add(5);
list.add(6);
list.add(8);
list.add(4);
list.add(2);


Iterator<Integer> iterator = list.iterator();
System.out.println("第一个数" + iterator.first());
System.out.println("--------------------------");
while (iterator.hasNext()) {
System.out.println("之前的数" + iterator.previous());
System.out.println("下一个数" + iterator.next());
System.out.println("-----------------------------");
}
}
}
第一个数1
--------------------------
之前的数null
下一个数5
-----------------------------
之前的数1
下一个数6
-----------------------------
之前的数5
下一个数8
-----------------------------
之前的数6
下一个数4
-----------------------------
之前的数8
下一个数2
-----------------------------

Process finished with exit code 0

实现了我们统一迭代的需求。这里只是实例迭代子模式的使用,实际上我们不用自己去封装,像List,Map等java提供了封装好的Iterator。

责任链模式

定义责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。

一般责任链是用链表的形式将每个处理者串起来,我比较喜欢用集合,然后一条链将他们串起来,这也是Okhttp责任链的做法。

角色

  • 抽象责任链(Chain):抽象责任链的传递方法,提供获取请求的接口。
  • 具体责任链:实现抽象责任链,串起每个处理者。
  • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法。
  • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

我们这里举个例子,发个消息然后让每个处理者决定是否处理。

首先抽象责任链:

public interface Chain<T> {
T getRequest();
void process();
}

提供两个方法,一个是获取数据,一个是将请求传递。

抽象处理者(Handler)角色:

public interface Handler<T> {
void handle(Chain<T> chain);
}

消息bean:

public class Message {
private String content;
private int level;

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public int getLevel() {
return level;
}

public void setLevel(int level) {
this.level = level;
}
}

责任链的具体实现:

public class RealChain implements Chain<Message> {
private final List<Handler<Message>> interceptors;
private final int index;
private Message message;

public RealChain(List<Handler<Message>> interceptors, int index, Message message) {
this.interceptors = interceptors;
this.index = index;
this.message = message;
}

@Override
public Message getRequest() {
return message;
}

@Override
public void process() {
if (index >= interceptors.size()) return;
RealChain next = new RealChain(interceptors, index + 1, message);
interceptors.get(index).handle(next);
}
}

拥有三个属性,处理者集合,当前处理者索引和请求体message。process将每个处理者串起来。

模拟具体的处理者:

public class ConcreteHandler1 implements Handler<Message> {
@Override
public void handle(Chain<Message> chain) {
Message request = chain.getRequest();
System.out.println("传递到处理者1,内容:" + request.getContent() + ",等级:" + request.getLevel());
request.setContent("我处理者1处理了消息。");
chain.process();
}
}
public class ConcreteHandler2 implements Handler<Message>{
@Override
public void handle(Chain<Message> chain) {
Message request = chain.getRequest();
System.out.println("传递到处理者2,内容:" + request.getContent() + ",等级:" + request.getLevel());
request.setContent("我处理者2处理了消息。");
chain.process();
}
}
public class ConcreteHandler3 implements Handler<Message>{
@Override
public void handle(Chain<Message> chain) {
Message request = chain.getRequest();
System.out.println("传递到处理者3,内容:" + request.getContent() + ",等级:" + request.getLevel());
request.setContent("我处理者3处理了消息。");
chain.process();
}
}

chain.process();即表示将请求传递,我这里并没有拦截,实际开发中可以自行判断是否拦截。

客户端测试:

public class Client {
public static void main(String[] args) {
ArrayList<Handler<Message>> chains = new ArrayList<>();
chains.add(new ConcreteHandler1());
chains.add(new ConcreteHandler2());
chains.add(new ConcreteHandler3());
Message message = new Message();
message.setContent("我是消息的具体内容。");
message.setLevel(99);
new RealChain(chains, 0, message).process();
System.out.println("责任链传递完毕,message:内容:" + message.getContent());
}
}
传递到处理者1,内容:我是消息的具体内容。,等级:99
传递到处理者2,内容:我处理者1处理了消息。,等级:99
传递到处理者3,内容:我处理者2处理了消息。,等级:99
责任链传递完毕,message:内容:我处理者3处理了消息。

Process finished with exit code 0

责任链适合用于多流程控制,权限的控制。更有助于优化if-else语句,方便维护和扩展。

命令模式

定义:命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

角色

  • 抽象接受者(Receiver):命令接受者的抽象,提供执行的方法。
  • 具体接受者(ConcreteReceiever):抽象接受者的具体实现,内部实现命令执行的方法。
  • 抽象命令角色(Command):下发命令的角色,执行的命令在这里声明。
  • 具体命令角色(ConcreteCommand):命令角色的具体实现,持有Receiver,收到命令后下发给每个Receiver
  • 调用者角色(Invoker):命令的发动者和调用者。

我们用代码简单实现一下:

首先是抽象接受者和命令者:

public interface Receiver {
void action();
}
public interface Command {
void run();
}

具体的接受者:

public class ConcreteReceiver1 implements Receiver{
@Override
public void action() {
System.out.println("接收者1执行命令。");
}
}
public class ConcreteReceiver2 implements Receiver{
@Override
public void action() {
System.out.println("接收者2执行命令。");
}
}
public class ConcreteReceiver3 implements Receiver{
@Override
public void action() {
System.out.println("接收者3执行命令。");
}
}

具体的命令角色:

public class ConcreteCommand implements Command {
private List<Receiver> receivers;

public ConcreteCommand(List<Receiver> receivers) {
this.receivers = receivers;
}

@Override
public void run() {
for (Receiver receiver : receivers) {
receiver.action();
}
}
}

命令的发动者和调用者,Invoker:

public class Invoker {
private Command command;

public Invoker(Command command) {
this.command = command;
}

public void execute(){
command.run();
}
}

客户端测试:

public class Client {
public static void main(String[] args) {
List<Receiver> receivers = new ArrayList<>();
receivers.add(new ConcreteReceiver1());
receivers.add(new ConcreteReceiver2());
receivers.add(new ConcreteReceiver3());
Invoker invoker = new Invoker(new ConcreteCommand(receivers));
invoker.execute();
}
}
接收者1执行命令。
接收者2执行命令。
接收者3执行命令。

Process finished with exit code 0

备忘录模式

定义: 在不破坏封装性的前提下, 捕获一个对象的内部状态, 并在该对象之外保存这个状态. 这样以后就可将该对象回复到原先保存的状态。

角色

  • Originator 发起人角色: 记录当前时刻的内部状态, 负责定义哪些属于备份范围的状态, 负责创建和恢复备忘录数据
  • Memento 备忘录角色: 负责存储 发起人对象的内部状态, 在需要的时候提供发起人需要的内部状态
  • Caretaker 备忘录管理员角色: 对备忘录进行管理、保存和提供备忘录.

还是老规矩写个demo:

Memento 备忘录角色:

public class Memento {
private String state;

public Memento(String state) {
this.state = state;
}

public String getState() {
return state;
}

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

Originator 发起人角色:

public class Originator {
private String state;

public String getState() {
return state;
}

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

public Memento createMemento() {
return new Memento(state);
}

public void restoreMemento(Memento memento) {
state = memento.getState();
}
}

Caretaker 备忘录管理员角色:

public class Caretaker {
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) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState("状态1");
caretaker.setMemento(originator.createMemento());
originator.setState("状态2");
System.out.println("恢复状态之前,状态:" + originator.getState());
originator.restoreMemento(caretaker.getMemento());
System.out.println("恢复状态之后,状态:" + originator.getState());

}
}
恢复状态之前,状态:状态2
恢复状态之后,状态:状态1

Process finished with exit code 0

备忘录模式用的比较多,像游戏存档,文档回退等等都用到了备忘录模式,demo实现的比较简单,篇幅原因真正的实现细节这里就不多说了。

状态模式

定义:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。

角色

  • State:抽象状态类或者接口,其中的方法表示对应状态下的行为。
  • StateA、StateBState的具体实现类,以实现对应状态下具体的行为。
  • Context:维护当前对象所对应的状态。

我们拿出租车举例子。

首先抽象State:

public interface State {
void run();
}

然后是出租车的两个状态:

public class NoPassengerState implements State{
@Override
public void run() {
System.out.println("空车,待接客。");
}
}
public class HasPassengerState implements State{
@Override
public void run() {
System.out.println("有客,不接单。");
}
}

出租车Context:

public class TaxiContext {
private State state;

public TaxiContext(State state) {
this.state = state;
}

public void receivePassenger(){
state = new HasPassengerState();
}
public void completeCarry(){
state = new NoPassengerState();
}
public void receiveOrder(){
System.out.println("----------------------");
System.out.println("收到订单...");
state.run();
}
}

客户端测试:

public class Client {
public static void main(String[] args) {
TaxiContext taxiContext = new TaxiContext(new NoPassengerState());
taxiContext.receiveOrder();
taxiContext.receivePassenger();
taxiContext.receiveOrder();
taxiContext.completeCarry();
taxiContext.receiveOrder();
}
}
----------------------
收到订单...
空车,待接客。
----------------------
收到订单...
有客,不接单。
----------------------
收到订单...
空车,待接客。

Process finished with exit code 0

访问者模式

定义:封装一些作用于某种数据结构中的各元素操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

角色

  • 元素类(Element):是一个抽象类或者接口,里面会定义一个接收(accept)访问者的抽象方法,使得每一个元素能被访问者访问。
  • 具体元素(ConcreteElement):继承或实现元素类,实现接收方法。
  • 访问者(Visitor):一般是一个抽象类,里面涵括了可以访问每个具体元素的方法,使得访问者可以访问每个具体元素(一般有几个具体元素就会有几个这个样的方法,这些方法的方法名相同参数不同,参数都是具体元素,所以一般来说具体元素的种类应该是比较固定的)。
  • 具体访问者(ConcreteVisitor):访问者的具体实现

模版代码:

元素类和具体元素:

public abstract class Element {
protected String content;

public Element(String content) {
this.content = content;
}

abstract void accept(Visitor visitor);
}
public class ConcreteElement1 extends Element{


public ConcreteElement1(String content) {
super(content);
}

@Override
void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class ConcreteElement2 extends Element{

public ConcreteElement2(String content) {
super(content);
}

@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}

访问者和具体访问者:

public interface Visitor {
void visit(ConcreteElement1 element);
void visit(ConcreteElement2 element2);
}
public class ConcreteVisitor implements Visitor{
@Override
public void visit(ConcreteElement1 element) {
System.out.println("访问到元素1,信息:"+element.content);
}

@Override
public void visit(ConcreteElement2 element) {
System.out.println("访问到元素2,信息:"+element.content);
}
}

测试:

public class Client {
public static void main(String[] args) {
ConcreteElement1 element1 = new ConcreteElement1("元素1的内容");
ConcreteElement2 element2 = new ConcreteElement2("元素2的内容");
element1.accept(new ConcreteVisitor());
element2.accept(new ConcreteVisitor());
}
}
访问到元素1,信息:元素1的内容
访问到元素2,信息:元素2的内容

Process finished with exit code 0

访问者模式适合数据结构稳定,作用于数据结构的操作经常变化的时候,对于新增的元素会非常困难,实现起来也比较复杂。

中介者模式

定义:用一个中介者对象来封装一系列的对象交互。中介者使得各对象不需要显式地相互引用,从而使其松散耦合,而且可以独立地改变它们之间的交互。

角色

  • Mediator:中介者定义一个接口用于与各同事(Colleague)对象通信。
  • ConcreteMediator:具体中介者通过协调各同事对象实现协作行为,了解并维护它的各个同事。
  • Colleague:抽象同事类。
  • Colleagueclass:具体同事类。每个具体同事类都只需要知道自己的行为即可,但是他们都需要认识中介者。

模版代码,我们以同事之间发消息为例:

Colleague和Mediator定义:

public abstract class Colleague {
protected Mediator mediator;

public Colleague(Mediator mediator) {
this.mediator = mediator;
}

abstract void contact(String content);
}
public interface Mediator {
void notify(String content, Colleague colleague);
}

同事A和同事B的定义:

public class ColleagueA extends Colleague{
public ColleagueA(Mediator mediator) {
super(mediator);
}

@Override
public void contact(String content) {
mediator.notify(content,this);
}
}
public class ColleagueB extends Colleague{

public ColleagueB(Mediator mediator) {
super(mediator);
}

@Override
void contact(String content) {
mediator.notify(content,this);
}
}

具体中介者的定义:

public class ConcreteMediator implements Mediator {
ColleagueA colleagueA;
ColleagueB colleagueB;

public ColleagueA getColleagueA() {
return colleagueA;
}

public void setColleagueA(ColleagueA colleagueA) {
this.colleagueA = colleagueA;
}

public ColleagueB getColleagueB() {
return colleagueB;
}

public void setColleagueB(ColleagueB colleagueB) {
this.colleagueB = colleagueB;
}

@Override
public void notify(String content, Colleague colleague) {
if (colleague == colleagueA) {
System.out.println("同事B收到消息A的消息:" + content);
} else if (colleague == colleagueB) {
System.out.println("同事A收到消息B的消息:" + content);
}
}
}

客户端测试:

public class Client {
public static void main(String[] args) {
ConcreteMediator mediator = new ConcreteMediator();
ColleagueA colleagueA = new ColleagueA(mediator);
ColleagueB colleagueB = new ColleagueB(mediator);
mediator.setColleagueA(colleagueA);
mediator.setColleagueB(colleagueB);
colleagueA.contact("同事A发的消息");
colleagueB.contact("同事B发的消息");
}
}
同事B收到消息A的消息:同事A发的消息
同事A收到消息B的消息:同事B发的消息

Process finished with exit code 0

中介者模式和代理模式有点类似,主要区别在于中介者模式是一对多的关系,解耦的是多个复杂对象之间的复杂交互,代理模式在于一对一,委托者和代理者是一一对应的。

解释器模式

定义:指给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。

角色

  • 抽象表达式(Expression)角色:声明一个所有的具体表达式角色都需要实现的抽象接口。
  • 终结符表达式(Terminal Expression)角色:实现了抽象表达式角色所要求的接口,主要是一个解释()方法。
  • 非终结符表达式(Nonterminal Expression)角色:文法中的每一条规则都需要一个具体的非终结符表达式。
  • 环境(Context)角色:这个角色的任务一般是用来存放文法中各个终结符所对应的具体值。

我们以解释java语句为例:

首先抽象表达式:

public interface Expression {
boolean explain(String context);
}

终结符表达式:

public class TerminalExpression implements Expression{
private String data;

public TerminalExpression(String data) {
this.data = data;
}

public void setData(String data) {
this.data = data;
}

@Override
public boolean explain(String context) {
return context.contains(data);
}
}

非终结者表达式:

public class AndExpression implements Expression {
private Expression expression1;
private Expression expression2;

public AndExpression(Expression expression1, Expression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}

@Override
public boolean explain(String context) {
return expression1.explain(context) && expression2.explain(context);
}
}
public class OrExpression implements Expression {
private Expression expression1;
private Expression expression2;

public OrExpression(Expression expression1, Expression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}

@Override
public boolean explain(String context) {
return expression1.explain(context) || expression2.explain(context);
}
}

环境Context这里就用字符串来代表java语言环境了。

客户端测试:

public class Client {
public static void main(String[] args) {
TerminalExpression aPublic = new TerminalExpression("public");
TerminalExpression aVoid = new TerminalExpression("void");
AndExpression isVoidFun = new AndExpression(aPublic, aVoid);

TerminalExpression aClass = new TerminalExpression("class");
TerminalExpression anInterface = new TerminalExpression("interface");
OrExpression isClassOrInterface = new OrExpression(aClass, anInterface);

String javaVoidFun = "public void test(){}";
String kotlinVoidFun = "fun test():Unit{}";
String javaClass = "class Test{}";
String javaInterface = "interface Expression{}";

System.out.println("\"" + javaVoidFun + "\"" + "是无返回值方法?:" + isVoidFun.explain(javaVoidFun));
System.out.println("\"" + kotlinVoidFun + "\"" + "是无返回值方法?:" + isVoidFun.explain(kotlinVoidFun));

System.out.println("\"" + javaClass + "\"" + "是类或者接口?:" + isClassOrInterface.explain(javaClass));
System.out.println("\"" + javaInterface + "\"" + "是类或者接口?:" + isClassOrInterface.explain(javaInterface));

}
}
"public void test(){}"是无返回值方法?:true
"fun test():Unit{}"是无返回值方法?:false
"class Test{}"是类或者接口?:true
"interface Expression{}"是类或者接口?:true

Process finished with exit code 0

解释模式应用场景很多,常见的有SQL解析,xml解析等等等等。

学完总结

23种设计模式在实际应用场景中常用的模式也就几种,比如工厂模式,建造者模式,代理模式,享元模式,观察者模式等等,其他模式不必过多研究,遇到再来琢磨即可。设计模式一个重要思想是面向接口编程,面向抽象编程,最大程度解耦。设计模式其实也是java三大特性的灵活运用。设计模式代码虽然耦合度低,扩展性好,但不可过度设计,过度设计只会写出虚而不实的代码,让人感觉是为了设计而设计。设计模式是在不断地迭代,不断地重构中考虑的。实际开发中,能简则简,以别人能看懂你写的代码为主,应用设计模式往往是多种设计模式组合使用,比如工厂模式+原型模式,建造者模式+代理模式。