在构造函数之外使用接口/逻辑时防止未初始化的属性

问题描述 投票:0回答:1
  • 到目前为止,我通常在类的构造函数中执行代码逻辑,这确保实例的属性已定义并且无法在无效状态下访问。
  • 最近我尝试提高代码质量并开始使用接口。但是,由于构造函数不是接口的一部分,因此现在可以在不执行代码逻辑的情况下创建实例(因为代码逻辑不包含在构造函数中,而是包含在名为
    Handle
    的接口方法中)。

除了我的文字描述之外,我还想包括这个简化的代码示例,以使我的问题更加清晰:

// my old coding style: no interface, logic in constructor
class MyClass1
{
    public string SomePropertyDerivedFromInput { get; set; }
    public MyClass1(string input)
    {
        SomePropertyDerivedFromInput = input + " derived";
    }
}

// my new coding style: using an interface, logic in instance method
internal interface IMyInterface
{
    void Handle(string input);
}

class MyClass2 : IMyInterface
{
    public string SomePropertyDerivedFromInput { get; set; }

    public void Handle(string input)
    {
        SomePropertyDerivedFromInput = input + " derived";
    }
}

// without interfaces I am required to pass my input to the constructor,
// so SomePropertyDerivedFromInput is always in a valid state
var myClass1 = new MyClass1("hello");
Console.WriteLine(myClass1.SomePropertyDerivedFromInput ?? "null");

// with interfaces however it is possible that SomePropertyDerivedFromInput
// isn't yet in a valid state when it's accessed, because the instance
// can be created without the Handle() method being called
var myClass2 = new MyClass2();
Console.WriteLine(myClass2.SomePropertyDerivedFromInput ?? "null");

这个的输出是

hello derived
null

Boom,我的带有接口的新代码在使用它时比我以前没有接口的代码更容易出错(输出中的第二行:null)。

那么,在使用接口时,如何确保实现该接口的类的属性始终处于有效状态或者至少在处于有效状态之前不会被意外使用(即在

Handle
方法之前)叫)?对于这种情况,最佳做法是什么?引入空检查并抛出异常?不敢相信这将是最佳实践解决方案,使我的代码变得越来越复杂。

c# oop constructor interface properties
1个回答
0
投票

这两个示例等效。在第一种情况(没有接口)中,

MyClass1
构造函数执行正确的初始化,确保不变量成立。没关系。

您可以将构造函数视为一个函数,它将输入参数转换为正确实例化的对象。在这个特定的示例中,它是一个函数,它将

string
作为输入并生成 valid
MyClass1
对象作为输出。

在第二个示例中,您现在提出了一个

Handle
方法,该方法将
string
作为输入,但不返回任何内容作为输出。 这与第一个示例不同。

我的意思并不是对这个问题的批评。相反,我试图解释为什么您在第二个示例中遇到封装问题。您希望实现平等,但设计“不”相同。这有助于解释为什么您遇到问题。 具体来说,在这种情况下,如果目标是从

string

生成一个 valid 对象,则建模的接口可能如下所示:

public interface IMyObjectFactory
{
    IMyObject Create(string input);
}

在继续之前,我想指出太多
IFooFactory

对象本身就是一种设计味道,但这是我可以使用OP中建议的示例代码做的最好的事情。

通常,避免太多工厂的更好设计是更好的,

并且

是可能的,但我不能基于OP提出任何建议,因为你没有描述你真正想要用对象做的事情。 然而,最重要的是,考虑对象契约(前置条件、后置条件和不变量)是一个非常好的主意,并且接口不会阻止您这样做。然而,初始化不是接口的一部分,因此必须被视为实现细节。 这并不意味着您无法定义良好的封装并确保对象始终处于有效状态。另一方面,它的真正含义是,同一接口的不同实现可能有不同的履行契约的方式。接口描述了所有实现共有的契约,这使得

构造函数成为放置实现细节的好地方

© www.soinside.com 2019 - 2024. All rights reserved.