这是实现ITaggable功能的合适方法吗?

本文关键字:方法 实现 ITaggable 功能 | 更新日期: 2023-09-27 18:12:44

我正在考虑为我所承担的项目添加可标记功能。该项目是3层的(mvc3 - Domain - Repositories)。

我需要在这个系统中添加标记各种对象的能力。因为标签可以附加到许多不同的聚合根,我认为最好有标签作为自己的根(rep/ITagManager in domain)。

我的想法是有一个ITaggable界面,类似于:

public interface ITaggable
{
    bool SaveTags(IList<ITag> _tags);
    bool SaveTag(ITag _tag);
    IList<ITag> GetTags();
    bool HasTag(IList<ITag> _tags);
    bool HasTag(ITag _tag);
    bool HasTag(string _tagName);
}

我有一个想法,有一个ITagManager,它有方法采取ITaggable对象和保存/加载标签附加到他们(也许使用类似String.Concat(typeof(this), this.ID)来生成一个唯一的ID为对象实现ITaggable接口)。现在有两种可能的路径,首先将任何实现ITaggable接口的对象传递到ITagManager本身,或者将ITaggable接口修改为如下内容:

public interface ITaggable
{
    bool SaveTags(IList<ITag> _tags, ITagManager _tagManager);
    bool SaveTag(ITag _tag, ITagManager _tagManager);
    IList<ITag> GetTags(ITagManager _tagManager);
    bool HasTag(IList<ITag> _tags, ITagManager _tagManager);
    bool HasTag(ITag _tag, ITagManager _tagManager);
    bool HasTag(string _tagName, ITagManager _tagManager);
}

第一种解决方案可能有贫血模型的味道,但第二种解决方案似乎很混乱。我知道依赖关系可以被注入,但我认为将它作为函数参数将使发生的事情变得明显。还是把它注入到对象中更好?

这些解决方案是否合适?

这是实现ITaggable功能的合适方法吗?

我不认为你的'ITagable'界面需要如此臃肿。此外,我不会将标签建模为聚合根,因为标签本身没有任何意义。

下面是我们使用的接口:
public interface ITagable
{
    IEnumerable<Tag> Tags { get; }
    void Tag(Tag tag);
    void Untag(Tag tag);
}

如果需要,接口上的许多方法可以很容易地实现为扩展方法。

有时候你不能处理域对象中的所有逻辑。这就是域服务有用的地方,也是我们用来处理'ITagable'实体上标签的"处理"的地方:

public interface ITagService
{
    void ProcessTags<TEntity>(TEntity entity, Func<IEnumerable<Tag>> featureTags, 
        string tagString) where TEntity : ITagable;
}

注意我们传入了'featureTags'列表。在博客示例中,这将是整个博客的标签列表,因为我们不想创建重复的标签。因此,'TagService'没有做任何类型的持久化,它只是在需要时创建新标签(在博客上),并在需要时对实体进行标记或取消标记:

public class TagService : ITagService
{      
    public void ProcessTags<TEntity>(TEntity entity, 
        Func<IEnumerable<Tag>> featureTagsFactory, string tagString) where TEntity : ITagable
    {
        var result = new List<Tag>();
        // remove any leading/trailing spaces
        tagString = tagString.Trim();
        if (tagString.IsNotNullOrEmpty())
        {
            result.AddRange(from str in tagString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Where(t => t.Length > 1).Distinct()
                            join tag in featureTagsFactory() on (Slug)str equals tag.Slug into tags
                            from tag in tags.DefaultIfEmpty()
                            select tag ?? new Tag(str.Trim()));
        }
        // merge tags
        foreach (var tag in entity.Tags.Except(result)) // remove unmatched tags
        {
            entity.Untag(tag);
        }
        foreach (var tag in result) // entity should check if already added
        {
            entity.Tag(tag);
        }
    }
}

当我们更新一个可标记实体(通常从UI层传递一个逗号分隔的标签列表)时,我们做以下操作:

// tags
if (command.TagString.IsNotNullOrEmpty())
{
    tagService.ProcessTags(post, () => blog.Tags, command.TagString);
}

在我的例子中,我们将所有标签映射到父博客对象。您可能无法直接复制粘贴这些代码,但它应该让您了解如何处理实体上的标签。

为什么不让标签有自己的边界上下文呢?我怀疑标签是否需要与各自聚合上的任何其他行为保持一致。