绑定子类的泛型基类,反模式?

简介:

这次总结一个个人认为的反模式:“绑定子类的泛型层基类”,这个模式在一些著名的框架中也见到过,如果CSLA、BlogEngine。我自己在原来的写的框架中,也用到过。

    当然了,个人认为是反模式,各们同仁并不一定这样认为,仁者见仁,智者见智了。不过我好几次都是受尽折磨,所以决定写出来给大家分享下心得。


 

模式介绍

    “层基类”是MF提出的一个基本模式,详见:《Layer Supertype》。这种模式在经典的层次型架构设计的实现中,是极其重要的。我相信,大家一般在做三层架构时,不可能不给出基类的。至少我没见过。:)

    .NET2.0推出后,带来了新的语言特性:《泛型》。它实现了类型的运行时多态,是一种强大的语言特性。

    今天要说的主题正是基于LayerSupertype,并结合了泛型技术而实现的,同样,它还有一个重要的约定:泛型的类型参数必须是最终的子类。看如下一个例子:

1
public  abstract  class  EntityBase<T>
1
2
3
4
5
6
7
8
9
10
11
12
13
     where  T : EntityBase<T>
{
     public  int  Id { get ; set ; }
     //sth else important......
}
public  class  User : EntityBase<User>
{
     public  string  Name { get ; set ; }
}
public  class  Article : EntityBase<Article>
{
     public  string  Title { get ; set ; }
}

    EntityBase作为所有实体类的基类,提供了统一的实体模板、约定和一些通用的基础实现。基于这个基类的代码重用,使得子类的代码非常简单。这里和普通继承、普通泛型的不同点在于父类在运行时绑定了具体子类的类型。


 

设计原理

    为什么要这样设计?基类为什么不直接使用非泛型的基类呢?这是为了在基类实现的通用方法中,能够以强类型的方式直接访问最终的子类。用上面的类举个例子,如果你使用“ActiveRecord模式”,那么要是使用非泛型的基类,你可能会在EntityBase中加入方法:

1
2
3
4
5
6
7
public  abstract  class  EntityBase
{
     public  static  EntityBase GetById( int  id)
     {
         //....
     }
}
1
使用时:
1
EntityBase user = User.GetById(id);

但是,使用泛型基类绑定具体的子类后,我们会这样写代码:

1
2
3
4
5
6
7
public  abstract  class  EntityBase<T>
{
     public  static  T GetById( int  id)
     {
         //....
     }
}
1
User user = User.GetById(id);

也就是说,这是一种更加类型安全的API,用起来会很方便。

     再举一个例子:由于泛型基类运行时绑定了不同的子类,使得它本身的静态字段绑定到最终的子类中的。例如上文中的例子,EntityBase<Article> 和 EntityBase<User>其实是不同的两个运行时类型。这样,当我在EntityBase<T>内声明的静态字段是绑定到各子类中的。如:我在EntityBase<T>中声明了静态字段:

public abstract class EntityBase<T>
{
    private static readonly string TypeName = typeof(T).Name;
}

那么这个字段并不是为所有子类共享,而是User.TypeName和Article.TypeName的值不同,分别是"User”和“Article”。同样的功能,如果你要使用非泛型的基类,由于所有类型共享一个运行时基类,你需要考虑为在基类中为每个具体的类型存储对应的值,例如,使用一个字典存储:

1
2
3
4
5
6
7
8
public  abstract  class  EntityBase
{
     private  static  readonly  Dictionary<Type, string > _allTypeNames = new  Dictionary<Type, string >();
     public  static  string  TypeName(Type concreteType)
     {
         return  _allTypeNames[concreteType];
     }
}

这样的API用起来,是不是很不易用呢?

     上面只是举了些最简单的例子,实际上,由于使用了绑定具体子类的泛型基类,还会有很多地方的设计变得更简单了,在此不再一一列举。


 

 

带来的问题

    使用这种模式,缺点是显而易见的:

    1. 不能直接使用基类进行统一的处理

    继续上面的例子,这样的设计,使得我们不能对所有的实体进行统一的处理。由于User和Article的基类其实是两个不同的运行时类型,所以我不能把它们转换为同一个“实体”类型。如:

EntityBase a = new Article();

a = new User();

我甚至都不可能用到抽象的EntityBase类,因为我要使用此类,必须指定具体的子类,但是我如果知道要使用哪个具体的子类,也就没有必要使用它们的基类了。也就是说,根本就不存在实体的抽象类,而EntityBase<T>存在的意义只是为了代码重用。我不知道这是否能看为违反了OO的Liskov替换原则,不过真是难以忍受。

 

    2. 无法直接实现实体的再继承

    第二个问题,同样是继承机制的问题。我无法从现在的具体实体类直接进行派生!!!我无法使用这样的语法:GoodArticle : Article。这是因为Article已经“告诉”基类EntityBase<T>绑定子类的类型是Article,而不是GoodArticle,这按照EntityBase<T>设计时的约定“T必须是最终的子类”相矛盾!

    无法继承……继承作为OO三大特性中的一个,这个问题简直无法忍受。


 

 

想办法绕开这两个问题

    其实,上面提到的两个问题,在技术上都是能够找到一些方法来解决的:

    1. 无法向基类转换。

    这个问题产生的原因,主要是因为没有一个“与子类无关的抽象”存在。我们可以为EntityBase<T>添加IEntity接口,这样,所有的子类都能转换为IEntity,也就能进行统一的处理。

    2. 无法再继承。

    要解决这个问题,我们需要把需要进行再继承的类也提取为一个泛型基类和一个继承此基类的空的子类。如:

1
2
3
4
5
6
7
8
9
10
11
public  class  Article<T> : EntityBase<T>
     where  T : Article<T>
{
     public  string  Title { get ; set ; }
     //...
}
public  class  Article : Article<Article> { }
public  class  GoodArticle : Article<GoodArticle>
{
     //...
}

这样的方案好像可以解决,但是这样的设计实在让人难以接受:

* 作为设计类库来说,我只是添加了一个单向依赖父类的子类,却不得不修改父类的代码,分离为两个类。

* 要不就是所有的类都直接写成一个泛型类+一个空子类的方法。(这个设计丑陋吗?)

* 没有解决根本的问题:TopArticle 并不是一个 Article,它只是一个和Article有重用代码的类而已。


 本文转自BloodyAngel博客园博客,原文链接:http://www.cnblogs.com/zgynhqf/archive/2010/09/28/1837567.html,如需转载请自行联系原作者

相关文章
|
JavaScript
vue周期函数的使用
vue周期函数的使用
二叉树OJ题:LeetCode--104.二叉树的最大深度
二叉树OJ题:LeetCode--104.二叉树的最大深度
179 0
|
存储 分布式计算 监控
2019年常见Elasticsearch 面试题答案详细解析(下)
12道Elasticsearch高频面试题答案详细解析
2019年常见Elasticsearch 面试题答案详细解析(下)
|
21小时前
|
云安全 人工智能 运维
阿里云SecOps Agent,全新安全跨产品执行体验
自然语言驱动 云安全中心/WAF/CFW/ 等多款安全产品联动
1555 0
|
11天前
|
缓存 测试技术 API
Qwen 3.7 Plus 与 Max 实测:性价比与多模态能力差异解析(2026)
2026 年 6 月 1 日,阿里悄无声息地发布了 Qwen 3.7 Plus,距 Qwen 3.7 Max 上线刚好 11 天。同样的 1M 上下文,同样的 35 小时自治上限。但价格才是头条:Plus 是 0.40/M输入,Max是 2.50/M——便宜约 6 倍——并且还能看图、看视频。Vision Arena 上 Plus 已经排到 #16。所以这周真正值得讨论的问题不是”要不要为视觉能力买单”,而是”Max 凭什么用 6 倍价格换来 2 个百分点的 benchmark 领先”。
|
12天前
|
JavaScript 定位技术 API
CodeGraph 爆火:编程 Agent 需要的不是更多上下文,而是一张提前画好的代码地图
CodeGraph 是一款爆火的本地代码智能工具,通过 tree-sitter 解析 AST 构建结构化知识图谱(存于 SQLite),为编程 Agent 提前生成“代码地图”。它显著降低 Agent 在中大型项目中的探索成本——实测工具调用减少71%、Token 降57%、速度提升46%,支持19+语言及主流框架路由识别,完全离线、无需 API Key。
851 11
CodeGraph 爆火:编程 Agent 需要的不是更多上下文,而是一张提前画好的代码地图
|
12天前
|
人工智能 运维 JavaScript
阿里云Qoder CN(原通义灵码)全解析 产品形态、版本划分与技术适配说明
在AI辅助开发与智能办公工具持续普及的当下,阿里云旗下原通义灵码正式更名为Qoder CN,同时延伸出QoderWork CN、Qoder CN CLI、Qoder CN Mobile等多款配套产品,形成覆盖代码开发、日常办公、终端交互、移动端使用的完整工具矩阵。Qoder CN核心定位为AI智能编码助手,深度适配主流代码编辑器、集成开发环境以及终端场景;QoderWork CN则偏向桌面端综合办公辅助,二者面向不同使用场景,划分了多个版本档位,搭配差异化资源配额、功能权限与计费规则,同时兼容多款主流大模型。
869 8
|
18小时前
|
机器学习/深度学习 人工智能 调度
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
HappyHorse 1.1 是新一代视频生成大模型,全面升级动态表现力、角色一致性、指令遵循、视觉质感与音画协同能力。支持I2V/T2V/R2V三类生成,适配短剧、电商广告、品牌营销等场景,提供高质、流畅、可控的AI视频生产力。
205 0
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~