【问题标题】:Are nullable properties, or properties allowed to be null, code smell (in OOP languages)? [closed]可以为空的属性,或允许为空的属性,代码异味(在 OOP 语言中)吗? [关闭]
【发布时间】:2019-05-18 09:30:31
【问题描述】:

我经常看到classes 喜欢

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

    string MiddleName { get; set; }

    string LastName { get; set; }

    int Age { get; set; }
}

其中一个属性,例如本例中的MiddleName(因为有些人在出生时没有中间名),允许为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-able 值类型的类,例如

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

    public double? SomeDouble { get; }
}

出于同样的原因。

但是我想知道这是否像“OOP 过度工程”,因为它显然会增加大量开发时间。

【问题讨论】:

  • 欢迎来到可空引用类型的 c#8 提案:blogs.msdn.microsoft.com/dotnet/2017/11/15/…
  • C# 语言的下一个版本将包含(相当容易混淆)称为 “可空引用类型” 的东西,这将简化对允许 null 的位置的规范并使 null 处理更加明确(其中很多是由编译器完成的)。在那之前,它可能是过度设计的
  • 编码风格问题通常是题外话,因为对于 SO 来说过于宽泛/基于意见。您可能想检查是否可以使您的问题适合Software Engineering(我也希望有很多关于可空值、空对象等的现有问题 - 请务必先在那里搜索)。
  • 你把事情复杂化了。把事情简单化。我会说你甚至不需要 MiddleName 属性。用户可以将他们的中间名和他们的名字放在一起。

标签: c# oop inheritance design-patterns


【解决方案1】:

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

1) 首先,关于 ?? operator 在另一个答案中建议: 它将允许您的代码使用 null 对象,但在某些时候,您通常仍需要处理 null 情况

示例:

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 意味着你的类有多种用途,并携带更多状态。在此示例中,您的类可以表示“具有中间名的名称”或“没有全名的名称”。这里有一些关于代码架构的想法

  • 通常,这意味着这个类做了太多的事情。当然,在您的示例中,班级很小。但在日常生活中,有一个可以为空的成员通常意味着类太大,支持的特性太多。一个班级应该只有一个目的。见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; }
}

现在没有特殊情况了。所有函数都必须考虑名称数组,其大小可能为 1。

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

在您的示例中,您可以这样做

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 实现示例

/* 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        
    }
}

【讨论】:

    【解决方案2】:

    有时空值肯定是有用的,例如当您有一个代表一组问题答案的对象时,例如布尔? TrueFalseAnswer == null 表示未回答问题,而不是默认为 false,或者 string 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# 让检查它们变得超级容易。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-02
      • 2021-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多