手撕四种常用设计模式(工厂,策略,代理,单例)

发布于:2025-05-17 ⋅ 阅读:(16) ⋅ 点赞:(0)

工厂模式

一、工厂模式的总体好处

  1. 解耦:客户端与具体实现类解耦,符合“开闭原则”。
  2. 统一创建:对象创建交由工厂处理,便于集中控制。
  3. 增强可维护性:新增对象种类时不需要大改动调用代码。
  4. 便于扩展:易于管理产品族或产品等级结构。、

手写静态工厂模式,通过一个汽车静态工厂负责创建汽车

特点:
  • 工厂类通过一个静态方法来返回不同的对象。
  • 客户端通过传入参数决定创建哪个类。
✅ 优点:
  • 实现简单,结构清晰。
  • 对客户端隐藏了对象的具体创建过程。
⚠️ 缺点:
  • 不符合开闭原则(新增产品需修改 createCar() 方法)。
  • 工厂职责过重,产品一多代码臃肿。
public class StaticFactoryModel {
    
    public static void main(String[] args){
        car car=new CarFactory.createCar("Tesla");
}
interface car{
    void drive();
}
class Tesla implements car{
    @Override
    public void drive(){
        System.out.println("drive Tesla");
    }
}
class toyota implements car{
    @Override
    public 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{
    @Override
    public void call(){
        System.out.println("iPhone call");
    }
}
class Huawei implements phone{
    @Override
    public void call(){
        System.out.println("Huawei call");
    }
}
interface phoneFactory{
    public phone createPhone();
}
class iPhoneFactory implements phoneFactory{
    @Override
    public phone createPhone(){
        return new iPhone();
    }
}
class HuaweiFactory implements phoneFactory{
    @Override
    public 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{

    @Override
    public void use() {
        System.out.println("useing window");
    }
}
class AppleCom implements  Computee{

    @Override
    public void use() {
        System.out.println("using apple");
    }
}

class window implements Os{

    @Override
    public void call() {
        System.out.println("calling window");
    }
}
class AppleOS implements Os{

    @Override
    public void call() {
        System.out.println("calling apple");
    }
}
interface ShowFactory{
    Computee createCom();
    Os createOs();
}
class WinFactory implements ShowFactory{
    @Override
    public Computee createCom() {
        return new hp();
    }
    @Override
    public Os createOs() {
        return new window();
    }
}
//..另外一个工厂对应行为

策略模式

策略模式

上下文负责生成具体的策略类并且负责与客户端交互

抽象策略类为抽象角色,通常由一个接口或者抽象类实现,给出所有的具体策略类需要的接口

具体策略类:是实现接口,提供具体算法或者行为

策略模式优点:

  • 算法解耦:将行为或算法封装在独立策略类中,便于切换和扩展。
  • 避免多重判断:通过多态替代 if-elseswitch,结构更清晰。
  • 符合开闭原则:新增策略时无需改动已有代码,只需增加新策略类。
  • 可复用性高:不同上下文可复用同一个策略类,提升代码复用率
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{

    @Override
    public void use() {
        System.out.println("useing window");
    }
}
class AppleCom implements  Computee{

    @Override
    public void use() {
        System.out.println("using apple");
    }
}

class window implements Os{

    @Override
    public void call() {
        System.out.println("calling window");
    }
}
class AppleOS implements Os{

    @Override
    public void call() {
        System.out.println("calling apple");
    }
}
interface ShowFactory{
    Computee createCom();
    Os createOs();
}
class WinFactory implements ShowFactory{
    @Override
    public Computee createCom() {
        return new hp();
    }
    @Override
    public 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{

    @Override
    public void add() {
        System.out.println("adding");
    }

    @Override
    public 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);
    }
    @Override
    public 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(); 时,实际上分为三步:

  1. 为 uniqueInstance 分配内存
  2. 初始化 uniqueInstance
  3. 将 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;
    }
}

静态内部类实现(线程安全)

  1. 延迟加载机制
    • 静态内部类 SingletonHolder 不会在类加载时初始化,只有在首次调用 getUniqueInstance() 方法并访问 SingletonHolder.INSTANCE 时才会被加载。
    • 此时 JVM 会保证 INSTANCE 的初始化过程是线程安全的,并且 仅执行一次。
  1. 线程安全保证
    • 由于类加载机制的特性,JVM 会通过 类初始化锁(Class Initialization Lock) 确保 INSTANCE 的唯一性,无需额外同步代码。
  1. 优势总结
    • 懒加载:实例仅在需要时创建,节省资源。
    • 线程安全:依赖 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() {
        // 实际功能逻辑写在这里
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到