什么情况下应用纯虚类

本文探讨了在C++和C#中纯虚类的使用,指出纯虚类是业务类型的高度抽象,用于被继承。纯虚类应包含至少一个纯虚方法,通常作为基类提供公共接口,而具体实现由派生类完成。文章通过商品价格计算的示例,说明纯虚方法的访问权限通常是private或protected,并讨论了何时将纯虚方法设为public以及与interface的区别。纯虚类通常在代码重构时产生,是需求逐渐演进的结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前几天跟同事brainstorm,讨论一个关于纯虚类的使用问题,挺有意思。回来心中久久不能平静,写出来一吐为快。

不论在C++中还是C#中,纯虚类都是不能实例化的,这是因为纯虚类其实是一个对业务类型的一种高度抽象,本质上是不存在这种东西的,所以也就不能实例化它。对于C++中只要类中含有一个纯虚函数就是纯虚类,而C#中是abstract修饰的类就是纯虚类,即使类中没有虚方法也可以是纯虚类,在这里我觉得C#的纯虚类没有C++的严谨,如果纯虚类中没有纯虚方法的话,那有何意义。

明白了纯虚类的原理,那纯虚类应该肯定需要被别的类继承的,因为如果不继承的话,它自己本身也不能实例化,就没有存在的意义了,所以它肯定是需要被继承的。那它需不需要含有一个或者多个纯虚方法呢?答案是肯定的,因为既然纯虚类是需要被继承的,那没有纯虚方法又有何意义。没有纯虚方法它就应该是可以实例化的,那就没有必要将其设定为纯虚类的,这也是我吐槽C#的abstract的一个原因。

有了纯虚类一定要有纯虚方法的这个基础,那就可以想象纯虚类的业务场景应该是下面这个样子的

    abstract class Goods
    {
        public int GetPrice()
        {
            int price = 0;
            //do some standard things
            price = this.CaculatePrice();
            //do some other steps
            return price;
        }


        protected abstract int CaculatePrice();
    }

我们有一类商品,它们都有一些统一的步骤需要做,先预处理,然后计算价格,这个计算价格是每种商品不一样的,最后还要统一再处理下,对一个售货员来说它其实并不关心这是什么商品有什么特性,她只关心这个商品的价格,如果购物车中有多个商品,计算出所有商品的总价格。所以一般会构建一个商品,它没有任何意义,只是将所有商品标准化东西统一起来,将不同的东西抽象为纯虚方法,这样不同的商品就可以只关心它是如何计算价格的,其它不同的部分交给虚基类来处理。这个是比较经典的业务场景,这里虚基类中可能会有多个public的方法,这些方法可以调用纯虚方法也是可以的。总之到最后Goods的使用者是直接调用Goods索引,而不是操作具体的商品类。这种情况一般纯虚类中的纯虚方法一般都是private或者protected(在C++中纯虚函数可以是private、protected和public,但是在C#中纯虚函数只能是protected和public),因为Goods的使用者一般不会直接操作商品内部的CaculatePrice的,这个应该是需要对使用者隐藏的。设成protected是有些时候需要对Goods再进行抽象,这类商品有它特定的使用者,这个时候它又需要调用Goods的方法,这种情况就开发成为protected,比如有一类商品,它们需要提供另外一种方法,计算另外一种类型的价格,但是这种计算方法又是依赖商品的CaculatePrice的结果,继承类需要再写一种方法调用基类的CaculatePrice的方法,如果是private,继承类就没有办法访问,此时就需要将其修改成为protected。

看样子纯虚类中的纯虚方法是不能为public的,一般是private或者protected的,那是否可以是public呢?答案是可以的。针对上面的例子,假设每种商品没有统一的步骤,计算方法各不一样,没有统一的步骤,那这个时候就需要将GetPrice改成纯虚函数,但是这里需要考虑的另外一个问题就是当一个纯虚类中有一个public的纯虚方法,要考虑这个纯虚方法是否是一种interface(这个是C#才有C++中没有的,这个是一个亮点,将is a和支持的方法隔离开来,因为is a的关系太强了),在这个例子我觉得还是纯虚类比较合适,interface不大合适。interface更多的像是一种技能,现实生活中不好想象这样的例子,在基础类库中的IEnumerator这个接口,这个是linq实现的基础,它就是一个典型的接口而不应该实现为纯虚类,list,array和dict它们没有公共关系,不好抽象出来一个基类来实现is a的关系,但是都需要支持遍历数据结构中所有元素的方法,所以都实现IEnumerator的接口,所以interface更像一种技能。

在实际开发过程中需求往往是经常渐进,一开始我们并不知道要设计一个纯虚类,更多的情况是一开始只有一种商品,设计了Goods,然后有另外一种商品,它们有很大的共性,于是将共性提出来,让第二个商品继承第一个商品,其实此时就应该考虑纯虚类的,如果未来看得出来有更多同类的商品出现的话,应该重构一个纯虚基类出来,但是暂时没有让商品2继承商品1没问题,因为我觉得增加一个纯虚基类出来,而且未来还不知道能不能用上,没有必要,因为增加纯虚基类意味着强类型,将来很难再refactor,第二类太多对代码的维护成本也是很高的,基于done is enough的原则,上面直接将商品2继承商品1没有问题,即使看上去两个不应该有继承关系,所以此时为了更好的解决这个问题,可以考虑将商品1改个名字,让它们的继承关系合法化。当商品越来越多的时候大多时候是不会重构出纯虚基类的,因为随着代码的不断增加,维护的成本会指数级增加,而且是修改一个基类,impact会比较大,一般也不会refactor一个纯虚基类。除非整个代码需要重构,可以考虑这么做,如果没有的话,这个代码结构不应该进行重构。所以纯虚基类一般都是代码整体重构的产物,当然如果在需求提出的时候就已经明确那是更好的。

在实际代码中我们会经常遇到一个虚方法是一个空函数的,那什么情况下会将一个虚方法设定为一个空函数呢?

空函数存在的价值是它是有意义的才可以,比如针对上面Goods类,假设它不是纯虚类,只是一个基类,如果有种商品就是没有价格,比如赠品,CalculatePrice直接返回0,没有任何计算,它的子类商品都有自己的计算价格方法,可以重写这个方法。所以说针对空函数的话,如果有意义才可以,一般不需要将其设成空函数,没有意义就可以直接将其改成纯虚方法。


总之:

纯虚类中是一定要有纯虚函数的,如果是私有纯虚方法那这个方法肯定是被纯虚类中其它public调用的,如果纯虚方法是public的时候是语义上更是is a的关系决定的,但是这个时候需要看看这个方法是否也算是has的方法,如果更多的是has的方法,需要考虑将这个方法改成interface,这个只有c#中才有这种功能,c++中是没有的,只是一个纯虚接口类而已。




<think>好的,我现在要解决的问题是用户询问C#中的接口是否等同于。首先,我需要理解这两个概念在C#中的定义和用途,然后比较它们的异同点。 首先,用户提到的“”可能指的是C++中的概念,即包含函数的,这样的不能被实例化,必须由派生实现所有函数。在C#中,似的概念是抽象,可以包含抽象方法(没有实现的方法)和非抽象方法。而C#的接口则是一组方法、属性、事件或索引器的契约,不包含任何实现,只能由或结构体来实现。 接下来,我需要参考用户提供的引用内容。例如,引用[2]提到接口是对行为的抽象,而抽象是对的抽象,说明接口更关注行为规范,而抽象则更偏向于的层次结构。引用[3]展示了C#函数和重写的例子,说明抽象可以包含具体实现,而引用[4]对比了Java的接口和C++,指出Java的接口似于C++,但在C#中可能情况有所不同。 然后,我需要明确C#中接口和抽象的区别。接口不能有实现(直到C# 8.0才允许默认实现),而抽象可以包含部分实现;一个可以实现多个接口,但只能继承一个抽象。接口更适用于定义契约,而抽象用于共享代码和共同功能。 此外,用户可能混淆了C++C#的抽象。在C#中,没有“”这一术语,但抽象中的抽象方法似于C++函数。因此,C#的接口更接近于C++,但仍有区别,比如接口不能有任何实现(除非使用默认方法),而C++可以有其他成员变量和函数。 最后,总结两者的异同,指出在C#中接口不等同于,但它们在设计模式中的作用有相似之处,比如强制派生实现特定方法,但实现机制和功能有所不同。</think>在C#中,**接口(Interface)**和****(即C++中的概念)有相似性但不等同。以下是具体分析: --- ### 一、核心概念对比 1. **接口(Interface)** - 定义行为的契约,不包含任何实现(C# 8.0前严格如此,之后允许默认实现)[^2]。 - 可以实现多个接口,实现时必须提供接口中所有成员的实现。 - 示例: ```csharp public interface IFlyable { void Fly(); // 无方法体 } ``` 2. **(抽象)** - 在C#中对应抽象(`abstract class`),可包含抽象方法(函数)和具体方法。 - 只能继承一个抽象,必须实现所有抽象方法。 - 示例: ```csharp public abstract class Animal { public abstract void Eat(); // 方法 public void Sleep() { /* 具体实现 */ } } ``` --- ### 二、关键区别 | **特性** | **接口** | **(抽象)** | |-----------------------|--------------------------------------------|------------------------------------| | 实现继承 | 支持多实现 | 仅支持单继承 | | 成员实现 | 无默认实现(C# 8.0前) | 可包含具体方法和抽象方法 | | 字段/属性 | 不能包含字段或属性(C# 8.0前) | 可以包含字段、属性和方法 | | 设计目标 | 定义行为契约,解耦实现 | 提供部分公共逻辑,强制子实现规范 | --- ### 三、C#C++/Java的关联 - **与C++对比** C++(含函数)更接近C#的抽象,但C#接口更严格(无字段、默认无实现)[^4]。 - **与Java接口对比** Java接口与C#接口似,但C# 8.0后允许接口添加默认实现,缩小了与抽象的差异[^2]。 --- ### 四、总结 - **接口 ≠ ** C#接口是粹的行为规范,而抽象)是层次的抽象,可包含实现。 - **选择依据** - 需要多继承或定义跨别的行为时,使用**接口**。 - 需要共享代码或定义族的基础结构时,使用**抽象**。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值