Object oriented design: when to make an abstract class
现在,我正在学习OOP,主要是C。我感兴趣的主要原因是什么使得类不能被实例化。什么时候创建抽象类是正确的例子?我发现自己在继承中使用抽象类的方式太热情了。当类在系统中是抽象的,而类不应该是抽象的时候,是否有一些规则?例如,我创建了医生和病人类,它们在某种程度上相似,所以我从抽象类人(因为两者都有名字和姓氏)中派生出来。那是错的吗?对不起,如果这个问题是愚蠢的,我是新来的。
到目前为止还没有人指出一些事情,所以我只想指出它们。
只能从一个基类继承(可以是抽象的),但可以实现许多接口。因此,从这个意义上讲,继承抽象类比实现接口更为紧密。
所以,如果你后来意识到你需要一个实现两个不同抽象类的类,那你就大错特错了:)
为了回答你的问题"什么时候做抽象类",我会说永远不要,尽可能避免它,从长远来看,它永远不会有回报,如果主类不适合作为普通类,它可能也不是真正需要的抽象类,使用一个接口。如果您曾经遇到过复制代码的情况,那么它可能适合于抽象类,但总是首先查看接口和行为模式(例如,策略模式解决了许多人们错误地使用继承来解决的问题,总是更喜欢组合而不是继承)。使用抽象类作为最后一手的解决方案,而不是设计。
为了更好地理解OOP的一般性,我建议您看一下设计模式:可重用面向对象软件(一本书)的元素,它对OO设计和OO组件的可重用性有了很好的概述。OO设计不仅仅是继承:)
例如:您有一个场景,需要从不同的源提取数据,如"Excel文件、XML、任何数据库等",并保存在一个公共目标中。它可以是任何数据库。所以在这种情况下,您可以使用这样的抽象类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | abstract class AbstractImporter { public abstract List<SoldProduct> FetchData(); public bool UploadData(List<SoldProduct> productsSold) { // here you can do code to save data in common destination } } public class ExcelImporter : AbstractImporter { public override List<SoldProduct> FetchData() { // here do code to get data from excel } } public class XMLImporter : AbstractImporter { public override List<SoldProduct> FetchData() { // here do code to get data from XML } } public class AccessDataImporter : AbstractImporter { public override List<SoldProduct> FetchData() { // here do code to get data from Access database } } |
打电话可以是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | static class Program { static void Main() { List<SoldProduct> lstProducts; ExcelImporter excelImp = new ExcelImporter(); lstProducts = excelImp.FetchData(); excelImp.UploadData(lstProducts); XMLImporter xmlImp = new XMLImporter (); lstProducts = xmlImp.FetchData(); xmlImp.UploadData(lstProducts); AccessDataImporterxmlImp accImp = new AccessDataImporter(); lstProducts = accImp .FetchData(); accImp.UploadData(lstProducts); } } |
因此,在上面的示例中,数据导入功能的实现在扩展(派生)类中是分开的,但数据上载功能对所有人来说都是通用的。
顺便说一句,另一个尚未提到的问题是,有可能将成员添加到抽象类中,使现有的实现自动支持它们,并允许使用者使用了解新成员和实现的实现,这些实现不能互换。虽然有一些看似合理的机制,未来的.NET运行时也可以通过这些机制允许接口以这种方式工作,但目前它们没有。
例如,如果IEnumerable是一个抽象类(当然有很多理由不这样做),那么当它的实用性变得明显时,可以添加类似于
可以添加从
如果有接口可以包含静态方法的方法,并且类加载器在发现声称实现
1 | stringIFoo.Bar(int p1) { return IFoo.classHelper_Bar(Boz this, int p1); } |
[假设接口包含该静态方法],那么可以让接口在不破坏现有实现的情况下添加成员,前提是它们还包含默认实现可以调用的静态方法。不幸的是,我知道没有计划添加任何此类功能。
关键是该类的实例化是否有意义。如果实例化该类永远不合适,那么它应该是抽象的。
一个典型的例子是一个图形基类,包含方形、圆形和三角形子类。形状不应该被实例化,因为根据定义,您不知道您希望它是什么形状。因此,将shape设置为抽象类是有意义的。
这可能是一个非学术性的定义,但抽象类应该表示一个如此"抽象"的实体,无法对其进行实例化。
它通常用于创建必须由具体类扩展的"模板"。因此抽象类可以实现公共特性,例如实现接口的某些方法、特定行为的具体类实现的委托。
从抽象类"人"中派生"医生"和"病人"是可以的,但您可能应该使人成为一个普通类。不过,这取决于"人"的使用环境。
例如,您可能有一个名为"gameobject"的抽象类。游戏中的每一个对象(如手枪、一个以上)都扩展了"游戏对象"。但是你不能单独拥有一个"gameobject",因为"gameobject"描述了一个类应该拥有什么,但是没有详细描述它们是什么。
例如,gameobject可能会说:"所有gameobject都必须看起来像某个东西"。一把手枪可以延伸到游戏物体所说的地方,它说:"所有的手枪必须看起来像一个长枪管,一端有一个把手,另一端有一个扳机。"
如前所述,没有人会强迫您使用抽象类,它只是一种抽象某些功能的方法,这在许多类中是常见的。
您的案例是使用抽象类的一个很好的例子,因为您在两个不同的类型之间有共同的属性。但是,因为它限制了你自己使用person作为一个类型。如果你想有这个限制,基本上取决于你自己。
一般来说,我不会像您所拥有的那样为模型类类使用抽象类,除非您希望防止对人进行实例化。
通常我使用抽象类,如果我还定义了一个接口,我需要为这个接口编写不同的实现代码,但是我也希望有一个已经包含了所有实现的一些公共功能的基类。
从本质上说,如果您不想实例化一个Person类,那么您所做的就是好的,但是正如我猜测的那样,您可能希望在将来的某个时候实例化一个Person类,那么它不应该是抽象的。
尽管有一个参数要求您编写代码来解决当前问题,而不是解决可能永远不会出现的问题,所以如果需要实例化Person类,则不要将其标记为抽象的。
抽象类不完整,必须在派生类中实现…一般来说,我更喜欢抽象的基类而不是接口。
研究抽象类和接口之间的区别…
"抽象类和接口之间的区别在于抽象类可以有方法的默认实现,因此如果不在派生类中重写这些方法,则使用抽象基类实现。接口不能有任何实现。"摘自此so post