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

Oop 类本身执行函数是一种好的约定吗?,oop,class,Oop,Class,我一直被教导,如果你对一个对象做了什么,那应该是一个外部的东西,因此人们会保存(类),而不是让对象保存自己:Class.Save() 我注意到,在.Net库中,一个类经常使用String.Format()修改自身,或者使用List.sort()对自身进行排序 我的问题是,在严格的面向对象编程中,当调用一个类来执行函数时,该类是否适合自己执行函数,或者该类函数是否应该是外部的并调用该类类型的对象?我猜答案是“这取决于”。。。对于对象的持久性,我支持在单独的存储库对象中定义该行为。因此,在您的Sav

我一直被教导,如果你对一个对象做了什么,那应该是一个外部的东西,因此人们会
保存(类)
,而不是让对象保存自己:
Class.Save()

我注意到,在.Net库中,一个类经常使用
String.Format()
修改自身,或者使用
List.sort()对自身进行排序

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

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

repository.Save(class)
但是,对于飞机对象,您可能希望类知道如何使用如下方法飞行:

airplane.Fly()
这是我从Fowler那里看到的关于aenemic数据模型的示例之一。我不认为在这种情况下,您会希望有这样一个单独的服务:

new airplaneService().Fly(airplane)
new listSorter().Sort(list)
对于静态方法和扩展方法,它的意义与List.Sort()示例类似。所以这取决于你的使用模式。您不希望为了能够对列表进行如下排序而必须新建ListSorter类的实例:

new airplaneService().Fly(airplane)
new listSorter().Sort(list)
在严格的OOP(Smalltalk或Ruby)中,所有方法都属于实例对象或类对象。在“实际”OOP(像C++或C语言)中,你将拥有完全独立于自己的静态方法。 回到严格的OOP,我更熟悉Ruby,Ruby有几个“对”方法,它们要么返回修改过的副本,要么返回原地的对象——一个以
结尾的方法表示消息修改其接收器。例如:

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

关键是要在纯OOP和纯过程之间找到一些中间地带,以满足您的需要。一个
应该只做一件事(而且要做好)。大多数情况下,这不包括将自身保存到磁盘,但这并不意味着
不应该知道如何将自身序列化为流。

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


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

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

在OOP教科书中,您有时会看到一些示例,如
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)对自己做一些事情。但是,如果您遵循这种编程风格,很快就会发现,这种类似于的流畅接口的代码非常冗长,虽然易于阅读,但编写起来可能会很累,甚至很难记住确切的语法


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

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

顺便说一句,在VB.NET中,您可以通过使用模块来模拟全局函数。例如:

Globals.vb

    Module Globals
        Public Sub Save(ByVal obj As SomeClass)
            ...
        End Sub
    End Module
    Imports Globals
    ...
    Dim obj As SomeClass = ...
    Save(obj)
Demo.vb

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

这取决于做这项工作需要什么样的信息。如果工作与类无关(大多数情况下是等价的,可以使其在具有公共接口的几乎任何类上工作),例如std::sort,则使i