深度嵌套字典是反模式吗?

Posted

技术标签:

【中文标题】深度嵌套字典是反模式吗?【英文标题】:Is a deep nested Dictionary an antipattern? 【发布时间】:2012-03-07 09:54:49 【问题描述】:

我有一个结构可以很容易地使用三层嵌套字典来表示,就像这样

private static Dictionary<string, Dictionary<string, Dictionary<string,string>>> PrerenderedTemplates;

这种结构可能会用到这样的地方

PrerenderedTemplates[instanceID][templategroup][templatepart]

现在,我意识到这段代码很难阅读,因为通过查看定义语句,您无法判断它的用途。将其更改为Dictionary&lt;string, PrerenderedTemplate&gt; 时我真正看到的唯一优势是可读性。将每个嵌套转换为自己的类(例如class PrerenderedTemplate class TemplateGroup class TemplatePart)将添加更多代码行,但计算优势很小(如果有的话)。据我所知。

那么,我的方法“可以”还是应该加倍努力并创建单独的课程? 是否可以在文档/cmets 中介绍嵌套 Dictionary 的工作原理 有处理这种嵌套的最佳做法吗? 请记住,这是一个私有成员,对于使用该类的人来说,它不需要直截了当。

更新

因此,受 Reza 的启发,但无法使用元组,我决定创建自己的密钥生成器并像这样实现他的模式:

private Dictionary<string, string> PrerenderedTemplates;
private string GetPrerenderedTemplateKey(string InstanceId, string FeatureId, string OptionId)

    return new StringBuilder(instanceId)
    .Append(FormatTools.LIST_ENTRY_DELIMITER)
    .Append(templategroup)
    .Append(FormatTools.LIST_ENTRY_DELIMITER)
    .Append(templatepart).ToString();

其中FormatTools.LIST_ENTRY_DELIMITER 是Unicode 专用字符0xe04d

【问题讨论】:

由于前两个嵌套本质上只是标识符,也许我可以使用简单的 Dictionary。所以PrerenderedTemplates["instance1"]["fruit"]["banana"] 可以只表示为PrerenderedTemplates["instance1_fruit_banana"],就像命名空间一样。 您是否需要能够使用PrerenderedTemplates 来列出您的模板组或模板部分?有点像PrerenderedTemplates[instanceID].KeysPrerenderedTemplates[instanceID][templateGroup]?如果是这样,那么这可能是处理它的最简单方法。 @M.Babcock,好吧,我正在循环一个对象集合,这些对象包含指向如何呈现模板的元数据。在渲染该模板之前,我想检查我的字典以确保它之前没有被渲染过。如果没有,我会渲染它并将结果添加到我的字典中。 (模板变更管理在别处处理) @Iain Fraser 我喜欢您的第一个选项,其中包含您不允许在键语言中作为分隔符的特殊字符。我过去做过,它对我有用。 类似***.com/questions/11908991/…, ***.com/questions/955982/… 【参考方案1】:

我提供另一种选择:

Dictionary<Tuple<string, string, string>, string> pt;

访问字典:

pt[Tuple.Create("id","group","part")]

更新

C# 7 中引入的

Value Tuples 最引人注目:

Dictionary<(string id, string group, string part), string> pt;

访问字典:

pt[("id", "group", "part")]

【讨论】:

很好,我一定会试一试,让你知道结果如何! 我不懂你的意思!! 对不起,Reza,我告诉过你我喜欢你的方法,我会尝试一下。我应该尽量不要使用这么多的俚语。 我应该指定我使用的是 C# v3.5,而 Tuple 仅在 C# v4.0 中引入。我也很想用你的模式:( 没有什么能阻止你将类似的类用作键 - 实现 Equals 和 GetHashCode ,你就可以开始了(添加其他东西,如 == 会很好,但可选,即示例***.com/questions/569903/multi-value-dictionary)。将键连接为字符串的更简洁的解决方案,也有点复杂。【参考方案2】:

我会创建一个自定义字典。像这样的

public class TrippleKeyDict

    private const string Separator = "<|>";
    private Dictionary<string, string> _dict = new Dictionary<string, string>();

    public string this[string key1, string key2, string key3]
    
        get  return _dict[GetKey(key1, key2, key3)]; 
        set  _dict[GetKey(key1, key2, key3)] = value; 
    

    public void Add(string key1, string key2, string key3, string value)
    
        _dict.Add(GetKey(key1, key2, key3), value);
    

    public bool TryGetValue(string key1, string key2, string key3, out string result)
    
        return _dict.TryGetValue(GetKey(key1, key2, key3), out result);
    

    private static string GetKey(string key1, string key2, string key3)
    
        return String.Concat(key1, Separator, key2, Separator, key3);
    

如果您认为连接字符串不够安全,因为键可能包含分隔符,那么请使用您自己的键类型或 Touple&lt;string,string,string&gt; 作为键。由于此实现细节隐藏在您的自定义字典中,您可以随时更改它。

你可以像这样使用字典

var dict = new TrippleKeyDict();

// Using the Add method
dict.Add(instanceID, templategroup, templatepart, "some value");

// Using the indexer
dict[instanceID, templategroup, templatepart] = "xy";
string result = dict[instanceID, templategroup, templatepart];

// Using the TryGetValue method
if (dict.TryGetValue(instanceID, templategroup, templatepart, out result)) 
    // Do something with result

【讨论】:

我明白你的意思。 hello_world &gt; hooray &gt; stuffhello &gt; world_hooray &gt; stuff 会发生冲突。两者都将使用密钥“hello_world_hooray_stuff”... 我使用"|" 作为分隔符。你可以使用另一个你知道它从未在你的密钥中使用过的,比如"&lt;|&gt;" 我正在构建的框架使用一个非常晦涩的 unicode 字符作为分隔符。敢说我会用! :) 哇,今天学到了:en.wikipedia.org/wiki/… 我不能使用 touples,因为我使用 .NET 3.5,但是将字典定义为 Dictionary&lt;Touple&lt;string,string,string&gt;,string&gt;,正如 Reza Arab 所展示的那样,并将其合并到 TrippleKeyDict 中应该不会太难。 【参考方案3】:

我想提供一种替代方法,使用 SortedDictionary 和自定义比较器:

    public class PrerenderedTemplate
    
        public string instanceID;
        public string templategroup;
        public string templatepart;

        public PrerenderedTemplate(string id, string tempGroup, string tempPart)
        
            instanceID = id;
            templategroup = tempGroup;
            templatepart = tempPart;
        

        // custom comparer instance used as argument 
        // to SortedDictionary constructor
        public class Comparer : IComparer<PrerenderedTemplate>
        
            public int Compare(PrerenderedTemplate x, PrerenderedTemplate y)
            
                int compare = 0;
                if (compare == 0) compare = x.instanceID.CompareTo(y.instanceID);
                if (compare == 0) compare = x.templategroup.CompareTo(y.templategroup);
                if (compare == 0) compare = x.templatepart.CompareTo(y.templatepart);
                return compare;
            
        
    

这样使用:

    var dictionary = new SortedDictionary<PrerenderedTemplate, string>(new PrerenderedTemplate.Comparer());

    dictionary.Add(new PrerenderedTemplate("1", "2", "3"), "123");
    dictionary.Add(new PrerenderedTemplate("4", "5", "6"), "456");
    dictionary.Add(new PrerenderedTemplate("7", "8", "9"), "789");

    Assert.AreEqual<string>(dictionary[new PrerenderedTemplate("7", "8", "9")], "789");

RezaArab 的回答是符合目的的,但我个人不喜欢元组,因为它们具有模棱两可的属性和冗长的语法。

如果任何需求发生变化,带有比较器的自定义类会提供更高的清晰度和灵活性。

【讨论】:

以上是关于深度嵌套字典是反模式吗?的主要内容,如果未能解决你的问题,请参考以下文章

INTERPRETER 是反模式吗?

像isInUnitTest()这样的检查是反模式吗?

什么是反模式?

馄饨代码 - 为啥是反模式? [关闭]

为啥 Singleton 被认为是反模式? [复制]

关于“贫血领域模型”被认为是反模式[闭合]的具体例子