C# 拆解 “显式接口实现 + 子类强类型扩展” 的设计思想

通俗理解 ADO.NET 中"属性+方法"的经典设计思想

在学习 ADO.NET 操作数据库时,相信很多人都会被 DbCommand、NpgsqlCommand 中的属性和方法实现绕晕------比如 Connection 属性的双重定义、CreateParameter 方法的子类重写与隐藏。其实这些看似"多余"的代码,背后藏着一套非常经典、通用的设计思想,核心就是"兼顾标准化、易用性和扩展性",今天用最通俗的语言,结合实际代码,把这套思想讲透,方便后续理解框架设计、复用这套思路。

先明确核心前提:ADO.NET 是跨数据库的通用框架,它需要满足"所有数据库驱动(PostgreSQL 的 Npgsql、SQL Server 的 SqlClient 等)都能统一调用",同时还要让开发者用起来方便(不用频繁强制类型转换),还要给不同数据库驱动留足扩展空间(各自实现专属逻辑)。而我们看到的"属性+方法"的特殊写法,就是为了同时实现这三个需求。

接口职责(标准化契约) :定义必须实现的方法 / 属性签名(无具体实现)

抽象类职责(通用基类 + 部分实现) :定义通用模板,可包含抽象成员(强制子类实现)和具体实现(复用通用逻辑);

一、先搞懂核心设计套路(全程围绕这一句话)

这套设计思想的核心套路可以总结为:用"显式接口实现"满足框架标准化契约,用"公共成员(属性/方法)"提升开发者易用性,用"抽象成员+子类实现"保证扩展性

简单说就是:框架要"通用",开发者要"便捷",子类要"可扩展",三者缺一不可,而这套写法就是平衡这三者的最佳方案。下面分别从「属性」和「方法」两个维度,结合你熟悉的代码,逐一拆解。

二、属性的设计:以 Connection 属性为例(最易理解)

我们直接拿 DbCommand 和 NpgsqlCommand 中的 Connection 属性来拆解,这是最典型的应用,也是我们之前重点讨论的内容。

1. 先看"框架标准化"需求:显式接口实现

ADO.NET 定义了 IDbCommand 接口,规定所有数据库命令对象必须有 Connection 属性,用于关联数据库连接,接口定义如下(简化版):

cs 复制代码
public interface IDbCommand
{
    // 接口契约:Connection 属性,类型是 IDbConnection(通用接口类型)
    IDbConnection Connection { get; set; }
}

DbCommand 作为所有数据库命令对象的基类,必须满足这个接口契约,但它没有直接实现,而是用了"显式接口实现+定义抽象(注意:改变名称)",代码如下:

cs 复制代码
public abstract class DbCommand : IDbCommand
{
    // 显式接口实现:只给接口类型调用,满足框架标准化
    IDbConnection IDbCommand.Connection
    {
        get { return DbConnection; }
        set { DbConnection = (DbConnection)value; }
    }

    // 抽象属性:交给子类(NpgsqlCommand 等)实现,留足扩展空间
    protected abstract DbConnection DbConnection { get; set; }
}

这里有两个关键细节,通俗解释:

  • 显式接口实现的写法(IDbCommand.Connection):不加 public 修饰符,只有当把 DbCommand 转换成 IDbCommand 接口时,才能访问这个属性。目的是"不污染公共API",避免开发者直接用类类型时,看到多余的接口属性。

  • 强制转换((DbConnection)value):过滤无效类型,保证只有 ADO.NET 体系内的连接对象(继承自 DbConnection)才能赋值,避免开发者传入不兼容的连接,保证类型安全。

2. 再看"开发者易用性"需求:公共属性扩展

如果只靠显式接口实现,开发者直接用 NpgsqlCommand 时会非常麻烦------每次赋值、取值都要强制类型转换,比如这样:

cs 复制代码
// 没有公共属性时,只能用接口类型访问,繁琐且易出错
IDbCommand cmd = new NpgsqlCommand();
cmd.Connection = (IDbConnection)new NpgsqlConnection(); // 手动转换
NpgsqlConnection conn = (NpgsqlConnection)cmd.Connection; // 手动转换

所以 DbCommand 增加了一个公共属性,专门给类类型调用,提升易用性:

cs 复制代码
public abstract class DbCommand : IDbCommand
{
    // 公共属性:给类类型(DbCommand、NpgsqlCommand)调用,不用转换
    public DbConnection Connection
    {
        get { return DbConnection; }
        set { DbConnection = value; }
    }

    // 显式接口实现(不变)
    IDbConnection IDbCommand.Connection
    {
        get { return DbConnection; }
        set { DbConnection = (DbConnection)value; }
    }

    // 抽象属性(不变)
    protected abstract DbConnection DbConnection { get; set; }
}

这样一来,开发者直接用 NpgsqlCommand 时,就不用强制转换了,代码变得简洁:

cs 复制代码
NpgsqlCommand cmd = new NpgsqlCommand();
cmd.Connection = new NpgsqlConnection(); // 直接赋值,不用转换
DbConnection conn = cmd.Connection; // 直接取值,类型清晰

3. 最后看"子类扩展性"需求:抽象属性落地

DbCommand 中的 DbConnection 是抽象属性,自己不实现,而是交给子类 NpgsqlCommand 实现,代码如下:

cs 复制代码
public class NpgsqlCommand : DbCommand
{
    // 子类实现抽象属性,返回 PostgreSQL 专属连接对象
    private NpgsqlConnection _connection;
    protected override DbConnection DbConnection
    {
        get { return _connection; }
        set { _connection = (NpgsqlConnection)value; }
    }
}

这里的核心:不同数据库驱动(Npgsql、SqlClient)可以实现自己的连接对象,DbCommand 不用关心具体是哪种连接,只负责提供通用接口和公共属性------这就是"扩展开放、修改关闭"的设计原则,也是框架能跨数据库的关键。

属性设计总结(一句话记牢)

显式接口实现(满足框架通用)+ 公共属性(方便开发者使用)+ 抽象属性(子类扩展),三者配合,既保证标准化,又保证易用性和扩展性。

三、方法的设计:以 CreateParameter 方法为例(和属性完全相通)

方法的设计思路和属性完全一致,只是把"属性的 get/set"换成了"方法的入参/返回值"。我们还是以 ADO.NET 中的 CreateParameter 方法为例,它的作用是创建数据库参数(比如 PostgreSQL 的 NpgsqlParameter)。

1. 框架标准化:显式接口实现

IDbCommand 接口定义了 CreateParameter 方法,规定返回 IDbDataParameter 类型(通用接口类型),满足跨数据库调用:

cs 复制代码
public interface IDbCommand
{
    // 接口契约:创建参数,返回通用接口类型
    IDbDataParameter CreateParameter();
}

DbCommand 用显式接口实现满足这个契约,同时转调抽象方法,留给子类实现:

cs 复制代码
public abstract class DbCommand : IDbCommand
{
    // 显式接口实现:只给接口类型调用
    IDbDataParameter IDbCommand.CreateParameter()
    {
        return CreateDbParameter(); // 转调抽象方法
    }

    // 抽象方法:交给子类实现,留足扩展空间
    protected abstract DbParameter CreateDbParameter();
}

2. 开发者易用性:公共方法扩展

和属性一样,显式接口实现的方法,开发者直接用类类型调用时会很麻烦------需要强制转换返回值。所以 DbCommand 增加了一个公共方法,返回更具体的 DbParameter 类型:

cs 复制代码
public abstract class DbCommand : IDbCommand
{
    // 公共方法:给类类型调用,返回具体类型,不用转换
    public DbParameter CreateParameter()
    {
        return CreateDbParameter(); // 转调抽象方法
    }

    // 显式接口实现(不变)
    IDbDataParameter IDbCommand.CreateParameter()
    {
        return CreateDbParameter();
    }

    // 抽象方法(不变)
    protected abstract DbParameter CreateDbParameter();
}

此时开发者用类类型调用,就不用转换了:

cs 复制代码
DbCommand cmd = new NpgsqlCommand();
DbParameter param = cmd.CreateParameter(); // 直接拿到具体类型

3. 子类扩展性:重写抽象方法+强类型隐藏

NpgsqlCommand 作为子类,需要实现抽象方法 CreateDbParameter,同时为了进一步提升易用性,用 new 关键字隐藏基类的公共方法,返回 PostgreSQL 专属的 NpgsqlParameter 类型(强类型):

cs 复制代码
public class NpgsqlCommand : DbCommand
{
    // 1. 重写基类抽象方法,履行框架契约
    protected override DbParameter CreateDbParameter()
    {
        return CreateParameter(); // 转调下面的强类型方法
    }

    // 2. 用 new 隐藏基类方法,返回强类型,极致易用
    public new NpgsqlParameter CreateParameter()
    {
        return new NpgsqlParameter(); // 专属参数实现
    }
}

这里重点解释 new 关键字的作用:

C# 不支持"协变返回值重写"------基类方法返回 DbParameter,子类不能直接重写成返回 NpgsqlParameter(哪怕是子类类型)。所以用 new 关键字隐藏基类方法,既保留基类方法的兼容性(框架层面调用),又给开发者提供强类型方法(不用转换)。

开发者最终使用的代码,简洁且安全:

cs 复制代码
NpgsqlCommand cmd = new NpgsqlCommand();
// 直接拿到 NpgsqlParameter,可调用专属属性,不用转换
NpgsqlParameter param = cmd.CreateParameter();
param.PostgresType = NpgsqlTypes.NpgsqlDbType.Varchar;

方法设计总结(和属性对应)

显式接口实现(框架通用)+ 公共方法(开发者便捷)+ 抽象方法+子类 new 强类型方法(极致扩展),和属性的设计思路完全一致,只是表现形式不同。

四、核心设计思想总结(通俗易懂版)

我们不用记复杂的术语,只用记住这套设计思想的核心目的和套路,以后不管在哪个框架中看到类似写法,都能一眼看懂。

1. 核心目的

解决"框架通用"和"开发者易用"的矛盾------框架要统一接口(方便跨组件、跨数据库调用),开发者要具体类型(不用强制转换、少出错),子类要专属实现(满足自身特性),这套写法能同时满足这三点。

2. 通用套路(属性和方法都适用)

  1. 第一步:用"显式接口实现"满足框架的标准化契约,隐藏接口细节,不污染公共API;

  2. 第二步:用"公共成员(属性/方法)"封装抽象成员,给类类型调用,提升易用性;

  3. 第三步:用"抽象成员(抽象属性/抽象方法)"留给子类实现,用"子类 new 强类型成员"实现极致易用和专属扩展。

3. 一句话通俗概括

对外(框架层面):用接口保证通用,谁来调用都符合规范;对内(开发者和子类):用具体类型保证便捷,谁来用都省心;底层(子类实现):用抽象成员保证扩展,谁来扩展都灵活。

五、实用价值(为什么要学这套设计思想)

这套设计思想不是 ADO.NET 专属的,而是 C# 框架设计中非常通用的思路,比如 ASP.NET Core、EntityFramework 中都有大量应用。学会它,不仅能看懂框架源码,还能在自己写工具类、组件时复用:

  • 比如你写一个跨文件类型的解析工具(Excel、Word),可以用接口定义通用方法,用显式接口实现保证通用,用子类强类型方法提升易用性;

  • 比如你写一个数据库操作工具类,支持多种数据库,这套思路能帮你兼顾通用接口和具体数据库的专属逻辑。

最后记住:好的框架设计,从来不是"越复杂越好",而是"兼顾通用、易用和扩展"------我们看到的 ADO.NET 中属性和方法的写法,就是最好的例子。

相关推荐
讯方洋哥2 小时前
HarmonyOS App开发——关系型数据库应用App开发
数据库·harmonyos
惊讶的猫2 小时前
Redis持久化介绍
数据库·redis·缓存
Apple_羊先森3 小时前
ORACLE数据库巡检SQL脚本--19、磁盘读次数最高的前5条SQL语句
数据库·sql·oracle
全栈前端老曹3 小时前
【MongoDB】Node.js 集成 —— Mongoose ORM、Schema 设计、Model 操作
前端·javascript·数据库·mongodb·node.js·nosql·全栈
神梦流3 小时前
ops-math 算子库的扩展能力:高精度与复数运算的硬件映射策略
服务器·数据库
让学习成为一种生活方式4 小时前
trf v4.09.1 安装与使用--生信工具42-version2
数据库
啦啦啦_99994 小时前
Redis-5-doFormatAsync()方法
数据库·redis·c#
生产队队长4 小时前
Redis:Windows环境安装Redis,并将 Redis 进程注册为服务
数据库·redis·缓存
老邓计算机毕设4 小时前
SSM找学互助系统52568(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架·javaweb 毕业设计