我们正在开发一个内部 NuGet 包,其中包含
ServiceRes
和 ServiceRes<T>
类,可用于将服务层的响应包装到通用结构中。这是我们目前拥有的代码:
public class ServiceRes
{
protected internal ServiceRes() { }
public bool IsSuccess { get; set; }
public string? Message { get; set; }
public static ServiceRes Success()
{
return Success(message: null);
}
public static ServiceRes Success(string? message)
{
return new ServiceRes
{
IsSuccess = true,
Message = message
};
}
public static ServiceRes MapFrom<TPrev>(ServiceRes<TPrev> othr)
where TPrev : class
{
return new ServiceRes
{
IsSuccess = othr.IsSuccess,
Message = othr.Message
};
}
}
public class ServiceRes<T> : ServiceRes where T : class
{
protected internal ServiceRes() { }
public T? ResponseObj { get; set; }
public static ServiceRes<T> Success(T responseObj)
{
return new ServiceRes<T>
{
IsSuccess = true,
Message = null,
ResponseObj = responseObj
};
}
public static new ServiceRes<T> MapFrom<TPrev>(ServiceRes<TPrev> othr)
where TPrev : class
{
return MapFrom(othr, null);
}
public static ServiceRes<T> MapFrom<TPrev>(ServiceRes<TPrev> othr, Func<TPrev?, T?>? responseObjMapper)
where TPrev : class
{
return new ServiceRes<T>
{
IsSuccess = othr.IsSuccess,
Message = othr.Message,
ResponseObj = responseObjMapper?.Invoke(othr.ResponseObj)
};
}
}
我们有一个 .NET Core Web 应用程序项目,它引用我们的私有 NuGet 包。我们可以这样使用它:
public class ServiceResTest
{
private class Obj1
{
public string? Name { get; set; }
}
private class Obj2
{
public string? Value { get; set; }
}
public void Test()
{
// base usage
var x1 = ServiceRes.Success();
var x2 = ServiceRes.Success(message: "User created successfully");
var x3 = ServiceRes<Obj1>.Success(new Obj1());
// mapping
var x4 = ServiceRes.MapFrom(x3); // ServiceResult<Obj1> -> ServiceResult
var x5 = ServiceRes<Obj2>.MapFrom(x3); // ServiceResult<Obj1> -> ServiceResult<Obj2> (ResponseObj = null)
var x6 = ServiceRes<Obj2>.MapFrom(x3, x => new Obj2() // ServiceResult<Obj1> -> ServiceResult<Obj2>
{
Value = $"{x?.Name}_value"
});
}
}
但是,我们的 Web 应用程序中有一些高级用例需要扩展
BaseService<T>
类。我们希望能够将 ServiceResult<T>
映射到 ServiceResultExt<T>
,最好采用与现有 MapFrom
方法类似的方式。下面的评论指出了该过程失败的地方。
public class ServiceResExt<T> : ServiceRes<T> where T : class
{
public int FailedLoginsCount { get; set; }
public static ServiceResExt<T> SuccessExt(T responseObj, int failedLoginsCount)
{
var baseRes = ServiceRes<T>.Success(responseObj);
// map ServiceRes<T> to ServiceResExt<T>
var x1 = ServiceResExt<T>.MapFrom(baseRes); // x1 is of type ServiceRes<T>
// we would like to assing parameter failedLoginsCount to property FailedLoginsCount
x1.FailedLoginsCount = failedLoginsCount;
return x1;
}
}
处理以下情况的最佳方法是什么:我们有一个扩展
ServiceRes<T>
(例如 ServiceResExt<T>
)的类,并且我们希望利用现有方法(例如 Success
),但将响应映射到 ServiceResExt<T>
?派生类的映射逻辑是否可以放置在 ServiceRes<T>
类中,以便对于从它继承的所有类都是通用的(我们不希望任何人手动实例化 ServiceRes
类,因此需要 protected internal
构造函数)? ServiceRes<T>
可以返回派生类的实例吗?
您可以在映射时使用受保护的虚拟方法来创建正确的对象类型。
我会这样写:
public class ServiceRes
{
protected ServiceRes(bool isSuccess, string message)
=> ( IsSuccess, Message ) = ( isSuccess, message );
public static ServiceRes Success => new (true, "");
public static ServiceRes Fail(string message) => new(false, message);
public bool IsSuccess { get; }
public string Message { get; }
}
public class ServiceRes<T> : ServiceRes
where T : class
{
private readonly T responseObj;
protected ServiceRes(T response, bool isSuccess, string message) : base(isSuccess, message)
=> responseObj = response;
protected virtual ServiceRes<T2> Create<T2>(T2 v) where T2 : class => new(v, IsSuccess, Message);
public T Response => IsSuccess ? responseObj : throw new InvalidOperationException();
public new static ServiceRes<T> Success(T response) => new(response, true, "");
public new static ServiceRes<T> Fail(string message) => new(null, false,message);
public ServiceRes<T2> MapFrom<T2>(Func<T, T2> select) where T2 : class
=> IsSuccess ? Create(select(Response)) : Create<T2>(null);
}
public class ServiceResExt<T> : ServiceRes<T>
where T : class
{
protected ServiceResExt(T value, bool isSuccess, string message, int failedLoginsCount) : base(value, isSuccess, message)
=> FailedLoginsCount = failedLoginsCount;
protected virtual ServiceResExt<T2> CreateExt<T2>(T2 v) where T2 : class
=> new(v, IsSuccess, Message, FailedLoginsCount);
protected override ServiceRes<T2> Create<T2>(T2 v) => CreateExt(v);
public int FailedLoginsCount { get; }
public new static ServiceResExt<T> Success(T response, int failedLoginsCount) => new(response, true, "", failedLoginsCount);
public new static ServiceResExt<T> Fail(string message, int failedLoginsCount) => new(null, false, message, failedLoginsCount);
public new ServiceResExt<T2> MapFrom<T2>(Func<T, T2> select) where T2 : class
=> IsSuccess ? CreateExt(select(Response)) : CreateExt<T2>(null);
}
映射
ServiceRes
基类是没有意义的,因此它缺少任何此类方法。
而且我不确定它是否达到了您在基础中拥有映射逻辑的目标
ServiceRes<T>
。但这不应该是那么多逻辑,您可以将 ServiceResExt.MapFrom
替换为 (ServiceResExt<T2>)base.MapFrom(select)
,即使如果添加进一步的扩展,这会不太安全。
但是无论引用的类型如何,它在映射之前和之后都应该保持相同的外部类型。由于隐藏了
MapFrom
方法,还返回相同的外部类型的引用。
您可能还需要添加一些
?
,我写的这个没有可为空的引用类型。我建议让这些类不可变,让公共设置器看起来很奇怪而且危险。