C# 对于多条消息和成功/失败,是否有通知模式的替代方案?
对于多条消息和成功/失败,是否有通知模式的替代方案 我有一个类OperationResult,用于返回C# 对于多条消息和成功/失败,是否有通知模式的替代方案?,c#,oop,design-patterns,return,messages,C#,Oop,Design Patterns,Return,Messages,对于多条消息和成功/失败,是否有通知模式的替代方案 我有一个类OperationResult,用于返回Success布尔值和一列“错误”消息。这些消息有时是意外错误,但更常见的是经常发生的普通情况。有时我们返回单个错误消息,但有时我们返回多个错误消息。我希望找到更好的方法 这似乎是或多或少的。然后,消费者对成功状态和错误进行合理处理,通常向用户显示错误,但有时在非致命错误的情况下继续显示 因此,我有很多服务方法(不是web服务方法)看起来像这样: private ThingRepository
Success
布尔值和一列“错误”消息。这些消息有时是意外错误,但更常见的是经常发生的普通情况。有时我们返回单个错误消息,但有时我们返回多个错误消息。我希望找到更好的方法
这似乎是或多或少的。然后,消费者对成功状态和错误进行合理处理,通常向用户显示错误,但有时在非致命错误的情况下继续显示
因此,我有很多服务方法(不是web服务方法)看起来像这样:
private ThingRepository _repository;
public OperationResult Update(MyThing thing)
{
var result = new OperationResult() { Success = true };
if (thing.Id == null) {
result.AddError("Could not find ID of the thing update.");
return result;
}
OtherThing original = _repository.GetOtherThing(thing.Id);
if (original == null) return result;
if (AnyPropertyDiffers(thing, original))
{
result.Merge(UpdateThing(thing, original));
}
if (result.Success) result.Merge(UpdateThingChildren(thing));
if (!result.HasChanges) return result;
OperationResult recalcResult = _repository.Recalculate(thing);
if (recalcResult.Success) return result;
result.AddErrors(recalcResult.Errors);
return result;
}
private OperationResult UpdateThing(MyThing ) {...}
private OperationResult UpdateThingChildren(MyThing) {...}
private bool AnyPropertyDiffers(MyThing, OtherThing) {...}
可以想象,UpdateThing
、UpdateThingChildren
和ThingRepository.Recompute
都有类似的操作结果
合并/操作代码,并与它们的业务逻辑交织在一起
除了在我返回的对象周围大嚼代码之外,还有其他选择吗?我希望我的代码只关注业务逻辑,而不必对操作OperationResult
如此挑剔
我希望代码看起来像下面这样,能够更好地表达其业务逻辑,而不需要太多的消息处理:
public ??? Update(MyThing thing, ???)
{
if (thing.Id == null) return ???;
OtherThing original = _repository.GetOtherThing(thing.originalId);
if (original == null) return ???;
if (AnyPropertyDiffers(thing, original))
{
UpdateThing(thing, original));
}
UpdateThingChildren(thing);
_repository.Recalculate(thing);
return ???;
}
有什么想法吗
注意:抛出异常在这里并不合适,因为消息不是异常的。有些人知道模式是不会被破坏的。但模式有很多批评者。而且确实有即兴创作的空间。即使修改了一些细节,您仍然可以考虑实现了模式。模式是一般的东西,可以有具体的实现 也就是说,您可以选择不使用这种细粒度操作来解析结果。我一直主张这样的伪代码
class OperationResult<T>
{
List<T> ResultData {get; set;}
string Error {get; set;}
bool Success {get; set;}
}
class Consumer<T>
{
void Load(id)
{
OperationResult<T> res = _repository.GetSomeData<T>(id);
if (!res.Success)
{
MessageBox.Show(DecorateMsg(res.Error));
return;
}
}
}
类操作结果
{
列表结果数据{get;set;}
字符串错误{get;set;}
bool成功{get;set;}
}
阶级消费者
{
空荷载(id)
{
OperationResult res=\u repository.GetSomeData(id);
如果(!res.Success)
{
MessageBox.Show(DecorateMsg(res.Error));
返回;
}
}
}
如您所见,服务器代码返回数据或错误消息。简单。服务器代码完成所有日志记录,您可以将错误写入数据库,无论什么。但我认为将复杂的操作结果传递给消费者没有任何价值。消费者只需要知道,成功与否。同样,如果你在同一个操作中得到多个东西,如果你第一件事情失败了,继续操作又有什么意义呢?问题可能是你试图一次做太多的事情?这是有争议的。你可以这样做
class OperationResult
{
List<type1> ResultData1 {get; set;}
List<type2> ResultData2 {get; set;}
List<type3> ResultData3 {get; set;}
string[] Error {get; set;} // string[3]
bool Success {get; set;}
}
类操作结果
{
列表结果数据1{get;set;}
列表结果数据2{get;set;}
列表结果数据3{get;set;}
字符串[]错误{get;set;}//string[3]
bool成功{get;set;}
}
在这种情况下,可以填充2个网格,但不能填充第三个网格。如果客户端因此发生任何错误,您将需要使用客户端错误处理来处理它
您当然应该根据自己的具体需要自由调整任何模式。我认为您的服务没有做一件事。它负责验证输入,然后验证是否成功更新内容。是的,我同意用户需要尽可能多的关于错误的信息(违规、未提供姓名、长描述、日期开始之前的日期结束等),因为您可以在单个请求中生成这些信息,并且例外情况不是解决方法
在我的项目中,我倾向于分离验证和更新的关注点,因此进行更新的服务几乎没有失败的机会。此外,我还喜欢策略模式同时执行验证和更新-用户请求更改通用服务接受验证/更新请求,调用特定的验证器/更新程序,然后调用通用服务来验证/更新某些依赖项。通用服务将合并结果并决定操作的成功或失败。显而易见的好处是,冲突消息合并在一些通用类中只进行一次,特定的验证器/更新程序可以集中在单个实体上。另一方面,您可能希望验证某些数据库的唯一性或数据库中对象的存在性。这暴露了两个问题:对数据库的额外查询(使用
Exist
最小化输出的轻度查询,但这是对数据库的访问)以及验证和更新之间的延迟(在这段时间内,数据库可能会更改,您的唯一性或存在性验证可能会更改(这段时间相对较小,但可能会发生)。此模式还最大限度地减少了重复的UpdateThingChildren
-当您有简单的多对多关系时,可以从连接的实体中的任意一个更新子项。我看到了两种可能的选项,您可以使用它们来避免引发异常,但最终我们只是减少了消息所需的代码量:
- _repository.GetOtherThing-将Id检查委托给存储库,以获得更简单的代码,并确保在未发生任何事件时返回错误
- UpdateThing-无更改后退出
- _重新计算-我们现在合并结果并返回它们
// We a scope class shared by all services,
// we don't need to create a result or decide what result to use.
// It is more whether it worked or didn't
public void UpdateWithScope(MyThing thing)
{
var original = _repository.GetOtherThing(thing.Id);
if (_workScope.HasErrors) return;
if (original == null)
{
_workScope.AddError("Invalid or ID not found of the thing update.");
return;
}
if (AnyPropertyDiffers(thing, original))
{
UpdateThing(thing, original);
if (_workScope.HasErrors) return;
}
UpdateThingChildren(thing);
if (_workScope.HasErrors) return;
_repository.Recalculate(thing);
}
- _repository.GetOtherThing必须将任何错误添加到\u工作范围
- 更新必须将任何错误添加到_工作范围
- UpdateThingChildren必须向_工作范围添加任何错误
- _存储库。重新计算必须将任何错误添加到_workSco
// We a scope class shared by all services, // we don't need to create a result or decide what result to use. // It is more whether it worked or didn't public void UpdateWithScope(MyThing thing) { var original = _repository.GetOtherThing(thing.Id); if (_workScope.HasErrors) return; if (original == null) { _workScope.AddError("Invalid or ID not found of the thing update."); return; } if (AnyPropertyDiffers(thing, original)) { UpdateThing(thing, original); if (_workScope.HasErrors) return; } UpdateThingChildren(thing); if (_workScope.HasErrors) return; _repository.Recalculate(thing); }
public class OperationResult { public bool Success { get; set; } public List<string> Errors { get; set; } = new List<string>(); } public class Thing { public string Id { get; set; } } public class OperationException : Exception { public OperationException(string error = null) { if (error != null) Errors.Add(error); } public List<string> Errors { get; set; } = new List<string>(); } public class Operation { public OperationResult Update(Thing thing) { var result = new OperationResult { Success = true }; try { UpdateInternal(thing); } catch(OperationException e) { result.Success = false; result.Errors = e.Errors; } return result; } private void UpdateInternal(Thing thing) { if (thing.Id == null) throw new OperationException("Could not find ID of the thing update."); var original = _repository.GetOtherThing(thing.Id); if (original == null) return; if (AnyPropertyDiffers(thing, original)) result.Merge(UpdateThing(thing, original)); result.Merge(UpdateThingChildren(thing)); if (result.HasChanges) _repository.Recalculate(thing); } }
using Optional;
using Optional.Unsafe;
public static class Wrap<Tin, Tout> { public static Option<Tout, Exception> Chain(Tin input, Func<Tin, Tout> f) { try { return Option.Some<Tout,Exception>(f(input)); } catch (Exception exc) { return Option.None<Tout, Exception>(exc); } } public static Option<Tout, Exception> TryChain(Option<Tin, Exception> input, Func<Tin, Tout> f) { return input.Match( some: value => Chain(value, f), none: exc => Option.None<Tout, Exception>(exc) ); } }
Type2 Update1 (Type1 t) { var r = new Type2(); // can throw exceptions return r; } Type3 Update2(Type2 t) { var r = new Type3(); // can throw exceptions return r; } Type4 Update3(Type3 t) { var r = new Type4(); // can throw exceptions return r; }
Option<Type4, Exception> HappyPath(Option<Type1, Exception> t1) { var t2 = Wrap<Type1,Type2>.TryChain(t1, Update1); var t3 = Wrap<Type2, Type3>.TryChain(t2, Update2); return Wrap<Type3, Type4>.TryChain(t3, Update3); }
public static class Extensions { public static Option<Type2, Exception> TryChain(this Option<Type1, Exception> input, Func<Type1, Type2> f) { return Wrap<Type1, Type2>.TryChain(input, f); } public static Option<Type3, Exception> TryChain(this Option<Type2, Exception> input, Func<Type2, Type3> f) { return Wrap<Type2, Type3>.TryChain(input, f); } public static Option<Type4, Exception> TryChain(this Option<Type3, Exception> input, Func<Type3, Type4> f) { return Wrap<Type3, Type4>.TryChain(input, f); } }
Option<Type4, Exception> HappyPath(Option<Type1, Exception> t1) { var t2 = t1.TryChain(Update1); var t3 = t2.TryChain(Update2); return t3.TryChain(Update3); }
public OperationResult Update(MyThing thing) { return new OperationResult { Errors = thing.ApplyEvent(Event.NullCheck) .ApplyEvent(Event.OriginalNullCheck) .ApplyEvent(Event.OriginalPropertyDiffersCheck) .CollectInfo(), Success = true }; } public class MyThing { private List<string> _errors = new List<string>(); private MyThing _original; public MyThingState ThingState {get;set;} public MyThing ApplyEvent(Event eventInfo) { MyThingState.ApplyEvent(this, eventInfo) } } public class NullState : MyThingState { public MyThing ApplyEvent(MyThing myThing, Event eventInfo) { if(mything == null) { // use null object pattern mything.AddErrors("Null value") // Based on the event, you select which state to instantiate // and inject dependencies mything.State = new OriginalNullState(); } } } public class OriginalNullState : MyThingState { public void ApplyEvent(MyThing myThing, Event eventInfo) { // Get original from database or repository // Save and validate null // Store relevant information in _errors; // Change state } }
public OperationResult<DataSet> Update(MyThing thing, OperationResult<DataSet> papa) { // Either you have a result object from enclosing block or you have null. var result = OperationResult<DataSet>.Create(papa); if (thing.Id == null) return result.Fail("id is null"); OtherThing original = _repository.GetOtherThing(thing.originalId); if (original == null) return result.warn("Item already deleted"); if (AnyPropertyDiffers(thing, original)) { UpdateThing(thing, original, result)); // Inside UpdateThing, take result in papa and do this dance once: // var result = OperationResult<DataSet>.Create(papa); } UpdateThingChildren(thing, result); // same dance. This adds one line per method of overhead. Eliminates Merge thingy _repository.Recalculate(thing, result); return result.ok(); }
class OperationResult<T> { enum SuccessLevel { OK, WARN, FAIL } private SuccessLevel _level = SuccessLevel.OK; private List<String> _msgs = new ... public T value {get; set}; public static OperationResult<T> Create(OperationResult<T> papa) { return papa==null ? new OperationResult<T>() : papa; } public OperationResult<T> Fail(string msg) { _level = SuccessLevel.Fail; _msgs.add(msg); return this; // this return self trick will help in reducing many extra lines in main code. } // similarly do for Ok() and Warn() }
private ThingRepository _repository; public OperationResult Update(MyThing thing) { return new OperationResult() //removed Success = true, just make that a default value. .Then(() => thing.ValidateId()) //moved id validation into thing .Then(() => GetOtherThing(thing.Id)) //GetOtherThing validates original is null or not .Then(() => thing.AnyPropertyDiffersFrom(original)) //moved AnyPropertyDiffers into thing .Then(() => thing.UpdateChildren()) .Then(() => _repository.Recalculate(thing)); } private OperationResult GetOtherThing(MyThing ) {...}