代码之家  ›  专栏  ›  技术社区  ›  Caleb Hearth

类本身执行函数是一种好的约定吗?

  •  3
  • Caleb Hearth  · 技术社区  · 15 年前

    一个物体,应该是一个外部物体,所以 Save(Class) 而不是让对象保存自己: Class.Save()

    String.Format() 或者把自己归类为 List.Sort()

    我的问题是,在严格的面向对象编程中,当调用一个类来执行函数时,是否应该让它自己执行函数,或者这样的函数应该是外部的并在类的类型的对象上调用?

    5 回复  |  直到 15 年前
        1
  •  3
  •   stakx - no longer contributing Saravana Kumar    15 年前

    好问题。我最近刚刚思考了一个非常类似的问题,并最终在这里提出了同样的问题。

    在面向对象的教科书中, 你有时会看到这样的例子 Dog.Bark() ,或 Person.SayHello() . 我得出的结论是,这些都是不好的例子。当你调用这些方法时,你让一只狗吠叫,或者一个人打招呼。然而,在现实世界中,你不能这样做;狗决定什么时候叫。一个人决定何时向某人问好。因此,将这些方法建模为 (如果编程语言支持)。

    你会有一个函数 Attack(Dog) , PlayWith(Dog) Greet(Person) 会触发相应的事件。

    Attack(dog)      // triggers the Dog.Bark event 
    Greet(johnDoe)   // triggers the Person.SaysHello event 
    

    一旦有多个参数,

    StoreInto(1, collection)    // the "classic" procedural approach
    1.StoreInto(collection)     // possible in .NET with extension methods
    Store(1).Into(collection)   // possible by using state-keeping temporary objects
    

    根据上面的思路,最后一种变体是首选的,因为它不会强迫一个物体(物体) 1 )对自己做点什么。但是,如果您遵循这种编程风格,很快就会发现 流畅的界面


    注:关于全局函数:以.NET为例 (您在问题中提到过),您没有太多选择,因为.NET语言不提供全局函数。我认为这些在技术上可以通过CLI实现,但是语言不允许这种特性。F#具有全局函数,但它们只能从C#或VB.NET打包到模块中使用。我相信Java也没有全局函数。

    我曾遇到过这样的情况,缺乏这种能力是一种遗憾(例如,使用流畅的接口实现)。但是 一般来说,如果没有全局函数,我们可能会过得更好 ,因为有些开发人员可能总是回到旧习惯中,并为OOP开发人员保留一个过程代码库。伊克斯。

    Globals.vb :

        Module Globals
            Public Sub Save(ByVal obj As SomeClass)
                ...
            End Sub
        End Module
    

    Demo.vb

        Imports Globals
        ...
        Dim obj As SomeClass = ...
        Save(obj)
    
        2
  •  1
  •   Rob Packwood    15 年前

    我猜答案是“视情况而定”。。。对于对象的持久性,我支持在单独的存储库对象中定义该行为。因此,在Save()示例中,我可能会看到:

    repository.Save(class)
    

    airplane.Fly()
    

    这是我从Fowler那里看到的关于aenemic数据模型的一个例子。我不认为在这种情况下,你会想有这样一个单独的服务:

    new airplaneService().Fly(airplane)
    

    new listSorter().Sort(list)
    
        3
  •  1
  •   Mark Rushakoff    15 年前

    严格的

    !

    >> s = 'hello'
    => "hello"
    >> s.reverse
    => "olleh"
    >> s
    => "hello"
    >> s.reverse!
    => "olleh"
    >> s
    => "olleh"
    

    关键是要在纯OOP和纯过程之间找到一个折中点,以满足您的需要。A Class 等级 不应该知道怎么做 例如,它自己变成了一条流。

        4
  •  1
  •   anton.burger    15 年前

    我不知道当你说“做某事”时,你看起来有什么区别 “一个物体”。在大多数情况下,类本身是定义其操作的最佳位置,因为在“严格OOP”中,它是唯一可以访问这些操作所依赖的内部状态(信息隐藏、封装等)的代码。

    也就是说,如果您有一个操作应用于几个不相关的类型,那么每个类型都应该公开一个接口,让该操作以或多或少的标准方式完成大部分工作。为了将它与您的示例联系起来,几个类可以实现一个接口 ISaveable 暴露了一个 Save 方法。个人 方法利用它们对内部类状态的访问,但是给定了 我可以 例如,一些外部代码可以定义一个操作,将它们保存到某种自定义存储中,而不必知道混乱的细节。

        5
  •  1
  •   Puppy    15 年前

    这取决于做这项工作需要什么信息。如果工作与类无关(基本上是等价的,可以在几乎任何具有公共接口的类上工作),例如,std::sort,那么将其作为一个自由函数。如果它必须了解内部结构,就将其作为成员函数。

    编辑:另一个重要的考虑因素是性能。例如,就地排序可以比返回新的、已排序的副本快几英里。这就是为什么在绝大多数情况下快速排序比合并排序快,尽管合并排序理论上更快,这是因为快速排序可以就地执行,而我从来没有听说过就地合并排序。仅仅因为技术上可以在类的公共接口中执行操作,并不意味着您实际上应该这样做。