c# - 如何排除对新子类的访问,同时仍允许从另一个程序集进行测试?
问题描述
我遇到了一种我不确定在 C# 中是否可行的情况,但我还是想问一下。我需要从具有两个具体子类的类库中公开一个类型,并且我不希望用户能够创建子类的新实例 - 但是,如果我将所有内容都设为内部,那么用户将无法为测试目的创建实例。
我目前拥有的是这样的(所有示例都已简化,但要理解这个想法):
public abstract class Result
{
internal Result() { }
}
internal class SuccessResult : Result { }
internal class FailureResult<TError> : Result
{
TError Error { get; init; }
}
库的用户可以访问如下所示的一对接口,一次提供一个接口,从而限制可以创建的结果类型:
public interface IPartialResultFactory
{
Result Success();
}
public interface IResultFactory : IPartialResultFactory
{
Result Error<TError>(TError error);
}
执行上述操作,我可以确保只有有权访问该程序集的内部类的代码才能创建 的新子类型Result
,但是当有人测试他们与库的集成时,他们将无法轻松测试代码,因为他们不能创建Result
类的新实例。
我目前正在使用的解决方案是重新定义Result
类,如下所示:
public abstract class Result
{
internal Result() { }
public static Result Success() => new SuccessResult();
public static Result Error<TError>(TError error) => new ErrorResult<TError>(error);
}
然而这有两个问题:
首先,它引入了
Result
与子类之间的耦合,如果可能的话,我想避免这些子类,以防我想在将来的某个时间点添加其他子类;其次,这意味着在为用户提供
IPartialResultFactory
实例的情况下,他们仍然可以返回一个ErrorResult
实例,我希望将他们限制为仅返回一个SuccessResult
.
所以,总而言之,有没有一种方法可以让用户测试与我的代码的集成,创建 的实例Result
,然后限制如何为实际集成创建这些实例?
解决方案
我不确定您是否使用公共工厂接口来创建您的 Result 类,但这样的工作是否可行。
我发现通常最好提供固定的接口定义而不是具有填充方法的类类型 - 请记住,完全抽象的类与接口相同。
使用接口类型,库用户可以轻松地生成他们自己的单元测试,这些单元测试使用它们的 Mock 实现。
我希望提供两种类型Result
,每种类型由以下接口定义:
public interface IResultBase
{
SupportedResultType Type { get; }
IResult Result { get; }
}
public interface IResultComplex : IResultBase
{
IError Error { get; }
}
在哪里
public enum SupportedResultType
{
Base,
Complex
}
我希望在我的Result
类型中传输的数据:
public interface IResult
{
string SomeValue { get; }
bool Success { get; }
}
public class Result : IResult
{
public Result(string someValue, bool success)
{
SomeValue = someValue;
Success = success;
}
public string SomeValue { get; }
public bool Success { get; }
}
public interface IError
{
string ErrorMessage { get; }
}
public class Error : IError
{
public Error(string errorMessage)
{
ErrorMessage = errorMessage;
}
public string ErrorMessage { get; }
}
只要观察到接口类型,它背后的具体类就无关紧要,代码库的用户仍然可以使用它们。
这是我的意思的一个例子。
我使用工厂创建它们,但实际上您的库用户可以使用返回具体实现的公共方法。
public sealed class ResultFactory
{
private readonly Dictionary<SupportedResultType, IResultLocator> _resultLocators;
private ResultFactory()
{
_resultLocators = new Dictionary<SupportedResultType, IResultLocator>();
foreach (SupportedResultType resultType in System.Enum.GetValues(typeof(SupportedResultType)))
{
IResultLocator creator;
switch (resultType)
{
case SupportedResultType.Base:
creator = new ResultBaseLocator();
break;
case SupportedResultType.Complex:
creator = new ResultComplexLocator();
break;
default:
throw new ArgumentOutOfRangeException();
}
_resultLocators.Add(resultType, creator);
}
}
private static readonly Lazy<ResultFactory> LazyInstantiation =
new Lazy<ResultFactory>(() => new ResultFactory());
public static ResultFactory Initialise() => LazyInstantiation.Value;
public IResultBase Create(SupportedResultType resultType) =>
_resultLocators[resultType].Get();
}
工厂实际上并没有创建具体的实例,这是由一个IResultLocator
实例执行的,这允许我们在以后扩展实例的数量,IResultBase
同时IResultCompex
最大限度地减少对工厂的更改。
internal class ResultBaseLocator : IResultLocator
{
public IResultBase Get()
{
return new ResultBase();
}
}
internal class ResultComplexLocator : IResultLocator
{
public IResultBase Get()
{
return new ResultComplex();
}
}
对于我的示例,这里是具体的实现:
internal class ResultBase : IResultBase
{
public SupportedResultType Type { get; } = SupportedResultType.Base;
public IResult Result { get; } = new Result("ResultBase", true);
}
internal class ResultComplex : IResultComplex
{
public SupportedResultType Type { get; } = SupportedResultType.Complex;
public IResult Result { get; } = new Result("ResultComplex", false );
public IError Error { get; } = new Error("Error message");
}
我将上述类放入类库中,然后创建一个引用它的控制台应用程序。控制台应用程序中的以下代码演示了它们。
static void Main(string[] args)
{
foreach (SupportedResultType resultType in System.Enum.GetValues(typeof(SupportedResultType)))
{
IResultBase result = ResultFactory.Initialise().Create(resultType);
Console.WriteLine($"Result is type {result.Type}");
Console.WriteLine($"Result.SomeValue = {result.Result.SomeValue}");
Console.WriteLine($"Result.Success = {result.Result.Success}");
if (result.Type == SupportedResultType.Complex)
{
Console.WriteLine($"Result.Error.ErrorMessage = {((IResultComplex)result).Error.ErrorMessage}");
}
if (result.GetType().GetInterfaces().Contains(typeof(IResultComplex)))
{
Console.WriteLine($"Result is IResultComplex type");
}
}
}
输出如下所示:
推荐阅读
- python - `concurrent.futures` 中的 `with` 函数
- android-studio - 重新加载片段后视图模型不返回任何数据
- python - ARM Thumb BL 指令循环到自身
- python - 当它是类的属性时,格式化硒定位器的正确方法是什么?
- css - 我应该对我的汉堡包进行什么轮换?
- javascript - 我可以通过鼠标点击在 Jekyll 主题之间切换吗?
- c# - 有什么方法可以将表单应用于所有列或整个表?
- ios - Firebase Crashlytics 无法与 DJI iOS SDK 一起使用
- delphi - 如何使用泛型创建不同类型的 MDI 子项?
- javascript - Google Apps 脚本中的去抖动或限制事件处理程序