18 KiB
设计模式
设计模式是对许多业务和算法的抽象,因此学习起来具有一定的难度。短时间内掌握是不可能的,需要通过不断的实践和理解来积累经验。对于已经在职场上工作,但后期才开始学习设计模式的人来说,可能会面临更多的挑战。
在学习设计模式时,很多概念趋于理论化,且种类繁多(设计模式总共有23种),如果没有进行系统的梳理,可能很难直接应用到实际场景中。
设计模式中的抽象类通常是接口或抽象类,这些类的实现会根据具体的业务需求进行调整,因而不一定是唯一的实现方式。
在我学习过程中,我试图将所学的内容总结下来,但由于篇幅有限,无法一一列举所有场景,只能举出一些常见的例子。
策略模式
常见应用场景
策略模式广泛应用于支付、计算、文件压缩、登录等场景,尤其是在游戏中,如王者荣耀和PUBG的角色处理。值得注意的是,在实际开发中,一种业务场景可能会同时使用多种设计模式,因此,下面的介绍仅为简化并专注于策略模式的核心概念。
策略模式基础介绍
策略模式涉及三个主要角色:
- 抽象策略(Strategy)
- 具体策略(Concrete Strategy)
- 上下文(Context)
其中,Context
的概念较为抽象,通常用来持有策略并在运行时动态地切换策略。
代码实现
策略模式的实现并不复杂,主要是定义三个角色:抽象策略、具体策略和上下文。为了更清晰地展示,我定义了两个具体策略,每个策略执行不同的操作。然后将策略放入上下文中,最终通过上下文来执行具体策略的方法。
public class BaseStrategy {
public static void main(String[] args) {
// 创建具体策略
ConcreteStrategy1 concreteStrategy1 = new ConcreteStrategy1();
ConcreteStrategy2 concreteStrategy2 = new ConcreteStrategy2();
// 通过上下文执行策略
new Context(concreteStrategy1).doIt();
new Context(concreteStrategy2).doIt();
}
// 抽象策略接口
public interface Strategy {
/**
* 执行策略方法
*/
void method();
}
// 具体策略实现1
public static class ConcreteStrategy1 implements Strategy {
@Override
public void method() {
System.out.println("执行具体策略1...");
}
}
// 具体策略实现2
public static class ConcreteStrategy2 implements Strategy {
@Override
public void method() {
System.out.println("执行具体策略2...");
}
}
// 上下文类,持有策略并执行
public static class Context {
private final Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
/**
* 执行策略的方法
*/
public void doIt() {
strategy.method();
}
}
}
执行结果:
执行具体策略1...
执行具体策略2...
示例-游戏角色
需要制作的游戏角色业务需求,基础角色是其他角色的抽象,包括国王、王后、骑士、士兵等,武器是一个接口所有武器都实现这个接口,切换武器(武器有主武器和副武器)可以调用setPrimaryWeapon
和setSubWeapon
。
代码实现
public class GameCharacter {
public static void main(String[] args) {
Character soldier = new Soldier();
soldier.fight();
Character king = new King();
king.setPrimaryWeapon(new AxeWeapon());
king.fight();
}
// 武器接口
interface Weapon {
String function();
}
// 各种武器实现
public static class SwordWeapon implements Weapon {
@Override
public String function() {
return "⚔️ ⚔️ ⚔️";
}
}
public static class AxeWeapon implements Weapon {
@Override
public String function() {
return "🪓 🪓 🪓";
}
}
public static class KnifeWeapon implements Weapon {
@Override
public String function() {
return "🔪 🔪 🔪";
}
}
// 抽象角色类
public abstract static class Character {
String name;
@Setter
private Weapon primaryWeapon;
@Setter
private Weapon subWeapon;
public Character(String name) {
this.name = name;
// 默认武器配置
this.primaryWeapon = new SwordWeapon();
this.subWeapon = new KnifeWeapon();
}
void fight() {
if (primaryWeapon != null && subWeapon != null) {
String primary = primaryWeapon.function();
String sub = subWeapon.function();
System.out.println(name + ":" + primary + "和" + sub);
} else {
System.out.println(name + "没有武器!");
}
}
}
// 国王角色
public static class King extends Character {
public King() {
super("King");
setPrimaryWeapon(new SwordWeapon());
setSubWeapon(new KnifeWeapon());
}
}
// 士兵角色
public static class Soldier extends Character {
public Soldier() {
super("Soldier");
setPrimaryWeapon(new AxeWeapon());
setSubWeapon(new SwordWeapon());
}
}
}
运行结果:
Soldier:🪓 🪓 🪓和⚔️ ⚔️ ⚔️
King:🪓 🪓 🪓和🔪 🔪 🔪
观察者模式
观察者有四个角色,观察者(Observe)和具体观察者(Concrete Observe)、主题(Subject)和具体主题(Concrete Subject),具体观察者和具体主题可以多个。
简单示例
其中主题中有3个方法,添加(或者理解成注册都可以)、移出、通知,差不多都是这三个方法,其中添加可以理解成注册加入总之是往数组中添加观察者。
代码实现
public class ObserverDemo {
public static void main(String[] args) {
// 定义的主题
News news = new News();
// 观察者
SisterObserver sisterObserver = new SisterObserver();
GirlfriendObserver girlfriendObserver = new GirlfriendObserver();
// 添加观察者
news.addObserver(sisterObserver);
news.notifyObservers("添加了妹妹");
System.out.println("\n-----------------分割线-----------------\n");
// 添加女朋友
news.addObserver(girlfriendObserver);
news.notifyObservers("添加了女朋友");
System.out.println("\n-----------------分割线-----------------\n");
news.removeObserver(girlfriendObserver);
news.notifyObservers("需要和妹妹说点悄悄话,将女朋友移除了,这时女朋友听不见我们说话。。。");
}
// 观察者接口
@FunctionalInterface
interface Observer {
void update(String message);
}
// 主题接口
interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(String message);
}
// 妹妹观察者
static class SisterObserver implements Observer {
@Override
public void update(String message) {
System.out.println("SisterObserver 接受到消息:" + message);
}
}
// 女朋友观察者
static class GirlfriendObserver implements Observer {
@Override
public void update(String message) {
System.out.println("GirlfriendObserver 接受到消息:" + message);
}
}
// 主题类
static class News implements Subject {
private final List<Observer> observerList = new ArrayList<>();
@Override
public void addObserver(Observer observer) {
observerList.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observerList.remove(observer);
}
@Override
public void notifyObservers(String message) {
for (Observer observer : observerList) {
observer.update(message);
}
}
}
}
运行结果:
SisterObserver 接受到消息:添加了妹妹
-----------------分割线-----------------
SisterObserver 接受到消息:添加了女朋友
GirlfriendObserver 接受到消息:添加了女朋友
-----------------分割线-----------------
SisterObserver 接受到消息:需要和妹妹说点悄悄话,将女朋友移除了,这时女朋友听不见我们说话。。。
装饰者模式
一共有四个角色。
- 抽象组件:一般是最原始、最基础的对象。
- 具体组件:一般是最原始最基础的实现对象,可以有多个(只是数量不多),可以大致看下
InputStream
UML结构图。 - 装饰角色:继承或者实现抽象组件这是最关键的地方。
- 具体装饰角色:可以有多个实现或者继承装饰角色。
简单示例
需要做一个抽象的相机功能,相机包含拍照功能(这是肯定的),之后随着业务需求要求相机有美颜功能,在这之后还要有滤镜功能,客户还需要延迟摄影功能。
代码实现
-
需要定义根组件(Component)
-
制作最原始的拍照功能,实现根组件
-
制作延迟摄影,实现根组件
-
作为相机之外的功能(美颜),需要定义装饰角色方便为以后的组件进行扩展
-
继承或者实现(装饰者角色),完成美颜
-
继承或者实现(装饰者角色),完成滤镜
public class CameraDemo {
public static void main(String[] args) {
TakePhotoCamera takePhotoCamera = new TakePhotoCamera();
takePhotoCamera.operation();
System.out.println("\n-----------------分割线-----------------\n");
BeautyCamera beautyCamera = new BeautyCamera(takePhotoCamera);
beautyCamera.operation();
System.out.println("\n-----------------分割线-----------------\n");
FilterCamera filterCamera = new FilterCamera(beautyCamera);
filterCamera.operation();
System.out.println("\n-----------------分割线-----------------\n");
TimerCamera timerCamera = new TimerCamera();
timerCamera.setTime(2);
timerCamera.operation();
filterCamera = new FilterCamera(beautyCamera);
filterCamera.operation();
}
// 相机功能
interface Camera {
/**
* 相机操作
*/
void operation();
}
// 相机装饰者角色
static abstract class CameraDecorator implements Camera {
Camera camera;
public CameraDecorator(Camera camera) {
this.camera = camera;
}
}
// 相机拍照
static class TakePhotoCamera implements Camera {
/**
* 相机操作
*/
@Override
public void operation() {
System.out.println("相机拍照。。。");
}
}
// 相机功能
static class BeautyCamera extends CameraDecorator {
public BeautyCamera(Camera camera) {
super(camera);
}
/**
* 相机操作
*/
@Override
public void operation() {
camera.operation();
System.out.println("美颜功能。。。");
}
}
// 滤镜效果
static class FilterCamera extends CameraDecorator {
public FilterCamera(Camera camera) {
super(camera);
}
/**
* 相机操作
*/
@Override
public void operation() {
camera.operation();
System.out.println("滤镜效果。。。");
}
}
// 实现相机延迟功能
@Setter
static class TimerCamera implements Camera {
// 延迟时间
private Integer time = 0;
/**
* 相机操作
*/
@Override
public void operation() {
System.out.println("开始拍照,延迟时间:" + time + "s");
try {
Thread.sleep(time * 1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
运行结果:
相机拍照。。。
-----------------分割线-----------------
相机拍照。。。
美颜功能。。。
-----------------分割线-----------------
相机拍照。。。
美颜功能。。。
滤镜效果。。。
-----------------分割线-----------------
开始拍照,延迟时间:2s
相机拍照。。。
美颜功能。。。
滤镜效果。。。
命令模式
Command:定义执行请求的接口(或抽象类)。
ConcreteCommand:实现命令接口并具体化执行请求的过程,通常还会持有接收者对象。
Receiver:具体的对象,完成实际的操作任务。
Invoker:调用命令的对象,触发命令的执行。
Client:客户端,负责创建并配置命令对象,将命令与接收者绑定,并传递给调用者执行。
单例模式
懒汉模式
双重检查锁定(DCL)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
// 构造函数私有化
}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton();
}
}
}
return instance;
}
}
静态内部类方式
利用 静态内部类(饿汉式)的特性来实现懒汉模式,并且可以保证线程安全。这种方式只有在调用 getInstance()
时才会加载内部类,从而实现懒加载。
public class Singleton {
private Singleton() {
// 构造函数私有化
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
适用场景
- 如果单例对象比较重,且只在程序中某些特定情况下才使用,懒汉模式可以带来性能优势。
- 在低并发环境或单线程环境下,懒汉模式使用较为简单,不会有多线程安全问题。
- 对于高并发且需要频繁访问单例的场景,推荐使用静态内部类或饿汉模式来避免懒汉模式可能带来的性能问题。
总结
懒汉模式有时为了延迟初始化而牺牲了性能,特别是在多线程环境下。为了保证线程安全,可以采用双重检查锁定(DCL)或静态内部类的方式,而静态内部类方式是最推荐的方式 ,它避免了同步开销,同时也能确保线程安全。
- 优点:
- 延迟加载,节省资源。
- 内存优化,只有在需要时才实例化。
- 减少不必要的初始化开销。
- 缺点:
- 传统懒汉模式线程不安全(多个线程可能会创建多个实例)。
- 使用同步机制会有性能开销,特别是在多线程环境下。
- 代码复杂性较高,容易引入错误。
- 不适合高并发场景,特别是频繁访问时会影响性能。
饿汉模式
饿汉模式(Eager Initialization)是单例模式的一种实现方式,在该方式中,单例实例在类加载时就已经创建完成,而不是在首次使用时才创建。换句话说,饿汉模式 的单例实例在类被加载时就会立即初始化,确保了单例模式的唯一性。
public class Singleton {
// 在类加载时就初始化单例对象
private static final Singleton INSTANCE = new Singleton();
// 私有构造函数,防止外部通过 new 创建实例
private Singleton() {
}
// 提供全局访问点
public static Singleton getInstance() {
return INSTANCE;
}
}
适用场景
- 简单应用:适合没有延迟初始化需求的小型应用,或者单例对象的创建非常轻量,不会影响系统性能的场景。
- 实例化不耗费太多资源:如果单例对象的初始化过程非常轻量级且无须延迟初始化,可以使用饿汉模式。
- 类加载顺序固定:如果不在乎单例对象在启动时是否初始化,且单例对象的初始化过程没有严重性能损失,可以选择饿汉模式。
总结
- 优点:简单、线程安全、性能高(没有锁机制)、实现方便。
- 缺点:不支持延迟加载、可能会浪费资源(如果单例对象不常用时)。
饿汉模式是单例模式中最简单的实现方式,适用于大多数情况下单例对象的创建开销较小,且不需要延迟初始化的场景。如果单例对象的创建非常轻量且不依赖于后期加载时的资源,饿汉模式是一个不错的选择。
枚举类型实现
枚举类型实现单例模式(Enum Singleton)是实现单例模式的另一种方式,优点是更加简洁、可靠,并且避免了许多常见的单例实现方式(如懒汉模式和饿汉模式)可能出现的问题。使用枚举类型的单例实现方式自 Java 5 引入以来,已经成为了最推荐的单例模式实现方式之一。
当你不需要懒加载或延迟初始化时,使用枚举实现单例模式是最理想的选择。
public enum Singleton {
// 唯一实例
INSTANCE;
// 可以添加其他的方法
public void someMethod() {
System.out.println("This is a singleton instance method.");
}
}
使用示例
public class Main {
public static void main(String[] args) {
// 获取单例对象
Singleton singleton = Singleton.INSTANCE;
// 调用单例方法
singleton.someMethod();
}
}