tl;dr: 面向对象编程(Object-oriented programming)不必是面向类编程(Class-oriented programming)
像 JavaScript 这种,声明对象比声明类还方便的语言中,何必非要定义一个类,再定义实例呢?
从“和面向过程相对”的意义上说,“面向对象”指的是,封装的时候,去封装一个名词而不是封装一个动词。至于实现上,是去实现一个类,还是实现一个接口,其实都是 OOP
经常看到言论说 JavaScript 正在远离 OOP,事实上我觉得被远离的只是“OOP原教旨主义”,Java 之流那种
什么意思呢,我觉得 interface 就是定义了一个对象应该是什么 shape,比如,如果我是一个瞎子,只靠声音认狗狗🐕,只要你能汪汪叫就算你是狗 U•ェ•*U
那传统的继承式 OOP 就要求你老老实实界门纲目科属种,你得是继承了动物界、脊椎动物门、哺乳动物纲、食肉目、犬科、犬属,但接口式的 OOP 只要求你能狗叫就行了,管你是 京中有善口技者,还是音乐播放器,你都是狗。基于接口的 OOP 很显然就是更进步的、实用主义的 OOP
interface DogLike {
bark: () => "Woff"
};
function getDog(): DogLike {
return {
bark() {
return "Woff"
}
}
}
const dog = getDog()
dog.bark()
这是面向过程吗?这也是面向对象。只要你在使用的时候不是在使用 doBark(dog)
而是 dog.bark()
你就是在 OOP 了。不需要用 class
只要你是在操作一个主体的方法,就是在 OOP
相对的,像这种
class Dog {
bark(): "Woff"
};
function doBark(dog: Dog) {
return dog.bark()
}
dog = Dog()
doBark(dog)
这种才是披着面向对象外皮的面向过程编程。
是的,在我的定义中,Rust 也可以是 OOP。
说起来,前两天我一个给 Gradio 的 PR 被合了:
这个 PR 也挺好玩的,起因是我发现 Gradio 5 实现热重载了,我一直挺关心热重载的,包括我 py3.online 上也自己实现了一套热重载。我看它代码时发现它的方法差不多(都是在一个模块的 __dict__
中 exec 它的 sources),还支持一个 NO_RELOAD
的常量,if gr.NO_RELOAD
块中可以执行一些不想被热重载重新执行的代码。我觉得还蛮有意思的。然后我就在想:这个值是怎么做到动态的呢?是在热重载的时候直接改这个值,从 True 改成 False 吗?那如果人家不是用 gr.NO_RELOAD
而是直接用 from gradio import NO_RELOAD
这样的方式导入的呢?这样不就失去响应性了吗?
后来一读源码,发现更离谱,它甚至不是动态的 bool,而是通过 AST 搜索 if gr.NO_RELOAD
这样的出现,然后把整个 if 语句都删掉。我觉得这也太 hack 了,因为这导致这个 if 语句没法用 and / or 之类的结构,也没法用别名,甚至 else 子句也删了,也没法把这个 bool 当参数传给别处。而且这些,在文档里都没有描述。害人不浅啊(具体的坏处我 PR 里描述的更清楚)
于是,我的实现,简单来说就是把它编程一个伪 bool,让它的 __bool__
是动态的就行了。简单的话直接用 list 之类的东西就行,我是继承了 bool 类(简单来说是这样,但是 Python 里 bool 不让继承,所以其实我继承的是 int)(插播一条冷知识,Python 中 bool 也是 int 的子类哦),虽然我这样也 hack,但我至少不会造成奇奇怪怪的 bug
class DynamicBoolean(int):
def __init__(self, value: int):
self.value = bool(value)
def __bool__(self):
return self.value
def set(self, value: int):
self.value = bool(value)
本来以为他们会不会对这个命名有异议的,但是居然没有。看来他们对贡献挺不 opinionated 的,毕竟也是一个 utils.py 文件 1500+ 行的项目。哈哈哈