LOADING

加载过慢请开启缓存 浏览器默认开启

Unity 常用面向对象设计模式系列(5):适配器模式(Adapter)


一、适配器模式定义与意图

意图:将一个类的接口转换成客户期望的另一个接口。适配器模式让原本由于接口不兼容而无法一起工作的那些类可以一起工作。

  • 目标(Target)接口:业务代码所期望调用的统一接口。
  • 适配者(Adaptee)接口:现有的不同平台/SDK 提供的多种接口。
  • 适配器(Adapter):实现 Target 接口,内部委托给具体 Adaptee,实现异构接口的统一。

AdapterPattern


二、为什么要用适配器

  1. 隔离平台差异
    客户端代码只依赖 IAnalytics,无须感知背后是 Firebase、Unity Analytics 还是自研系统。
  2. 便于测试
    在单元测试中,可以注入 MockAnalyticsAdapter,模拟上报逻辑,无需依赖真实 SDK。
  3. 降低业务代码耦合
    业务模块只调用 Analytics.Instance.TrackEvent(),无需在各处散落 #if UNITY_ANDROID
  4. 按需替换实现
    随着运营需求变化,只要替换 Adapter 绑定即可,无需大规模修改业务逻辑。

三、Unity 中的典型适配场景

  1. 第三方统计 SDK
    Firebase、Adjust、Unity Analytics、腾讯统计等多家 SDK 需要统一调用。
  2. 跨平台 IAP
    Google Play、App Store、Steam、WeChat Mini Game 的购买流程不同,需统一接口。
  3. 平台文件存储路径
    Application.persistentDataPathApplication.temporaryCachePath 在不同平台差异化。
  4. 输入系统切换
    旧版 Input 与新 InputSystem 接口不一致,可以用适配器封装。

四、实战示例:Analytics 适配器

4.1 定义目标接口

// <summary>业务期望的统计接口</summary>
public interface IAnalytics {
    /// <summary>上报一次事件,附带键值对属性</summary>
    void TrackEvent(string eventName, Dictionary<string, object> properties = null);
}

4.2 Firebase 适配器

// <summary>Firebase Analytics SDK 的原生调用</summary>
public class FirebaseAnalyticsSDK {
    public static void LogEvent(string name, IDictionary<string, object> parameters) {
        // Firebase 原生调用
        Firebase.Analytics.FirebaseAnalytics.LogEvent(name, parameters);
    }
}

/// <summary>将 IAnalytics 转发到 FirebaseAnalyticsSDK</summary>
public class FirebaseAnalyticsAdapter : IAnalytics {
    public void TrackEvent(string eventName, Dictionary<string, object> properties = null) {
        var paramsDict = properties != null
            ? new Dictionary<string, object>(properties)
            : new Dictionary<string, object>();
        FirebaseAnalyticsSDK.LogEvent(eventName, paramsDict);
    }
}

4.3 Unity Analytics 适配器

public class UnityAnalyticsAdapter : IAnalytics {
    public void TrackEvent(string eventName, Dictionary<string, object> properties = null) {
        if (properties == null) {
            UnityEngine.Analytics.Analytics.CustomEvent(eventName);
        } else {
            UnityEngine.Analytics.Analytics.CustomEvent(eventName, properties);
        }
    }
}

4.4 统一访问点

public class AnalyticsManager {
    private static IAnalytics _instance;
    public static IAnalytics Instance {
        get {
            if (_instance == null) {
                // 根据平台/配置选择具体适配器
    #if USE_FIREBASE
                    _instance = new FirebaseAnalyticsAdapter();
    #else
                    _instance = new UnityAnalyticsAdapter();
    #endif
            }
            return _instance;
        }
    }

    // 可选:允许运行时替换
    public static void SetAdapter(IAnalytics adapter) {
        _instance = adapter;
    }
}

4.5 客户端调用

public class GameFlow : MonoBehaviour {
    void Start() {
        AnalyticsManager.Instance.TrackEvent("GameStart");
    }

    public void OnLevelComplete(int level, float time) {
        AnalyticsManager.Instance.TrackEvent(
            "LevelComplete",
            new Dictionary<string, object> {
                { "level", level },
                { "time",  time }
            }
        );
    }
}

五、高级用法

5.1 抽象工厂 + 适配器

结合抽象工厂,在运行时或配置驱动下批量注册多种适配器:

public abstract class AnalyticsFactory : ScriptableObject {
    public abstract IAnalytics CreateAdapter();
}

[CreateAssetMenu("Analytics/FirebaseFactory")]
public class FirebaseFactory : AnalyticsFactory {
    public override IAnalytics CreateAdapter() => new FirebaseAnalyticsAdapter();
}

[CreateAssetMenu("Analytics/UnityFactory")]
public class UnityFactory : AnalyticsFactory {
    public override IAnalytics CreateAdapter() => new UnityAnalyticsAdapter();
}

Client 只需持有一个 AnalyticsFactory 资产,通过 Inspector 或热更新替换即可。

5.2 适配器链(Adapter Chain)

当需要在上报前后统一处理(如打点前加用户 ID、后写日志),可采用责任链包装多个适配器:

public class LoggingAdapter : IAnalytics {
    private IAnalytics inner;
    public LoggingAdapter(IAnalytics innerAdapter) { inner = innerAdapter; }
    public void TrackEvent(string name, Dictionary<string, object> props) {
        Debug.Log($"TrackEvent: {name}");
        inner.TrackEvent(name, props);
    }
}

// 链式组合
var baseAdapter = new FirebaseAnalyticsAdapter();
var logging = new LoggingAdapter(baseAdapter);
AnalyticsManager.SetAdapter(logging);

六、注意事项

场景 陷阱 建议
忘记剥离 SDK 依赖 直接在业务里调用 Firebase.Analytics,难以替换 统一封装到 Adapter,客户端仅依赖接口
过度适配 每个微小差异都写一个 Adapter,类数量激增 针对公用差异做抽象,对少量特殊情况可在 Adapter 内做分支
运行时切换成本 动态替换 Adapter 时,旧实例未释放导致状态残留 SetAdapter 前清理静态缓存或事件订阅
多线程 / 异步调用 在异步回调中调用 Adapter,若底层 SDK 线程不安全会出问题 在主线程封装接入,或在 Adapter 中加锁、排队
测试 Mock 直接 new FirebaseAnalyticsAdapter() 无法注入 Mock 在 AnalyticsManager 支持通过 SetAdapter 注入 Mock

七、小结

  1. 只依赖接口:业务代码永远使用 IAnalytics,不直接引用具体 SDK 类。
  2. 集中封装:所有平台/SDK 差异都在 Adapter 内部处理,客户端零感知。
  3. 配置驱动:结合 ScriptableObject 或 DI 容器,运行时切换不同的适配器实现。
  4. 链式组合:利用责任链或装饰器模式在适配器外层增加功能(日志、节流、错误重试)。
  5. Mock-Friendly:在测试中通过 AnalyticsManager.SetAdapter(new MockAnalytics()) 完全替换上报逻辑。