coffee-js / languages

编程语言学习论坛
https://github.com/coffee-js/languages/issues
112 stars 11 forks source link

从原型理解面向对象的尝试.. #34

Open tiye opened 11 years ago

tiye commented 11 years ago

一直无法体会面向对象设计的意思, 也没有找到能坦白其核心的说明, 也比较担心结果是我无法接受的, 因为想 Python 的代码对我来说太复杂了 之前看到的 Lua 的例子也不能解开疑惑, 想不通有什么设计思想.

在我遇到所能理解的例子中, OO 似乎是能在局部维持若干数据, 而且能被复制 比如网页上一个标签有内部的 HTML 内容, 有一个操作内容的函数, 内容是私有的, 函数在逻辑部分是一致的, 然而函数作用域不能满足需求 可如果每次手动传入对象来改变作用域的话, 就不符合减少重复的原则 等有了 class 的概念, 能够私有变量和共享操作, 我觉得还是重复了

像 CoffeeScript 官网的例子:

log = console.log

class Animal
  constructor: (@name) ->

  move: (meters) ->
    log @name + " moved #{meters}m."

class Snake extends Animal
  move: ->
    log "Slithering..."
    super 5

class Horse extends Animal
  move: ->
    log "Galloping..."
    super 45

sam = new Snake "Sammy the Python"
tom = new Horse "Tommy the Palomino"

sam.move()
tom.move()

但我不理解为什么要弄出复杂的 class 出来, 因为那是变量索引的问题 JS 中 __proto__ 就能在对象不存在一个键时查找父对象是否有对应的值, 这实现了了对象继承父对象时, 属性和方法简单的继承关系 接着是随之而来的作用域问题, 到早上想起, this 就是指向当前对象的 于是上边的例子可以改写, this 能一直索引到原型链中想要的对象上

log = console.log

Animal =
  Move: (meter) -> log "#{@name} move #{meter}m"

Snake =
  __proto__: Animal
  name: "Sammy the Python"
  move: ->
    log "Slithering..."
    @Move 5

Horse =
  __proto__: Animal
  name: "Tommy the Palomilo"
  move: ->
    log "Galloping..."
    @Move 45

sam = __proto__: Snake
tom = __proto__: Horse

sam.move()
tom.move()

输出:

Slithering...
Sammy the Python moved 5m.
Galloping...
Tommy the Palomino moved 45m.

按我理解的思路, 编程核心在于定位和操作数据, 还是那种扩展作用域的思路 等接触到复杂的应用模型时, 我很难想象用一个更复杂的概念怎样去保持灵活 猜想搭建复杂一些的界面还是免不了要设计模型和消息相互沟通的 那至少能更清楚关于对象的概念... 希望我没把问题搅得更加复杂

Liutos commented 11 years ago

但我不理解为什么要弄出复杂的 class 出来, 因为那是变量索引的问题JS 中 proto 就能在对象不存在一个键时查找父对象是否有对应的值,这实现了了对象继承父对象时, 属性和方法简单的继承关系

没什么差别啊,一个基于类,一个基于原型,class怎么就复杂了呢?

tiye commented 11 years ago

@Liutos 我觉得我开始纠结 class extends super 那几个关键字了... 他们在编程里既不是数据类型也不是变量名, 仅仅是控制结构的语法.

Liutos commented 11 years ago

@jiyinyiyong 其实你不必太纠结语法,语言写给人看的,语法的作用其实是表意'',你一看就知道它告诉你我现在开始定义类了,而且还集成了XXX'',这不就行了?;-)

tiye commented 11 years ago

@Liutos 按这样想其实问题关键也就在学没学会 OO 那整套的代码习惯了 我得花时间去学会和适应. 现在说什么反而比较无力了

tiye commented 11 years ago

@Liutos 按这样想其实问题关键也就在学没学会 OO 那整套的代码习惯了 我得花时间去学会和适应. 现在说什么反而比较无力了

ghost commented 11 years ago

对,OOP是被捧得最高但最没价值的概念。现在编程语言本质一切都是函数和模版。Flow对这个表现的最清楚。

一个对象就是一个数据模版实例化。方法可以直接放里面。

更灵活的对象系统,一般的OOP语言自己都没实现,只有那种架构很好的高级游戏引擎里有:每个对象只有一个ID,由ID找到对象Meta信息,然后由Meta信息控制对象的实际信息数据。实际信息数据会完全分离到不同模块中(图形、物理、AI...),统一标识只是那个ID。

tiye commented 11 years ago

@emerge 我对此比较怀疑. 如果 OOP 是完全没价值的的, 那么其成功就无法解释.

ghost commented 11 years ago

@jiyinyiyong 忽悠成功也能算成功的话,OOP的确可以算成功的概念。因为计算机科学从Prolog到现在从没前进反而在不断退步,所以到最近人们才“重新发现”函数式/lisp。。。

扯远一点,目前这个世界的“常理”基本都是错误/忽悠人的东西,不只是技术上,如果看人数多少,很可能是一起都错了。

tiye commented 11 years ago

@emerge 我倾向与认为 OOP 的成功是因为的确简化了开发, 但这也算成功 因为没有熟练, 我没法从语言上讲 OOP 哪里是真的成功或者怎样... 看 Smalltalk 的作者和和后继的 Objective-C, Python, Ruby 前者获得图领奖, 或者在 iOS 和 Web 编程分别取得了巨大的成功, 这些都不能小看

同时 Lisp 的函数到了 OOP 就是一个 Method 的概念, 并不是 OOP 比 Lisp 就残疾了函数式的功能 我讨厌 Java 那种复杂的处理对象的方式, 或者 Python 仍然冗长, 但那不是 OOP 本身的概念 OOP 的目标之一是简化大型系统的复杂度, 我很怀疑 Lisp 在这方面有没有

Liutos commented 11 years ago

@jiyinyiyong Java处理对象复杂?

奉劝楼上两位一句,如果想深入讨论,最好你们可以先拟定一个一起认可的``面向对象''这个概念的定义,否则只是堆砌名词是没用的,先告诉看的人,你们所提到的OOP三个字母是什么意思?

tiye commented 11 years ago

@Liutos 不想往深了说.. 面向对象编程我还没掌握. 只是从他的影响来判断它并不是失败 主要是讨厌 Java.. 别的不多说..

tiye commented 11 years ago

编译一个十分钟都编译不完的 Wayland 的时候就体会到我现在说的东西多无谓了. 找到问题比找技术更重要, 而后者又因前者而改变. 基本上我很多操心都白想了

ghost commented 11 years ago

OOP就是WIKI的OOP:就是将代码分解成对象,对象间互发消息,对象作为数据,消息处理作为函数的软件方法。 这是最通用原始的定义,不包括“继承”、“对象封装”等更加糟粕的概念。

但即使是这个定义,实际上就可看出它是一个不通用的糟糕概念:

一个模型是一个虚拟世界,虚拟世界由概念组成,单一的概念并不存在,每个概念存在于和其他概念的关系中。

明显的是OOP违背这里概念抽象的基本特性,它试图强制分离一个概念到类(OOP“哲学”(听说也不是发明OOP的人,OOP的发明者好像更懂它的局限)鼓吹者以为概念可以独立存在,并且还能以概念为单位分层打包),做毫无意义的函数封装等扭曲的操作。 (尝试做个OOP风格的数学库就知道我在说什么了。)

OOP不是完全无用,用途是它的模型正好恰当的抽象领域。(比如我做一个多agent系统,1个agent正好一个对象来表达,但那也只是这一层。)

“影响”什么都是“假象”,含义是很好歪曲的,当年OOP这种看上去很cool的理念被很多人学习也很正常,但终究只是个泡沫而已。

tiye commented 11 years ago

@emerge 你说的定义还有抽象的领域我基本都同意.. 关于使用方面, 我没有受过 OOP 代码的训练, 我从 JS OOP 风格的框架上理解到的大概和你不一样 Backbone 中 Model 和 View 继承, 我的确能看到将组建模块化的方便 因为把 element, event 自己做绑定, 代码就会穿插在一起, 不方便分工或者他人维护

而且我上边的代码, 对于 OOP 在语言中增加的复杂概念我一开始就在反感的 对我而言, 语言中的概念如果不能真实反映在模型当中, 使用就会很困难 我倾向于把现在语言中出现的 OOP 认为是另一个维度的作用域, 尽管可能会是错的

关于你说的"强制分离一个概念到类", 我似乎在很多地方听说过, 不过显然, 这在 JS 里没有... 我不懂 Java.. 猜测那是语言的限制, 而不是 OOP 本身的限制, 因为概念太多都是混在一起的

ghost commented 11 years ago

@jiyinyiyong 模块化、封装都是正确的,MVC也是有用的,并且是和OOP无关(平行)的概念。

OOP“哲学”的例子通常是: class Car;Car carA;carA.run(); class Vector;Vector v1;v1.norm(); Car、Vector即是这里的概念,但是要知道车自己并不会开,开车的运动是相对于地图、驾驶员。。。的一个运动,所以这个run()的绑定是糟糕的。Vector同理,norm计算是vector的,但是一个更复杂的比如vector到Matrix的转换是vec还是mat的?这个转换实际是上还依赖于你的坐标系等具体数学环境设定,也就是不同环境会有不同同名运算。

所以车这个概念并不存在,车相对于地图时是地图上的坐标点移动,车相对于驾驶员是车的驾驶人机界面。

tiye commented 11 years ago

@emerge 的确我混淆了模块化和 OOP, 可按你说的与 OOP 无关的那种封装是怎么做? 我印象里是 Lisp 类语言会在函数名头部加上对应的类型, 可我不觉得这是好的方案呀..

后面一个例子, 我认为更多是变量名的问题, 都不是代码本身逻辑上有什么错误 比如执行了 car.run(), 对应的状态可以修改, 事件可以触发, 那么也是可以正常运行的

ghost commented 11 years ago

@jiyinyiyong 那当然,意大利面式代码都能正常运行呢。多态和OOP也是平行的概念。

这里是结构,不是名字。名字实际上联系着更深奥的问题。

理解这些工程上的思想还是需要些实践,实践加思考才能知道为什么。

关于这个,Linus也比较清楚,记得他大骂C++时好像也顺便提到了一点。

tiye commented 11 years ago

加一个例子, 对象在拷贝后 this 的指向:

a =
  zero: "my name is a"
  method: (x) -> @b = x

b = {}
for key, value of a
  b[key] = value

b.method "insert"

log = console.log
str = (json) -> JSON.stringify json, null, 2

log (str a), (str b)

结果是:

➤➤ coffee clone.coffee 
{
  "zero": "my name is a"
} {
  "zero": "my name is a",
  "b": "insert"
}
tiye commented 11 years ago

@emerge 我这重心已经偏离了... 问题先搁着吧. 我刚刚在开始尝试的阶段

倒是这篇文章你有什么看法? http://www.evanmiller.org/mathematical-hacker.html

ghost commented 11 years ago

@jiyinyiyong 对作者的看法没看明白。我的看法是:只有数学模型是本质模型,所以Equa的目标就是建立方程描述变量间的关系。

Liutos commented 11 years ago

@emerge 的确我混淆了模块化和 OOP, 可按你说的与 OOP 无关的那种封装是怎么做?我印象里是 Lisp 类语言会在函数名头部加上对应的类型, 可我不觉得这是好的方案呀..

你混淆了语言和背后的思路本身,emerge的想法是比较根本的,和具体语言无关~

封装不局限于任何语言,封装是一种编程方式,只要坚守就可以了,即使是C语言的结构体也可以写成封装性良好的代码,和是否面向对象无关;另外,Lisp类语言我还真没见过在函数头加上类型声明的,当然也可以这么做;你说的那个应该是ML类的语言,如Haskell,不过那个也可以缺省。

tiye commented 11 years ago

@Liutos 我在 Racket 看到比如 hash-eqv, 好多函数前都跟上对应类型了 http://docs.racket-lang.org/reference/hashtables.html 另外 Chicken 对 Qt 的封装, 用了 qt:hide 形式的命名 http://wiki.call-cc.org/eggref/4/qt#qtdelete

前一条我不确定是我错了... JS 不是基于类的, 我说的因此有偏离

Liutos commented 11 years ago

@Liutos 我在 Racket 看到比如 hash-eqv, 好多函数前都跟上对应类型了http://docs.racket-lang.org/reference/hashtables.html另外 Chicken 对 Qt 的封装, 用了 qt:hide 形式的命名http://wiki.call-cc.org/eggref/4/qt#qtdelete

因为它们是特定用途的啊,只是个名字罢了,什么语言都可以这么做。Chicken的封装很正常,Common Lisp的gtk+的绑定也是类似的名字风格,前面加上gtk:前缀,ZeroMQ的也是加上zmq:前缀。一般通过FFI实现的库,都是这么些的~

ypyf commented 11 years ago

OO是伴随着GUI的普及而发展起来的