非面向对象编程中多态的替代方案是什么?

6
假设我们有一个绘图程序,其中包含不同的元素,例如圆形、矩形、三角形等。这些不同种类的对象都需要类似的功能,例如draw()以显示自己。
我想知道程序员如何解决现今通常通过多态性来解决的问题,即遍历非相同元素集合并在不同对象之间调用公共功能。
一种方法是使用具有指向适当函数的函数指针(或函数指针数组中的索引)以及实际实例的void指针的结构体,并传递被转换为适当类型的指针到函数中。但这只是我 - 一个对此一无所知的人 - 怎么做的。
我意识到这可能是一个初学者的问题,但由于我没有在“古老”的日子里出现过,我真的很好奇该问题是如何解决的。过程式编程使用了什么样的方法,并且是否具有性能优势,因为我们都知道多态性即使在快速语言(如C++)中也会有开销,由于虚拟方法查找。

那么你的问题是,在面向对象编程语言出现之前,编程是什么样子的?我想像着围绕如何构建程序结构的概念进行工作,最终演变成了面向对象编程?例如,一个类通常与一个带有方法捆绑的结构体相同。 - Cratylus
@Cratylus - 不,我的问题并不打算那么宽泛,我知道类在幕后是什么,我只是想知道那个特定的问题,主要是想知道是否有一种更有效的方法来避免虚拟方法的性能开销。 - dtech
也许这可以帮助:https://dev59.com/4XRB5IYBdhLWcg3wv5tA - John
这似乎是一个优化问题。因为理论上它很慢而放弃面向对象编程太仓促了。在我看来,你应该只有在分析器告诉你需要时才“优化面向对象编程”。 - EthanB
你描述的在结构体中实现的方式是我的第一猜测。在Linux内核中,你会看到很多这样的用法。 - Steven Eckhoff
3个回答

2
一个非常简单的例子。
如果您对此感兴趣,您可以在Linux内核中找到更多相关内容。
#include <stdio.h>                                                              

struct shape {                                                                  
    void (*say_hello)(void);                                                    
};                                                                              

void circle_say_hello(void)                                                     
{                                                                               
    printf("Hi I am circle!\n");                                                
}                                                                               

void square_say_hello(void)                                                     
{                                                                               
    printf("Meh I am square.\n");                                               
}                                                                               

#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))                                  

int main(int argc, char *argv[])                                                
{                                                                               
    struct shape circle = { .say_hello = circle_say_hello, };                   
    struct shape square = { .say_hello = square_say_hello, };                   
    struct shape* shapes[] = {&circle, &square};                                
    int i;                                                                      

    for (i = 0; i < ARRAY_SIZE(shapes); i++) {                                  
        if (shapes[i] && shapes[i]->say_hello)                                  
            shapes[i]->say_hello();                                             
    }                                                                           

    return 0;                                                                   
}  

0
在过程式语言(如C)中,这可以通过为每个自定义数据类型(可能表示为结构体)定义单独的draw()函数实现来解决。任何共同功能都将被分解成一个单独的函数,该函数操作每个结构体的共享元素(例如对象中心的x和y坐标,在每个对象中都会出现)。从代码和功能的角度来看,这与使用多态性的OOP布局并没有太大区别,其中您仍然必须在基类中实现共享的draw()方法,并在特定的子类中覆盖它。在过程式语言的情况下,我们只是不会将这些函数定义拆分成单独的“对象”。
有一些花哨的方法可以从过程式语言中获得类似对象的行为,例如联合类型或具有额外布尔值的单个单片式类型,以确定特定元素是否正在使用。这将允许您编写一个单个的draw()函数,该函数可以根据启用了哪些元素执行逻辑切换。在实践中,我只看到过CORBA系统中有很多这样的东西,其中用C编写的程序必须模仿OOP语言的某些行为,这些行为通过IDL传播(即将Java对象转换为可解码为C风格结构体的构造)。
关于C++和Java等面向对象语言中虚拟方法查找的开销,这是无法完全避免的。但是,通过适当使用final关键字(它允许编译器/JVM优化方法查找表),可以在很大程度上减轻这种开销。

0

这不是对您示例的直接回答,而是针对您的评论提出的,我认为它展示了一个错误的视角。

我只是在想那个特定的问题,主要是想知道是否有更高效的方法可以避免虚拟方法的性能开销。

这里有一些需要理解的东西。 每件事 都有权衡。 设计模式和OO具有我们已经喜欢的所有已知优点,但也有缺点,例如过多的类、内存开销、由于许多方法调用而产生的性能开销等等。

另一方面,旧的“过程式”方式也有一些优点,客观地说;它编码“简单”(无需思考如何设计系统,只需将所有内容放入主函数中),在许多方面都有较少的开销(需要较少的类,对象更紧凑-不需要虚拟表等-并且调用的方法较少,因此可能有更好的性能,在动态绑定方面没有性能开销-无论现在的开销是什么...-)。

但问题不在于一个特定问题实例的权衡,而是经验表明构建软件的正确方式是什么。代码的重用(模块化)和协助分离测试(质量保证)、可读性、可维护性、易于扩展的属性已经被充分理解,应该成为软件开发的主要驱动力。

因此,在某些情况下,一位真正优秀的C/C++程序员可以按照“旧方法”进行编程,但这种做法所带来的性能收益是否值得之后没有人能够维护或维持它呢?

再举一个类似的例子:你可以用同样的方式提问吗?
为什么在Web开发中需要多层架构?把所有东西都放在一个服务器上,速度会非常快,因为查询后端和UI数据或远程数据库查询等的所有层次不会有延迟或网络延迟。
当然,你有一定道理。但是请问自己,随着负载的增加,这种方法是否可扩展?答案是否定的。那么,可扩展性对你来说重要还是想保持“把所有东西放在一个服务器上”的想法?如果你的收入来自电子网站,无法为更多客户提供服务将不会让你的客户感到满意,仅仅因为你为前100个客户提供了快速服务...总之,这是我的观点。


1
我并不打算质疑面向对象编程的优点。我只是想了解一下,仅此而已 :) - dtech
正如我所说,我并不打算直接回答你的问题。我只是试图向你展示一个不同的视角,来阐述我理解的你的观点。除非我没有理解你的想法,否则我的回答就完全脱离了上下文(在这种情况下我向你道歉)。 - Cratylus
别担心,我已经知道你说的大部分内容了,但我很感谢你所投入的时间和精力,毫无疑问,你的帖子将对社区有所帮助 :) - dtech

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接