我读过许多博客,经常遇到的主题之一是担忧(至少是Rails定义它们的方式)正在损害软件。总的来说,我同意-将行为纳入模型只是违反了单一责任原则。您最终会做得太多。
但是,正如从博客中收集到的许多意见一样,很少提供替代架构。
因此,让我们以一个示例应用程序为例,基于我必须维护的一个应用程序。正如许多Rails应用程序倾向于的那样,它本质上是CMS。
当前,每个模型都有很多问题。让我们在这里使用一些:
class Article < ActiveRecord::Base
include Concerns::Commentable
include Concerns::Flaggable
include Concerns::Publishable
include Concerns::Sluggable
...
end
您可以想象,“可注释”仅需要向文章中添加少量代码。足以与注释对象建立关系并提供一些实用的方法来访问它们。
可标记性允许用户标记不适当的内容,最终在模型中添加了一些字段:flagged, flagged_by, flagged_at
例如。以及一些添加功能的代码。
Sluggable会添加一个Slug字段,以在URL中进行引用。还有更多代码。
可发布可添加发布日期和状态字段,以及更多代码。
现在,如果我们添加一种新的内容会怎样?
class Album < ActiveRecord::Base
include Concerns::Flaggable
include Concerns::Sluggable
include Concerns::Publishable
...
end
相册有些不同。您无法对它们发表评论,但仍可以发布它们并对其进行标记。
然后,我添加其他一些内容类型:假设是事件和个人资料。
我看到这种体系结构存在一些问题:
flagged_by, published_on
等)的数据库表那么有什么更好的方法呢?
我已经看到装饰器被提升为在需要的地方添加功能的一种方式。这可以帮助解决所包含的代码的问题,但是数据库结构不一定得到改善。它看起来也很多余,涉及到在代码中添加额外的循环来装饰模型数组。
到目前为止,我的想法是这样的:
创建一个通用的“内容”模型(和表):
class Content < ActiveRecord::Base
end
关联的表可能很小。它可能应该具有某种“类型”字段,并且可能具有绝对所有内容共有的一些东西,例如可能是URL的类型标记。
然后,我们可以为每个行为创建一个关联的模型,而不是添加关注点:
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纯粹主义者更加快乐。
为了实现通常需要模型挂钩的行为,before_create
例如,我可以看到观察者模式更有用。(一旦发送了一个“ content_created”事件,就会创建一个段,等等。)
看起来它将清理一切都没有尽头。现在,我可以通过一个查询来搜索所有内容,数据库中没有重复的字段名称,也不需要在内容模型中包含代码。
在我愉快地将其发布到下一个项目之前,是否有人尝试过这种方法?能行吗?还是将事情分解成这么多最终会导致SQL查询,联接和纠结的代码陷入困境?你能建议一个更好的选择吗?
基本上,顾虑只是围绕mixin模式的薄包装。这是一种围绕可重用特征组成软件的非常有用的模式。
在多个模型中具有相同列的问题通常可以通过“单表继承”解决。但是,STI仅在模型非常相似时才真正适用。
因此,让我们考虑一下您的CMS示例。我们有几种不同类型的内容:
Page, NewsArticle, BlogPost, Gallery
具有几乎相同的数据库字段:
id
title
content
timestamps
published_at
published_by
# ...
因此,我们决定摆脱重复并使用公用表。调用它会很诱人,contents
但这是非常模棱两可的-内容的内容...
因此,让我们复制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纯度理想的想法是荒谬的。
特质是其他任何构图技术中面向对象的一部分。但是,担忧并不是许多博客文章都相信的魔咒。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句