【问题标题】:Is there some literature on this type of programming?有没有关于这种编程的文献?
【发布时间】:2010-08-05 14:25:22
【问题描述】:

在大学里,我上了一门现代物理学课,我们在其中学习了狭义相对论。我完全被不同的参照系如何实际观察到一个物体的物理特性不同而既不正确而震惊。随着时间的推移,这个概念慢慢地改变了我的编程方式,现在我倾向于将类分为两大类,数据对象和观察(仅限函数)对象。

为了避免对这么简单的问题写一篇详尽而冗长的文章,我将尝试通过两个例子来解释我的意思。

首先,以我以前经常写的这种类型的代码为例:

class Deck
{
    private Card[] cards;

    public void Shuffle()
    {
        // shuffle algorithm
    }

    // other deck related functions like draw, cut, etc...
}

我现在写的场景通常是这样的:

class Deck
{
    // by intention, i just mean some kind of immutable collection of cards
    private ReadonlyCollection<Card> _Cards;

    public Card[] Cards { get { return this._Cards; } }
}

interface IDeckHandler
{
    Deck Shuffle(Deck deck);
    // other deck related functions like draw, cut, etc..
}

class Dealer : IDeckHandler
{
    // IDeckHandler implementation
}

Deck 不再负责实现对其起作用的功能。或者为了符合术语,套牌只是一组价值观,观察它的方式是观察者的责任。当然,可以有许多观察者以不同的方式执行操作。

对于第二个示例,我将使用一些我尝试过解释的人,以便更轻松地了解这一点。以我们在彩色纸上用彩色字母拼出一个单词的情况为例。我们有一个代理人负责阅读纸上的文字。现在假设代理是某种类型的色盲。从纸上发出的图像是相同的,但感知可能不同。观察者对对象没有深入的了解,也无法对其进行修改,只能对其进行解释。

正如我所说,这个概念推动了我的许多开发决策。回到这个问题,这是一种已发布的编程类型吗?如果是,您能否指出一些关于它的文献?我遇到了一些常见和不常见的场景,它们很难做出决定,当然还有一些我还没有想到或遇到过的事情,希望在文献中得到检验。

【问题讨论】:

  • 一个想法:阅读控制反转。你会喜欢的。
  • 哎呀,我的“现代物理学”课程主要是关于量子力学的。 (狭义相对论已经在“普通物理学”中介绍过。)我想看看不确定性原理会对你的编程产生什么影响! ;-)
  • 这门课更像是一门调查课程,我们还涵盖了原子物理学和其他一些东西,但它侧重于关于世界如何运作的思想进程以及基于此的理论发展比什么都重要。
  • @Rui:我在观察者中大量使用 IoC 容器(尽管我试图不让人们误以为我指的是观察者模式)。当您尝试在应用程序中分配责任时,IoC 会产生一个非常自然的思考过程。

标签: design-patterns oop functional-programming


【解决方案1】:

在我看来,您正在以 OOP 方式实现函数式编程。 不管物理学上关于“狭义相对论”的解释如何,OOP 的整个思想基本上都是封装——你想让对象知道自己应该如何做每一件事。基本上你在这里说的是“不,只有数据和对数据起作用的函数”。如果甲板发生变化怎么办?你有一个新的经销商?你怎么知道应该创建哪个经销商来处理新牌组?

如果您考虑过“switch”语句,那么您就是在将 OOP 概念锤击到函数式编程模板中。

【讨论】:

  • 我理解封装的概念。我通常将其调整为隐藏存储对象属性的结构。例如,没有理由不能在内部将卡片组存储为单个 64 位整数,而对象的公开属性是相同的。然而,要回答你的问题,如果套牌改变了,是的,经销商必须改变,但我愿意接受这一点,因为我认为这更能模拟现实世界。
  • 我遇到的一个主要问题是,如果不深入了解值对象的存储,许多对值对象进行操作的算法效率低得多,而且效率低得多比他们聪明。例如,我使用这个概念实现了井字游戏,并将状态存储为单个 32 位整数。检查获胜者的过程可以是单位 AND,如果获胜者确定内置于状态本身,而是更多地访问暴露的多维棋盘属性。我正在想办法解决这个问题。
  • 关于您的第二条评论:您所描述的问题实际上在许多情况下都是好的设计。围绕特定(相当棘手的)实现事实(该状态存储为单个整数)构建逻辑通常很糟糕。最好围绕状态(板)的含义构建逻辑。当您想直接访问实现时,当然存在优化情况,但这样做是一种过早的优化,并且会导致代码的可读性和可维护性降低。
  • @Jakob:我完全同意你关于围绕状态构建逻辑的过早优化,这是我最喜欢这种编程风格的属性之一。我特别喜欢它如何将观察者的角色分开,以在任意实现上执行他们的操作。然而,优化的壮举仍然存在,在我看来,值得在完整的设计模式规范中加以克服。然而,为了概念上的可读性,它清理了开发过程。
  • @yossale:我特别喜欢你的第一句话,It seems to me like you're implementing functional programming in an OOP way。我在定义这个概念时遇到的最大困难是功能元素应该在哪里构建到 OOP 模型中。但是,我不明白您对 switch 语句的评论。
【解决方案2】:

面向数据的编程(一种专注于数据布局和转换的开发风格)涵盖了这种编程风格的某些方面。

我对这种风格的主要问题是,如果给定类型存在隐含的假设/约束(例如,说牌组在洗牌后绝不能连续有 2 个小丑),您必须在整个过程中重复这些约束/检查所有管理器类型,因为您正在操作的数据类型完全是愚蠢的——它不能照顾自己,它只是一个数据包。您可以将重复的逻辑提取到一个单独的方法中,但是使用这种方法编写好的、干净的代码通常有点困难。

将此与实现采用IDeckShuffle 策略的Deck.Shuffle 方法进行比较。在这种情况下,您可以执行 shuffle,然后添加不变检查作为 post 步骤,以确保无论使用什么 shuffle 策略,deck 都不会进入无效状态;强制完整性的代码在一个地方,易于验证和更新。

此外,由于对 IDeckShuffler.Shuffle(...) 的调用来自于 Deck 内部,因此 Deck 可以访问所有隐藏字段和封装状态。因此,您可以将 minimum 细节暴露给 Deck shuffler 实现,而不是默认传递 Deck。相反,您可以传递IEnumerable&lt;Card&gt; 或更不具体的内容,而不是默认传递整个数据包。

无论如何,您所询问的开发形式基本上是过程式编程。因此,隐藏信息和封装事物变得更加困难。在性能关键型系统中,这可能是一个可接受的折衷方案(按类型对所有数据进行分组,然后使用管理器“进程”功能对其进行迭代 = 良好的缓存一致性)。

在一般开发中,我远离这种编程风格,因为它严重阻碍了我管理复杂性的能力。 Ayende had a good post about this a while back。虽然他说的是数据存储对象和作用于它的操作,但原理是完全一样的——数据的分离和作用于该数据的功能以及其中的问题。

【讨论】:

  • 虽然我同意您的替代方案,但我不同意这是过程编程。对我来说,它更像是(对象命名空间)函数式编程,如yossale mentioned in his answer
  • 不发表我对您建议的方法和模式的经验的多重评论,将井字棋想象成一个数组,其中空格可以是 X 或 O 或空。现在扩展这个棋盘结构,使其也支持连接四、奥赛罗等......在我的场景中,棋盘只是一个任意大小的棋盘,根据正在玩的游戏,棋盘在每一步中如何受到影响的规则各不相同。
  • 对于你的例子,两个小丑并排在一起,同样的情况也是如此,事实上纸牌就是一个很好的例子......它只是一副纸牌。
【解决方案3】:

您所做的是将概念的数据与作用于该概念的操作分开。系统是什么来自系统做什么。这为许多不同的场景打开了大门,在这些场景中,您可以通过使用不同的行为类来更改系统的行为。这些行为类也可以重复用于不同的数据类。许多模式都解决了这个问题,例如访问者、命令、策略或观察者。

但这里还有更深层次的东西在起作用。也许我们的(主流)编程语言(或者可能只是在我们的脑海中)需要另一个概念,这将使我们能够分离和重用这些行为。

DCI architecture 解决了这些问题,并将角色或traits (pdf) 作为行为和代码重用的基本单元。

【讨论】:

    【解决方案4】:

    这是大多数应用程序的典型布局方式。我认为类形成了二分法——数据容器对象和策略对象。数据容器对象是信息的载体,策略是各种算法的封装,可以应用在数据容器上。

    命令模式与策略模式非常接近。策略也往往表现为控制器、外观等。

    数据容器对象表现为实体、模型对象、值对象、数据传输对象等。

    四人组是一个好的开始,但您可能需要查看其他经典设计模式论文以进一步阐述。根据您的编程语言偏好,您可能还需要考虑更具体的模式书。亚马逊有大量关于设计模式的列表。

    我在this article 中谈到了阶级二分法。

    【讨论】:

    • 那是一篇很棒的文章。我认为策略模式是最接近回答任何人发布的问题的方法。我特别喜欢它们旨在实现的无状态方式。 @Jordão 的回答谈到了这比该线程上列出的任何模式都更深入,我认为这也适用于策略模式。我的意思是策略模式不受逻辑可能性的限制,而我的概念将功能限制为解释,我认为这是一个更可重用的概念。
    【解决方案5】:

    有趣。好吧,我认为您正在执行 Model->Controller->View(MVC 模式),但在这种情况下,您仅分别使用 Controller 和 Model 部分。

    如果您有多个使用对象的场景,这里的收益是显而易见的,这是典型的 POJO+Managers 做事方式。在这种情况下,对象本身是哑的,除了它们自己的状态之外没有任何功能。

    责任分离的优点是显而易见的,虽然缺点是在经理方面处理得更多。

    如果您考虑一下,如果对象自己不做任何事情,那么您基本上是在削减一个间接级别(基本级别)并且所有东西都必须间接使用。这意味着需要更多代码来处理意外情况、空对象等。也许比需要更多的胶水代码。

    灵活?是的。实际的?只有你能回答。

    【讨论】:

      【解决方案6】:

      这种设计模式可以很好地工作,尤其是在操作不会改变原始数据对象的情况下。例如,在 .NET 中,LINQ 及其相关的扩展方法可以被看作是处理枚举的一般操作,因此枚举本身不需要知道如何使用它们。然而,这些方法不会改变被枚举的集合,而只是提供一种解释、过滤和分组枚举的新方法。

      但是,如果功能正在改变原始对象,那么我倾向于将该功能封装为对象上的方法。这样,对象负责维护自己的不变量,并且您将更多的实现细节与对象的客户端隔离开来。为了使用该对象,您必须泄漏的实现细节越多,它被错误使用并导致错误的机会就越大。

      【讨论】:

      • +1 用于区分改变和不改变原始数据对象的功能。毕竟:观察者通常不会干预数据,它只是观察它。确实想影响数据变化的事情应该告诉不要问。告诉数据类做某事(改变它的值)而不是要求当前值)s_,然后改变其中的一些。洗牌最好留给甲板。
      • @Marjan,当然,由于 Deck 只是 Card 实例的集合,您可以将 Shuffle 视为返回一个恰好包含相同 Card 实例的全新 Deck 的操作。谈论牌组洗牌本身可能没有意义,但您可以交替地将可变牌组和洗牌操作封装到发牌员中,发牌员在不透露他正在处理的牌组的情况下处理 Card 实例。这样其他玩家就不能在庄家不注意的时候乱弄牌组。
      • 我喜欢你的回答,因为它关注对象的可变性,但是在我的模型中,所有数据对象都是不可变的。任何物体的状态都不需要改变它们的位置,因为它们只被观察到,并且在观察中发生了转变。如果一个对象的状态需要改变,例如在一个持久化的地方,持久化的地方应该实现一个观察者(在它自己的参考框架中),它只是用最新的转换替换原来的。
      • @NickLarsen,听起来你会对函数式语言非常感兴趣,比如 Haskell(一种纯函数式语言)或 F#(一种可以访问 .NET 类型的函数式语言。)不可变的观察和转换供其他观察者使用的状态是功能模型的全部意义所在。
      【解决方案7】:

      您所说的是伟大的“哈哈!”之一面向对象的程序员遇到的时刻。发生的时候很有趣。我将从四本“设计模式”一书开始,然后从那里扩展。

      【讨论】:

      • 没有看过这本书的人会知道“四人帮”是什么意思吗?
      • @Robert Harvey:不,他们不会。但他们可以使用谷歌找出答案。
      • 罗伯特:在谷歌搜索时它会有所帮助,因为仅“设计模式”就可以找到 83 个 kerbillion 的点击量。
      • “kerbillion”,讽刺的是,只有 1,490 次点击。这是异质的。
      • 在我研究过的所有设计模式中,四人组是我只听说过而没有学过的一种。感谢您引起我的注意。
      【解决方案8】:

      在 Java 世界中,我们有很好的容器,其中包含无状态会话 bean,可以精确地用于将行为与数据分离,正如 DCI 体系结构所规定的那样。这有时被称为面向服务的编程。

      OOP 通过要求将行为放置在数据所在的类中来限制设计人员,以增加连贯性,而不是让设计人员将相关数据和相关行为放在一起,但不一定也将行为推入那些数据类。

      在一个好的设计中,我们有时在类中有行为,有时我们在高阶类中有行为。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-10-05
        • 1970-01-01
        • 2020-06-07
        • 1970-01-01
        • 2011-06-26
        • 1970-01-01
        • 2011-02-12
        相关资源
        最近更新 更多