OOP和动态打字(非静态与动态)

时间:2009-12-16 22:37:02

标签: design-patterns oop static-typing dynamic-typing static-language

在动态类型环境中,与静态类型环境(例如Ruby vs C#)相比,哪些OOP原则(如果有)不适用或应用不同?这不是对静态与动态辩论的呼吁,而是我想看看是否存在适用于一方而非另一方适用于该范围的任何一方的公认原则,或以不同方式应用。在“静态类型”OOP文献中,诸如“喜欢组合到继承”的短语是众所周知的。它们是否适用于动态方面?

例如,在动态类型环境中,似乎耦合的粒度不会超过方法的级别。换句话说,任何给定的函数调用只将调用者耦合到该特定接口,任何类可能满足 - 或者换句话说,任何像那个特定的鸭子那样嘎嘎叫的东西。

另一方面,在Java中,耦合的粒度可以与包一样高。一个特定的方法调用不仅与另一个类/接口建立契约,而且还将它耦合到该类/接口的package / jar / assembly中。

这样的差异会产生不同的原则和模式吗?如果是这样,这些差异是否明确表达了? Ruby Pickaxe书中有一节向这个方向发展(Duck Typing / Classes Are Types),但我想知道是否还有其他内容。我知道Design Patterns in Ruby但还没看过。

编辑 - 有人认为Liskov在动态环境中不会像在静态环境中那样应用相同的内容,但我不禁想到它会这样做。一方面,没有与全班同学的高级合同。但是,不是所有对任何给定类的调用构成一个隐式契约,需要像Liskov所规定的那样由子类来满足吗?考虑以下。 “做一些酒吧”的电话会创建一个需要由子课程来处理的合同。这不是“将专门对象视为基类吗?”的案例:

class Bartender
    def initialize(bar)
       @bar = bar
    end

    def do_some_bar_stuff
        @bar.open
        @bar.tend
        @bar.close
    end
end

class Bar
    def open
        # open the doors, turn on the lights
    end
    def tend
        # tend the bar
    end
    def close
        #clean the bathrooms
    end
end

class BoringSportsBar < Bar
    def open
        # turn on Golden Tee, fire up the plasma screen
    end

    def tend
        # serve lots of Bud Light
    end
end

class NotQuiteAsBoringSportsBar < BoringSportsBar
    def open
        # turn on vintage arcade games
    end
end

class SnootyBeerSnobBar < Bar
    def open
        # replace empty kegs of expensive Belgians
    end

    def tend
        # serve lots of obscure ales, porters and IPAs from 124 different taps
    end
end

# monday night
bartender = Bartender.new(BoringSportsBar.new)
bartender.do_some_bar_stuff

# wednesday night
bartender = Bartender.new(SnootyBeerSnobBar.new)
bartender.do_some_bar_stuff

# friday night
bartender = Bartender.new(NotQuiteAsBoringSportsBar.new)
bartender.do_some_bar_stuff

4 个答案:

答案 0 :(得分:5)

我认为你所触及的本质区别是:

  • 语言组1.调用object.method1,object.method2,object.method3时调用的实际方法可以在对象的生命周期内更改。

  • 语言组2.在调用object.method1,object.method2,object.method3时调用的实际方法在对象的生命周期内不能更改。

组1中的语言往往具有动态类型,并且不支持编译时检查的接口,组2中的语言往往具有静态类型并支持编译时间的chcked接口。

我会说所有OO原则都适用于两者,但

  • 在组1中可能需要一些额外的(显式)编码来实现(运行时而不是编译时)检查,以断言使用所有适当的方法创建新对象以满足接口合同没有编译时接口协议检查,(如果你想使第1组代码更像第2组)

  • 在第2组中可能需要一些额外的编码来模拟为方法调用调用的实际方法的更改,方法是使用额外的状态标志来调用子方法,或者在引用中包装方法或一组方法连接到主对象的几个对象之一,其中几个对象中的每一个都有不同的方法实现,(如果你想使第2组代码更像组1代码)

  • 第2组语言对设计的限制使它们更适合更容易沟通(而不是理解)变得更加重要的大型项目

  • 第1组语言中对设计缺乏限制使得小型项目更好,程序员可以更容易地检查是否只是因为代码较小而一目了然地满足各种设计管道约束

  • 从另一组语言中制作代码非常有趣并值得研究,但语言差异的关键在于它们如何帮助不同规模的团队( - 我相信!:))

  • 还有其他各种差异

  • 根据所涉及的确切原则,可能需要或多或少的腿部工作来用一种语言或另一种语言实施OO设计。


修改

所以为了回答你原来的问题,我检查了

http://c2.com/cgi/wiki?PrinciplesOfObjectOrientedDesign

http://www.dofactory.com/patterns/Patterns.aspx

在实践中,系统中没有遵循面向对象原则的各种原因(当然也有一些不好)。很好的理由包括性能问题超过纯粹的设计质量问题,无论替代结构/命名的文化优势超过纯粹的设计质量问题,以及以特定语言的标准方式实现功能的额外工作的成本超过了一个纯粹的设计。

粗粒度模式,如抽象工厂,构建器,工厂方法,原型,适配器,策略,命令链,桥,代理,观察者,访问者甚至MVC / MMVM往往在小型系统中使用较少,因为关于代码的沟通较少,因此创建此类结构的好处并不那么好。

像State,Command,Factory Method,Composite,Decorator,Facade,Flyweight,Memento,Template方法这样的细粒度模式在组1代码中可能更常见,但通常有几种设计模式不适用于对象,而是对象的不同部分,而在第2组中,代码模式往往存在于每个对象的一个​​模式上。

恕我直言,在大多数第1组语言中,将所有全局数据和函数视为一种单独的“应用程序”对象是很有意义的。我知道我们正在模糊Procedural和OO编程之间的界限,但这种代码在很多情况下肯定像“应用程序”对象一样嘎嘎作响! :)

一些非常细粒度的设计模式(如Iterator)倾向于构建为第1组语言。

答案 1 :(得分:3)

首先我要说的是,就个人而言,不适用于动态和静态类型语言的OOP原则并不是原则。

那就是说,这是一个例子:

接口隔离原则(http://objectmentor.com/resources/articles/isp.pdf)指出客户端应该依赖于满足其需求的最具体的接口。如果客户端代码需要使用C类的两个方法,那么C应该实现接口I,只包含这两个方法,客户端将使用I而不是C.这个原则与不需要接口的动态类型语言无关(因为接口)在变量是无类型的语言中不需要定义类型和类型

[编辑]

第二个例子 - 依赖性倒置原则(http://objectmentor.com/resources/articles/dip.pdf)。这个原则认为“依赖于接口或抽象函数和类的策略,而不是具体的函数和类”。同样,在动态类型语言中,客户端代码不依赖于任何东西 - 它只是指定方法签名 - 从而避免了这一原则。

第三个例子 - Liskov替换原则(http://objectmentor.com/resources/articles/lsp.pdf)。该原理的教科书示例是一个Square类,它是Rectangle类的子类。然后,当高度也因为实际对象是Square而改变时,调用Rectangle变量上的setWidth()方法的客户端代码会感到惊讶。同样,在动态类型语言中,变量是无类型的,在客户端代码中将不会提及Rectangle类,因此不会出现这样的意外。

答案 2 :(得分:1)

我对这一切有一个“激进”的观点:在我看来,在数学的支持下,OOP在静态类型的环境中无法解决任何有趣的问题。我将有趣的定义为涉及抽象关系的含义。这很容易证明(参见“协方差问题”)。

这个问题的核心是OOP的概念承诺它是一种模拟抽象的方法,并结合静态类型提供的契约编程,在不破坏封装的情况下无法实现关系。只需尝试任何协变二元运算符即可:尝试在C ++中实现“小于”或“添加”。您可以轻松编写基本抽象代码,但无法实现它。

在动态系统中,没有高级形式化类型,也没有任何封装可以打扰,因此OO实际上有效,特别是像原始Smalltalk这样的基于原型的系统实际上提供了无法使用静态类型约束进行编码的工作模型。 / p>

以另一种方式回答这个问题:这个问题的基本假设具有本质上的缺陷。 OO没有任何连贯的原则,因为它不是一致的理论,因为它没有任何模型,只有简单的编程任务才能处理任何事情。你放弃的是什么不同:在动态系统中你放弃封装,在静态系统中你只需切换到有效的模型(函数式编程,模板等),因为所有静态类型的系统都支持这些东西。

答案 3 :(得分:0)

接口可能会增加一些开销,特别是如果您直接依赖其他人的API。简单的解决方案 - 不依赖于其他人的API。

让每个对象与它希望在理想世界中存在的接口对话。如果这样做,最终会得到范围小的小型接口。通过这样做,您将在接口更改时获得编译时失败。

界面越小越具体,界面变化时你需要做的“簿记”就越少。

静态类型的一个真正好处是静态地知道你可以调用哪些方法,但保证值对象已经被验证...如果你需要一个名字,并且名字必须是&lt; 10个字符,创建一个封装该验证的Name类(虽然不一定是任何I / O方面 - 保持纯值类型),编译器可以帮助您在编译时捕获错误,而不是必须在运行时验证。

如果您打算使用静态语言,请将其用于您的优势。