面向对象、面向对象过程
(此文本的先前版本发布在Medium上)
大约在2018年左右,部分原因是由于跟随乔纳森·布洛(Jonathan Blow)在Jai上的工作以及该语言中明显缺乏OO,我开始重新考虑OO是否是一件好事。 我最终听到并阅读了对OO的各种批评,并思考了很多问题。 我还开始关注鲍勃·尼斯特罗姆(Bob Nystrom)的“手Craft.io品译者”系列,该系列的第二部分以非常清晰易懂的程序C编写。它提醒我如何直接针对曾经适合我的问题代码。
在OO之前,只是编写代码
我于1982年开始编程。当时我9岁,在家用计算机上进行编程几乎总是BASIC(或汇编器,如果您想提高速度的话)。 那是第一批“家用计算机”的时代。 电脑杂志会印刷游戏和应用程序清单。 这部分是“廉价获取程序”,另一部分是“学习编程”。
BASIC并不是完全结构化的编程,至少不是那些早期版本。 “结构化编程”仅限于GOSUB 1340
类的语句。 不过,您绝对可以用它构建事物。 游戏和应用程序都是用BASIC编写的。 限制通常是机器的内存(通常为16 kb),而不是结构。 它可能不是精美的代码,但是可以完成工作。
最终,我选择了Pascal,尽管后来的BASIC迭代可以改善大多数8位实现,但Pascal却要好得多。 它功能更强大,更重要的是,编写清晰且结构化的代码非常容易。 但是即使这样,程序设计与编写汇编器或BASIC并没有太大区别。 您从一开始就进行构建,直到完成为止。 仅仅“完成工作”真的很容易。
我最终学习了一些C ++,但是该语言的OO部分大部分使我逃脱了。 直到我接触Java时,情况才发生了变化-当时我认为情况会变得更好...
Java和“真实” OO
我曾经告诉人们,在我学习Java之前,我不了解面向对象的编程。 这可能不太准确,但是的确,我在学习Java之前没有尝试任何“面向对象的设计”。
Java确实迫使您执行对象。 当applet成为新的热门话题时,我自己就开始了,而该语言仍然是其1.0.2版本。 酷又神奇。 这些小型单元可以用来做事的对象,几乎就像对小型机器人进行编程一样。 您进行了这种设计 ,而不仅仅是简单的编程,在这种设计中 ,独立的对象正在交谈并产生结果。 太棒了。 当然,这也是一个谎言。
随着Java成为主流,有关如何进行真正的OO的文章和书籍也兴旺起来。 重点似乎是,应该抛弃有关过程编程的大多数东西。 据说这与过程式编程中的非结构化编程一样,是很糟糕的。 我们对对象的思考越多,事情就越好。 很显然, 必杀技已经接近了-那些可以获得Java程序为生的幸运者可以使用它。
面向对象的建模噩梦
在我的业余时间里,我正在研究复杂的在线游戏的下一个迭代版本,该游戏最初被编写为BBS门(当我们使用拨号调制解调器时,BBS在线游戏又回来了)。 原始版本是用QBasic编写的,我还用Turbo Pascal进行了重写,覆盖了大约80%的游戏。 编写游戏花费了大约半年的晚上和周末。
QBasic版本非常棘手,因为您必须在实现文件之间显式传递所有全局数据,并且每个文件都有大小限制-因此必须使用每个文件之上的大量全局声明来拆分它。 Pascal版本非常容易编写。 您无需显式传递全局变量,而将事物传递给过程也很简单-而对于QBasic,您必须传递参数并生成您专门为此目的预留的全局变量。 显然,具有OO优点的Java版本必须比Pascal 更容易实现!
事实证明这是“不太正确的”。 我逐页地写了类的设计,每个实体如何知道什么,每个实体将包含什么状态以及如何对其他实体采取行动。 感觉真的很酷,但是它也很复杂,并且对象模型的每个版本似乎都有一些问题,最终所有需要了解的知识和每个类都将变得非常复杂,几乎无法确保一致性。 感觉这是不可能完成的任务。
做实事
同时,我开始专业从事程序员工作。 我用Perl,Java编写,学习了Objective-C和Ruby。 使用Objective-C,我发现Java / C ++的“ OO”只是OO的一个品牌。 在Java中,其想法是创建组装成一个整体的微型类,但在ObjC对象中,它们被用作内部直接用程序C代码编写的较大组件之间的高级“胶水”。 Java的细粒度类将被视为与Objective-C的良好设计完全相反。 因此,如果面向对象程序设计是面向过程程序设计,而面向过程程序设计则是面向非结构化—那么,关于如何进行“合适的面向对象”,甚至还没有达成共识?
我的宠物项目仍然失败。 我仍然尝试以Java方式对事物建模,但一直失败。 但是后来我尝试用Ruby编写它,但发生了意外情况。
把事情做好
在Java中,很容易陷入编写类的工作中。 Java相对冗长,因此仅编写一堆类,编写getter和setter似乎就完成了很多工作。 这就是为什么很容易浪费时间测试模型并仍然觉得自己要到某个地方。
另一方面,在Ruby中,编写类很简单。 即使对其中的很多进行建模也不是一件容易的事。 如果您很懒,甚至可以使用元编程来生成一堆。 Ruby非常非常快速地进行原型制作。
突然我再也不能自欺欺人了。 在Ruby中实现了模型的某些部分后,突然很明显,我并没有通过创建这些游戏模型类来增加任何价值。 这是工作,但不是真正的工作。 我需要一个新主意。
当时,我在扑克服务器上从事专业工作,很明显,扑克游戏实例只是一个带有甲板,当前下注和玩家的数据结构。 玩家动作只是根据某些规则对这些数据进行操作的命令。 也许这个主意行得通...?
作为我的Ruby代码的原型,我只使用了一个嵌套的哈希图-根本没有模型对象。 玩家的每个动作都将简单地调用相应的方法,该方法将直接编辑地图分支的值。 一种非常程序化的方法-即使当时我并不这么认为。
这种设计立即带来了一些好处:添加“撤消”功能非常简单,可以在其中撤消对数据的更改。 数据树自然死了,很容易序列化和反序列化(比较我以前的设计的头痛之处,在每个旧类中,每个类都需要自己实现序列化/反序列化),而且我还可以准确地跟踪进行了哪些更改以组合更新到登录的玩家。
我已经通过采用更具程序性的思维方式解决了我的大型OO问题。
还不在那里
尽管解决了问题,但我实际上并未意识到问题是面向对象的。 我只是以为自己找到了一个非常聪明的解决方案。
从专业上讲,即使我可以使用反射,多态等来完成所有“花哨”的OO解决方案,但我自己的OO风格仍倾向于使用简单,显式和显而易见的解决方案。 但是我之所以选择解决方案是因为我的经验表明它们是最好的,而不是因为我意识到OO有任何问题。
一个新问题
我已经建立了游戏服务器,一切都很好。 扩展很简单,添加更多功能却很简单。 只有一个问题:客户。
我以前写过客户,但写得较小。 只要您只说10到15个屏幕,您就可以摆脱大多数设计的束缚。 我的游戏客户端具有50多个不同的对话框屏幕和许多不同的状态。 事情变得混乱了。
我有了模型,视图和控制器,但仍然感到无法控制, 到处都是如此多的状态 。 因为这是Java / C ++风格OO的本质,所以:将状态分成小块,让每个对象管理应用程序状态的特定部分。 这确实是一个非常糟糕的主意,因为复杂度大致对应于不同交互状态的平方。 此外,将状态隐含在某些成员变量值(的组合)中非常诱人。 “为什么当您可以检查变量的值以弄清楚时为何具有显式状态?”
结论
在过程编程中,您倾向于将状态保持在可以看到的状态。 与OO不同,在这种情况下,鼓励您拆分状态并隐藏状态,您几乎必须保持状态明确,这确实是一件好事。
这并不是说使用对象一定是一件坏事。 这是一个非常强大的工具,可用于一件事构建UI呈现层次结构,并且命名空间和链接可以创建非常平滑且可读的代码,将urlescape(substr(string, 0, strlen(string) - 2)
与string.substr(0, -2).urlescape()
(仍然有一个争论,尽管前者更清楚!)但是,面向对象的设计带有保持状态或作用于其他对象的对象,这是OO出错的地方。
还有一种(最被遗忘的)Objective-C风格的OO,它比Java / C ++在构建GUI时碰巧更好,因为分派和运行时的后期绑定使它更接近成为一种脚本语言。 可悲的是,曾经是Objective-C倡导者的苹果公司已经很大程度上忘记了ObjC背后的想法,现在正以采用Java / C ++ OO风格的Swift取代它。
尽管如此,仍然有一些语言试图回归基础。 Golang是其中之一,其他许多新的“系统编程语言”也可以使用。 尤其是Go语言(尽管我对语言有所保留)反驳了“不可能用程序编程来构建大规模产品”的神话。 语言的日益普及可能会破坏OO是“不可避免的”的观念
但是,Java风格的OO根深蒂固,几乎没有消失的趋势。 看看未来会带来什么会很有趣。
面向对象、面向对象过程