创建型模式
请解释什么是单例模式,并给出一个使用场景
单例模式(Singleton Pattern) 就是一个类只能有一个实例,它主要用于资源管理(避免资源冲突)保证全局唯一的场景。
具体使用场景
- 配置管理
基本上应用都会有一个全局配置,这个配置从理论上来说需要保证唯一性,确保读取到的配置是同一份,是一致的,所以天然适合单例实现。
- 连接池、线程池
池化资源需要保证唯一性,不然就没有池化的意义了,总不能每次访问池化资源都新建一个吧? 需要保持单例,控制具体池化资源的数量,便于管理和监控。
还有日志、缓存等需要全局唯一避免资源冲突的场景。
单例模式有哪几种实现?如何保证线程安全?
单例模式有很多种,首先最简单的是懒汉式单例
懒汉式
(线程不安全) 单例:【不可用】
为了避免内存空间浪费,采用懒汉式单例,即用到该单例对象的时候再创建。但是,存在很大问题,单线程下这段代码没有问题,但是在多线程下有很大问题。
public class LazyMan {
private LazyMan(){};
public static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan = new LazyMan(); //用到该单例对象的时候再创建,这里没加锁,如果多个线程进入,会并发产生多个实例
}
return lazyMan;
}
}
这时可以改进为下面的线程安全版懒汉式
(线程安全)单例:【不推荐使用】
同步方法
public class LazyMan {
private LazyMan(){}; //私有化构造函数,防止外部实例化
public static LazyMan lazyMan;
public static synchroized LazyMan getInstance(){ //加锁
if (lazyMan==null){
lazyMan = new LazyMan(); //用到该单例对象的时候再创建
}
return lazyMan;
}
}
缺点:效率低,每次getInstance时都要同步处理,存在性能问题,实际开发不推荐
双重检查单例(线程安全):【推荐使用】
-
实例化代码只用执行一次,后面再次访问时,判断
if (singleton == null)
,不为空则直接return实例化对象。 -
利用
volatile
关键字保证了可见性,利用双重检查机制减少了同步带来的性能损耗。
public class Singleton {
private static volatile Singleton instance;
// 私有构造函数,防止外部实例化
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
饿汉式
(静态常量)【可用】
类一旦加载就创建一个单例 ,保证在调用getInstance
方法之前单例已经存在,即没有延迟加载,这种饿汉式单例会造成空间浪费。
public class Hungry {
private Hungry(){}
private final static Hungry HUNGRY = new Hungry(); //在类内部就创建了一个静态对象,并且get方法返回该对象
public static Hungry getInstance(){
return HUNGRY;
}
}
//或者 静态代码块形式
public class Hungry {
static{
private final static Hungry HUNGRY = new Hungry();
}
private Hungry(){}
public static Hungry getInstance(){
return HUNGRY;
}
}
Hungry in1=Hungry.getInstance()
Hungry in2=Hungry.getInstance() //in1==in2
改进方法,使用下面的静态内部类形式
静态内部类
-
(线程安全)单例:【推荐使用】
-
不仅线程安全,还实现了延迟加载
public class Inner {
private Inner(){}
//直到调用 getInstance() 方法之前,Inner 实例都不会被创建,实现了延迟初始化
public static Inner getInstance(){
return InnerClass.INNER;
}
private static class InnerClass{
private static final Inner INNER = new Inner(); //静态字段只会在类加载时被初始化一次,线程安全
}
}
枚举
-
(线程安全)【推荐使用】
-
不仅线程安全,还能防止反序列化导致重新创建新的对象
class SingletonEnum{
// 1、创建一个枚举
public enum CreateInstance{
// 枚举实例,底层变量定义是public static final,因此它在 JVM 中只会被初始化一次,并且是线程安全的
INSTANCE;
private SingletonEnum instance;
// 保证不能在类外部通过new构造器来构造对象
private CreateInstance() {
instance = new SingletonEnum();
System.out.println(Thread.currentThread().getName());
}
// 创建一个公共的方法,由实例调用返回单例类
public SingletonEnum getInstance() {
return instance;
}
}
public static void main(string[] args){
Singleton instance = CreateInstance.INSTANCE.getInstance();
singleton instance2= CreateInstance.INSTANCE.getInstance();
System.out.println(instance == instance2); //输出 true
}
}
工厂模式和抽象工厂模式有什么区别?
工厂模式
工厂模式定义了一个创建对象的接口,一个具体的工厂类负责生产一种产品,如果需要添加新的产品,仅需新增对应的具体工厂类而不需要修改原有的代码实现。
// 抽象产品
interface Product {
void use();
}
// 具体产品A
class ConcreteProductA implements Product {
public void use() {
System.out.println("Using ConcreteProductA");
}
}
// 具体产品B
class ConcreteProductB implements Product {
public void use() {
System.out.println("Using ConcreteProductB");
}
}
// 抽象工厂
interface Factory {
Product createProduct();
}
// 具体工厂A
class ConcreteFactoryA implements Factory {
public Product createProduct() {
return new ConcreteProductA();
}
}
// 具体工厂B
class ConcreteFactoryB implements Factory {
public Product createProduct() {
return new ConcreteProductB();
}
}
// 使用工厂方法创建产品
public class Client {
public static void main(String[] args) {
new ConcreteFactoryA().createProduct();
new ConcreteFactoryB().createProduct() ;
}
}
抽象工厂模式(Abstract Factory)
-
是工厂模式的一种变体,创建一系列相关或相互依赖对象的接口,即 生产一系列产品
-
在工厂模式基础上,给具体工厂类 添加一个工厂接口,使得工厂类可以多实现
// 抽象产品A
public interface ProductA {
void use();
}
// 具体产品A1
public class ConcreteProductA1 implements ProductA {
@Override
public void use() {
System.out.println("Using ConcreteProductA1");
}
}
// 具体产品A2
public class ConcreteProductA2 implements ProductA {
@Override
public void use() {
System.out.println("Using ConcreteProductA2");
}
}
// 抽象产品B
public interface ProductB {
void eat();
}
// 具体产品B1
public class ConcreteProductB1 implements ProductB {
@Override
public void eat() {
System.out.println("Eating ConcreteProductB1");
}
}
// 具体产品B2
public class ConcreteProductB2 implements ProductB {
@Override
public void eat() {
System.out.println("Eating ConcreteProductB2");
}
}
// 抽象工厂
public interface AbstractFactory {
ProductA createProductA();
ProductB createProductB();
}
// 具体工厂1
public class ConcreteFactory1 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA1();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB1();
}
}
// 具体工厂2
public class ConcreteFactory2 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA2();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB2();
}
}
// 使用抽象工厂创建产品
public class Client {
public static void main(String[] args) {
AbstractFactory factory1 = new ConcreteFactory1();
ProductA productA1 = factory1.createProductA();
ProductB productB1 = factory1.createProductB();
productA1.use();
productB1.eat();
AbstractFactory factory2 = new ConcreteFactory2();
ProductA productA2 = factory2.createProductA();
ProductB productB2 = factory2.createProductB();
productA2.use();
productB2.eat();
}
}
简单工厂模式的工作原理。
-
前面提到的工厂模式是 通过创建对应工厂来创建产品,允许子类决定实例化哪一个类,符合开放封闭原则
-
而简单工厂模式(Simple Factory Pattern)不属于 GoF 23 种经典设计模式之一,但是在实际开发中非常常见
-
其通过传入的不同的参数来控制创建哪个具体产品。它的实现较为简单,但不够灵活,违反了开放封闭原则。
// 产品接口
public interface Product {
void use();
}
// 具体产品A
public class ConcreteProductA implements Product {
@Override
public void use() {
System.out.println("Using ConcreteProductA");
}
}
// 具体产品B
public class ConcreteProductB implements Product {
@Override
public void use() {
System.out.println("Using ConcreteProductB");
}
}
// 简单工厂类
public class SimpleFactory {
public static Product createProduct(String type) {
switch (type) {
case "A":
return new ConcreteProductA();
case "B":
return new ConcreteProductB();
default:
throw new IllegalArgumentException("Unknown product type");
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Product productA = SimpleFactory.createProduct("A");
productA.use(); // Output: Using ConcreteProductA
Product productB = SimpleFactory.createProduct("B");
productB.use(); // Output: Using ConcreteProductB
}
}
什么是建造者模式?一般用在什么场景?
建造者模式(Builder)使用相同的代码基础构建不同类型的对象,通过将对象构建过程分解为多个较小的步骤来实现此目的,如StringBuilder
两个主要组成部分:建造者和产品 建造者 是负责构建产品的类,产品则是最终构建的对象
public class Product { //最终构建的对象,即产品
private String partA;
private String partB;
private String partC;
public void setPartA(String partA) {
this.partA = partA;
}
public void setPartB(String partB) {
this.partB = partB;
}
public void setPartC(String partC) {
this.partC = partC;
}
// other product-related methods
}
public interface Builder { //Builder接口或抽象类,定义了构建过程的关键步骤
void buildPartA();
void buildPartB();
void buildPartC();
Product getResult();
}
public class ConcreteBuilder implements Builder { //实现了Builder接口中定义的方法,以构建具体的Product对象
private Product product = new Product();
public void buildPartA() {
product.setPartA("Part A");
}
public void buildPartB() {
product.setPartB("Part B");
}
public void buildPartC() {
product.setPartC("Part C");
}
public Product getResult() {
return product;
}
}
public class Director { //指导类,它负责使用Builder对象来构建最终的Product对象
private Builder builder;
public Director(Builder builder) { //实现依赖倒置原则,依赖于抽象
this.builder = builder;
}
public void construct() { //确保Product对象按照指定的顺序创建
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
}
}
Builder builder = new ConcreteBuilder(); //创建建造者
Director director = new Director(builder); //创建指导
director.construct();
Product product = builder.getResult();
//这将构建一个Product对象,并将其存储在product变量中。
适用场景
一般情况下,只有复杂对象的创建才需要使用建造者模式,比如一个对象有十几个参数,如果我们用构造器填参,可读性很差,还可能会搞混参数的赋值
例如 hutool 内的 ExecutorBuilder 就提供了建造者模式创建线程池的方法
public ExecutorService buildTaskPool() {
return ExecutorBuilder.create()
.setCorePoolSize(10)
.setMaxPoolSize(20)
.setWorkQueue(new LinkedBlockingQueue<>(100))
.setKeepAliveTime(3L, TimeUnit.SECONDS)
.setThreadFactory(new ThreadFactoryBuilder().setNamePrefix("task-pool-").build())
.build();
}
-
建造者模式可以很容易地增加新的类型,只需要创建新的子类即可,不需要修改现有的代码。
-
通过建造者模式,可以对 对象的各个部分进行细致的控制,从而更好地管理对象的创建过程。
什么是原型模式 ? 一般用在什么场景?
原型模式也是一种创建型设计模式,主要通过复制(克隆)现有的实例来创建新的对象,避免复杂创建过程,提升创建对象的效率。
在 java 中 可以利用 clone 实现对象的拷贝,
-
注意:Java 的 clone 仅是浅拷贝,默写场景需要 使用深拷贝避免共享数据的导致错乱
-
在 Spring 中,将 Bean 的作用范围设置为 prototype,这样每次从容器中获取 Bean 时,都会返回一个新的实例。
浅拷贝 :将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型(指的是该对象内部的引用类型属性),就会拷贝指向原有对象的内存地址
深拷贝:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。
简单来说,就是深复制进行了完全彻底的复制,而浅拷贝不彻底。
一个简单的原型模式实现例子如下
先创建一个原型类:
public class Prototype implements Cloneable { //一个原型类,只需要实现Cloneable接口,覆写clone方法
@Override
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone(); //因为此处的重点是super.clone()这句话
return proto;
}
}
Prototype pro=new Prototype();
Prototype pro1=(Prototype)pro.clone(); //克隆 二者地址不同
//不管深浅,拷贝出来的对象的地址是不一样的,只是若对象里面有引用,那么深浅拷贝会不一样
@Data
public class Prototype implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private String string;
private SerializableObject obj;
/* 浅拷贝 */
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
/* 深拷贝 */
public Object deepClone() throws IOException, ClassNotFoundException {
/* 写入当前对象的二进制流 */
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();
}
}
class SerializableObject implements Serializable {
private static final long serialVersionUID = 1L;
}
结构型模式
什么是适配器模式?一般用在什么场景?
相当于转接头,比如整合老系统的时候,由于老系统的接口已经跟不上新接口的设计,此时就可以利用适配器模式作为中间层,兼容老系统的接口调用。
适配器模式可以分为两种实现方式:类适配器和对象适配器。
类适配器 : 通过多重继承,让适配器类同时继承目标接口和现有类,从而实现接口的适配。但在Java等语言中,由于不支持多重继承,类适配器的实现较为复杂,通常不常用。
// 目标接口
public interface Target {
void request();
}
// 适配者类
public class Adaptee {
public void specificRequest() {
System.out.println("Adaptee's specificRequest");
}
}
// 类适配器
public class Adapter extends Adaptee implements Target {
@Override
public void request() {
specificRequest();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Target target = new Adapter();
target.request(); // Output: Adaptee's specificRequest
}
}
对象适配器 : 通过组合,让适配器类持有现有类的实例,并实现目标接口**。对象适配器使用组合关系来实现接口的适配**,较为常用。
// 目标接口
public interface Target {
void request();
}
// 适配者类
public class Adaptee {
public void specificRequest() {
System.out.println("Adaptee's specificRequest");
}
}
// 对象适配器
public class Adapter implements Target { //这里不再是和类适配器一样继承适配器,而是在类中装配适配器对象
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new Adapter(adaptee);
target.request(); // Output: Adaptee's specificRequest
}
}
Java 日志中的 slf4j 其实就是使用了适配器模式来统一不同日志框架接口,使得我们不需要修改代码就可以替换不同的底层日志实现。
什么是桥接模式?一般用在什么场景?
桥接模式使用场景比较少,它主要的作用是将抽象和实现解耦,使它们可以独立地变化。
我们熟知的JDBC 就使用了桥接模式,借着它我们来理解下这个设计模式。
JDBC定义了抽象的规范,不同的数据库厂商遵循这些规范,但是它们各自又会有不同的实现。 在使用中,如果我们数据库是 mysql,则传入 com.mysql.jdbc.Driver 驱动实现类,如果要换成 oracle替换实现类为oracle.jdbc.driver.OracleDriver 即可,这就是典型的抽象与实现解耦。
实际执行数据库操作,例如获取连接,实际上就是委托给具体的实现类,JDBC源码如下:
这样设计后,如果更换数据库,除了替换实现类,关于JDBC的使用代码我们不需要做任何修改,这就是桥接模式带来的好处,提升了系统的可扩展性,也符合开闭原则。
代码实例
实现形状颜色解耦
-
实现化(Implementor):定义颜色接口
public interface Color { void applyColor(); }
-
具体实现化(Concrete Implementor):实现具体颜色
public class RedColor implements Color { @Override public void applyColor() { System.out.println("Applying red color"); //红色 } } public class BlueColor implements Color { //蓝色 @Override public void applyColor() { System.out.println("Applying blue color"); } }
-
抽象化(Abstraction):定义形状接口
public abstract class Shape { protected Color color; public Shape(Color color) { this.color = color; } public abstract void draw(); }
-
细化抽象化(Refined Abstraction):实现具体形状
public class Circle extends Shape { public Circle(Color color) { super(color); } @Override public void draw() { System.out.print("Drawing a circle with "); color.applyColor(); } } public class Rectangle extends Shape { public Rectangle(Color color) { super(color); } @Override public void draw() { System.out.print("Drawing a rectangle with "); color.applyColor(); } }
-
客户端代码
public class Client { public static void main(String[] args) { // 创建红色圆形 Shape redCircle = new Circle(new RedColor()); redCircle.draw(); // 输出: Drawing a circle with Applying red color // 创建蓝色矩形 Shape blueRectangle = new Rectangle(new BlueColor()); blueRectangle.draw(); // 输出: Drawing a rectangle with Applying blue color } }
什么是组合模式?一般用在什么场景?
又叫部分整体模式,将对象组合成树状结构以表示"整体------部分"的层次关系
注意,数据必须是树形结构,也因此这个设计模式的应用场景有限,但在业务上还是比较常见,因为很多业务都有后台管理系统,含菜单管理、部门管理等。 例如菜单、子菜单、权限等关系就可以组合成树形结构,部门、子部分以及人员也可以组合成树形结构,这种场景就很契合组合模式的实现。
以部门为示例,展示组织内的部门和人员信息,我们简单看下实现代码:
// 抽象组织
interface OrganizationComponent {
void showDetails();
}
// 组织人员
class Employee implements OrganizationComponent {
private String name;
private String position;
public Employee(String name, String position) {
this.name = name;
this.position = position;
}
@Override
public void showDetails() {
System.out.println("Employee: " + name + ", Position: " + position);
}
}
// 组织部门
class Department implements OrganizationComponent {
private String name;
private List<OrganizationComponent> components = new ArrayList<>();
public Department(String name) {
this.name = name;
}
@Override
public void showDetails() {
System.out.println("Department: " + name);
for (OrganizationComponent component : components) {
component.showDetails();
}
}
public void add(OrganizationComponent component) { //往部门添加人员 或 部门
components.add(component);
}
public void remove(OrganizationComponent component) {
components.remove(component);
}
}
// 使用代码
public class Client {
public static void main(String[] args) {
// 创建人员
OrganizationComponent employee1 = new Employee("John Doe", "Developer");
OrganizationComponent employee2 = new Employee("Jane Smith", "Designer");
OrganizationComponent employee3 = new Employee("Emily Davis", "Manager");
// 创建部门
Department engineeringDepartment = new Department("Engineering Department");
Department designDepartment = new Department("Design Department");
Department headDepartment = new Department("Head Department");
// 添加人员到部门
engineeringDepartment.add(employee1);
designDepartment.add(employee2);
headDepartment.add(employee3);
// 添加子部门到上级部门
headDepartment.add(engineeringDepartment);
headDepartment.add(designDepartment);
// 显示整个组织结构的详情
headDepartment.showDetails();
// 输出
// Department: Head Department
// Employee: Emily Davis, Position: Manager
// Department: Engineering Department
// Employee: John Doe, Position: Developer
// Department: Design Department
// Employee: Jane Smith, Position: Designer
}
}
通过组合模式,提供统一的接口,简化对层次结构的处理,使得使用方代码更加简洁与灵活。
什么是装饰器模式?一般用在什么场景?
-
装饰器模式(Decorator Pattern) 主要作用是通过创建包装类 来实现功能的增强,而不是修改原始类。
-
通过创建一个装饰器类,该类实现了与原始对象相同的接口,并持有一个原始对象的引用。通过将原始对象传递给装饰器
最典型的装饰器实现就是 Java 中的I/O类库,示例代码如下:
import java.io.*;
public class IOExample {
public static void main(String[] args) throws IOException {
File file = new File("test.txt");
FileInputStream fis = new FileInputStream(file); //读取文件流
BufferedInputStream bis = new BufferedInputStream(fis); //fis被BufferedlnputStream 装饰,提供了缓存的功能
DataInputStream dis = new DataInputStream(bis); //被 DatalnputStream 装饰,提供了按数据类型读取的功能
while (dis.available() > 0) {
System.out.println(dis.readLine());
}
dis.close();
}
}
可以看到,多个装饰器叠加在一起,实现了多个功能的增强,且不会增加类的实现复杂度,每个装饰器仅需关注自己的加强功能即可,提高代码的灵活性和可维护性。
文件流的代码比较复杂,这里就不展示了,我们简单看个多装饰器叠加的代码实现:
// 组件
interface Component {
void operation();
}
// 具体构件
class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("ConcreteComponent operation");
}
}
// 装饰器
abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
// 具体装饰器 A
class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedBehavior();
}
private void addedBehavior() {
System.out.println("ConcreteDecoratorA added behavior");
}
}
// 具体装饰器 B
class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedState();
}
private void addedState() {
System.out.println("ConcreteDecoratorB added state");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
Component decoratorA = new ConcreteDecoratorA(component);
Component decoratorB = new ConcreteDecoratorB(decoratorA);
decoratorB.operation();
// Output:
// ConcreteComponent operation
// ConcreteDecoratorA added behavior
// ConcreteDecoratorB added state
}
}
装饰器、适配器、代理、桥接这四种设计模式有什么区别?
这四种设计模式看起来非常相像,有些甚至从代码上来看差不了多少,那它们之间有什么区别呢?
其实设计模式最大的区别就是 设计思路,它们的出发点都是不一样的。
-
装饰器模式: 目的是在不改变原始类接口、不对原始类复杂化的情况下,对原始类进行本身功能增强,并且支持嵌套多装饰器多功能增强。
-
适配器模式: 目的是面对不兼容的接口进行适配,主要是兼容的问题,不得已的做法。好比我们出国旅游,每国的插头标准不一样,我们只能做转接头适配。
-
代理模式: 目的是在不改变原始类接口的情况下,为原始类提供代理类,对其进行访问控制,实现额外功能,它和装饰器的区别在于不是增强原始类本身对应的功能。
-
桥接模式: 它的目的是将抽象与实现分离,使它们可以独立地变化,例如JDBC场景,不同数据库厂商的不同实现。
什么是外观模式 ? 一般用在什么场景?
外观模式(Facade Pattern)也叫门面模式,是一种结构型设计模式,它提供了一个简化的接口,用于访问复杂系统中的一组接口。将复杂系统的功能封装起来,让客户端可以更方便地使用系统功能而不需要了解其内部复杂结构。
举个例子就清晰了。例如 A系统有 a、b、c三个接口,B需要分别调用 a、b、c三个接口,这样比较麻烦,所以A将 a、b、c封装成一个接口给B调用,对B来说使用起来就方便了,这就是外观模式
不过在使用外观模式的同时,我们也需要考虑接口的设计粒度,如果接口的粒度太大,可复用性就低了,接口的粒度太小,易用性就低了。所以设计是一个权衡的过程,我们需要综合考虑,理论上可复用性为主,然后特殊情况利用外观模式封装一个大接口提供出去。
class CPU { //CPU
public void start() {
System.out.println("CPU 启动");
}
public void shutdown() {
System.out.println("CPU 关闭");
}
}
class Memory { //内存
public void start() {
System.out.println("内存启动");
}
public void shutdown() {
System.out.println("内存关闭");
}
}
class HardDrive { //硬盘
public void start() {
System.out.println("硬盘启动");
}
public void shutdown() {
System.out.println("硬盘关闭");
}
}
class ComputerFacade { //外观类,封装了计算机系统的一组复杂接口,包括 CPU、内存和硬盘的启动和关闭
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public ComputerFacade() {
cpu = new CPU();
memory = new Memory();
hardDrive = new HardDrive();
}
public void start() { //启动计算机
System.out.println("计算机启动开始");
cpu.start();
memory.start();
hardDrive.start();
System.out.println("计算机启动完成");
}
public void shutdown() { //关闭计算机
System.out.println("计算机关闭开始");
cpu.shutdown();
memory.shutdown();
hardDrive.shutdown();
System.out.println("计算机关闭完成");
}
}
ComputerFacade computerFacade = new ComputerFacade();
computerFacade.start();
// 输出:
// 计算机启动开始
// CPU 启动
// 内存启动
// 硬盘启动
// 计算机启动完成
computerFacade.shutdown();
// 输出:
// 计算机关闭开始
// CPU 关闭
// 内存关闭
// 硬盘关闭
// 计算机关闭完成
什么是享元模式?一般用在什么场景?
享元模式(Flyweight Pattern)是一种结构型设计模式,主要用于通过共享来减少内存占用,提高内存效率。
例如一个系统中可能会存在大量的重复对象,并且这些对象实际是不可变的,在设计上我们就可以仅在内存中保留一份实例,然后多处引用即可,这样就能节省内存的开销。
在 Integer 中就采用了享元模式,即 integer 中缓冲池。即 Integer -128 到 127 之内的相等,而超过这个范围用 == 就不对了,因为这个范围内采用了享元模式,本质就是同一个对象,所以用 == 判断相等。
具体原理可看 Java基础 29.什么是Java的Integer缓冲池?
示例:
假设我们要绘制棋盘上的棋子,棋子的种类有很多,但是每种棋子的形状和颜色是固定的。我们可以使用享元模式来共享相同种类的棋子对象,从而减少对象的创建数量。
// 棋子接口
interface ChessPiece {
void setColor(String color);
void display(int x, int y);
}
// 具体棋子类
class ConcreteChessPiece implements ChessPiece {
private String color;
public ConcreteChessPiece(String color) {
this.color = color;
}
@Override
public void setColor(String color) {
this.color = color;
}
@Override
public void display(int x, int y) {
System.out.println("Chess Piece color: " + color + ", position: (" + x + "," + y + ")");
}
}
// 享元工厂类
class ChessPieceFactory {
private Map<String, ChessPiece> chessPieces;
public ChessPieceFactory() {
this.chessPieces = new HashMap<>(); //用集合存chessPieces
}
public ChessPiece getChessPiece(String color) { //返回单例
ChessPiece chessPiece = chessPieces.get(color);
if (chessPiece == null) {
chessPiece = new ConcreteChessPiece(color);
chessPieces.put(color, chessPiece);
}
return chessPiece;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ChessPieceFactory chessPieceFactory = new ChessPieceFactory();
ChessPiece blackPiece1 = chessPieceFactory.getChessPiece("black");
ChessPiece blackPiece2 = chessPieceFactory.getChessPiece("black");
ChessPiece whitePiece1 = chessPieceFactory.getChessPiece("white");
ChessPiece whitePiece2 = chessPieceFactory.getChessPiece("white");
blackPiece1.display(1, 2); // 输出:Chess Piece color: black, position: (1,2)
blackPiece2.display(3, 4); // 输出:Chess Piece color: black, position: (3,4)
whitePiece1.display(5, 6); // 输出:Chess Piece color: white, position: (5,6)
whitePiece2.display(7, 8); // 输出:Chess Piece color: white, position: (7,8)
}
}
什么是代理模式?一般用在什么场景?
代理模式就是 通过一个代理对象来控制对另一个对象的访问,在不改变原始对象的情况下,提供了对原始对象的间接访问,实现额外的功能。
这个模式在我们业务中太常见了,例如动态代理,Spring AOP ,RPC 框架也是使用了动态代理才使得调用远程方法和本地方法一样。
所以,统一报错、监控、限流、鉴权等等,需要跟业务解耦的功能,我们基本上都是使用代理类进行统一处理的。 简单举个计数代理的例子,比如统计文档展示的次数,通过 DocumentProxy 代理类就附加了这个功能:
核心是创建一个代理类,该类实现了与原始对象相同的接口,并持有一个原始对象的引用。在代理类的方法中,我们可以添加额外的功能,然后将请求转发给原始对象进行处理。
// 图片接口
interface Image {
void display();
}
// 具体图片类
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading " + filename + " from disk.");
}
@Override
public void display() {
System.out.println("Displaying " + filename);
}
}
// 代理图片类
class ProxyImage implements Image {
private RealImage realImage; //引入具体图片类
private String filename; //实现了对原始图片对象的控制,而不需要修改原始图片类的代码
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
beforeDisplay();
realImage.display();
}
private void beforeDisplay() {
System.out.println("Before displaying " + filename + ", do some pre-processing.");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Image image1 = new ProxyImage("image1.jpg");
Image image2 = new ProxyImage("image2.jpg");
image1.display(); // 输出:Before displaying image1.jpg, do some pre-processing.
// Loading image1.jpg from disk.
// Displaying image1.jpg
image2.display(); // 输出:Before displaying image2.jpg, do some pre-processing.
// Loading image2.jpg from disk.
// Displaying image2.jpg
}
}
行为型模式
什么是观察者模式?一般用在什么场景?
观察者模式其实也称为发布订阅模式,它定义了对象之间的一种一对多 的依赖关系,让多个观察者对象 同时监听某一个主题对象。当主题对象状态发生变化时,它会通知所有观察者对象。
目的:将观察者和被观察者代码解耦,使得一个对象或者事件的变更,让不同观察者可以有不同的处理,非常灵活,扩展性很强,是事件驱动编程的基础。
观察者模式的组成部分
-
Subject(主题/被观察者): 状态发生变化时,通知所有注册的观察者
-
Observer(观察者): 接收来自主题的更新通知,并进行相应的操作。
-
ConcreteSubject(具体主题): 实现具体的主题对象,保存需要被观察的状态。
-
ConcreteObserver(具体观察者): 实现具体的观察者对象,更新自己以与主题的状态同步
我们以一个新闻发布系统来理解下观察者模式。
1)定义接口
新闻发布者(Subject)可以发布新闻,观察者(Observer)是订阅者,希望获取新闻更新
2)实现具体类
-
具体新闻发布者(Concrete Subject)维护一个订阅者列表,并在发布新闻时通知他们
-
具体观察者(Concrete Observer)如报纸、新闻网站等,实现更新方法来展示新闻
3)订阅和退订
订阅者可以订阅(注册)或退订(注销)新闻发布者的新闻。
4)发布新闻
当新闻发布者有新新闻时,"观察者更新自己的新闻内容。它通知所有订阅的观察者
5)客户端代码
客户端代码创建新闻发布者和观察者对象,订阅者选择订新闻,并在接收到新闻时更新显示。
// 定义观察者接口
interface Observer {
void update(String news);
}
// 定义主题接口
interface Subject {
void attach(Observer o);
void detach(Observer o);
void notifyObservers();
}
// 具体新闻发布者
class NewsPublisher implements Subject {
private List<Observer> observers = new ArrayList<>();
public void attach(Observer o) {
observers.add(o);
}
public void detach(Observer o) {
observers.remove(o);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update("新闻更新");
}
}
public void publishNews() {
// 假设这里是新闻发布逻辑,会通知发布者里面集合的观察者
notifyObservers();
}
}
// 具体观察者
class NewsPaper implements Observer {
public void update(String news) {
System.out.println("新报 " + news);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
NewsPublisher publisher = new NewsPublisher(); //发布者
Observer newsPaper = new NewsPaper(); //观察者
publisher.attach(newsPaper); //导入观察者
publisher.publishNews(); //发布新闻
publisher.detach(newsPaper); //移除观察者
publisher.publishNews(); // 此时报纸订阅者不会接收到新闻
}
}
例如消息队列、 Spring 内的监听器机制等都是其应用场景
什么是迭代器模式?一般用在什么场景?
迭代器模式,它提供一种方法顺序访问一个集合对象中的各个元素,而又不暴露该对象(可能是数组、链表、树等等)的内部实现。
将遍历逻辑与集合对象的实现分离,提供一致的遍历方式,使得代码统一化,在不改变遍历代码的情况下就能替换底层集合实现。
像 Java 的java.util.Iterator
接口和Iterable
接口是迭代器模式的直接应用。所有集合类(如ArrayList、HashSet、LinkedList 等)都实现了 Iterable 接口,并提供了 iterator() 方法来获取迭代器,例如,遍历 ArrayList 中的元素:
List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
list.add("three");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
再比如 JDBC 的ResultSet
接口遍历数据库査询结果,也是迭代器模式的实现。
迭代器模式的组成部分
-
lterator(迭代器接口): 定义访问和历元素的接口
-
Aggregate(聚合接口): 定义创建迭代器的接口
-
Concretelterator(具体迭代器): 实现迭代器接口,负责遍历聚合对象中的元素
-
ConcreteAggregate(具体聚合类): 实现聚合接口,返回一个具体的迭代器实例。
我们来简单实现一个迭代器
//定义迭代器接口
interface Iterator<T> {
boolean hasNext();
T next();
}
//定义聚合接口
interface Aggregate<T> {
Iterator<T> createIterator();
}
//定义具体迭代器
class ConcreteIterator<T> implements Iterator<T> {
private List<T> items;
private int position = 0; //当前遍历位置
public ConcreteIterator(List<T> items) {
this.items = items;
}
@Override
public boolean hasNext() { //如果进行集合遍历时位置<集合大小,则返回true
return position < items.size();
}
@Override
public T next() { //获取当前遍历位置的下一个元素
return items.get(position++);
}
}
//定义具体聚合类
class ConcreteAggregate<T> implements Aggregate<T> {
private List<T> items = new ArrayList<>();
public void addItem(T item) { //添加元素方法
items.add(item);
}
@Override
public Iterator<T> createIterator() {
return new ConcreteIterator<>(items);
}
}
//简单使用
public class Client {
public static void main(String[] args) {
ConcreteAggregate<String> aggregate = new ConcreteAggregate<>();
aggregate.addItem("Item 1"); //添加集合元素
aggregate.addItem("Item 2");
aggregate.addItem("Item 3");
Iterator<String> iterator = aggregate.createIterator(); //返回对应该集合的迭代器
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
什么是命令模式?一般用在什么场景?
命令模式(Command Pattern)是一种行为设计模式,它将请求封装成对象,使得请求参数化,便于对请求排队或记录请求日志,以及支持可撤销的操作。
大部分编程语言都不支持以函数作为参数传递,利用命令模式就可以实现这点。听起来稍微有点抽象,其实命令模式非常少见,被问的频率极低,稍作了解即可。
简单看下以开关灯为例实现命令模式的代码,更容易理解上面那段含义:
// 命令接口
interface Command {
void execute();
}
// 接收者
class Light {
public void on() { //用于控制灯光的开关
System.out.println("The light is on.");
}
public void off() {
System.out.println("The light is off.");
}
}
// 具体命令 开灯
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
}
// 具体命令 关灯
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
}
// 调用者,传入了开关命令,因此可以开关灯
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Light light = new Light();
Command lightOn = new LightOnCommand(light); //开灯
Command lightOff = new LightOffCommand(light); //关灯
RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton(); //开灯
remote.setCommand(lightOff);
remote.pressButton(); //关灯
}
}
什么是状态模式?一般用在什么场景?
状态模式 允许对象在其内部状态发生改变时改变其行为,将状态的行为封装在独立的类中,并将这些状态对象组合在拥有状态的对象中,这样就可以在状态改变时切换状态对象,从而改变对象的行为
它主要是状态机的一种实现方式,状态机可分为: 状态、事件、动作三个部分。事件的触发就会导致状态的改变,并且可作出一定的动作(也可以没有动作,只有状态的改变)
// 定义状态接口
interface State {
void handle(Context context);
}
// 上下文环境,状态模式的核心,它持有一个 State 对象,并提供了 setState 方法来改变当前状态
class Context {
private State state;
public Context(State state) {
this.state = state;
}
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle(this);
}
}
// 具体实现类
//AB每个类在处理请求时都会打印一条消息,并且会改变上下文 Context 的状态为另一种状态
class ConcreteStateA implements State {
@Override
public void handle(Context context) {
System.out.println("State A is handling the request.");
context.setState(new ConcreteStateB());
}
}
class ConcreteStateB implements State {
@Override
public void handle(Context context) {
System.out.println("State B is handling the request.");
context.setState(new ConcreteStateA());
}
}
public class Client {
public static void main(String[] args) {
Context context = new Context(new ConcreteStateA()); //新建A状态环境
context.request(); // State A is handling the request. //请求A状态,然后设置当前状态为B
context.request(); // State B is handling the request.
context.request(); // State A is handling the request.
context.request(); // State B is handling the request.
}
}
什么是责任链模式?一般用在什么场景?
责任链模式允许将多个对象连接成一条链,并且沿着这条链传递请求,让多个对象都有机会处理这个请求,请求会顺着链传递,直到某个对象处理它为止。
它主要避免了请求发送者和接受者之间的耦合,增强了系统的灵活性和可扩展性。
在很多场景都能看到责任链模式,比如日志的处理,不同级别不同输出。再比如 Spring 过滤器的 Chain 也是责任链模式。
下面是一个简单的日志处理示例代码。
-
日志处理器有三种类型: 控制台日志处理器(ConsoleLogger)、文件日志处理器(FileLogger)、错误日志处理器(ErrorLogger)。
-
日志处理器按照优先级进行处理,如果当前处理器不能处理,则将请求传递给下一个处理器。
// 责任链模式的抽象日志类
abstract class Logger {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;
protected int level;
protected Logger nextLogger; //日志中可以存下一个日志
public void setNextLogger(Logger nextLogger) {
this.nextLogger = nextLogger;
}
public void logMessage(int level, String message) {
if (this.level <= level) {
write(message);
}
if (nextLogger != null) {
nextLogger.logMessage(level, message);
}
}
protected abstract void write(String message);
}
// 具体处理器类:控制台日志处理器
class ConsoleLogger extends Logger {
public ConsoleLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Standard Console::Logger: " + message);
}
}
// 具体处理器类:文件日志处理器
class FileLogger extends Logger {
public FileLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("File::Logger: " + message);
}
}
// 具体处理器类:错误日志处理器
class ErrorLogger extends Logger {
public ErrorLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Error Console::Logger: " + message);
}
}
// 客户端代码
public class ChainPatternDemo {
private static Logger getChainOfLoggers() { //获取日志责任链
Logger errorLogger = new ErrorLogger(Logger.ERROR);
Logger fileLogger = new FileLogger(Logger.DEBUG);
Logger consoleLogger = new ConsoleLogger(Logger.INFO);
errorLogger.setNextLogger(fileLogger); //存下一个日志
fileLogger.setNextLogger(consoleLogger); //存再下一个日志
return errorLogger; //最后返回
}
public static void main(String[] args) {
Logger loggerChain = getChainOfLoggers();
loggerChain.logMessage(Logger.INFO, "mianshiya.com");
loggerChain.logMessage(Logger.DEBUG, "小程序:面试鸭");
loggerChain.logMessage(Logger.ERROR, "网页端:mianshiya.com");
}
}
什么是中介者模式?一般用在什么场景?
中介模式通过引入了一个中介对象,来封装一组对象之间的交互,来避免对象之间的直接交互。通过引入一个中介者对象,使对象之间的关系变得简单且易于维护。
听起来不就是和现实生活中的中介一样嘛。
引入中介模式的原因:多对象交互可能会使得关系图很混乱,代码也不清晰,让多对象都和中介交互就能避免这点

聊天室实现其实就是运用了中介模式。信息的传递都由服务器这个中介来做,所有用户都把消息发给服务器,不然如果点对点传输大家可以想象下有多复杂。我们可以简单的看下聊天室的实现:
interface ChatMediator { //中介者接口
void sendMessage(String message, User user);
void addUser(User user);
}
class ChatMediatorImpl implements ChatMediator { //具体中介者
private List<User> users; //存储加入的用户
public ChatMediatorImpl() {
this.users = new ArrayList<>();
}
@Override
public void addUser(User user) {
this.users.add(user);
}
@Override
public void sendMessage(String message, User user) {
for (User u : this.users) {
if (u != user) {
u.receive(message);
}
}
}
}
abstract class User { //抽象用户
protected ChatMediator mediator;
protected String name;
public User(ChatMediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
public abstract void send(String message);
public abstract void receive(String message);
}
class UserImpl extends User { //用户
public UserImpl(ChatMediator mediator, String name) {
super(mediator, name);
}
@Override
public void send(String message) {
System.out.println(this.name + " sends: " + message);
mediator.sendMessage(message, this);
}
@Override
public void receive(String message) {
System.out.println(this.name + " receives: " + message);
}
}
public class ChatClient { //客户端
public static void main(String[] args) {
ChatMediator mediator = new ChatMediatorImpl();
User user1 = new UserImpl(mediator, "Alice");
User user2 = new UserImpl(mediator, "Bob");
User user3 = new UserImpl(mediator, "Charlie");
mediator.addUser(user1); //用户加入中介
mediator.addUser(user2);
mediator.addUser(user3);
user1.send("Hello, everyone!");
}
}
什么是访问者模式?一般用在什么场景?
访问者模式它将数据结构与操作分离,使得你可以在不改变数据结构的前提下定义新的操作。访问者模式通过将操作封装到独立的访问者对象中,使得新的操作可以很容易地添加到系统中。
例如对象序列化场景,比如需要将对象转成 JSON 或者 XML 等格式,利用访问者模式将对象的结构和序列化操作分离,这样就能很方便的扩展新的序列化格式。先看下示例代码:
//访问者接口(类比序列化接口)
interface Visitor {
void visit(ElementA element);
void visit(ElementB element);
}
//具体访问者类(类比具体序列化实现)
class ConcreteVisitor implements Visitor {
@Override
public void visit(ElementA element) {
System.out.println("Processing ElementA: " + element.getName());
}
@Override
public void visit(ElementB element) {
System.out.println("Processing ElementB: " + element.getName());
}
}
//需要对序列化的元素接口
interface Element {
void accept(Visitor visitor);
}
// 具体被序列化的元素A
class ElementA implements Element {
private String name;
public ElementA(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 具体被序列化的元素B
class ElementB implements Element {
private String name;
public ElementB(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
//对象结构,包含很多元素
class ObjectStructure {
private List<Element> elements = new ArrayList<>();
public void addElement(Element element) {
elements.add(element);
}
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 组装对象
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.addElement(new ElementA("Element A1"));
objectStructure.addElement(new ElementB("Element B1"));
objectStructure.addElement(new ElementA("Element A2"));
//访问者 (如果是序列化场景,ConcreteVisitor 可以当做 JSON 序列化)
Visitor visitor = new ConcreteVisitor();
objectStructure.accept(visitor);
// 假设后面要替换 xml 序列化,仅需新建 xml 的访问者,然后传入到对象内部即可
Visitor visitorXML = new XMLVisitor();
objectStructure.accept(visitorXML);
}
}
可以看到,将数据结构和操作分离开之后,如果要替换具体的操作,仅需新增一个操作即可,不需要修改任何数据结构,这就符合开闭原则,也保证了类的职责单一。
什么是备忘录模式,一般用在什么场景?
备忘录模式指的是在不违背封装原则的前提下,捕获对象内部的状态,将其保存在外部,便于后面对象恢复之前的状态,使得系统更具灵活性和可维护性。
听来像不像保留个快照作为备份,后面基于这个快照进行恢复? 所以备忘录模式其实也叫快照模式。主要用于撤销、恢复等场景。
来看下示例代码,我先大致解释一下几个类的含义:
-
Memento,存储状态的备忘录
-
Originator,需要备份状态的对象类
-
Caretaker,管理者,仅保存备忘录
// 备忘录类
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
// 需要备份状态的对象类
class Originator {
private String state;
public void setState(String state) {
this.state = state;
System.out.println("State set to: " + state);
}
public String getState() {
return state;
}
public Memento createMemento() {
return new Memento(state);
}
public void restoreMemento(Memento memento) {
this.state = memento.getState();
System.out.println("State restored to: " + state);
}
}
//管理存储类
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("State1");
caretaker.setMemento(originator.createMemento()); //保存快照
originator.setState("State2");
System.out.println("Current State: " + originator.getState());
originator.restoreMemento(caretaker.getMemento()); //恢复快照
System.out.println("Restored State: " + originator.getState());
}
}
如果直接利用 set 方法修改内部状态,就违反了封装的原则,因为如果你暴露了 set 方法,则对象内部的状态很可能被别的业务调用修改了。而 restoreMemento 是一个很清晰的方法定义,即恢复之前的状态,不会被乱用。这也是备忘录模式的前提,不违反封装原则。