UML类图
概述
类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。
类图的作用
- 在软件工程中,类图是一种静态的结构图,描述了系统的类的集合,类的属性和类之间的关系,可以简化了人们对系统的理解;
- 类图是系统分析和设计阶段的重要产物,是系统编码和测试的重要模型。
类图表示法
类的表示方式
在UML类图中,类使用包含类名、属性(field) 和方法(method) 且带有分割线的矩形来表示,比如
下图表示一个Employee类,它包含name,age和address这3个属性,以及work()方法。
属性/方法名称前加的加号和减号表示了这个属性/方法的可见性,UML类图中表示可见性的符号有三种:
- +:表示public
- -:表示private
- #:表示protected
属性的完整表示方式是: 可见性 名称 :类型 [ = 缺省值]
方法的完整表示方式是: 可见性 名称(参数列表) [ : 返回类型]
注意:
1,中括号中的内容表示是可选的
2,也有将类型放在变量名前面,返回值类型放在方法名前面
例如 :
上图Demo类定义了三个方法:
- method()方法:修饰符为public,没有参数,没有返回值。
- method1()方法:修饰符为private,没有参数,返回值类型为String。
- method2()方法:修饰符为protected,接收两个参数,第一个参数类型为int,第二个参数类型为String,返回值类型是int。
类与类之间关系的表示方式
关联关系
关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系。
关联又可以分为单向关联,双向关联,自关联。
单向关联
在UML类图中单向关联用一个带箭头的实线表示。上图表示每个顾客都有一个地址,这通过让
Customer类持有一个类型为Address的成员变量类实现。
双向关联
双向关联就是双方各自持有对方类型的成员变量。
在UML类图中,双向关联用一个不带箭头的直线表示。上图中在Customer类中维护一个
List,表示一个顾客可以购买多个商品;在Product类中维护一个Customer类型的成员
变量表示这个产品被哪个顾客所购买。
自关联
自关联在UML类图中用一个带有箭头且指向自身的线表示。上图的意思就是Node类包含类型为Node的成员变量,也就是"自己包含自己"。
聚合关系
聚合关系是关联关系的一种,是强关联关系,是整体和部分之间的关系。
聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。
在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。下图所示是大学和教师的
关系图 :
组合关系
组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。
在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,没有了头,嘴也就不存在了。
在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。下图所示是头和嘴的关系图:
依赖关系
依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示是司机和汽车的关系图,司机驾驶汽车:
继承关系
继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。
在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。例如,Student 类和 Teacher 类都是 Person 类的子类,其类图如下图所示:
实现关系
实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如,汽车和船实现了交通工具,其类图如下图所示:
软件设计原则
开闭原则
对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
想要达到这样的效果,需要使用接口和抽象类。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
java
public abstract class AbstractSkin {
//显示的方法
public abstract void display();
}
java
public class DefaultSkin extends AbstractSkin {
@Override
public void display() {
System.out.println("默认皮肤");
}
}
java
public class MySkin extends AbstractSkin{
@Override
public void display() {
System.out.println("我的皮肤");
}
}
java
public class SougouInput {
private AbstractSkin skin;
public void setSkin(AbstractSkin skin) {
this.skin = skin;
}
public void display() {
skin.display();
}
}
java
public class Client {
public static void main(String[] args) {
//创建搜狗输入法
SougouInput sougouInput = new SougouInput();
//创建皮肤
//AbstractSkin skin = new DefaultSkin();
AbstractSkin skin = new MySkin();
//设置皮肤
sougouInput.setSkin(skin);
//显示皮肤
sougouInput.display();
}
}
里氏代换原则
里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
java
public interface Quadrilateral {
//获取长
double getLength();
//获取宽
double getWidth();
}
java
public class Rectangle implements Quadrilateral {
private double length;
private double width;
public void setLength(double length) {
this.length = length;
}
public void setWidth(double width) {
this.width = width;
}
@Override
public double getLength() {
return length;
}
@Override
public double getWidth() {
return width;
}
}
java
public class Square implements Quadrilateral {
private double side;
public double getSide() {
return side;
}
public void setSide(double side) {
this.side = side;
}
@Override
public double getLength() {
return side;
}
@Override
public double getWidth() {
return side;
}
}
java
public class RectangleDemo {
public static void main(String[] args) {
//创建长方形对象
Rectangle rectangle = new Rectangle();
//设置长和宽
rectangle.setLength(20);
rectangle.setWidth(10);
//调用方法进行扩展操作
resize(rectangle);
//打印长和宽
printLengthAndWidth(rectangle);
}
//扩宽的方法
public static void resize(Rectangle rectangle) {
//判断宽度是否小于长度,如果小于则将宽度+1
while (rectangle.getLength() >= rectangle.getWidth()) {
rectangle.setWidth(rectangle.getWidth() + 1);
}
}
//打印长和宽
public static void printLengthAndWidth(Quadrilateral quadrilateral) {
System.out.println(quadrilateral.getLength());
System.out.println(quadrilateral.getWidth());
}
}
依赖倒转原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
java
public interface Cpu {
//运行cpu
public void run();
}
java
public class IntelCpu implements Cpu {
public void run() {
System.out.println("使用Intel处理器");
}
}
java
public interface HardDisk {
//存储数据
public void save(String data);
//获取数据
public String get();
}
java
public class XiJieHardDisk implements HardDisk {
public void save(String data) {
System.out.println("使用希捷硬盘存储数据" + data);
}
public String get() {
System.out.println("使用希捷希捷硬盘取数据");
return "数据";
}
}
java
public interface Memory {
//存储数据
public void save();
}
java
public class KingstonMemory implements Memory {
public void save() {
System.out.println("使用金士顿作为内存条");
}
}
java
public class Computer {
private HardDisk hardDisk;
private Memory memory;
private Cpu cpu;
public HardDisk getHardDisk() {
return hardDisk;
}
public void setHardDisk(HardDisk hardDisk) {
this.hardDisk = hardDisk;
}
public Memory getMemory() {
return memory;
}
public void setMemory(Memory memory) {
this.memory = memory;
}
public Cpu getCpu() {
return cpu;
}
public void setCpu(Cpu cpu) {
this.cpu = cpu;
}
public void run() {
System.out.println("计算机工作");
String data = hardDisk.get();
System.out.println("从硬盘中获取的数据为:" + data);
cpu.run();
memory.save();
}
}
java
public class ComputerDemo {
public static void main(String[] args) {
//创建计算机的组件对象
HardDisk hardDisk = new XiJieHardDisk();
Memory memory = new KingstonMemory();
Cpu cpu = new IntelCpu();
//创建计算机对象
Computer computer = new Computer();
//组装计算机
computer.setCpu(cpu);
computer.setHardDisk(hardDisk);
computer.setMemory(memory);
//运行计算机
computer.run();
}
}
接口隔离原则
客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上。
java
public interface Waterproof {
void waterproof();
}
java
public interface Fireproof {
void fireproof();
}
java
public interface AntiTheft {
void antiTheft();
}
java
public class HeiMaSafetyDoor implements AntiTheft, Fireproof, Waterproof {
public void antiTheft() {
System.out.println("防盗");
}
public void fireproof() {
System.out.println("防火");
}
public void waterproof() {
System.out.println("防水");
}
}
java
public class ItcastSafetyDoor implements AntiTheft, Fireproof {
public void antiTheft() {
System.out.println("防盗");
}
public void fireproof() {
System.out.println("防火");
}
}
java
public class Client {
public static void main(String[] args) {
//创建安全门对象
HeiMaSafetyDoor door = new HeiMaSafetyDoor();
//调用功能
door.antiTheft();
door.fireproof();
door.waterproof();
System.out.println("====================");
ItcastSafetyDoor door1=new ItcastSafetyDoor();
door1.antiTheft();
door1.fireproof();
}
}
迪米特法则
迪米特法则又叫最少知识原则。
其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
java
public class Star {
private String name;
public Star(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
java
public class Fans {
private String name;
public Fans(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
java
public class Company {
private String name;
public Company(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
java
public class Agent {
private Star star;
private Fans fans;
private Company company;
public void setStar(Star star) {
this.star = star;
}
public void setFans(Fans fans) {
this.fans = fans;
}
public void setCompany(Company company) {
this.company = company;
}
//和粉丝见面的方法
public void meeting() {
System.out.println(fans.getName() + "与明星" + star.getName() + "见面 了。");
}
//和公司洽谈业务的方法
public void business() {
System.out.println(company.getName() + "与明星" + star.getName() + "洽淡业务。");
}
}
java
public class Client {
public static void main(String[] args) {
//创建经纪人类
Agent agent = new Agent();
//创建明星对象
Star star = new Star("林青霞");
agent.setStar(star);
//创建粉丝对象
Fans fans = new Fans("王祖贤");
agent.setFans(fans);
//创建公司对象
Company company = new Company("阿里巴巴");
agent.setCompany(company);
//和粉丝见面
agent.meeting();
//和媒体公司洽谈业务
agent.business();
}
}
合成复用原则
合成复用原则是指:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
通常类的复用分为继承复用和合成复用两种:
继承复用虽然有简单和易实现的优点,但它也存在以下缺点:
继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为"白箱"复用。
子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可
能发生变化。
采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为"黑箱" 复用。
对象间的耦合度低。可以在类的成员位置声明抽象。
复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
单例模式
概述
单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例设计模式分类两种:
饿汉式 :类加载就会导致该单实例对象被创建 。
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建。
饿汉式
静态变量方式
java
public class Singleton {
// 1.私有构造方法
private Singleton() {}
// 2.创建本类对象
private static Singleton instance = new Singleton();
// 3.提供公共的访问方式,让外界获取该对象
public static Singleton getInstance() {
return instance;
}
}
该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
java
public class Client {
public static void main(String[] args) {
//创建Singleton类的对象
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
//判断获取的对象是否是同一个
System.out.println(instance == instance1);
}
}
静态代码块方式
java
public class Singleton {
// 1. 构造器私有化
private Singleton() {
}
// 2. 本类内部创建对象实例
private static Singleton instance;//null
// 3.在静态代码块中复制
static {
instance = new Singleton();
}
// 4. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本上一样,当然该方式也存在内存浪费问题。
懒汉式
线程不安全
java
public class Singleton {
//私有构造方法
private Singleton() {
}
//声明Singleton类型的变量instance
private static Singleton instance;//只是声明一个该类型的变量,并没有进行赋值
//对外提供访问方式
public static Singleton getInstance() {
//判断instance是否为null,如果为null,则创建对象并赋值给instance,
// 如果不为null,则直接返回instance
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
从上面代码可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。
线程安全
java
public class Singleton {
//私有构造方法
private Singleton() {
}
//声明Singleton类型的变量instance
private static Singleton instance;//只是声明一个该类型的变量,并没有进行赋值
//对外提供访问方式
public static synchronized Singleton getInstance() {
//判断instance是否为null,如果为null,则创建对象并赋值给instance,
// 如果不为null,则直接返回instance
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
双重检查锁
java
public class Singleton {
//私有构造方法
private Singleton() {
}
private static Singleton instance; //对外提供静态方法获取该对象
public static Singleton getInstance() {
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if (instance == null) {
synchronized (Singleton.class) {
//抢到锁之后再次判断是否为null
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化 和指令重排序 操作。
要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。
java
public class Singleton { //私有构造方法
private Singleton() {
}
private static volatile Singleton instance; //对外提供静态方法获取该对象
public static Singleton getInstance() {
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if (instance == null) {
synchronized (Singleton.class) {
//抢到锁之后再次判断是否为null
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。
静态内部类方式
静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被static 修饰,保证只被实例化一次,并且严格保证实例化顺序。
java
public class Singleton {
//私有构造方法
private Singleton() {
}
//定义一个静态内部类
private static class SingletonHolder {
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder,并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任
何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
枚举方式
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
java
public enum Singleton {
INSTANCE;
}
存在的问题
破坏单例模式:
- 序列化反序列化
java
public class Singleton implements Serializable {
//私有构造方法
private Singleton() {
}
//定义一个静态内部类
private static class SingletonHolder {
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
java
public class Client {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// writeDataToFile();
readDataFromFile();
readDataFromFile();
}
//从文件读取数据(对象)
public static void readDataFromFile() throws IOException, ClassNotFoundException {
//1.创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\javacode\\design-patterns\\src\\main\\resources\\a.txt"));
//2.读取对象
Singleton instance = (Singleton) ois.readObject();
System.out.println(instance);
//3.释放资源
ois.close();
}
//向文件中写数据(对象)
public static void writeDataToFile() throws IOException {
// 1.获取Singleton类的对象
Singleton instance = Singleton.getInstance();
//2.创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\javacode\\design-patterns\\src\\main\\resources\\a.txt"));
//3.写对象
oos.writeObject(instance);
//4.释放资源
oos.close();
}
}
上面代码运行结果是 false ,表明序列化和反序列化已经破坏了单例设计模式。
解决方案
在Singleton类中添加 readResolve() 方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
java
public class Singleton implements Serializable {
//私有构造方法
private Singleton() {
}
//定义一个静态内部类
private static class SingletonHolder {
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
//当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
- 反射
java
public class Singleton implements Serializable {
private static boolean flag = false;
//私有构造方法
private Singleton() {
synchronized (Singleton.class){
if(flag){
throw new RuntimeException("不能创建多个对象");
}
flag = true;
}
}
//定义一个静态内部类
private static class SingletonHolder {
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
}
java
public class Client {
public static void main(String[] args) throws Exception {
//获取Singleton类的字节码对象
Class clazz = Singleton.class;
//获取Singleton类的私有无参构造方法对象
Constructor constructor = clazz.getDeclaredConstructor();
//取消访问检查
constructor.setAccessible(true);
//创建Singleton类的对象s1
Singleton s1 = (Singleton) constructor.newInstance();
//创建Singleton类的对象s2
Singleton s2 = (Singleton) constructor.newInstance();
//判断通过反射创建的两个Singleton对象是否是同一个对象
System.out.println(s1 == s2);
}
}
上面代码运行结果是 false ,表明序列化和反序列化已经破坏了单例设计模式
注意:枚举方式不会出现这两个问题。
解决方案
当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此中操作。
java
public class Singleton implements Serializable {
private static boolean flag = false;
//私有构造方法
private Singleton() {
synchronized (Singleton.class){
if(flag){
throw new RuntimeException("不能创建多个对象");
}
flag = true;
}
}
//定义一个静态内部类
private static class SingletonHolder {
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
举例
Runtime类就是使用的单例设计模式。
java
package pattern.singleton.demo9;
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/*** Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object.** @return the <code>Runtime</code> object associated with the current* Java application. */
public static Runtime getRuntime() {
return currentRuntime;
}
/**
* Don't let anyone else instantiate this class
*/
private Runtime() {
}
...
}
从上面源代码中可以看出Runtime类使用的是恶汉式(静态属性)方式来实现单例模式的。
java
public class RuntimeDemo {
public static void main(String[] args) throws IOException {
//获取Runtime类对象
Runtime runtime = Runtime.getRuntime();
//返回 Java 虚拟机中的内存总量。
System.out.println(runtime.totalMemory());
//返回 Java 虚拟机试图使用的最大内存量。
System.out.println(runtime.maxMemory());
//创建一个新的进程执行指定的字符串命令,返回进程对象
Process process = runtime.exec("ipconfig");
//获取命令执行后的结果,通过输入流获取
InputStream inputStream = process.getInputStream();
byte[] arr = new byte[1024 * 1024 * 100];
int b = inputStream.read(arr);
System.out.println(new String(arr, 0, b, "gbk"));
}
}
工厂模式
简单工厂模式
简单工厂不是一种设计模式,反而比较像是一种编程习惯。
结构
- 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品 :实现或者继承抽象产品的子类
- 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。
实现
java
public abstract class Coffee {
public abstract String getName();
//加糖
public void addSugar() {
System.out.println("加糖");
}
//加奶
public void addMilk() {
System.out.println("加奶");
}
}
java
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
java
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
java
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
//根据不同的类型创建不同的咖啡对象
Coffee coffee = null;
if ("american".equals(type)) {
coffee = new AmericanCoffee();
} else if ("latte".equals(type)) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("对不起,您所点的咖啡没有");
}
return coffee;
}
}
java
public class CoffeeStore {
public Coffee orderCoffee(String type) {
//创建一个简单工厂对象
SimpleCoffeeFactory simpleCoffeeFactory = new SimpleCoffeeFactory();
//根据用户需求获取咖啡对象
Coffee coffee = simpleCoffeeFactory.createCoffee(type);
//加配料
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
java
public class Client {
public static void main(String[] args) {
//创建咖啡店对象
CoffeeStore coffeeStore = new CoffeeStore();
//点咖啡
Coffee coffee = coffeeStore.orderCoffee("american");
System.out.println(coffee.getName());
}
}
优缺点
优点 :封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:增加新产品时还是需要修改工厂类的代码,违背了"开闭原则"。
静态工厂
在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是23种设计模式中的。
java
public class SimpleCoffeeFactory {
public static Coffee createCoffee(String type) {
//根据不同的类型创建不同的咖啡对象
Coffee coffee = null;
if ("american".equals(type)) {
coffee = new AmericanCoffee();
} else if ("latte".equals(type)) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("对不起,您所点的咖啡没有");
}
return coffee;
}
}
java
public class CoffeeStore {
public Coffee orderCoffee(String type) {
Coffee coffee = SimpleCoffeeFactory.createCoffee(type);
//加配料
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
工厂方法模式
概念
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一产品类的实例化延迟到其工厂的子类。
结构
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
实现
java
public abstract class Coffee {
public abstract String getName();
//加糖
public void addSugar() {
System.out.println("加糖");
}
//加奶
public void addMilk() {
System.out.println("加奶");
}
}
java
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
java
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
java
public interface CoffeeFactory {
//创建咖啡对象的方法
Coffee createCoffee();
}
java
public class AmericanCoffeeFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
java
public class LatteCoffeeFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
}
java
public class CoffeeStore {
private CoffeeFactory factory;
public void setFactory(CoffeeFactory factory) {
this.factory = factory;
}
//点咖啡的方法
public Coffee orderCoffee(){
Coffee coffee = factory.createCoffee();
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
java
public class Client {
public static void main(String[] args) {
//创建咖啡店对象
CoffeeStore coffeeStore = new CoffeeStore();
//创建咖啡工厂对象
CoffeeFactory factory = new LatteCoffeeFactory();
//CoffeeFactory factory = new AmericanCoffeeFactory();
coffeeStore.setFactory(factory);
Coffee coffee = coffeeStore.orderCoffee();
System.out.println(coffee.getName());
}
}
优缺点
优点 :用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
缺点:每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
抽象工厂模式
概念
一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
结构
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
实现
java
public abstract class Coffee {
public abstract String getName();
//加糖
public void addSugar() {
System.out.println("加糖");
}
//加奶
public void addMilk() {
System.out.println("加奶");
}
}
java
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
java
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
java
public abstract class Dessert {
public abstract void show();
}
java
public class Trimisu extends Dessert {
@Override
public void show() {
System.out.println("提拉米苏");
}
}
java
public class MatchaMousse extends Dessert {
@Override
public void show() {
System.out.println("抹茶慕斯");
}
}
java
public interface DessertFactory {
//生产咖啡的功能
Coffee createCoffee();
//生产甜品的功能
Dessert createDessert();
}
java
public class AmericanDessertFactory implements DessertFactory {
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
@Override
public Dessert createDessert() {
return new MatchaMousse();
}
}
java
public class ItalyDessertFactory implements DessertFactory {
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
@Override
public Dessert createDessert() {
return new Trimisu();
}
}
java
public class Client {
public static void main(String[] args) {
//创建工厂对象
//ItalyDessertFactory factory = new ItalyDessertFactory();
AmericanDessertFactory factory = new AmericanDessertFactory();
//通过工厂对象创建产品对象
Coffee coffee = factory.createCoffee();
Dessert dessert = factory.createDessert();
System.out.println(coffee.getName());
dessert.show();
}
}
优缺点
优点 :当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
使用场景
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
如:输入法换皮肤,一整套一起换。生成不同操作系统的程序。
模式扩展
简单工厂+配置文件解除耦合
properties
american=pattern.factory.config_factory.AmericanCoffee
latte=pattern.factory.config_factory.LatteCoffee
java
public abstract class Coffee {
public abstract String getName();
//加糖
public void addSugar() {
System.out.println("加糖");
}
//加奶
public void addMilk() {
System.out.println("加奶");
}
}
java
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
java
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
java
public class CoffeeFactory {
//加载配置文件,获取配置文件中的全类名,并创建该类的对象进行存储。
//1.定义容器对象存储咖啡对象。
private static HashMap<String, Coffee> map = new HashMap<>();
//2.加载配置文件,一次性创建好对象。
static {
//创建Properties对象
Properties p = new Properties();
//获取配置文件的流对象
InputStream inputStream = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
//加载配置文件
try {
p.load(inputStream);
//从p集合中获取全类名并创建对象
Set<Object> keys = p.keySet();
for (Object key : keys) {
//获取全类名
String className = p.getProperty((String) key);
//通过反射技术创建对象
Class clazz = Class.forName(className);
Coffee coffee = (Coffee) clazz.newInstance();
//将名称和对象存储到容器中
map.put((String) key, coffee);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//根据名称获取对象
public static Coffee createCoffee(String name) {
return map.get(name);
}
}
java
public class Client {
public static void main(String[] args) {
Coffee coffee = CoffeeFactory.createCoffee("american");
System.out.println(coffee.getName());
System.out.println("====================");
Coffee coffee1 = CoffeeFactory.createCoffee("latte");
System.out.println(coffee1.getName());
}
}
静态成员变量用来存储创建的对象(键存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只需要执行一次。
实例
java
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("令狐冲");
list.add("风清扬");
list.add("任我行"); //获取迭代器对象
Iterator<String> it = list.iterator(); //使用迭代器遍历
while (it.hasNext()) {
String ele = it.next();
System.out.println(ele);
}
}
}
使用迭代器遍历集合,获取集合中的元素。而单列集合获取迭代器的方法就使用到了工厂方法模式。
Collection接口是抽象工厂类,ArrayList是具体的工厂类;Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。
1,DateForamt类中的getInstance()方法使用的是工厂模式;
2,Calendar类中的getInstance()方法使用的是工厂模式;
原型模式
概述
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
结构
- 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
实现
- 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
Java中的Object类中提供了 clone() 方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。
java
public class Realizetype implements Cloneable {
public Realizetype() {
System.out.println("创建对象成功!");
}
@Override
protected Realizetype clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (Realizetype) super.clone();
}
}
java
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
//创建一个原型对象
Realizetype realizetype = new Realizetype();
//调用Realizetype的clone方法创建一个新的对象
Realizetype clone = realizetype.clone();
System.out.println("原型对象和克隆出来的是否是同一个对象?" + (realizetype == clone));
}
}
案例

浅克隆
java
public class Citation implements Cloneable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
public void show(){
System.out.println("姓名:" + name);
}
}
java
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
//创建一个原型对象
Citation citation = new Citation();
//克隆对象
Citation clone = citation.clone();
citation.setName("张三");
clone.setName("李四");
//展示
citation.show();
clone.show();
}
}
使用场景
- 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
- 性能和安全要求比较高。
深克隆
java
public class Student implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
java
public class Citation implements Cloneable, Serializable {
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
@Override
protected Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
public void show() {
System.out.println("姓名:" + student.getName());
}
}
java
public class Client {
public static void main(String[] args) throws Exception {
//创建一个原型对象
Citation citation = new Citation();
//创建张三对象
Student student = new Student();
student.setName("张三");
citation.setStudent(student);
//创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\javacode\\design-patterns\\src\\main\\resources\\a.txt"));
//写入对象
oos.writeObject(citation);
//关闭流
oos.close();
//创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\javacode\\design-patterns\\src\\main\\resources\\a.txt"));
//读取对象
Citation clone = (Citation) ois.readObject();
//关闭流
ois.close();
Student student1 = clone.getStudent();
student1.setName("李四");
//展示
citation.show();
clone.show();
}
}
注意:Citation类和Student类必须实现Serializable接口,否则会抛NotSerializableException异常。
建造者模式
概述
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
- 分离了部件的构造(由Builder来负责)和装配(由Director负责)。
从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。 - 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
- 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
结构
- 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
- 具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
- 产品类(Product):要创建的复杂对象。
- 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

实例

java
public class Bike {
private String frame;
private String seat;
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
}
java
public abstract class Builder {
//声明Bike类型的变量,并进行赋值
protected Bike bike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
//构建自行车的方法
public abstract Bike createBike();
}
java
public class MobileBuilder extends Builder {
@Override
public void buildFrame() {
bike.setFrame("碳纤维车架");
}
@Override
public void buildSeat() {
bike.setSeat("真皮车座");
}
@Override
public Bike createBike() {
return bike;
}
}
java
public class OfoBuilder extends Builder{
@Override
public void buildFrame() {
bike.setFrame("铝合金车架");
}
@Override
public void buildSeat() {
bike.setSeat("橡胶车座");
}
@Override
public Bike createBike() {
return bike;
}
}
java
public class Director {
//声明builder类型的变量
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
//组装自行车的功能
public Bike construct() {
builder.buildFrame();
builder.buildSeat();
return builder.createBike();
}
}
java
public class Client {
public static void main(String[] args) {
//创建指挥者对象
Director director = new Director(new MobileBuilder());
//让指挥者只会组装自行车
Bike bike = director.construct();
System.out.println(bike.getFrame());
System.out.println(bike.getSeat());
}
}
注意:
上面示例是 Builder模式的常规用法,指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合
java
public abstract class Builder {
protected Bike mBike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
public Bike construct() {
this.buildFrame();
this.BuildSeat();
return this.createBike();
}
}
这样做确实简化了系统结构,但同时也加重了抽象建造者类的职责,也不是太符合单一职责原则,如果construct() 过于复杂,建议还是封装到 Director 中。
优缺点
优点:
- 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
缺点 :
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
使用场景
建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
模式扩展
java
package pattern.builder.demo2;
/**
* @author: LunBoWang
* @description: TODO
* @date: 2025/4/30 下午5:20
* @version: 1.0
*/
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
//私有构造方法
public Phone(Builder builder) {
this.cpu = builder.cpu;
this.screen = builder.screen;
this.memory = builder.memory;
this.mainboard = builder.mainboard;
}
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Builder cpu(String cpu){
this.cpu = cpu;
return this;
}
public Builder screen(String screen){
this.screen = screen;
return this;
}
public Builder memory(String memory){
this.memory = memory;
return this;
}
public Builder mainboard(String mainboard){
this.mainboard = mainboard;
return this;
}
//使用构建者创建手机对象
public Phone build() {
return new Phone(this);
}
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}
java
public class Client {
public static void main(String[] args) {
//创建手机对象 通过构建者对象获取手机对象
Phone phone=new Phone.Builder()
.cpu("intel")
.screen("三星")
.memory("金士顿")
.mainboard("华硕")
.build();
System.out.println(phone);
}
}
创建型模式对比
工厂方法模式VS建造者模式
工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。
抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
代理模式
概述
- 由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
- Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
结构
- 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
静态代理

java
public interface SellTickets {
void sell();
}
java
public class TrainStation implements SellTickets {
@Override
public void sell() {
System.out.println("火车站卖票");
}
}
java
public class ProxyPoint implements SellTickets {
//声明火车站类对象
private TrainStation station = new TrainStation();
@Override
public void sell() {
System.out.println("代售点收取服务费用");
station.sell();
}
}
java
public class Client {
public static void main(String[] args) {
ProxyPoint proxyPoint = new ProxyPoint();
proxyPoint.sell();
}
}
从上面代码中可以看出测试类直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强(代理点收取一些服务费用)。
JDK动态代理
java
public interface SellTickets {
void sell();
}
java
public class TrainStation implements SellTickets {
@Override
public void sell() {
System.out.println("火车站卖票");
}
}
java
public class ProxyFactory {
private TrainStation station = new TrainStation();
public SellTickets getProxyObject() {
//使用Proxy获取代理对象
/* newProxyInstance()方法参数说明:
ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载 器即可
Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代 理对象实现相同的接口
InvocationHandler h : 代理对象的调用处理程序
*/
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
/* InvocationHandler中invoke方法参数说明:
proxy : 代理对象
method : 对应于在代理对象上调用的接口方法的 Method
实 例 args : 代理对象调用接口方法时传递的实际参数 */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
//执行真实对象
Object result = method.invoke(station, args);
return result;
}
});
return sellTickets;
}
}
java
public class Client {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory();
SellTickets proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}
CGLIB动态代理
xml
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
java
public class TrainStation {
public void sell() {
System.out.println("火车站卖票");
}
}
java
public class ProxyFactory implements MethodInterceptor {
private TrainStation trainStation = new TrainStation();
public TrainStation getProxyObject() {
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer = new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(TrainStation.class);
//设置回调函数
enhancer.setCallback(this);
//创建子类对象,即代理对象
TrainStation proxyObject = (TrainStation) enhancer.create();
return proxyObject;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
//执行目标对象的方法
Object obj = method.invoke(trainStation, objects);
return obj;
}
}
java
public class Client {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory();
//获取代理对象
TrainStation proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}
三种代理的对比
-
jdk代理和CGLIB代理
如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。
-
动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。
优缺点
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点:
- 增加了系统的复杂度;
使用场景
- 远程(Remote)代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
- 防火墙(Firewall)代理
当将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
- 保护(Protect or Access)代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
适配器模式
概述
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
结构
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
类适配器模式

java
public interface TFCard {
//从TF卡中读取数据
String readTF();
//往TF卡中写数据
void writeTF(String msg);
}
java
public class TFCardImpl implements TFCard {
@Override
public String readTF() {
String msg = "TFCard read msg: hello word TFCard";
return msg;
}
@Override
public void writeTF(String msg) {
System.out.println("TFCard write msg: " + msg);
}
}
java
public interface SDCard {
//从SD卡中读取数据
String readSD();
//往SD卡中写数据
void writeSD(String msg);
}
java
public class SDCardImpl implements SDCard {
@Override
public String readSD() {
String msg = "SDCard read msg: hello word SD";
return msg;
}
@Override
public void writeSD(String msg) {
System.out.println("SDCard write msg: " + msg);
}
}
java
public class SDAdapterTF extends TFCardImpl implements SDCard {
@Override
public String readSD() {
System.out.println("adapter read tf card");
return readTF();
}
@Override
public void writeSD(String msg) {
System.out.println("adapter write tf card");
writeTF(msg);
}
}
java
public class Computer {
//从SD卡中读取数据
public String readSD(SDCard sdCard) {
if (sdCard == null) {
throw new NullPointerException("sd card is not null");
}
return sdCard.readSD();
}
}
java
public class Client {
public static void main(String[] args) {
//创建适配器对象
Computer computer = new Computer();
//读取SD卡中的数据
String msg = computer.readSD(new SDCardImpl());
System.out.println(msg);
System.out.println("=========================");
//使用该电脑读取TF卡中的数据
//定义适配器类
String msg1 = computer.readSD(new SDAdapterTF());
System.out.println(msg1);
}
}
类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。
对象适配器模式

java
public interface TFCard {
//从TF卡中读取数据
String readTF();
//往TF卡中写数据
void writeTF(String msg);
}
java
public class TFCardImpl implements TFCard {
@Override
public String readTF() {
String msg = "TFCard read msg: hello word TFCard";
return msg;
}
@Override
public void writeTF(String msg) {
System.out.println("TFCard write msg: " + msg);
}
}
java
public interface SDCard {
//从SD卡中读取数据
String readSD();
//往SD卡中写数据
void writeSD(String msg);
}
java
public class SDCardImpl implements SDCard {
@Override
public String readSD() {
String msg = "SDCard read msg: hello word SD";
return msg;
}
@Override
public void writeSD(String msg) {
System.out.println("SDCard write msg: " + msg);
}
}
java
public class SDAdapterTF implements SDCard {
//声明适配者类
private TFCard tfCard;
public SDAdapterTF(TFCard tfCard) {
this.tfCard = tfCard;
}
@Override
public String readSD() {
System.out.println("adapter read tf card");
return tfCard.readTF();
}
@Override
public void writeSD(String msg) {
System.out.println("adapter write tf card");
tfCard.writeTF(msg);
}
}
java
public class Computer {
//从SD卡中读取数据
public String readSD(SDCard sdCard) {
if (sdCard == null) {
throw new NullPointerException("sd card is not null");
}
return sdCard.readSD();
}
}
java
public class Client {
public static void main(String[] args) {
//创建适配器对象
Computer computer = new Computer();
//读取SD卡中的数据
String msg = computer.readSD(new SDCardImpl());
System.out.println(msg);
System.out.println("=====================");
//读取TF卡中的数据
//创建适配器对象
SDAdapterTF adapter = new SDAdapterTF(new TFCardImpl());
msg = computer.readSD(adapter);
System.out.println(msg);
}
}
注意:还有一个适配器模式是接口适配器模式。当不希望实现一个接口中所有的方法时,可以创建一个抽象类Adapter ,实现所有方法。而此时我们只需要继承该抽象类即可。
应用场景
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
装饰者模式
概述
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
结构
- 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色 :继承或实现抽象构件,并包含具体构件的实例,可以通过其子 类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附 加的责任。
案例

java
public abstract class FastFood {
private float price;
private String desc;
public FastFood(float price, String desc) {
this.price = price;
this.desc = desc;
}
public FastFood() {
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public abstract float cost();
}
java
public class FriedNoodles extends FastFood {
public FriedNoodles() {
super(12, "炒面");
}
@Override
public float cost() {
return getPrice();
}
}
java
public class FriedRice extends FastFood {
public FriedRice() {
super(10, "炒饭");
}
@Override
public float cost() {
return getPrice();
}
}
java
public abstract class Garnish extends FastFood {
//声明快餐类的数量
private FastFood fastFood;
public FastFood getFastFood() {
return fastFood;
}
public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}
public Garnish(FastFood fastFood, float price, String desc) {
super(price, desc);
this.fastFood = fastFood;
}
}
java
public class Bacon extends Garnish {
public Bacon(FastFood fastFood) {
super(fastFood, 2, "培根");
}
@Override
public float cost() {
return getPrice() + getFastFood().cost();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
java
public class Bacon extends Garnish {
public Bacon(FastFood fastFood) {
super(fastFood, 2, "培根");
}
@Override
public float cost() {
return getPrice() + getFastFood().cost();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
java
public class Egg extends Garnish {
public Egg(FastFood fastFood) {
super(fastFood, 1, "鸡蛋");
}
@Override
public float cost() {
return getPrice() + getFastFood().cost();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
java
public class Client {
public static void main(String[] args) {
FastFood friedRice = new FriedRice();
System.out.println(friedRice.getDesc() + " " + friedRice.cost() + "元");
System.out.println("======================");
friedRice = new Egg(friedRice);
System.out.println(friedRice.getDesc() + " " + friedRice.cost() + "元");
System.out.println("======================");
friedRice = new Egg(friedRice);
System.out.println(friedRice.getDesc() + " " + friedRice.cost() + "元");
System.out.println("======================");
friedRice = new Bacon(friedRice);
System.out.println(friedRice.getDesc() + " " + friedRice.cost() + "元");
}
}
- 装饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
- 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
使用场景
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
- 不能采用继承的情况主要有两类:
- 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
- 第二类是因为类定义不能继承(如final类)
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
桥接模式
概述
将抽象与实现分离,使两者可以独立变化。通过组合关系替代继承关系,降低抽象层与实现层两个可变维度的耦合度。
结构
角色名称 | 描述 |
---|---|
抽象化(Abstraction) | 定义抽象类,包含对实现化对象的引用(组合关系)。 |
扩展抽象化(Refined Abstraction) | 抽象化角色的子类,实现业务方法,并通过组合调用实现化角色的方法。 |
实现化(Implementor) | 定义实现化角色的接口,供扩展抽象化角色调用。 |
具体实现化(Concrete Implementor) | 实现化接口的具体实现类。 |
|
案例
java
// 实现化接口:视频文件
public interface VideoFile {
// 解码功能
void decode(String fileName);
}
// 具体实现化:RMVB文件
public class RmvbFile implements VideoFile {
@Override
public void decode(String fileName) {
System.out.println("rmvb视频文件:" + fileName);
}
}
// 具体实现化:AVI文件
public class AviFile implements VideoFile {
@Override
public void decode(String fileName) {
System.out.println("avi视频文件:" + fileName);
}
}
// 抽象化:操作系统
public abstract class OpratingSystem {
// 组合实现化对象
protected VideoFile videoFile;
public OpratingSystem(VideoFile videoFile) {
this.videoFile = videoFile;
}
public abstract void play(String fileName);
}
// 扩展抽象化:Windows系统
public class Windows extends OpratingSystem {
public Windows(VideoFile videoFile) {
super(videoFile);
}
@Override
public void play(String fileName) {
videoFile.decode(fileName); // 调用实现化方法
}
}
// 扩展抽象化:Mac系统
public class Mac extends OpratingSystem {
public Mac(VideoFile videoFile) {
super(videoFile);
}
@Override
public void play(String fileName) {
videoFile.decode(fileName);
}
}
// 客户端测试
public class Client {
public static void main(String[] args) {
// 组合不同维度:Windows系统 + AVI文件
OpratingSystem os = new Windows(new AviFile());
os.play("战狼3"); // 输出:avi视频文件:战狼3
}
}
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
- 实现细节对客户透明。
使用场景
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
- 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
外观模式
概述
又名门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观(Facade)模式是"迪米特法则"的典型应用。
结构
- 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
- 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
案例

java
public class TV {
// 打开电视机
public void on() {
System.out.println("打开电视机...");
}
// 关闭电视机
public void off() {
System.out.println("关闭电视机...");
}
}
java
public class Light {
//开灯
public void on() {
System.out.println("电灯打开了...");
}
//关灯
public void off() {
System.out.println("电灯关闭了...");
}
}
java
public class AirCondition {
public void on() {
System.out.println("打开空调...");
}
public void off() {
System.out.println("关闭空调...");
}
}
java
public class SmartApplicationFacade {
//聚合电灯对象,电视机对象,空调对象
private Light light;
private TV tv;
private AirCondition airCondition;
public SmartApplicationFacade() {
light = new Light();
tv = new TV();
airCondition = new AirCondition();
}
//通过语言控制
public void say(String message) {
if (message.contains("打开")) {
on();
} else if (message.contains("关闭")) {
off();
} else {
System.out.println("我还听不懂你说的!!!");
}
}
private void on() {
light.on();
tv.on();
airCondition.on();
}
private void off() {
light.off();
tv.off();
airCondition.off();
}
}
java
public class Client {
public static void main(String[] args) {
//创建智能音箱对象
SmartApplicationFacade smartApplicationFacade = new SmartApplicationFacade();
//控制家电
smartApplicationFacade.say("打开家电");
System.out.println("-----------------");
smartApplicationFacade.say("关闭家电");
}
}
优缺点
好处:
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
缺点:
- 不符合开闭原则,修改很麻烦。
使用场景
- 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
- 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
- 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
组合模式
概述
&esmp;又名部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
结构
- 抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。
- 树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
- 叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。
案例

java
public abstract class MenuComponent {
//菜单组件的名称
protected String name;
//菜单组件的等级
protected int level;
//添加子菜单
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
//移除子菜单
public void remove(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
//获取指定的子菜单
public MenuComponent getChild(int index) {
throw new UnsupportedOperationException();
}
//获取菜单或者菜单项的名称
public String getName() {
return name;
}
//打印菜单名称的方法(包含子菜单和子菜单项)
public abstract void print();
}
java
public class MenuItem extends MenuComponent {
public MenuItem(String name, int level) {
this.name = name;
this.level = level;
}
@Override
public void print() {
//打印菜单项名称
for (int i = 0; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
}
}
java
public class Menu extends MenuComponent {
//菜单可以有多个子菜单或者子菜单项
private List<MenuComponent> menuComponentList = new ArrayList<>();
//构造方法
public Menu(String name, int level) {
this.name = name;
this.level = level;
}
@Override
public void add(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponentList.remove(menuComponent);
}
@Override
public MenuComponent getChild(int index) {
return menuComponentList.get(index);
}
@Override
public void print() {
//打印菜单的名称
for (int i = 0; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
//打印子菜单或者子菜单项名称
for (MenuComponent menuComponent : menuComponentList) {
menuComponent.print();
}
}
}
java
public class Client {
public static void main(String[] args) {
//创建菜单树
MenuComponent menu1 = new Menu("菜单管理", 2);
menu1.add(new MenuItem("页面访问", 3));
menu1.add(new MenuItem("展开菜单", 3));
menu1.add(new MenuItem("编辑菜单", 3));
menu1.add(new MenuItem("删除菜单", 3));
menu1.add(new MenuItem("新增菜单", 3));
MenuComponent menu2 = new Menu("权限管理", 2);
menu2.add(new MenuItem("页面访问", 3));
menu2.add(new MenuItem("提交保存", 3));
MenuComponent menu3 = new Menu("角色管理", 2);
menu3.add(new MenuItem("页面访问", 3));
menu3.add(new MenuItem("新增角色", 3));
menu3.add(new MenuItem("修改角色", 3));
//创建一级菜单
MenuComponent component = new Menu("系统管理", 1);
//将二级菜单添加到一级菜单中
component.add(menu1);
component.add(menu2);
component.add(menu3);
//打印菜单名称(如果有子菜单一块打印)
component.print();
}
}
组合模式的分类
透明组合模式
透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例MenuComponent 声明了 add 、 remove 、 getChild 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。
透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)。
安全组合模式
在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu 类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具 有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端 不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。
优点
- 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
- 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
- 在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合"开闭原则"。
- 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
使用场景
- 组合模式正是应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方。比如:文件目录显示,多级目录呈现等树形结构数据的操作。
享元模式
概述
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。
结构
享元( Flyweight )模式中存在以下两种状态:
- 内部状态,即不会随着环境的改变而改变的可共享部分。
- 外部状态,指随环境改变而改变的不可以共享的部分。
享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。
享元模式的主要有以下角色:
- 抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
- 具体享元(Concrete Flyweight)角色:它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
- 非享元(Unsharable Flyweight)角色:并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
- 享元工厂(Flyweight Factory)角色 :负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
案例

java
public abstract class AbstractBox {
//获取图形的方法
public abstract String getShape();
//显示图形及颜色
public void display(String color) {
System.out.println("图形:" + this.getShape() + " 颜色:" + color);
}
}
java
public class IBox extends AbstractBox{
@Override
public String getShape() {
return "I";
}
}
java
public class LBox extends AbstractBox{
@Override
public String getShape() {
return "L";
}
}
java
public class OBox extends AbstractBox{
@Override
public String getShape() {
return "O";
}
}
java
public class BoxFactory {
private HashMap<String, AbstractBox> map;
private static BoxFactory factory = new BoxFactory();
//在构造方法中进行初始化操作
private BoxFactory() {
map = new HashMap<>();
map.put("I", new IBox());
map.put("L", new LBox());
map.put("O", new OBox());
}
//提供一个方法获取工厂类对象
public static BoxFactory getInstance() {
return factory;
}
//根据名称获取图形对象
public AbstractBox getShape(String name) {
return map.get(name);
}
}
java
public class Client {
public static void main(String[] args) {
//获取I图形对象
AbstractBox box1=BoxFactory.getInstance().getShape("I");
box1.display("红色");
//获取L图形对象
AbstractBox box2=BoxFactory.getInstance().getShape("L");
box2.display("绿色");
//获取O图形对象
AbstractBox box3=BoxFactory.getInstance().getShape("O");
box3.display("黄色");
//获取O图形对象
AbstractBox box4=BoxFactory.getInstance().getShape("O");
box4.display("蓝色");
System.out.println(box3==box4);
}
}
优缺点
优点
- 极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
- 享元模式中的外部状态相对独立,且不影响内部状态
缺点:
- 为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
使用场景
- 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
- 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
- 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
模板方法模式
概述
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
结构
- 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
- 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:
- 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。
- 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
- 钩子方法(Hook Method):在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。
- 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级 逻辑的组成步骤。
案例

java
public abstract class AbstractClass {
//模板方法定义
public final void cookProcess() {
portOil();
heatOil();
putVegetable();
putSauce();
fry();
}
public void portOil() {
System.out.println("倒油");
}
public void heatOil() {
System.out.println("热油");
}
public abstract void putVegetable();
public abstract void putSauce();
public void fry() {
System.out.println("翻炒");
}
}
java
public class ConcreteClass_BaoCai extends AbstractClass{
@Override
public void putVegetable() {
System.out.println("放入包菜");
}
@Override
public void putSauce() {
System.out.println("放入辣椒");
}
}
java
public class ConcreteClass_CaiXin extends AbstractClass {
@Override
public void putVegetable() {
System.out.println("放入菜心");
}
@Override
public void putSauce() {
System.out.println("放入蒜蓉");
}
}
java
public class Client {
public static void main(String[] args) {
ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
baoCai.cookProcess();
System.out.println("-----------------");
ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
caiXin.cookProcess();
}
}
优缺点
优点:
- 提高代码复用性,将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
- 实现了反向控制通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合"开闭原则"。
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
适用场景
- 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
- 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
策略模式
定义
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
结构
- 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
案例

java
public interface Strategy {
void show();
}
java
public class StrategyA implements Strategy {
@Override
public void show() {
System.out.println("买一送一");
}
}
java
public class StrategyB implements Strategy {
@Override
public void show() {
System.out.println("满200减50");
}
}
java
public class StrategyC implements Strategy {
@Override
public void show() {
System.out.println("满1000元加一元换购任意200元一下的商品");
}
}
java
public class SalesMan {
//聚合策略类
private Strategy strategy;
public SalesMan(Strategy strategy) {
this.strategy = strategy;
}
//向客户展示促销活动
public void salesManShow() {
strategy.show();
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
}
java
public class Client {
public static void main(String[] args) {
SalesMan salesMan = new SalesMan(new StrategyA());
salesMan.salesManShow();
System.out.println("------------------");
salesMan.setStrategy(new StrategyB());
salesMan.salesManShow();
System.out.println("------------------");
salesMan.setStrategy(new StrategyC());
salesMan.salesManShow();
}
}
优缺点
优点:
- 策略类之间可以自由切换:由于策略类都实现同一个接口,所以使它们之间可以自由切换。
- 易于扩展 :增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合"开闭原则"
- 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
使用场景
- 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
- 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
- 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
- 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
命令模式
概述
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
结构
- 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
- 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
- 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
案例

java
public interface Command {
//执行命令
void execute();
}
java
public class Order {
//餐桌号码
private int diningTable;
//所下的餐品及份数
private Map<String, Integer> foodDir = new HashMap<String, Integer>();
public int getDiningTable() {
return diningTable;
}
public void setDiningTable(int diningTable) {
this.diningTable = diningTable;
}
public Map<String, Integer> getFoodDir() {
return foodDir;
}
public void setFood(String name, Integer num) {
foodDir.put(name, num);
}
}
java
public class OrderCommand implements Command {
//持有接收者对象
private SeniorChef receiver;
private Order order;
public OrderCommand(SeniorChef receiver, Order order) {
this.receiver = receiver;
this.order = order;
}
@Override
public void execute() {
System.out.println(order.getDiningTable() + "桌的订单:");
Map<String, Integer> foodDir = order.getFoodDir();
Set<String> keys = foodDir.keySet();
for (String foodName : keys) {
receiver.makeFood(foodName, foodDir.get(foodName));
}
System.out.println(order.getDiningTable() + "桌的饭准备完毕!!!");
}
}
java
public class SeniorChef {
public void makeFood(String name, int num) {
System.out.println(num + "份" + name);
}
}
java
public class Waitor {
//持有多个命令对象
private List<Command> commands = new ArrayList<>();
public void setCommand(Command command) {
commands.add(command);
}
//发出命令
public void orderUp() {
System.out.println("美女服务员:叮咚,大厨订单来了!");
for (Command cmd : commands) {
if (cmd != null) {
cmd.execute();
}
}
}
}
java
public class Client {
public static void main(String[] args) {
//创建第一个订单
Order order1 = new Order();
order1.setDiningTable(1);
order1.getFoodDir().put("西红柿鸡蛋面", 1);
order1.getFoodDir().put("小杯可乐", 2);
//创建第二个订单
Order order2 = new Order();
order2.setDiningTable(2);
order2.getFoodDir().put("尖椒肉丝盖饭", 1);
order2.getFoodDir().put("小杯雪碧", 1);
//创建厨师对象
SeniorChef receiver = new SeniorChef();
//创建命令对象
OrderCommand cmd1 = new OrderCommand(receiver, order1);
OrderCommand cmd2 = new OrderCommand(receiver, order2);
//创建调用者(服务员对象)
Waitor invoker = new Waitor();
invoker.setCommand(cmd1);
invoker.setCommand(cmd2);
//发出命令
invoker.orderUp();
}
}
优缺点
优点:
- 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足"开闭原则",对扩展比较灵活。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销 与恢复。
缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类。
- 系统结构更加复杂。
使用场景
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
责任链模式
定义
又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
结构
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
案例

java
public class LeaveRequest {
private String name;
private int num;
private String content;
public LeaveRequest(String name, int num, String content) {
this.name = name;
this.num = num;
this.content = content;
}
public String getName() {
return name;
}
public int getNum() {
return num;
}
public String getContent() {
return content;
}
}
java
public abstract class Handler {
protected final static int NUM_ONE = 1;
protected final static int NUM_THREE = 3;
protected final static int NUM_SEVEN = 7;
private int numStart;
private int numEnd;
//声明后继者
private Handler nextHandler;
public Handler(int numStart) {
this.numStart = numStart;
}
public Handler(int numStart, int numEnd) {
this.numEnd = numEnd;
this.numStart = numStart;
}
//设置上级领导对象
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
//处理请求的方法
protected abstract void handleLeave(LeaveRequest leaveRequest);
//提交请求的方法
public final void submit(LeaveRequest leave) {
this.handleLeave(leave);
if (this.nextHandler != null && leave.getNum() > this.numEnd) {
this.nextHandler.submit(leave);
} else {
System.out.println("流程结束!");
}
}
}
java
public class GroupLeader extends Handler {
public GroupLeader() {
super(0, Handler.NUM_ONE);
}
@Override
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天," + leaveRequest.getContent() + "。");
System.out.println("小组长审批:同意。");
}
}
java
public class Manager extends Handler {
public Manager() {
super(Handler.NUM_ONE, Handler.NUM_THREE);
}
@Override
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天," + leaveRequest.getContent() + "。");
System.out.println("部门经理审批:同意。");
}
}
java
public class GeneralManager extends Handler {
public GeneralManager() {
super(Handler.NUM_THREE, Handler.NUM_SEVEN);
}
@Override
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天," + leaveRequest.getContent() + "。");
System.out.println("总经理审批:同意。");
}
}
java
public class Client {
public static void main(String[] args) {
//创建一个请求
LeaveRequest leaveRequest = new LeaveRequest("小明", 9, "身体不适");
//创建相关的负责人
Handler groupLeader = new GroupLeader();
Handler manager = new Manager();
Handler generalManager = new GeneralManager();
//设置处理者链
groupLeader.setNextHandler(manager);
manager.setNextHandler(generalManager);
//提交请求
groupLeader.submit(leaveRequest);
}
}
优缺点
优点:
- 降低了对象之间的耦合度:该模式降低了请求发送者和接收者的耦合度。
- 增强了系统的可扩展性:可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性:当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接:一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者if···else 语句。
- 责任分担:每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点:
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
状态模式
概述
对有状态的对象,把复杂的"判断逻辑 " 提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
结构
- 环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
- 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为。
案例

java
public class Context {
//定义对应的状态对象的常量
public final static OpeningState OPENING_STATE = new OpeningState();
public final static ClosingState CLOSING_STATE = new ClosingState();
public final static RunningState RUNNING_STATE = new RunningState();
public final static StoppingState STOPPING_STATE = new StoppingState();
//定义当前电梯的状态
private LiftState liftState;
public LiftState getLiftState() {
return liftState;
}
//设置电梯的状态
public void setLiftState(LiftState liftState) {
this.liftState = liftState;
//把当前的环境通知到各个实现类中
this.liftState.setContext(this);
}
public void open() {
this.liftState.open();
}
public void close() {
this.liftState.close();
}
public void run() {
this.liftState.run();
}
public void stop() {
this.liftState.stop();
}
}
java
public abstract class LiftState {
//声明环境角色类变量
protected Context context;
public void setContext(Context context) {
this.context = context;
}
//电梯开启操作
public abstract void open();
//电梯关闭操作
public abstract void close();
//电梯运行操作
public abstract void run();
//电梯停止操作
public abstract void stop();
}
java
public class OpeningState extends LiftState {
@Override
public void open() {
System.out.println("电梯门开启...");
}
@Override
public void close() {
//状态修改
super.context.setLiftState(Context.CLOSING_STATE);
//调用当前状态中的context中的方法
super.context.close();
}
@Override
public void run() {
//什么也不做
}
@Override
public void stop() {
//什么也不做
}
}
java
public class ClosingState extends LiftState{
@Override
public void open() {
super.context.setLiftState(Context.OPENING_STATE);
super.context.open();
}
@Override
public void close() {
System.out.println("电梯门关闭...");
}
@Override
public void run() {
super.context.setLiftState(Context.RUNNING_STATE);
super.context.run();
}
@Override
public void stop() {
super.context.setLiftState(Context.STOPPING_STATE);
super.context.stop();
}
}
java
public class RunningState extends LiftState{
@Override
public void open() {
//什么也不做
}
@Override
public void close() {
//什么也不做
}
@Override
public void run() {
System.out.println("电梯正在运行...");
}
@Override
public void stop() {
super.context.setLiftState(Context.STOPPING_STATE);
super.context.stop();
}
}
java
public class StoppingState extends LiftState {
@Override
public void open() {
super.context.setLiftState(Context.OPENING_STATE);
super.context.getLiftState().open();
}
@Override
public void close() {
super.context.setLiftState(Context.CLOSING_STATE);
super.context.getLiftState().close();
}
@Override
public void run() {
super.context.setLiftState(Context.RUNNING_STATE);
super.context.getLiftState().run();
}
@Override
public void stop() {
System.out.println("电梯停止了。。。");
}
}
java
public class Client {
public static void main(String[] args) {
//创建环境角色对象
Context context = new Context();
//设置电梯的初始状态
context.setLiftState(new ClosingState());
//调用电梯的方法
context.open();
context.run();
context.close();
context.stop();
}
}
优缺点
优点:
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
缺点:
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对"开闭原则"的支持并不太好。
使用场景
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
观察者模式
概述
又被称为发布- 订阅( Publish/Subscribe )模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
结构
- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
案例

java
public interface Observer {
void update(String message);
}
java
public interface Subject {
//添加订阅者(添加观察者对象)
void attach(Observer observer);
//删除订阅者
void detach(Observer observer);
//通知订阅者更新消息
void notify(String message);
}
java
public class WeiXinUser implements Observer {
private String name;
public WeiXinUser(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + "接收到推送消息:" + message);
}
}
java
public class SubscriptionSubject implements Subject {
//定义一个集合,用来存储多个观察者模式
private List<Observer> weiXinUserList = new ArrayList<>();
@Override
public void attach(Observer observer) {
weiXinUserList.add(observer);
}
@Override
public void detach(Observer observer) {
weiXinUserList.remove(observer);
}
@Override
public void notify(String message) {
//遍历集合,调用观察者的update方法,发送消息
for (Observer observer : weiXinUserList) {
observer.update(message);
}
}
}
java
public class Client {
public static void main(String[] args) {
//创建主题对象
Subject subject = new SubscriptionSubject();
//订阅公众号
subject.attach(new WeiXinUser("孙悟空"));
subject.attach(new WeiXinUser("猪八戒"));
subject.attach(new WeiXinUser("沙和尚"));
//公众号更新发出消息
subject.notify("你不是人");
}
}
优缺点
优点:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】。
缺点:
- 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时。
- 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃。
使用场景
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时。
JDK中提供的实现
1,Observable类
Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
- void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
- void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的
update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。 - void setChange() 方法:用来设置一个 boolean
类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。
2 , Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作
java
public class Thief extends Observable {
private String name;
public Thief(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void steal() {
System.out.println("小偷:我偷东西了,有没有人来抓我!!!");
super.setChanged(); //changed = true super.notifyObservers();
}
}
java
public class Policemen implements Observer {
private String name;
public Policemen(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久 了,你可以保持沉默,但你所说的将成为呈堂证供!!!");
}
}
java
public class Client {
public static void main(String[] args) {
//创建小偷对象
Thief t = new Thief("隔壁老王");
//创建警察对象
Policemen p = new Policemen("小李");
//让警察盯着小偷
t.addObserver(p);
//小偷偷东西
t.steal();
}
}
中介者模式
概述
又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。
结构
- 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
- 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List
来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。 - 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
- 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交 互时,由中介者对象负责后续的交互。
案例

java
public abstract class Mediator {
public abstract void constact(String message,Person person);
}
java
public abstract class Person {
protected String name;
protected Mediator mediator;
public Person(String name, Mediator mediator) {
this.name = name;
this.mediator = mediator;
}
}
java
public class Tenant extends Person {
public Tenant(String name, Mediator mediator) {
super(name, mediator);
}
//和中介者联系
public void constact(String message) {
mediator.constact(message, this);
}
//获取信息
public void getMessage(String message) {
System.out.println("租房者" + name + "获得信息:" + message);
}
}
java
public class HouseOwner extends Person {
public HouseOwner(String name, Mediator mediator) {
super(name, mediator);
}
//和中介者联系
public void constact(String message) {
mediator.constact(message, this);
}
//获取信息
public void getMessage(String message) {
System.out.println("房主" + name + "获得信息:" + message);
}
}
java
public class MediatorStructure extends Mediator {
//聚合房主和租房者对象
private HouseOwner houseOwner;
private Tenant tenant;
public HouseOwner getHouseOwner() {
return houseOwner;
}
public void setHouseOwner(HouseOwner houseOwner) {
this.houseOwner = houseOwner;
}
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
@Override
public void constact(String message, Person person) {
if (person == houseOwner) {
tenant.getMessage(message);
} else {
houseOwner.getMessage(message);
}
}
}
java
public class Client {
public static void main(String[] args) {
//创建一个中介者对象
MediatorStructure mediator = new MediatorStructure();
//创建租房者和房主
Tenant tenant = new Tenant("张三", mediator);
HouseOwner houseOwner = new HouseOwner("李四", mediator);
//中介者认识房主和租房者
mediator.setHouseOwner(houseOwner);
mediator.setTenant(tenant);
//房主和租房者联系中介者
tenant.constact("我要租三室的房子");
houseOwner.constact("租三室的房子,需要2000元/月");
}
}
优缺点
优点:
- 松散耦合
中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可以独立地变化和复用,而不再像以前那样"牵一处而动全身 " 了。
- 集中控制交互
多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改。
- 一对多关联转变为一对一的关联
没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现。
缺点:
- 当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
使用场景
- 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
- 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
迭代器模式
概述
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
结构
- 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
- 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
- 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
- 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
案例

java
public class Student {
private String name;
private String number;
public Student() {
}
public Student(String name, String number) {
this.name = name;
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
'}';
}
}
java
public interface StudentIterator {
// 判断是否还有下一个元素
boolean hasNext();
// 获取下一个元素
Student next();
}
java
public class StudentIteratorImpl implements StudentIterator {
private List<Student> list;
private int position = 0;
public StudentIteratorImpl(List<Student> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return position < list.size();
}
@Override
public Student next() {
//从集合中获取指定位置的元素
Student currentStudent = list.get(position);
position++;
return currentStudent;
}
}
java
public interface StudentAggregate {
// 添加学生
void addStudent(Student student);
// 删除学生
void removeStudent(Student student);
// 获取迭代器
StudentIterator getStudentIterator();
}
java
public class StudentAggregateImpl implements StudentAggregate {
private List<Student> list = new ArrayList<>();
@Override
public void addStudent(Student student) {
list.add(student);
}
@Override
public void removeStudent(Student student) {
list.remove(student);
}
@Override
public StudentIterator getStudentIterator() {
return new StudentIteratorImpl(list);
}
}
java
public class Client {
public static void main(String[] args) {
//创建聚合对象
StudentAggregate studentAggregate = new StudentAggregateImpl();
//添加元素
studentAggregate.addStudent(new Student("小明", "001"));
studentAggregate.addStudent(new Student("小红", "002"));
studentAggregate.addStudent(new Student("小刚", "003"));
//获取迭代器
StudentIterator studentIterator = studentAggregate.getStudentIterator();
//遍历元素
while (studentIterator.hasNext()) {
Student student = studentIterator.next();
System.out.println(student);
}
}
}
优缺点
优点:
- 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
- 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
- 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 "开闭原则" 的要求。
缺点:
- 增加了类的个数,这在一定程度上增加了系统的复杂性。
使用场景
- 当需要为聚合对象提供多种遍历方式时。
- 当需要为遍历不同的聚合结构提供一个统一的接口时。
- 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
访问者模式
概述
&esmp;封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
结构
- 抽象访问者(Visitor)角色:定义了对每一个元素 (Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
- 具体访问者(ConcreteVisitor)角色:给出对每一个元素类访问时所产生的具体行为。
- 抽象元素(Element)角色:定义了一个接受访问者的方法( accept ),其意义是指,每一个元素都要可以被访问者访问。
- 具体元素(ConcreteElement)角色: 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
- 对象结构(Object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素( Element ),并且可以迭代这些元素,供访问者访问。
案例

java
public interface Animal {
//接收访问者访问的功能
void accept(Person person);
}
java
public class Cat implements Animal {
@Override
public void accept(Person person) {
person.feed(this);
System.out.println("好好吃,喵喵喵!");
}
}
java
public class Dog implements Animal {
@Override
public void accept(Person person) {
person.feed(this);
System.out.println("好好吃,汪汪汪!");
}
}
java
public interface Person {
//喂食宠物猫
void feed(Cat cat);
//喂食宠物狗
void feed(Dog dog);
}
java
public class Owner implements Person {
@Override
public void feed(Cat cat) {
System.out.println("主人喂猫");
}
@Override
public void feed(Dog dog) {
System.out.println("主人喂狗");
}
}
java
public class SomeOne implements Person {
@Override
public void feed(Cat cat) {
System.out.println("其他人喂猫");
}
@Override
public void feed(Dog dog) {
System.out.println("其他人喂狗");
}
}
java
public class Home {
//声明一个集合对象,用来存储元素对象
private List<Animal> nodeList = new ArrayList<>();
//添加元素功能
public void add(Animal animal) {
nodeList.add(animal);
}
public void action(Person person) {
//遍历集合,得到每一个元素,让访问者访问这个元素
for (Animal animal : nodeList) {
animal.accept(person);
}
}
}
java
public class Client {
public static void main(String[] args) {
//创建Home对象
Home home = new Home();
//添加元素到Home对象中
home.add(new Cat());
home.add(new Dog());
//创建主人对象
Owner owner = new Owner();
//让主人喂食所有的宠物
home.action(owner);
}
}
优缺点
优点:
- 扩展性好:在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好:通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
- 分离无关行为:通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
缺点:
- 对象结构变化很困难:在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了"开闭原则"。
- 违反了依赖倒置原则:访问者模式依赖了具体类,而没有依赖抽象类。
使用场景
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
备忘录模式
概述
又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。
结构
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录有两个等效的接口:
窄接口 :管理者 (Caretaker) 对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface) ,这个窄接口只允许他把备忘录对象传给其他的对象。
宽接口 :与管理者看到的窄接口相反,发起人对象可以看到一个宽接口 (wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
案例
"白箱"备忘录模式

java
public class RoleStateMemento {
private int vit;
private int atk;
private int def;
public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
public RoleStateMemento(){
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
java
public class RoleStateCaretaker {
//声明RoleStateMemento类型的变量
private RoleStateMemento roleStateMemento;
public RoleStateMemento getRoleStateMemento() {
return roleStateMemento;
}
public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
this.roleStateMemento = roleStateMemento;
}
}
java
public class GameRole {
private int vit;//生命值
private int atk;//攻击力
private int def;//防御力
//初始化内部状态
public void initState() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
//战斗
public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}
//保存角色状态功能
public RoleStateMemento saveState() {
return new RoleStateMemento(vit, atk, def);
}
//恢复角色状态功能
public void recoveryState(RoleStateMemento memento) {
this.vit = memento.getVit();
this.atk = memento.getAtk();
this.def = memento.getDef();
}
//展示状态
public void stateDisplay() {
System.out.println("角色当前状态:");
System.out.println("体力:" + vit);
System.out.println("攻击力:" + atk);
System.out.println("防御力:" + def);
System.out.println("-----------------------");
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
java
public class Client {
public static void main(String[] args) {
System.out.println("--------------------大战前----------------------");
//创建游戏角色
GameRole gameRole = new GameRole();
gameRole.initState();
gameRole.stateDisplay();
//将该游戏角色内部状态进行备份
//创建管理者对象
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setRoleStateMemento(gameRole.saveState());
System.out.println("--------------------大战后----------------------");
gameRole.fight();
gameRole.stateDisplay();
System.out.println("--------------------恢复之前的状态----------------------");
gameRole.recoveryState(roleStateCaretaker.getRoleStateMemento());
gameRole.stateDisplay();
}
}
分析:白箱备忘录模式是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。
"黑箱"备忘录模式

java
public interface Memento {
}
java
public class RoleStateCaretaker {
//声明RoleStateMemento类型的变量
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
java
public class GameRole {
private int vit;//生命值
private int atk;//攻击力
private int def;//防御力
//初始化内部状态
public void initState() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
//战斗
public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}
//保存角色状态功能
public Memento saveState() {
return new RoleStateMemento(vit, atk, def);
}
//恢复角色状态功能
public void recoveryState(Memento memento) {
RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
//展示状态
public void stateDisplay() {
System.out.println("角色当前状态:");
System.out.println("体力:" + vit);
System.out.println("攻击力:" + atk);
System.out.println("防御力:" + def);
System.out.println("-----------------------");
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
private class RoleStateMemento implements Memento {
private int vit;
private int atk;
private int def;
public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
public RoleStateMemento(){
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
}
java
public class Client {
public static void main(String[] args) {
System.out.println("--------------------大战前----------------------");
//创建游戏角色
GameRole gameRole = new GameRole();
gameRole.initState();
gameRole.stateDisplay();
//将该游戏角色内部状态进行备份
//创建管理者对象
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setMemento(gameRole.saveState());
System.out.println("--------------------大战后----------------------");
gameRole.fight();
gameRole.stateDisplay();
System.out.println("--------------------恢复之前的状态----------------------");
gameRole.recoveryState(roleStateCaretaker.getMemento());
gameRole.stateDisplay();
}
}
优缺点
优点:
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
缺点:
- 资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
使用场景
- 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
- 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,idea等软件在编辑时按 Ctrl+Z组合键,还有数据库中事务操作。
解释器模式
概述
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
文法(语法)规则:文法是用于描述语言的语法结构的形式规则。
expression ::= value | plus | minus
plus ::= expression '+' expression
minus ::= expression '-' expression
value ::= integer
表达式可以是一个值,也可以是 plus 或者 minus 运算,而 plus 和 minus 又是由表达式结合运算符构成,值的类型为整型数。
抽象语法树: 是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示 源代码中的一种结构。

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

java
public class Context {
//定义一个map集合,用于存储变量及对应的值
private Map<Variable,Integer> map=new HashMap<>();
//添加变量的功能
public void assign(Variable var,int value){
map.put(var, value);
}
//根据变量获取对应的值
public int getValue(Variable var){
return map.get(var);
}
java
public abstract class AbstractExpression {
public abstract int interpreter(Context context);
}
java
public class Minus extends AbstractExpression {
//声明两个成员变量,分别存储"-"符号两边的表达式
private AbstractExpression left;
private AbstractExpression right;
public Minus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpreter(Context context) {
//将左右两边的表达式进行相减
return left.interpreter(context) - right.interpreter(context);
}
public String toString() {
return "(" + left.toString() + "-" + right.toString() + ")";
}
}
java
public class Plus extends AbstractExpression {
//声明两个成员变量,分别存储"+"符号两边的表达式
private AbstractExpression left;
private AbstractExpression right;
public Plus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpreter(Context context) {
//将左右两边的表达式进行相加
return left.interpreter(context) + right.interpreter(context);
}
public String toString() {
return "(" + left.toString() + "+" + right.toString() + ")";
}
}
java
public class Client {
public static void main(String[] args) {
//创建环境对象
Context context = new Context();
//创建多个变量对象
Variable a = new Variable("a");
Variable b = new Variable("b");
Variable c = new Variable("c");
Variable d = new Variable("d");
//为变量赋值
context.assign(a, 1);
context.assign(b, 2);
context.assign(c, 3);
context.assign(d, 4);
//创建抽象语法树
AbstractExpression expression = new Minus(new Plus(a, b), new Plus(c, d));
//解释
int interpreter = expression.interpreter(context);
System.out.println(expression+"="+interpreter);
}
}
优缺点
优点:
- 易于改变和扩展文法:由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
- 实现文法较为容易:在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂。
- 增加新的解释表达式较为方便:如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合 "开闭原则"。
缺点:
- 对于复杂文法难以维护:在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护。
- 执行效率较低:由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
使用场景
- 当语言的文法较为简单,且执行效率不是关键问题时。
- 当问题重复出现,且可以用一种简单的语言来进行表达时。
- 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候。