c# - lista - ¿Es un diccionario anidado profundo un antipatrón?
antipatrones del arquitecto de software (3)
Tengo una estructura que se puede representar muy fácilmente usando un diccionario anidado de tres profundidades, como
private static Dictionary<string, Dictionary<string, Dictionary<string,string>>> PrerenderedTemplates;
Donde la estructura podría ser utilizada algo como esto.
PrerenderedTemplates[instanceID][templategroup][templatepart]
Ahora, me doy cuenta de que este código es difícil de leer, porque al observar la definición de definición, no se puede saber para qué se usa. La única ventaja que realmente puedo ver al cambiarlo a Dictionary<string, PrerenderedTemplate>
es la legibilidad. Convertir cada agrupamiento en su propia clase (por ejemplo, class PrerenderedTemplate{} class TemplateGroup{} class TemplatePart{}
) agregaría muchas más líneas de código para obtener una pequeña ventaja computacional. Por lo que puedo ver.
- Entonces, ¿mi enfoque es "correcto" o debo ir más allá y crear clases separadas?
- ¿Está bien cubrir cómo funciona el
Dictionary
anidado en la documentación / comentarios? - ¿Existe una mejor práctica para manejar este tipo de anidación?
- Tenga en cuenta que este es un miembro privado, no es necesario que sea sencillo para las personas que usan la clase.
Actualizar
Así que, inspirado por Reza, pero sin poder utilizar Tuples, decidí crear mi propio generador de claves e implementar su patrón de esta manera:
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();
}
Donde FormatTools.LIST_ENTRY_DELIMITER
es el carácter de uso privado de Unicode 0xe04d
.
Me gustaría crear un diccionario personalizado. Algo como esto
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);
}
}
Si piensa, concatenar las cadenas no es lo suficientemente seguro, ya que las claves podrían contener los separadores, luego use su propio tipo de clave o una Touple<string,string,string>
como clave. Dado que este detalle de implementación está oculto dentro de su diccionario personalizado, puede cambiarlo en cualquier momento.
Puedes usar el diccionario como este
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
}
Me gustaría ofrecer un enfoque alternativo, utilizando un SortedDictionary y un comparador personalizado:
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;
}
}
}
Se usa como tal:
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");
La respuesta de RezaArab es apta para un propósito, pero personalmente no me gustan las Tuplas por sus propiedades ambiguas y su sintaxis detallada.
Una clase personalizada con comparador ofrece más claridad y también flexibilidad, en caso de que cambien los requisitos.
Ofrezco otra opción:
Dictionary<Tuple<string, string, string>, string> pt;
Acceso al diccionario:
pt[Tuple.Create("id","group","part")]
ACTUALIZACIÓN :
Las tuplas de valor introducidas en C # 7 son las más llamativas:
Dictionary<(string id, string group, string part), string> pt;
Acceso al diccionario:
pt[("id", "group", "part")]