“静态域助手类”是环境上下文反模式吗?

3
在Steven van Deursen和Mark Seemann的Manning书籍《依赖注入原理、实践和模式》中定义了一个反模式,即Ambient Context Anti-Pattern(我推荐必读),这个反模式很有趣。它也在这里进行了描述。一般问题是,如果你将某些不稳定的依赖项放在静态类后面,你就将代码库中的多个部分耦合到那个不稳定的依赖项上了。
我的困惑始于对不稳定依赖项的定义。不稳定依赖项的定义之一是该依赖项仍在开发或未开发。所以我的问题是:如果该依赖项是一些明确定义的领域知识,例如一些特定领域的电子邮件验证器,放置在领域层中,并且它仍在进行一些代码更改,那么如果我们将其放在静态类后面并在整个代码库中使用它,是否会被视为反模式?(我们正在谈论洋葱架构,其中领域位于中心,一切都引用领域层) 编辑1 这是一个简化的示例领域类:
public class EmailValidationService
{
   private static readonly Regex _emailValidationRegex = /*some regex*/;

   public static EmailValidationStatus IsValid(string email)
   {
     var isValid = _emailValidationRegex.Match(email).Length > 0; ;

     return isValid;
   }
}

这个验证器的消费者被放置在多个层级上,例如用户输入验证、文件导入验证等等。以下是两个简化的消费者:

public class RawDataImporter{

    private Employee ProcessEmployee(EmployeeRowData data)
    {
        if (string.IsNullOrEmpty(data.Identifier)) { return new InvalidEmployee(); }
    
        if (EmailValidationService.IsValid(data.Identifier) { return new Employee(data.Identifier); }
    
    }
}

public class EmailAttribute :
        ValidationAttribute
{
    public override bool IsValid(object value)
    {
        var status = EmailValidationService.IsValid((string)value);
        if (status.IsValid)
        {
            return true;
        }

        return false;
    }
}

你能添加一个领域特定的电子邮件验证的示例以及它的消费者吗? - Steven
@Steven 很抱歉回复晚了。我已经通过编辑1更新了问题。这里有两个消费者,一个是验证属性,另一个是一些Excel文件导入服务。非常感谢您提前的帮助,听到您对这个话题的直接意见真的很不错。 - Marko Krstic
1个回答

3
易变依赖的定义之一是该依赖仍在开发中或未被开发出来。恐怖的是,在这本书(我是共同作者)中,这个说法并没有得到充分阐述。书中写道:“如果Dependency不存在或仍在开发中,则应将其视为易变的Dependency。”除此之外没有更多的信息。幸运的是,第一版包含了更多的信息。在那个版本中,这个先前的说明后面有一句话:“这种依赖的明显症状是无法进行并行开发。” 当一个软件开发项目变得足够大时,需要让多个开发人员在同一代码库上并行工作。在更大的范围内,需要将开发团队分成可管理的多个小团队。每个团队通常负责整个应用程序的一个区域。为了划分责任,每个团队都会开发一个或多个模块,这些模块需要集成到完成的应用程序中。除非每个团队的区域真正独立,否则某些团队可能依赖于其他团队开发的功能。因此,当开发团队规模越大或者多个团队同时在一段软件上工作时,正在开发中的部分会妨碍开发。这些难题可以通过将更有风险的部分隐藏在抽象层后来减轻。这意味着组织的结构和规模可能会对代码库的设计和结构产生影响。你应该选择一种最经济有效(从长远来看)的设计。因此,可以得出结论:易变依赖和稳定依赖并不总是一门硬科学。这与包含不确定行为的依赖完全不同;它们总是易变的。

注意:我会研究一下是否可以将这些信息放到书的勘误表中。不幸的是,更新下一版的文本是不可能的,因为它会导致页面布局和编号的连锁变化,这是我们的出版商不允许的。

如果依赖关系是一些明确定义的领域知识,比如某个特定领域的电子邮件验证器,放在领域层中,并且仍在进行一些代码更改,如果我们将其放在静态类后面并在整个代码库中使用它,这是否被认为是反模式?

您的电子邮件验证器是一个很好的例子,我会把它归为灰色范畴;它可以两种方式处理。它的行为是确定性的,因为:给定相同的输入,它总是产生相同的输出。你也可以问另外一个问题,你是否需要松耦合?如果你想要用另一个电子邮件验证器替换一个,或者更普遍地说,添加更多的验证检查是否需要更改电子邮件验证器的消费方?这将是你再次谈论易变依赖的一个很好的指标。

现在让我们回到你关于Ambient Context反模式的问题上。假设你已经确定了这种验证是易变依赖;但这仍然不一定意味着它是Ambient Context。Ambient Context的一个特点是行为可以被替换。例如,看一下以下示例,它演示了一个Ambient Context;这是书中第5.11节的简化版本:

public static class TimeProvider
{
    public static ITimeProvider Current { get; set; }
        = new DefaultTimeProvider();
}

静态的TimeProvider类提供了访问ITimeProvider依赖项的方法。如果您希望能够替换时间提供程序行为,那么这个依赖关系就是一个易变依赖项。

如果无法以某种方式替换易变依赖项为另一个依赖项,则没有环境上下文。例如,当您允许替换底层的Regex时,您的验证器将成为环境上下文,如下所示:

public class EmailValidationService
{
   // Ambient Context
   public static Regex EmailValidationRegex { get; set; } = /*some regex*/;

   public static EmailValidationStatus IsValid(string email)
   {
     var isValid = EmailValidationRegex.Match(email).Length > 0; ;

     return isValid;
   }
}
Regex是一个具体类,而不是抽象类,但这并不能改变它仍然是Ambient Context的示例的事实。
相反,如果您确定该静态类由易变行为组成,并且此类直接由消费者调用,则该书将考虑这是控制狂反模式的一种实现:
“控制狂反模式”发生在任何地方依赖于不稳定的依赖项而不是组合根时。 这是依赖反转原则的违反[...]

1
感谢您对这个话题进行详细的回复,它确实帮助我理解了“环境上下文”反模式。再次感谢,这本书非常棒,它真正澄清了日常开发人员工作中的许多问题,也感谢您写这本书 :) - Marko Krstic

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