何时使用集合而不是多个属性

5

我正在为一个ASP.NET应用程序构建一个相对简单的类(例如:Person),其中有几个相关的布尔属性(例如:Certifications)。我可以将它们存储为多个属性:

Class Person
    Property HasCertL1
    Property HasCertL2
    Property HasCertL3
End Class

或者使用集合和枚举:

Enum Cert
    L1
    L2
    L3
End Enum

Class Person
    Property Certs as List(Of Cert)
End Class

这个 class 不会在其他地方使用,而且 Certs(证书)的项在没有重新编译 class 的情况下也不会被更新。我正在寻找选择哪个的原因。有人能提供任何最佳实践或指向一些我可能错过的涵盖此内容的资源吗?谢谢!

谢谢, JE

4个回答

3
我的建议是使用集合。原因如下:当您设置类并为每个认证级别设置属性时,如果要添加认证级别,则必须添加额外的代码以检查人员是否具有该额外级别。这可能需要更改使用Person的每个其他类,而如果您使用列表,Person类不会改变;只需更改列表的可用值即可。这样需要更少的代码更改来检查特定的认证级别。

这是我的直觉,但实现起来比较困难,因为我有一个带有复选框的表单,所以 Person.HasCertL1 = CertL1Box.Checked 比 If Cert1Box.Checked Then Person.Certs.Add(Certs.CertL1) 更干净,尤其在尝试删除它们时。不过,我会使用您的建议,因为您永远不知道何时需要重用某些东西! :) - Jens Ehrich
软件设计的乐趣在于,你永远无法编写与其依赖和从属完全解耦的代码。你所能做的是针对某些可能发生变化的情况,并确保在这些情况下外部代码需要最小或不需要任何更改。 - KeithS

2

我认为,始终关注应用程序的可扩展性是一个好方法。考虑到这一点,我会选择集合方法。但是从你的问题中并不清楚是否只有三个证书,还是有更多。

如果你只谈论更新证书并重新编译,那么在这种情况下似乎存在扩展选项。


我把它给了@KeithS,因为他解释了它会如何影响其他类。 - Jens Ehrich

1

为什么需要"集合和枚举"? 使用带有 "Flags" 属性的 Bit Field 枚举存储 "个人" 拥有的 "证书" 有什么问题吗?这实际上只是选项1的更清晰版本。

根据您的评论,以下是一个非常简单的示例:


class Person
{
    public string Name;
    public Certifications Certifications;
}

[Flags]
enum Certifications : int
{
    A = 1,
    B = 2,
    C = 4,
    D = 8,
    E = 16,
    F = 32,
    G = 64,
    H = 128,
    I = 256
};

static void Main()
{
    Person a = new Person() { Name = "rob", Certifications =  Certifications.A | Certifications.D | Certifications.G };
    Person b = new Person() { Name = "jeb", Certifications = Certifications.B | Certifications.C | Certifications.I };

    // Easy way using [Flags] ToString() override.
    DisplayPerson(a);
    DisplayPerson(b);

    Console.WriteLine();

    // Traditional Way
    DisplayPersonInfo( a );
    DisplayPersonInfo( b );
}

static IEnumerable<string> GetCerts( Person person )
{
    foreach( Certifications cert in Enum.GetValues( typeof( Certifications ) ) )
    {
        if( PersonHasCert( person, cert ) )
            yield return ( Enum.GetName( typeof( Certifications ), cert ) );
    }
}

static bool PersonHasCert( Person person, Certifications cert )
{
    return ( ( cert & person.Certifications ) == cert );
}

static void DisplayPersonInfo( Person p )
{
    Console.WriteLine
    (
        String.Format
        (
            "Name: {0}, Certifications: {1}", 
            p.Name, 
            String.Join( ", ", GetCerts( p ) )
        )   
    );
}

static void DisplayPerson(Person p)
{
    Console.WriteLine
    (
        String.Format
        (
            "Name: {0}, Certifications: {1}",
            p.Name,
            p.Certifications
        )
    );
}

输出:

姓名:rob,认证:A、D、G
姓名:jeb,认证:B、C、I

姓名:rob,认证:A、D、G
姓名:jeb,认证:B、C、I

上述“[Flags]”属性帮助编译器知道您正在使用此数据结构并显示它包含的值(在具有该属性的“Enum”的ToString()方法上使用),因此当您想要显示所有值时,您无需迭代枚举并对每个值进行位测试)。

您对使用此方法还有什么疑虑吗?它似乎非常高效和干净,完全符合您想要实现的内容。

this是一篇涵盖该主题的很好的文章。

编辑2:

更新了示例代码,包括使用两种方式实现此功能以及一些帮助程序中的辅助方法,您应该可以在应用程序中利用它们。


哇,我没想到 flags 属性会让它变得这么简单。我一定会尝试的!谢谢 Brandon。 - Jens Ehrich
我当然看到使用位域的好处,但我仍然无法理解如何使用它编写一个干净的 For Each 循环。我理解你展示的示例是如何工作的,但如果我想做一些其他的事情而不是打印(例如:For Each Cert in Certs; Cert.Validate; Next Cert),我该怎么办?你有任何示例可以提供吗?再次感谢! - Jens Ehrich
再次感谢Brandon。我总是喜欢学习新知识,这肯定会改变我未来构建对象的方式。干杯! - Jens Ehrich

1

我的观点是,这绝对取决于您将如何使用这些属性。对于任何用例,通常可以说,使用对象作为集合或多个属性集更方便。这应该驱动您的选择,尽管将几个属性转换为集合或将集合拆分回项目并不那么困难。

与无法知道集合可能有多少项的情况相比,您的情况(您知道您有Cert1、Cert2和Cert3,其他什么都不重要)非常罕见,因此大多数时候具有有限数量的属性不是一个选项。在您的情况下,这可能是一种选择。单独的属性可以让您以更强类型的方式存储对象。

如果您不知道选择哪个选项,我建议同时选择两个选项。实现一个Person类,它将具有集合属性和每个项目的单独属性。内部实现并不重要,只有接口在这里很重要。然后,您将能够使用来自其他类和用法的任何内容,这样使用可能会更清楚地说明应该如何实现。示例实现(它没有完全实现集合部分,因为您无法向其中添加项目):

class Person
{
    public Cert1 Cert1 { get; set; }
    public Cert2 Cert2 { get; set; }
    public Cert3 Cert3 { get; set; }

    public IEnumerable<Cert> AllCertificates
    {
        get
        {
            return new Cert[] {Cert1, Cert2, Cert3}
                        .Where(c => c != null)
                        .ToArray();
        }
    }
}

我喜欢这个想法,将来可能会在更大的项目中使用它。 - Jens Ehrich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接