【面试准备】设计模式

修订记录时间
首次发布2023.07

一、单例模式

单例模式是一种创建型的设计模式,保证一个类只有一个实例,且提供一个可以全局访问的入口。构造方法为私有的。
优点

  • 只有一个实例,节省了内存,避免了频繁创建和销毁实例。
  • 因为只有一个实例,可以很方便地控制客户访问的实例。

缺点

  • 没有接口,扩展不方便。
  • 与单一职责原则冲突,一个类应关心内部逻辑,即产品定义,不需要关心实例化。

1. 饿汉式

类加载时就实例化线程安全,但可能会浪费内存,加载了不需要的类的实例。

public class HungrySingleton {
	// 构造方法
	private HungrySingleton() {}
	// 类加载的时候就实例化
	private static final instance = new HungrySingleton();
	// 获取实例的入口
	public static HungrySingleton getInstance() {
		return instance;
	}
}

2. 懒汉式

第一次使用时初始化可能存在线程安全问题,但节省了内存。

2.1 线程不安全

public class LazySingleton1 {
	// 构造方法
	private LazySingleton1() {}
	// instance实例先不加载
	private static instance;
	// 获取实例的入口
	public static LazySingleton1 getInstance() {
		// 线程不安全
		if (instance == null) {
			instance = new LazySingleton1();
		}
		return instance;
	}
}

2.2 线程安全但效率低

为了避免并发问题,可以给getInstance方法加synchronized锁。

public class LazySingleton2 {
	// 构造方法
	private LazySingleton2() {}
	// instance实例先不加载
	private static instance;
	// 获取实例的入口
	public static synchronized LazySingleton2 getInstance() {
		// 并发问题只出现在创建时,但synchronized锁会影响到创建完成后
		if (instance == null) {
			instance = new LazySingleton2();
		}
		return instance;
	}
}

2.3 双重校验锁Double-Checked Locking(建议写法)

为了提升小轮车,只针对创建时加synchronized锁。

public class LazySingleton3 {
	// 构造方法
	private LazySingleton3() {}
	// instance实例先不加载
	private static instance;
	// 获取实例的入口
	public static LazySingleton3 getInstance() {
		// 仅针对创建时的并发问题进行加锁
		if (instance == null) {
			// 对这个类加锁
			synchronized(LazySingleton3.class) {
				// 防止一个线程创建时,另一个线程在等待获取synchronized锁
				// 所以需要再次判断instance是否为null
				if (instance == null) {
					instance = new LazySingleton3();
				}
				return instance;
			}
		}
		return instance;
	}
}

二、工厂模式

单例模式是一种创建型的设计模式,将对象的创建与使用代码分离,提供一种统一的接口来创建不同类型的对象。
优点

  • 一个调用者想创建一个对象,只要知道其名称就可以了。
  • 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
  • 屏蔽产品的具体实现,调用者只关心产品的接口。

缺点

  • 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加。

JDK 中的工厂设计模式实例

  • Java.util.Calendar, ResourceBundle and NumberFormat getInstance() 使用了工厂方法模式;
  • valueOf() 在包装类中,如Boolean, Integer也使用了工厂方法模式;

在下面示例中,用shape和具体形状来举例。

1. 简单工厂

创建类别较少的时候可以使用if或map来区分具体的实现类。
有三个角色:工厂类、抽象产品(接口)和具体产品(实现类)

public class SimpleFactory1 {
	public static Shape createShape(String type) {
		if ("CIRCLE".equals(type)) {
			return new Circle();
		} else if ("TRIANGLE".equals(type)) {
			return new Triangle();
		}
		return null;
	}
}
// 使用
Circle circle = (Circle) SimpleFactory1.createShape("CIRCLE");

// 当对象全局唯一时可以使用map
public class SimpleFactory2 {
	public static final Map<String, Shape> cachedShape = new HashMap<String, Shape>();
	// 静态代码块会在类加载的时候将map初始化
	static {
		cachedShape.put("CIRCLE", new Circle());
		cachedShape.put("TRIANGLE", new Triangle());
	}

	public static Shape getShape(String type) {
		Shape shape = cachedShape.get(type);
		return shape;
	}
}
// 使用
Circle circle = (Circle) SimpleFactory2.getShape("CIRCLE");

2. 工厂方法

简单工厂在每次添加新产品就需要修改工厂类。在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展维护。且如果创建对象时有很复杂的逻辑,堆砌if-else会很臃肿。
工厂方法有四个角色:抽象工厂(接口)、具体工厂(实现类)、抽象产品(接口)和具体产品(实现类)
示例中省略抽象产品和具体产品。

// 抽象工厂
public interface ShapeFactory {
	Shape createShape();
}

// 具体工厂
public class CircleFactory implements ShapeFactory {
	@Override
	public Circle createShape() {
		Circle circle = new Circle();
		// 其他逻辑
		// ...
		return circle;
	}
}

// 使用
CircleFactory circleFactory = new CircleFactory();
Circle circle = circleFactory.createShape();

3. 抽象工厂

当产品可以分为多个产品族的时候,可以使用抽象工厂使结构更清晰。抽象工厂模式主要用于创建相关对象的家族。当一个产品族中需要被设计在一起工作时,通过抽象工厂模式,能够保证客户端始终只使用同一个产品族中的对象。
抽象工厂中也有四个角色:抽象工厂、具体工厂、抽象产品(族)、具体产品(族)。
举例:服装品牌A和品牌B都有T恤和牛仔裤卖,在搭配时需要是同品牌的。

// 抽象工厂
public interface AbstractFactory {
	Tshirt buyTshirt();
	Jeans buyJeans();
}

// 具体工厂
public class BrandAFactory implements AbstractFactory {
	@Override
	public TshirtA buyTshirt() {
		return new TshirtA();
	}
	@Override
	public JeansA buyJeans() {
		return new JeansA();
	}
}

// 使用
BrandAFactory brandAFactory = new BrandAFactory();
TshirtA tshirtA = brandAFactory.buyTshirt();
JeansA jeansA = breandAFactory.buyJeans();

三、建造者模式

建造者模式是一种创建型的设计模式,需要和构造函数、set方法、工厂方法区分开。

方法对比
构造函数多参数时入参很长,且增加参数需要改动构造函数
set方法相比构造函数更灵活,但无法在构造时校验必填项,且参数可以用set方法再次修改
工厂模式工厂模式适用于一个type字段来区分不同类对象,不适合一个对象下多个可配入参

建造者模式的优点:

  • 构造完成后不可变,无法通过set来修改。
  • 对于对象的校验可以在build时统一进行,比如必填校验、参数范围校验。
public class PostRequest {
	private String api;
	private String bodyStr;
	// 构造方法为私有
	private PostRequest(Builder builder) {
		this.api = builder.api;
		this.bodyStr = builder.bodyStr;
	}
	// 对象类内只有getter,没有setter
	public String getApi() {
		return api;
	}
	public String getBodyStr() {
		return bodyStr;
	}
	// Builder也可以是非内部类
	public static class Builder {
		private String api;
		private String bodyStr;
		
		public Builder() {}
		// set方法
		public Builder setApi(String api) {
			this.api = api;
			return this;
		}
		public Builder setBodyStr(String bodyStr) {
			this.bodyStr = bodyStr;
			return this;
		}
		// 构造
		public PostRequest build() {
			// 可以进行校验
			if (StringUtils.isBlank(api) {
				throw new IllegalArgumentException("api missing");
			}
			// 返回构造的对象
			return new PostRequest(builder);
		}
	}
}

// 使用
PostRequest postRequest = new PostRequest.Builder()
							.setApi("api")
							.setBodyStr("body content")
							.build();

四、观察者模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值