【问题标题】:Classes with virtually common code具有几乎通用代码的类
【发布时间】:2017-02-27 11:08:50
【问题描述】:

我有许多自定义集合类。每个都用于提供各种自定义类型的集合 - 一个自定义类型到一个自定义集合。自定义集合继承 List<T> [这里的 T 是特定的自定义类型,而不是泛型] 并提供一些额外的功能。

我之前取消了自定义集合,并在其他地方使用了自定义方法,但我在扩展代码时发现我需要使用自己的方法的集合。

一切正常,一切都很愉快。但这让我很恼火,因为我知道我做得不好。问题是每个类都使用几乎相同的代码,只改变类型和参数,所以我觉得它可以实现为抽象类,或泛型,或 List 的扩展,或者......但我没有真正理解足够的差异或如何解决这些差异,无法理清我需要什么。

这是我的几个系列中的两个,以便您了解:

    // JourneyPatterns
public class JourneyPatterns : List<JourneyPattern>
{
    private Dictionary<string, JourneyPattern> jpHashes;       // This is a hash table for quick lookup of a JP based on its values

    /* Add a journey pattern to the JourneyPatterns collection. Three methods for adding:
         1. "Insert Before" (=at) a particular point in the list. This is the method used by all three methods.
         2. "Insert After" a particular point in the list. This is "before" shifted by 1 e.g. "after 6" is "before 7"
         3. "Append" to the end of the list. This is "before" with a value equal to the list count, and is the same as inherited "Add", but with checks
    */
    public JourneyPattern InsertBefore(JourneyPattern JP, int before)
    {
        // check for a pre-existing JP with the same parameters (ignore ID). Do this by constructing a "key" based on the values to check against
        // and looking it up in the private hash dictionary
        JourneyPattern existingJP;
        if (jpHashes.TryGetValue(JP.hash, out existingJP)) { return existingJP; }
        else
        {
            // construct a new ID for this JP
            if (string.IsNullOrWhiteSpace(JP.id)) JP.id = "JP_" + (Count + 1).ToString();
            // next check that the ID specified isn't already being used by a different JPS
            if (Exists(a => a.id == JP.id)) JP.id = "JP_" + (Count + 1).ToString();
            // now do the add/insert
            if (before < 0) { Insert(0, JP); } else if (before >= Count) { Add(JP); } else { Insert(before, JP); }
            // finally add to the hash table for fast compare / lookup
            jpHashes.Add(JP.hash, JP);
            return JP;
        }
    }
    public JourneyPattern InsertAfter(JourneyPattern JP, int after) { return InsertBefore(JP, after + 1); }
    public JourneyPattern Append(JourneyPattern JP) { return InsertBefore(JP, Count); }
}

// JourneyPatternSections
public class JourneyPatternSections : List<JourneyPatternSection>
{
    private Dictionary<string, JourneyPatternSection> jpsHashes;       // This is a hash table for quick lookup of a JPS based on its values

    /* Add a journey pattern section to the journeyPatternSections collection. Three methods for adding:
         1. "Insert Before" (=at) a particular point in the list. This is the method used by all three methods.
         2. "Insert After" a particular point in the list. This is "before" shifted by 1 e.g. "after 6" is "before 7"
         3. "Append" to the end of the list. This is "before" with a value equal to the list count, and is the same as inherited "Add", but with checks
    */
    public JourneyPatternSection InsertBefore(JourneyPatternSection JPS, int before)
    {
        // check for a pre-existing JPS with the same parameters (ignore ID). Do this by constructing a "key" based on the values to check against
        // and looking it up in the private hash dictionary
        JourneyPatternSection existingJPS;
        if (jpsHashes.TryGetValue(JPS.hash, out existingJPS)) { return existingJPS; }
        else
        {
            // construct a new ID for this JPS
            if (string.IsNullOrWhiteSpace(JPS.id)) JPS.id = "JPS_" + (Count + 1).ToString();
            // next check that the ID specified isn't already being used by a different JPS
            if (Exists(a => a.id == JPS.id)) JPS.id = "JPS_" + (Count + 1).ToString();
            // now do the add/insert
            if (before < 0) { Insert(0, JPS); } else if (before >= Count) { Add(JPS); } else { Insert(before, JPS); }
            // finally add to the hash table for fast compare / lookup
            jpsHashes.Add(JPS.hash, JPS);
            return JPS;
        }
    }
    public JourneyPatternSection InsertAfter(JourneyPatternSection JPS, int after) { return InsertBefore(JPS, after + 1); }
    public JourneyPatternSection Append(JourneyPatternSection JPS) { return InsertBefore(JPS, Count); }
}

如您所见,不同之处在于类型(JourneyPattern 或 JourneyPatternSection),以及我用于该类型的“id”属性的前缀(“JP_”或“JPS_”)。其他一切都很常见,因为确定“唯一性”(属性“哈希”)的方法是自定义类型的一部分。

我的一些自定义集合需要更多涉及这些方法的不同实现,这很好,但这是最常见的一种,到目前为止我已经实现了大约 6 次,这似乎 a) 毫无意义,b) 更难维护。

感谢您的想法和帮助!

【问题讨论】:

  • 让你的类实现一些包含属性“id”和“hash”的接口,并将类型限制为该接口(class YourCollection : List where T: IYourInterface)。
  • 好的,我知道它是如何工作的。大概我还需要自定义类型来实现接口中的另一个属性,例如"id_pfx" 以便集合知道为 id 使用什么前缀
  • 是的,或者让集合本身存储前缀,如下面的答案所示。

标签: c# class generics collections


【解决方案1】:

假设JourneyPatternJourneyPatternSection 实现了一个共同的interface,例如:

public interface IJourney
{
    string hash { get; set; }
    string id { get; set; }
}

您可以为您的集合实现一个基类:

public abstract class SpecializedList<T> : List<T> where T : class, IJourney
{
    private Dictionary<string, T> jpHashes;       // This is a hash table for quick lookup of a JP based on its values

    protected abstract string IdPrefix { get; }

    /* Add a journey pattern to the JourneyPatterns collection. Three methods for adding:
            1. "Insert Before" (=at) a particular point in the list. This is the method used by all three methods.
            2. "Insert After" a particular point in the list. This is "before" shifted by 1 e.g. "after 6" is "before 7"
            3. "Append" to the end of the list. This is "before" with a value equal to the list count, and is the same as inherited "Add", but with checks
    */
    public T InsertBefore(T JP, int before)
    {
        // check for a pre-existing JP with the same parameters (ignore ID). Do this by constructing a "key" based on the values to check against
        // and looking it up in the private hash dictionary
        T existingJP;
        if (jpHashes.TryGetValue(JP.hash, out existingJP)) { return existingJP; }
        else
        {
            // construct a new ID for this JP
            if (string.IsNullOrWhiteSpace(JP.id)) JP.id = "JP_" + (Count + 1).ToString();
            // next check that the ID specified isn't already being used by a different JPS
            if (Exists(a => a.id == JP.id)) JP.id = IdPrefix + (Count + 1).ToString();
            // now do the add/insert
            if (before < 0) { Insert(0, JP); } else if (before >= Count) { Add(JP); } else { Insert(before, JP); }
            // finally add to the hash table for fast compare / lookup
            jpHashes.Add(JP.hash, JP);
            return JP;
        }
    }
    public T InsertAfter(T JP, int after) { return InsertBefore(JP, after + 1); }
    public T Append(T JP) { return InsertBefore(JP, Count); }
}

然后实现每个集合:

public class JourneyPatterns : SpecializedList<JourneyPattern>
{
    protected override string IdPrefix => "JP_";
}

public class JourneyPatternSections : SpecializedList<JourneyPatternSection>
{
    protected override string IdPrefix => "JPS_";
}

【讨论】:

  • 很遗憾没有。 JourneyPatterns 包含 JourneyPatternSections(又包含 JourneyPatternTimingLinks),但没有共同的祖先。但是谢谢 - 巧妙的解决方案,否则。
  • 您可以使用interface 代替共同的祖先。
  • 啊哈!感谢您显示interface 实现的编辑。这太棒了——正是我所需要的,并且给了我一个很好的例子来说明接口和抽象的使用。
猜你喜欢
  • 2022-12-14
  • 1970-01-01
  • 2016-03-02
  • 1970-01-01
  • 2012-02-18
  • 2022-07-11
  • 1970-01-01
  • 2023-03-27
  • 1970-01-01
相关资源
最近更新 更多