- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
我阅读了很多博客,经常遇到的主题之一是担心(至少 Rails 定义它们的方式)正在损害软件。总的来说,我同意——简单地将行为包含到模型中违反了单一责任原则。你最终得到了一个做太多事情的神级。
但与从博客中收集的许多意见一样,很少提供替代架构。
那么让我们举一个示例应用程序,大致基于我必须维护的应用程序。它本质上是一个 CMS,就像许多 Rails 应用程序一样。
目前每个模型都有大量的关注点。让我们在这里使用一些:
class Article < ActiveRecord::Base
include Concerns::Commentable
include Concerns::Flaggable
include Concerns::Publishable
include Concerns::Sluggable
...
end
您可以想象“可评论”只需要在文章中添加少量代码。足以与评论对象建立关系并提供一些实用方法来访问它们。
Flaggable,允许用户标记不当内容,最终会向模型添加一些字段:例如 flagged、flagged_by、flagged_at
。以及一些添加功能的代码。
Sluggable 添加了一个用于在 URL 中引用的 slug 字段。还有一些代码。
Publishable 添加发布日期和状态字段,以及更多代码。
现在如果我们添加一种新的内容会怎样?
class Album < ActiveRecord::Base
include Concerns::Flaggable
include Concerns::Sluggable
include Concerns::Publishable
...
end
专辑有点不同。您不能对它们发表评论,但您仍然可以发布它们并标记它们。
然后我添加更多内容类型:比方说事件和个人资料。
我发现这个架构目前存在一些问题:
flagged_by, published_on
等)那么什么是更好的方法呢?
我看到装饰器被提升为在需要的地方添加功能的一种方式。这可能有助于解决包含代码的问题,但不一定会改进数据库结构。它还看起来不必要地繁琐,并且涉及向代码添加额外的循环来修饰模型数组。
目前我的想法是这样的:
创建一个通用的“内容”模型(和表格):
class Content < ActiveRecord::Base
end
关联表可能很小。它可能应该有某种类型的“类型”字段,并且可能有一些对所有内容都通用的东西——比如 URL 的类型 slug。
然后我们可以为每个行为创建一个关联模型,而不是添加关注点:
class Slug < ActiveRecord::Base
belongs_to :content
...
end
class Flag < ActiveRecord::Base
belongs_to :content
...
end
class Publishing < ActiveRecord::Base
belongs_to :content
...
end
class Album < ActiveRecord::Base
belongs_to :content
...
end
...
这些中的每一个都与一个内容相关联,因此外键可以存在于特征的模型中。所有与该功能相关的行为也可以单独存在于该功能的模型上,这让 OO 纯粹主义者更开心。
为了实现通常需要模型 Hook 的行为(例如 before_create
),我认为观察者模式更有用。 (一旦发送“content_created”事件等,就会创建一个 slug。)
这看起来会无休止地清理一切。我现在可以使用单个查询搜索所有内容,数据库中没有重复的字段名称,也不需要将代码包含到内容模型中。
在我愉快地在我的下一个项目中释放它之前,有没有人尝试过这种方法?行得通吗?或者将事情拆分成这么多最终会创建大量的 SQL 查询、连接和困惑的代码?你能推荐一个更好的选择吗?
最佳答案
关注点基本上只是混合模式的一层薄薄的包装。对于围绕可重用特性编写软件片段而言,这是一种非常有用的模式。
跨多个模型具有相同列的问题通常通过单表继承来解决。然而,STI 仅在模型非常相似时才真正适合。
那么让我们考虑一下您的 CMS 示例。我们有几种不同类型的内容:
Page, NewsArticle, BlogPost, Gallery
它们具有几乎相同的数据库字段:
id
title
content
timestamps
published_at
published_by
# ...
因此我们决定摆脱重复并使用公用表。将其称为 contents
很诱人,但这非常含糊 - content 的内容 ...
因此让我们复制 Drupal 并调用我们的通用类型 Node
。
class Node < ActiveRecord::Base
include Concerns::Publishable
end
但是我们希望每种类型的内容都有不同的逻辑。所以我们为每种类型创建子类:
class Node < ActiveRecord::Base
self.inheritance_column = :type
include Concerns::Publishable
end
class NewsArticle < Node
has_and_belongs_to_many :agencies
end
class Gallery < Node
has_and_belongs_to_many :photos
end
# ...
在 STI 模型之间开始出现太大差异之前,这种方法一直有效。那么数据库模式中的某些重复问题可能比试图将所有内容硬塞到同一个表中所导致的大量复杂问题小得多。大多数基于关系数据库构建的 CMS 系统都在努力解决这个问题。一种解决方案是使用无模式的非关系数据库。
这里面没有任何内容表明关注点需要您存储在模型表中。让我们看看您列出的几个问题:
Flaggable
Sluggable
Commentable
其中每一个都将使用一个表flags
、slugs
、comments
。关键是使与他们标记、标记或注释的对象的关系成为多态的。
comment:
commented_type: string
commented_id: int
slugs:
slugged_type: string
slugged_id: int
flags:
flagged_type: string
flagged_id: int
# ...
class Comment
belongs_to: :commented, polymorphic: true
end
module Concerns::Commentable
# ...
has_many: :comments
end
我建议您查看一些解决此类常见任务的库,例如 FriendlyId , ActsAsTaggedOn等等,看看它们是如何构成的。
并行继承的思想从根本上没有错。并且您应该放弃它只是为了安抚某种极端的 OO 纯度理想的想法是荒谬的。
Traits 是面向对象的一部分,就像任何其他组合技术一样。然而,担忧并不是许多博客文章想让您相信的万能药。
关于ruby-on-rails - 在设计 Rails 数据库模式时如何避免使用关注点?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30956825/
对此感到疯狂,真的缺少一些东西。 我有webpack 4.6.0,webpack-cli ^ 2.1.2,所以是最新的。 在文档(https://webpack.js.org/concepts/mod
object Host "os.google.com" { import "windows" address = "linux.google.com" groups = ["linux"] } obj
每当我安装我的应用程序时,我都可以将数据库从 Assets 文件夹复制到 /data/data/packagename/databases/ .到此为止,应用程序工作得很好。 但 10 或 15 秒后
我在 cc 模式缓冲区中使用 hideshow.el 来折叠我不查看的文件部分。 如果能够在 XML 文档中做到这一点就好了。我使用 emacs 22.2.1 和内置的 sgml-mode 进行 xm
已结束。此问题不符合 Stack Overflow guidelines .它目前不接受答案。 我们不允许提出有关书籍、工具、软件库等方面的建议的问题。您可以编辑问题,以便用事实和引用来回答它。 关闭
根据java: public Scanner useDelimiter(String pattern) Sets this scanner's delimiting pattern to a patt
我读过一些关于 PRG 模式以及它如何防止用户重新提交表单的文章。比如this post有一张不错的图: 我能理解为什么在收到 2xx 后用户刷新页面时不会发生表单提交。但我仍然想知道: (1) 如果
看看下面的图片,您可能会清楚地看到这一点。 那么如何在带有其他一些 View 的简单屏幕中实现没有任何弹出/对话框/模式的微调器日期选择器? 我在整个网络上进行了谷歌搜索,但没有找到与之相关的任何合适
我不知道该怎么做,我一直遇到问题。 以下是代码: rows = int(input()) for i in range(1,rows): for j in range(1,i+1):
我想为重写创建一个正则表达式。 将所有请求重写为 index.php(不需要匹配),它不是以/api 开头,或者不是以('.html',或'.js'或'.css'或'.png'结束) 我的例子还是这样
MVC模式代表 Model-View-Controller(模型-视图-控制器) 模式 MVC模式用于应用程序的分层开发 Model(模型) - 模型代表一个存取数据的对象或 JAVA PO
我想为组织模式创建一个 RDF 模式世界。您可能知道,组织模式文档基于层次结构大纲,其中标题是主要的分组实体。 * March auxiliary :PROPERTIES: :HLEVEL: 1 :E
我正在编写一个可以从文件中读取 JSON 数据的软件。该文件包含“person”——一个值为对象数组的对象。我打算使用 JSON 模式验证库来验证内容,而不是自己编写代码。符合代表以下数据的 JSON
假设我有 4 张 table 人 公司 团体 和 账单 现在bills/persons和bills/companys和bills/groups之间是多对多的关系。 我看到了 4 种可能的 sql 模式
假设您有这样的文档: doc1: id:1 text: ... references: Journal1, 2013, pag 123 references: Journal2, 2014,
我有这个架构。它检查评论,目前工作正常。 var schema = { id: '', type: 'object', additionalProperties: false, pro
这可能很简单,但有人可以解释为什么以下模式匹配不明智吗?它说其他规则,例如1, 0, _ 永远不会匹配。 let matchTest(n : int) = let ran = new Rand
我有以下选择序列作为 XML 模式的一部分。理想情况下,我想要一个序列: 来自 my:namespace 的元素必须严格解析。 来自任何其他命名空间的元素,不包括 ##targetNamespace和
我希望编写一个 json 模式来涵盖这个(简化的)示例 { "errorMessage": "", "nbRunningQueries": 0, "isError": Fals
首先,我是 f# 的新手,所以也许答案很明显,但我没有看到。所以我有一些带有 id 和值的元组。我知道我正在寻找的 id,我想从我传入的三个元组中选择正确的元组。我打算用两个 match 语句来做到这
我是一名优秀的程序员,十分优秀!