手撕四种常用设计模式(工厂,策略,代理,单例)
工厂模式
一、工厂模式的总体好处
- 解耦:客户端与具体实现类解耦,符合“开闭原则”。
- 统一创建:对象创建交由工厂处理,便于集中控制。
- 增强可维护性:新增对象种类时不需要大改动调用代码。
- 便于扩展:易于管理产品族或产品等级结构。、
手写静态工厂模式,通过一个汽车静态工厂负责创建汽车
特点:
- 工厂类通过一个静态方法来返回不同的对象。
- 客户端通过传入参数决定创建哪个类。
✅ 优点:
- 实现简单,结构清晰。
- 对客户端隐藏了对象的具体创建过程。
⚠️ 缺点:
- 不符合开闭原则(新增产品需修改
createCar()
方法)。 - 工厂职责过重,产品一多代码臃肿。
public class StaticFactoryModel {public static void main(String[] args){car car=new CarFactory.createCar("Tesla");
}
interface car{void drive();
}
class Tesla implements car{@Overridepublic void drive(){System.out.println("drive Tesla");}
}
class toyota implements car{@Overridepublic void drive(){System.out.println("drive toyota");}
}class CarFactory{public static car createCar(String type){switch(type){case"Tesla": return new Tesla();case"toyota":return new toyota();default:throw new IllegalArgumentException("UnKnow Car");}}
}
手写工厂方法模式
特点:
- 将创建对象的工作延迟到子类,通过不同工厂子类创建不同对象。
✅ 优点:
- 满足开闭原则,新增产品只需新增对应工厂。
- 结构清晰,职责单一,每个工厂只负责一种产品的创建。
⚠️ 缺点:
- 类的数量变多,增加系统复杂度。
- 只能生产单一类型的产品。
package com;public class FactoryMethod {public static void main(String[] args) {phoneFactory factory=new iPhoneFactory();phone myphone=factory.createPhone();myphone.call();}
}interface phone{public void call();
}
class iPhone implements phone{@Overridepublic void call(){System.out.println("iPhone call");}
}
class Huawei implements phone{@Overridepublic void call(){System.out.println("Huawei call");}
}
interface phoneFactory{public phone createPhone();
}
class iPhoneFactory implements phoneFactory{@Overridepublic phone createPhone(){return new iPhone();}
}
class HuaweiFactory implements phoneFactory{@Overridepublic phone createPhone(){return new Huawei();}
}
手写抽象工厂模式
✅ 特点:
- 一个工厂可以生产多个相关的产品(如电脑 + 操作系统)。
✅ 优点:
- 更强的扩展能力,可以生产“产品族”(多个相关产品)。
- 高度封装了产品的创建细节,对客户端透明。
⚠️ 缺点:
- 不易新增“新产品”(比如新加一个 Printer 接口)需修改所有工厂。
- 抽象程度更高,理解成本稍大。
package com;public class AbstractFactory {public static void main(String[] args){ShowFactory factory=new WinFactory();Computee myCom=factory.createCom();Os myOs=factory.createOs();}
}interface Computee{public void use();
}
interface Os{public void call();
}class hp implements Computee{@Overridepublic void use() {System.out.println("useing window");}
}
class AppleCom implements Computee{@Overridepublic void use() {System.out.println("using apple");}
}class window implements Os{@Overridepublic void call() {System.out.println("calling window");}
}
class AppleOS implements Os{@Overridepublic void call() {System.out.println("calling apple");}
}
interface ShowFactory{Computee createCom();Os createOs();
}
class WinFactory implements ShowFactory{@Overridepublic Computee createCom() {return new hp();}@Overridepublic Os createOs() {return new window();}
}
//..另外一个工厂对应行为
策略模式
策略模式
上下文负责生成具体的策略类并且负责与客户端交互
抽象策略类为抽象角色,通常由一个接口或者抽象类实现,给出所有的具体策略类需要的接口
具体策略类:是实现接口,提供具体算法或者行为
策略模式优点:
- 算法解耦:将行为或算法封装在独立策略类中,便于切换和扩展。
- 避免多重判断:通过多态替代
if-else
或switch
,结构更清晰。 - 符合开闭原则:新增策略时无需改动已有代码,只需增加新策略类。
- 可复用性高:不同上下文可复用同一个策略类,提升代码复用率
package com;public class AbstractFactory {public static void main(String[] args){ShowFactory factory=new WinFactory();Computee myCom=factory.createCom();Os myOs=factory.createOs();}
}interface Computee{public void use();
}
interface Os{public void call();
}class hp implements Computee{@Overridepublic void use() {System.out.println("useing window");}
}
class AppleCom implements Computee{@Overridepublic void use() {System.out.println("using apple");}
}class window implements Os{@Overridepublic void call() {System.out.println("calling window");}
}
class AppleOS implements Os{@Overridepublic void call() {System.out.println("calling apple");}
}
interface ShowFactory{Computee createCom();Os createOs();
}
class WinFactory implements ShowFactory{@Overridepublic Computee createCom() {return new hp();}@Overridepublic Os createOs() {return new window();}
}
//..另外一个工厂对应行为
代理模式
代理模式(Proxy Pattern)是结构型设计模式的一种,
定义如下:
为其他对象提供一种代理以控制对这个对象的访问。
这里使用jdk代理实现代理模式
优点:
- 增强功能:在不修改原始对象的情况下增加额外逻辑(如权限校验、日志、事务等)。
- 解耦结构:将业务逻辑与通用功能分离,代码更清晰、职责更单一。
- 灵活控制:可以在调用前后做一些处理,比如安全控制、延迟加载、访问控制等。
- 支持动态扩展:通过 JDK 动态代理可根据接口生成代理对象,运行时更灵活。
package com;import java.awt.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;public class ProxyModel {
}interface UserDao{public void add();public void delete();
}
class UserDaoImpl implements UserDao{@Overridepublic void add() {System.out.println("adding");}@Overridepublic void delete() {System.out.println("deleteling");}
}
class UserProxy implements InvocationHandler{Object object;public UserProxy(Object obb){object=obb;}public Object getProxy(){return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理加强前");Object invoke = method.invoke(object, args);System.out.println("代理加强后");return invoke;}
}
单例模式
定义:
单例模式就是一个类只有一个实例,并且还提供这个实例的全局访问点(避免一个全局使用的类频繁创建和销毁,耗费系统资源)
设计要素
- 一个私有的构造函数(确保只能由单例类自己创建实例)
- 一个私有的静态变量(确保只有一个实例)
- 一个公有的静态函数(给调用者提供调用方法)
单例类的构造方法不让其他人修改和使用;并且单例类自己只创建一个实例,这个实例,其他人也无法修改和直接使用;然后单例类提供一个调用方法,想用这个实例,只能调用。这样就确保了全局只创建了一次实例。
六种实现方式
懒汉式(线程不安全)
先不创建实例,当第一次被调用的时候再创建实例,延迟了实例化,不需要使用该类就不会实例化,节省了系统资源
线程不安全,如果多个线程同时进入了lazyd==null,此时如果还没有实例化,多个线程就会进行实例化,导致实例化了多个实例
package com;public class LazyD {private static LazyD lazyd;private LazyD(){}public static LazyD getUniqueInstance(){if(lazyd==null){lazyd=new LazyD();}return lazyd;}
}
饿汉式不管使用还是不使用这个实例,直接实例化好实例即可,然后如果需要使用的时候,直接调用方法即可
优点:提前实例化了,避免了线程不安全的问题
缺点:直接实例花了这个实例,不会再延迟实例化,如果系统没有使用这个实例,就会导致操作系统的资源浪费
package com;public class HungryD {private static HungryD uniqueInstance=new HungryD();private HungryD(){};public static HungryD getUniqueInstance(){return uniqueInstance;}
}
懒汉式(线程安全)
和基本的懒汉式的区别就是在get方法上 加了一把 锁。如此一来,多个线程访问,每次只有拿到锁的的线程能够进入该方法,避免了多线程不安全问题的出现。
package com;public class Singletion {private static Singletion uniqueInstance;private Singletion(){};public static synchronized Singletion getUniqueInstance(){if(uniqueInstance==null){uniqueInstance=new Singletion();}return uniqueInstance;}
}
双重校验锁实现(线程安全)
双重检查锁定(Double-Check Locking)是一种对线程安全的懒汉式单例模式的优化。传统的线程安全懒汉式存在性能问题——即使单例已经创建,每次调用仍然需要获取锁,导致性能下降。
而双重检查锁定通过在加锁前先判断实例是否已存在,避免了不必要的锁开销:
- 如果实例已创建,直接返回实例,不进入加锁代码块,提升了效率。
- 如果实例未创建,多个线程同时进入时,由于加锁机制,只有一个线程能够进入锁内创建实例,保证线程安全。
因此,只有在首次实例化时会发生线程阻塞,之后的调用都不会再产生锁竞争,从而实现了高效且安全的延迟初始化。
核心就是对比懒汉式的线程安全版本有性能提升
还有就是使用volatile关键字修饰uniqueInstance实例变量的原因如下
执行 uniqueInstance = new Singleton();
时,实际上分为三步:
- 为 uniqueInstance 分配内存
- 初始化 uniqueInstance
- 将 uniqueInstance 指向分配的内存地址
虽然正常顺序是 1 → 2 → 3,但 JVM 可能因指令重排导致执行顺序变为 1 → 3 → 2。
在单线程环境中这不会有问题,但在多线程环境下可能导致安全隐患:例如线程 A 执行了步骤 1 和 3,还未完成初始化(步骤 2),线程 B 看到 uniqueInstance 非空后直接使用它,结果是使用了未初始化的实例。
为避免这种情况,使用 volatile
关键字修饰 uniqueInstance,可以禁止指令重排,确保多线程环境下实例的正确初始化和可见性,保证线程安全。
package com;public class Singletion {private volatile static Singletion uniqueInstance;private Singletion(){};public static Singletion getUniqueInstance(){if(uniqueInstance==null){synchronized (Singletion.class){if(uniqueInstance==null){uniqueInstance=new Singletion();}}}return uniqueInstance;}
}
静态内部类实现(线程安全)
- 延迟加载机制
-
- 静态内部类
SingletonHolder
不会在类加载时初始化,只有在首次调用getUniqueInstance()
方法并访问SingletonHolder.INSTANCE
时才会被加载。 - 此时 JVM 会保证
INSTANCE
的初始化过程是线程安全的,并且 仅执行一次。
- 静态内部类
- 线程安全保证
-
- 由于类加载机制的特性,JVM 会通过 类初始化锁(Class Initialization Lock) 确保
INSTANCE
的唯一性,无需额外同步代码。
- 由于类加载机制的特性,JVM 会通过 类初始化锁(Class Initialization Lock) 确保
- 优势总结
-
- 懒加载:实例仅在需要时创建,节省资源。
- 线程安全:依赖 JVM 的类加载机制,无需双重检查锁(DCL)或
synchronized
。 - 高性能:无锁竞争,访问效率高
package com;public class Singletion {private Singletion(){};private static class SingletionHolder{private static final Singletion INSTANCE=new Singletion()l}public static Singletion getUniqueInstance(){return SingletionHolder.INSTANCE;}
}
枚举类实现(线程安全)
枚举类的创建就是线程安全的,任何情况下都是单例的
枚举实现单例时,其实例的创建由 JVM 保证线程安全,且天然是单例。
优点
- 写法简洁
- 天然线程安全
- 自动防止反射和反序列化攻击
关于反序列化问题
- 序列化:将 Java 对象转换为字节序列
- 反序列化:根据字节序列重建 Java 对象
常规单例模式在反序列化时可能会创建新的实例,破坏单例性。
为了避免这一问题,通常需要重写 readResolve()
方法来确保反序列化返回同一个实例:
package com;public enum Singletion {INSTANCE;// 添加业务逻辑方法public void using() {// 实际功能逻辑写在这里}
}