C# 对于多条消息和成功/失败,是否有通知模式的替代方案?

C# 对于多条消息和成功/失败,是否有通知模式的替代方案?,c#,oop,design-patterns,return,messages,C#,Oop,Design Patterns,Return,Messages,对于多条消息和成功/失败,是否有通知模式的替代方案 我有一个类OperationResult,用于返回Success布尔值和一列“错误”消息。这些消息有时是意外错误,但更常见的是经常发生的普通情况。有时我们返回单个错误消息,但有时我们返回多个错误消息。我希望找到更好的方法 这似乎是或多或少的。然后,消费者对成功状态和错误进行合理处理,通常向用户显示错误,但有时在非致命错误的情况下继续显示 因此,我有很多服务方法(不是web服务方法)看起来像这样: private ThingRepository

对于多条消息和成功/失败,是否有通知模式的替代方案

我有一个类OperationResult,用于返回
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 ) {...}