(OOP语言中)是否存在可空属性或允许属性为空的代码气味?

时间:2018-12-18 00:58:30

标签: c# oop inheritance design-patterns

我经常看到class喜欢

public class Person
{
    string FirstName { get; set; }

    string MiddleName { get; set; }

    string LastName { get; set; }

    int Age { get; set; }
}

其中一个属性(在此示例中为MiddleName(因为某些人出生时没有使用中间名))被允许为null。我认为这是错误的,因为该类的所有使用者都必须知道属性“可能”为null并执行

这样的检查
public void PrintName(Person p)
{
    Console.WriteLine(p.FirstName);

    if (p.MiddleName != null)
    {
        Console.WriteLine(p.MiddleName);
    }

    Console.WriteLine(p.LastName);
}

我如何通过使用像这样的组合

public interface IPerson
{
    string FirstName { get; }

    string LastName { get; }

    int Age { get; }
}

public interface IMiddleNamed
{
    string MiddleName { get; }
}

public void PrintName(IPerson p)
{
    Console.WriteLine(p.FirstName);

    if (p is IMiddleNamed)
    {
        Console.WriteLine((p as IMiddleNamed).MiddleName);
    }

    Console.WriteLine(p.LastName);
}

即使我的模型代表数据合同(例如通过服务间通信进行序列化/反序列化的“价值袋”),情况也是如此。

以同样的方式避免使用具有null-值类型的类,例如

public class Something
{
    public int SomeInt { get; set; }

    public double? SomeDouble { get; }
}

出于同样的原因。

但是我想知道这是否像“ OOP过度设计”,因为它显然增加了很多开发时间。

2 个答案:

答案 0 :(得分:1)

我看到当前的答案是关于String的(例如c#中的字符串类处理它),但是我想您的问题涵盖了所有可能的可为空的对象。

1)首先,关于?运营商在另一个答案中建议: 它将使您的代码可以与空对象一起使用,但是在某些时候,您通常仍必须处理空情况

示例:

if(a != null && a.b != null and a.b.c != null) 
{
    a.b.c.doSomething();
}

将成为

if(a?.b?.c? != null)
{
    a.b.c.doSomething();
}

是的,虽然好一点,但并不能真正解决您的问题。

2)代码体系结构主张

我发现通常,允许null意味着您的课程有多种用途,并带有更多状态。在此示例中,您的类可以表示“带有中间名的名称”或“没有全名的名称”。这是有关代码体系结构的一些想法

  • 通常,这意味着该类做了太多事情。当然,在您的示例中,班级很小。但是在日常生活中,拥有可以为null的成员通常意味着该类太大并且支持太多功能。一堂课只有一个目的。参见Single Responsibility Principle

  • 在某些情况下(例如在示例中),您可能会认为您不想拆分该类,因为它按原样有意义。然后,您可以尝试查看是否有一种方法可以认为此类具有相同的功能,而无需处理特殊情况。

在您的示例中,您可以执行以下操作:

public class Person
{
    string[] FirstNames { get; set {if(value == null) FirstName } = new string[]{};

    string LastName { get; set; } = string.Empty;

    int Age { get; set; }
}

现在没有特殊情况。所有函数都必须考虑一个名称数组,该名称数组的大小可以为一个。

  • 如果您真的想要空对象,还可以将处理该对象的逻辑放在类中,并确保没有其他人访问该空对象。但这可能会导致在同一个班级中进行大量逻辑交流,因此这不是最佳解决方案

在您的示例中,您可以

public class Person
{
    private string FirstName { get; set {if(value == null) FirstName } = string.Empty;

    private string MiddleName { get; set; } = string.Empty;

    string LastName { get; set; } = string.Empty;

    int Age { get; set; }

    public string getName()
    {
        // Here handle the null case
    }
}

您可以看到这如何在课堂上增加更多的逻辑。

  • 最后,您可以了解Null object pattern。此模式需要更多代码,但允许您用对象的实际实例替换null,当您在对象上调用函数时,实例不会崩溃。我从来没有积极使用过它,因为我通常设法通过上面的原理获得更清晰的代码,但是您可能会发现这样做有用的情况。

下面来自维基百科的“空对象”实现示例

/* Null object pattern implementation:
 */
using System;

// Animal interface is the key to compatibility for Animal implementations below.
interface IAnimal
{
    void MakeSound();
}

// Animal is the base case.
abstract class Animal : IAnimal
{
    // A shared instance that can be used for comparisons
    public static readonly IAnimal Null = new NullAnimal();

    // The Null Case: this NullAnimal class should be used in place of C# null keyword.
    private class NullAnimal : Animal
    {
        public override void MakeSound()
        {
            // Purposefully provides no behaviour.
        }
    }
    public abstract void MakeSound();
}

// Dog is a real animal.
class Dog : IAnimal
{
    public void MakeSound()
    {
        Console.WriteLine("Woof!");
    }
}

/* =========================
 * Simplistic usage example in a Main entry point.
 */
static class Program
{
    static void Main()
    {
        IAnimal dog = new Dog();
        dog.MakeSound(); // outputs "Woof!"

        /* Instead of using C# null, use the Animal.Null instance.
         * This example is simplistic but conveys the idea that if the         Animal.Null instance is used then the program
         * will never experience a .NET System.NullReferenceException at runtime, unlike if C# null were used.
         */
        IAnimal unknown = Animal.Null;  //<< replaces: IAnimal unknown = null;
    unknown.MakeSound(); // outputs nothing, but does not throw a runtime exception        
    }
}

答案 1 :(得分:0)

有时候,对于确定无疑的null很有用,例如当您有一个表示一组问题答案的对象时,例如布尔? TrueFalseAnswer == null表示未回答问题,而不是默认为false,或者字符串name == null表示未填写名称字段,等等。

就您的课程而言,您至少可以将它们初始化为空字符串。

public class Person
{
    string FirstName { get; set {if(value == null) FirstName } = string.Empty;

    string MiddleName { get; set; } = string.Empty;

    string LastName { get; set; } = string.Empty;

    int Age { get; set; }
}

此外,我认为这不是您的确切意思,而是:

string s = null;
Console.WriteLine(s);

将新行输出到控制台,并且不会导致错误。

然后再次输入,也很容易

Person p = new Person();
//if(p == null || p.MiddleName == null) s = "";
string s = p?.MiddleName ?? "";

因此,空值非常有用,C#使检查它们变得非常容易。