【读书活动感悟分享】极客充电队——《Head First 设计模式》第十一章代理模式读书心得_文章

【读书活动感悟分享】极客充电队——《Head First 设计模式》第十一章代理模式读书心得

迟政
发表于 2025-11-08 19:24:50

Head First 代理模式读书分享第一部分:概述1.1 什么是代理模式?

  • 定义:为其他对象提供一种代理以控制对这个对象的访问。

  • 本质:用“同接口的替身”在调用前后加入你想要的“控制与增强”,在需要时“改变位置/时机/协议/生命周期”,且对使用者透明。

  • 关键点:

    • 控制访问:权限、限流、熔断、缓存、审计、路由、重试等。

    • 透明一致:保持与真实主题相同接口,客户端无感。

    • 位置透明:本地/远程一致调用体验。

    • 生命周期与开销:按需创建与复用,降低成本。

1.2 代理模式的分类

  • 远程代理(Remote Proxy)

    • 作用:为远程对象提供本地代表,隐藏网络/协议/序列化差异,让“像本地调用”的代码在远端执行。

    • 常见形态:.NET Remoting(历史)、WCF、REST API 客户端、gRPC Stub、前端生成的 TypeScript API 客户端、服务网关/SDK。

    • 横切点:认证与租户透传、错误标准化、重试/熔断、跟踪ID/日志、带宽优化(压缩)。

  • 虚拟代理(Virtual Proxy)

    • 作用:延迟创建或替代开销大的对象(Lazy Loading/缓存/占位符),用“轻量替身”先行响应或缓存响应。

    • 常见形态:延迟加载图片/大对象;首次加载后缓存;加载中显示骨架屏/占位对象。

  • 保护代理(Protection Proxy)

    • 作用:在访问前强制权限/数据域控制(行/列级数据权限、上下文隔离)。

    • 常见形态:仓储/查询代理在入库/出库前注入权限过滤;方法级授权拦截;字段脱敏。

  • 智能引用代理(Smart Reference)

    • 作用:在引用真实对象时执行“附加动作”(引用计数、连接池、指标采集、日志、限流/熔断/重试、审计)。

    • 常见形态:带统计的连接/客户端、带熔断/重试的远程调用包装器。
      -(可选扩展)防火墙/反向代理(基础设施层):如 Nginx 作为“服务器端代理”,对外统一入口,对内路由/负载均衡/缓存/SSL 终止。

第二部分:Head First 设计模式中的代理模式2.1 经典例子回顾(糖果机远程监控:Remote Proxy)

  • 核心:远程监控端通过“本地代理”像调用本地糖果机对象一样,实际请求被发送到远程糖果机。

  • .NET 对应简述(与 Java RMI 同构):

    • .NET Remoting 使用“透明代理(Transparent Proxy)”与“编组(序列化)”将本地方法调用转为网络调用,服务器端由远程对象执行,再将结果返回;性质与书中 Java 远程代理一致。

    • 后续技术演进:.NET Remoting → WCF → REST/gRPC;本质一直是远程代理思想。

2.2 代理模式的设计原则

  • 开闭原则(OCP): 代理在“相同接口”上扩展行为(认证/缓存/路由/监控),无需修改真实主题和客户端;新增能力=新增代理/拦截器即可。

  • 单一职责原则(SRP): 真实主题只做业务;代理只做横切或访问控制;职责清晰、变更互不牵连。
    -(常与代理相伴)

第三部分:综合示例

【客户端】 【服务端】
Component → Service → HTTP → DynamicController(代理) → AppService → CatchedWithPermissionUtilty
​ (Remote Proxy) (Dynamic Proxy) (Virtual Proxy)

  • Remote Proxy 示例

    • 后端服务(真实主题):

      public class DocumentManagementAppService : ApplicationService
      {
         public async Task<DocumentDto> GetDocumentWithStatusAsync(Guid id)
        {
             var document = await _repository.GetAsync(id);
             return ObjectMapper.Map<Document, DocumentDto>(document);
        }
      }
    • 前端代理(代理对象):

      export class DocumentManagementService {
       getDocumentWithStatus = (documentId: string) =>
         this.restService.request<any, DocumentDto>({
           method: 'GET',
           url: `/api/app/document-management/document-with-status/${documentId}`,
        });
      }
    • 客户端使用(客户端):

      export class DocumentComponent {
       constructor(private docService: DocumentManagementService) {}
       loadDocument(id: string) {
         this.docService.getDocumentWithStatus(id).subscribe(console.log);
      }
      }
  • Dynamic Proxy(后端动态控制器)

    • 启动期:将 ApplicationService 映射为控制器动作(内存模型)

      • 反射扫描程序集中的 IApplicationService 实现(可按名称/可见性等过滤)。

      • 为每个服务方法解析并约定:

      • 控制器前缀:/api/app/{service-kebab}(如 DocumentManagementAppService → /api/app/document-management)。

      • 动作名:去掉 Async 后 kebab 化(如 GetDocumentWithStatusAsync → document-with-status)。

      • HTTP 动词推断:Get/Find→GET;Create→POST;Update→PUT;Delete/Remove→DELETE;其余默认 POST(均可被特性覆盖)。

      • 为每个方法生成 ActionModel(路由/HTTP 动词/参数来源),注册为 ActionDescriptor(ApiExplorer/Swagger 自然可见)。

      • 预编译 MethodInfo 为调用委托并缓存,减少反射热路径开销。

    • 请求期:命中“内存控制器”的某个 Action → 模型绑定 → 解析服务实例 → 调用方法

      • MVC 路由命中 ActionDescriptor

      • Model Binding 把 Route/Query/Body 绑定到方法参数(含 DTO、CancellationToken)

      • 解析对应 ApplicationService 实例

      • 授权→验证→工作单元→审计→异常标准化

      • 最终用 MethodInfo(通常缓存为已编译委托)在该服务实例上执行

  • Virtual Proxy /Protection Proxy 示例(缓存+权限控制)

    public static ChargeBillEntity HAGetChargeBillByBillID(string billID)
    {
       var chargeBillEntity = RequestProxyUtility.InvokeWithFallbackCondition<ChargeBillEntity>(() =>
          {
               var hashId = string.Format(CacheKey.ChargeBill, billID);
               ChargeBillEntity bill = null;
               using (ICacheClient client = HACacheUtility.HAGetClient(billID, true))
              {
                   var dic = client.GetAllEntriesFromHash(hashId);
                   bill = ConvertUtility.ToObject<ChargeBillEntity>(JsonConvert.SerializeObject(dic));
              }
               return bill;
          }
          , () =>
          {
               return PaySDKRepository.GetChargeBill(billID);
          }
          , FallbackCondition.Null | FallbackCondition.Exception, nameof(HAGetChargeBillByBillID));

           if (chargeBillEntity == null)
               throw new TeldException(PayExpCode.GetChargeBillException, MultiLangHelper.GetText("T_S_FM_Pay_1804066229", "未获取到收款订单"));
     
      // if(currentuser has permission)
           return chargeBillEntity;
      // else
      //throw new TeldException(PayExpCode.GetChargeBillException, MultiLangHelper.GetText("T_S_FM_Pay_1804066229", "没有权限"));
     
    }

  • Smart Reference 示例(重试/熔断/指标)

    // 扩展

第四部分:总结4.1 代理模式的优缺点

  • 优点

    • 职责清晰:业务(真实主题)与横切(代理)解耦。

    • 高扩展性:对扩展开放;增减能力靠“加减代理”。

    • 智能化:透明加入重试/熔断/限流/租户/审计/观测。

  • 缺点

    • 复杂度提升:链路更长,需要完善观测性与治理。

    • 性能折衷:多一层调用/序列化;需缓存/批量/压缩等优化。

4.2 何时使用

  • 需要远程访问(RPC/HTTP/gRPC):隐藏位置/协议差异。

  • 需要统一控制访问:认证、授权、限流、数据权限、审计、租户透传。

  • 需要延迟加载与开销优化:虚拟代理 + 缓存。

  • 需要增强能力:日志、监控、重试/熔断、路由、灰度、压缩/加密。

4.3 与相似模式关系

  • 装饰器模式

    • 相同:同接口、可叠加、透明替换。

    • 差异:装饰器强调“功能增强”;代理强调“访问控制/位置/生命周期/远程编组”。

  • 适配器模式

    • 适配器“改变接口”以适配调用方;代理“保持接口相同”以控制访问。

  • 外观模式

    • 外观提供“更简化的统一接口”;代理在“相同接口”下控制访问/位置/时机/策略。

  • 其他模式的影子

    • 动态代理:运行时生成控制器/拦截器(动态控制器)。

    • 适配器:把 AppService 的方法“适配”为 HTTP 动作。

    • 装饰器:在代理中叠加横切(日志/授权/审计)。

    • 外观:为复杂后端提供统一 HTTP 门面。




96 0

评论


意见反馈