如何在Unity客户端与Azure Functions服务端间优雅共享代码?
优雅实现跨端领域类的客户端增强方案
核心目标:在不污染共享领域类库(避免服务端引入Unity依赖)的前提下,让客户端给领域类添加专属增强属性/逻辑,以下是几种实用且优雅的方案:
方案1:对象关联+客户端注册表(推荐轻量场景)
核心思路是把客户端专属数据和共享领域类解耦关联,而非通过继承或泛型强行绑定。共享类库只保留核心字段,客户端用注册表来关联专属资源。
共享类库代码(服务端+客户端共用)
public class DomainBullets { public string Property; // 唯一标识用于客户端关联 public Guid Id { get; } = Guid.NewGuid(); } public class DomainGun { public int BulletCount; public string Name; public DomainBullets Bullets; }
Unity客户端代码(仅存在于Unity项目,不进入共享DLL)
// 客户端专属的渲染器类 public class BulletRenderer { public UnityRenderingThing UnityDependentThing; } // 注册表用于关联领域对象和客户端资源 public static class BulletClientRegistry { private static readonly Dictionary<Guid, BulletRenderer> _bulletRendererMap = new(); public static void RegisterBulletRenderer(DomainBullets bullet, BulletRenderer renderer) { _bulletRendererMap[bullet.Id] = renderer; } public static BulletRenderer GetBulletRenderer(DomainBullets bullet) { _bulletRendererMap.TryGetValue(bullet.Id, out var renderer); return renderer; } }
使用方式
// 客户端拿到DomainGun后直接获取渲染器 var bulletRenderer = BulletClientRegistry.GetBulletRenderer(gun.Bullets); if (bulletRenderer != null) { bulletRenderer.UnityDependentThing.DoRender(); }
优势:共享类库完全无客户端依赖,避免转型操作,代码逻辑清晰,适合轻量扩展场景。
方案2:接口分离+封装增强(适合复杂业务场景)
通过接口定义核心领域契约,客户端在自己的项目中实现增强接口,封装共享领域类,既保留核心数据一致性,又添加专属能力。
共享类库代码
// 核心领域契约,服务端和客户端都依赖 public interface IBulletCore { string Property { get; set; } } // 基础实现,服务端直接使用 public class DomainBullets : IBulletCore { public string Property { get; set; } } public class DomainGun { public int BulletCount; public string Name; public IBulletCore Bullets; }
Unity客户端代码
// 客户端增强接口,仅客户端可见 public interface IClientBullet : IBulletCore { BulletRenderer Renderer { get; set; } } // 客户端实现,封装核心领域对象 public class ClientBullet : IClientBullet { private readonly IBulletCore _coreBullet; public BulletRenderer Renderer { get; set; } // 代理核心属性,保证数据一致性 public string Property { get => _coreBullet.Property; set => _coreBullet.Property = value; } public ClientBullet(IBulletCore coreBullet) { _coreBullet = coreBullet; } }
使用方式
// 客户端从服务端拿到DomainGun后,封装为增强版对象 var clientBullet = new ClientBullet(gun.Bullets); // 直接访问Renderer属性,无需转型 clientBullet.Renderer = new BulletRenderer();
优势:遵循依赖反转原则,业务逻辑分层清晰,适合需要扩展更多客户端专属行为的复杂场景。
方案3:Unity组件模式(贴合Unity生态)
如果是Unity特有的场景,直接利用Unity的组件系统,把共享领域类和客户端渲染组件分离,符合Unity开发者的惯用思维。
共享类库代码(保持纯净)
public class DomainBullets { public string Property; } public class DomainGun { public int BulletCount; public string Name; public DomainBullets Bullets; }
Unity客户端组件
public class BulletRendererComponent : MonoBehaviour { // 直接在Inspector中关联共享领域对象 public DomainBullets BulletData; // 客户端专属渲染资源 public BulletRenderer Renderer; private void Start() { // 初始化时绑定数据和渲染逻辑 Renderer.SetBulletData(BulletData); } }
优势:完全贴合Unity的开发模式,无需额外写关联逻辑,可视化配置更方便,适合Unity项目的快速迭代。
对原有方案的问题分析
- 继承方案:强制客户端进行向下转型,不仅代码冗余,还违反了里氏替换原则(客户端子类添加了服务端不需要的属性,破坏了父类的契约纯净性)。
- 泛型空接口方案:空接口属于代码异味,本质是用泛型强行“占位”,既增加了共享类库的复杂度,又没有实际的契约约束,后期维护成本高。
内容的提问来源于stack exchange,提问作者user3010678
相关产品推荐
相关产品推荐

