简体   繁体   English

创建方法“模板”(通过将某些方法传递给模板方法)会被认为是不良设计吗?

[英]Would creating a method “template” (by passing some method to the template method) be considered bad design?

I had a difficult time determining a good title, so feel free to change it if necessary. 我很难确定一个好标题,因此如有必要,随时进行更改。 I wasn't really sure how to describe what I'm trying to achieve and the word "template" came to mind (obviously I'm not trying to use C++ templates). 我不太确定如何描述我要实现的目标,并且想到了“模板”一词(显然,我不是在尝试使用C ++模板)。

If I have a class that performs some action in every method, let's pretending doing a try/catch and some other stuff: 如果我有一个在每种方法中都执行某些操作的类,让我们假装进行try / catch和其他操作:

public class SomeService
{
    public bool Create(Entity entity)
    {
        try
        {
            this.repository.Add(entity);
            this.repository.Save();
            return true;
        }
        catch (Exception e)
        {
            return false;
        }
    }
}

Then I add another method: 然后添加另一种方法:

public bool Delete(Entity entity)
{
    try
    {
        this.repository.Remove(entity);
        this.repository.Save();
        return true;
    }
    catch (Exception e)
    {
        return false;
    }
}

There's obviously a pattern in the methods here: try/catch with the return values. 这里的方法显然有一种模式:尝试/捕获返回值。 So I was thinking that since all methods on the service need to implement this pattern of working, could I refactor it into something like this instead: 所以我在想,由于服务上的所有方法都需要实现这种工作模式,因此我可以将其重构为如下形式:

public class SomeService
{
    public bool Delete(Entity entity)
    {
        return this.ServiceRequest(() =>
        {
            this.repository.Remove(entity);
            this.repository.Save();
        });
    }

    public bool Create(Entity entity)
    {
        return this.ServiceRequest(() =>
        {
            this.repository.Add(entity);
            this.repository.Save();
        });
    }

    protected bool ServiceRequest(Action action)
    {
        try
        {
            action();
            return true;
        }
        catch (Exception e)
        {
            return false;
        }
    }
}

This way all methods follow the same "template" for execution. 这样,所有方法都遵循相同的“模板”来执行。 Is this a bad design? 这是一个不好的设计吗? Remember, the try/catch isn't all that could happen for each method. 请记住,try / catch并不是每种方法都可能发生的全部事情。 Think of adding validation, there would be the need to say if(!this.Validate(entity))... in each method. 考虑添加验证,每种方法中都需要说if(!this.Validate(entity))...

Is this too difficult to maintain/ugly/bad design? 这太难维护/丑陋/不良设计了吗?

Using lambda expressions usually reduces readability. 使用lambda表达式通常会降低可读性。 Which basically means that in a few months someone will read this and get a headache. 从根本上讲,这意味着几个月后会有人阅读并感到头痛。

If it's not necessary, or there's no real performance benefit, just use the 2 separate functions. 如果没有必要,或者没有真正的性能优势,只需使用两个单独的功能。 IMO it's better to have readable code then to use nifty techniques. IMO最好具有可读的代码,然后再使用精巧的技术。

This seems like a technique that would be limited to only small "actions" -- the more code in the "action" the less useful this would be as readability would be more and more compromised. 这似乎是一种仅限于小的“动作”的技术-“动作”中的代码越多,其实用性就越差,因为可读性会越来越受到损害。 In fact, the only thing you're really reusing here is the try/catch block which is arguably bad design in the first place. 实际上,您真正在这里重复使用的唯一方法是try / catch块,这在开始时就可以说是糟糕的设计。

That's not to say that it's necessarily a bad design pattern, just that your example doesn't really seem to be a good fit for it. 这并不是说这必然是一种不良的设计模式,只是您的示例似乎并不适合它。 LINQ, for example, uses this pattern extensively. 例如,LINQ广泛使用此模式。 In combination with extension methods and the fluent style it can be very handy and still remain readable. 结合扩展方法和流畅的样式,它可以非常方便并且仍然保持可读性。 Again, though, it seems best suited to replace small "actions" -- anything more than a couple of lines of code and I think it gets pretty messy. 再次,尽管如此,它似乎最适合替换小的“动作”-除了几行代码外,我认为它变得非常混乱。

If you are going to do it you might want to make it more useful by passing in both the action and the entity the action uses as parameters instead of just the action. 如果要执行此操作,则可能希望通过传入动作和该动作用作参数而不只是动作的实体来使其更加有用。 That would make it more likely that you could do additional, common computations in your action. 这将使您更有可能在操作中执行其他常见计算。

 public bool Delete( Entity entity )
 {
      return this.ServiceRequest( e => {
          this.repository.Remove( e );
          this.repository.Save();
      }, entity );
 }

 protected bool ServiceRequest( Action<Entity> action, Entity entity )
 {
      try
      {
          this.Validate( entity );
          action( entity );
          return true;
      }
      catch (SqlException) // only catch specific exceptions
      {
          return false;
      }
 }

I would try to look for a way to break the repository action (add/update/delete) from the repository changes flush (save). 我将尝试寻找一种从存储库更改刷新(保存)中中断存储库操作(添加/更新/删除)的方法。 Depending on how you use your code (web/windows) you might be able to use a 'session' manager for this. 根据您使用代码(Web / Windows)的方式,您可能可以使用“会话”管理器。 Having this separation will also allow you to have multiple actions flushed in a single transaction. 进行这种分离还可以使您在单个事务中刷新多个操作。

Other think, not related to the topic but to the code, don't return true/false, but let exception pass through or return something that will allow you to distinguish on the cause or failure (validation, etc.). 其他与主题无关但与代码无关的想法则不返回true / false,而是让异常通过或返回使您能够区分原因或失败(验证等)的东西。 You might want to throw on contract breach (invalid data passed) and return value on normal invalid business rules (to not use exception as a business rule as they are slow). 您可能要抛出合同违约(传递无效数据)并为正常的无效业务规则返回值(不要将异常用作业务规则,因为它们的速度很慢)。

An alternative would be to create an interface, say IExample which expresses more of your intent and decouples the action from the actor. 一种替代方法是创建一个接口,例如IExample,该接口表达您的更多意图并将动作与参与者分离。 So in your example at the end you mention perhaps using this.Validate(entity). 因此,在最后的示例中,您可能提到了使用this.Validate(entity)。 Clearly that won't work with your current design as you'd have to pass action and entity and then pass entity to action. 显然,这不适用于您当前的设计,因为您必须先传递动作和实体,然后再将实体传递给动作。

If you express it as an interface on entity you simply pass any entity that implements IExample. 如果将其表示为实体上的接口,则只需传递实现IExample的任何实体。

public interface IExample { bool Validate(); 公共接口IExample {bool Validate(); void Action(); void Action(); } }

Now entity implements IExample, ServiceRequest now takes an IExample as its parameter and bob's your uncle. 现在,实体实现了IExample,ServiceRequest现在将IExample作为其参数,而bob是您的叔叔。

Your original design isn't bad at all, it's perfectly common actually, but becomes restrictive as requirements change (this time action has to be called twice, this time it needs to call validation and then postvalidation). 您的原始设计一点也不差劲,实际上是很普通的,但是随着需求的变化而变得受限(这一次必须调用两次,这一次它需要调用验证,然后再进行验证)。 By expressing it through an interface you make it testable and the pattern can be moved to a helper class designed to replay this particular sequence. 通过通过接口表达它,可以使其可测试,并且可以将模式移到旨在重播此特定序列的帮助器类中。 Once extracted it also becomes testable. 一旦提取,它也可以测试。 It also means if the requirements suddenly require postvalidation to be called you can revisit the entire design. 这也意味着如果需求突然需要调用后验证,则可以重新访问整个设计。

Finally, if the pattern isn't being applied a lot, for example you have perhaps three places in the class, then it might not be worth the effort, just code each long hand. 最后,如果没有大量应用该模式,例如,您在类中可能有三个位置,那么可能不值得花些功夫,只需编写每个长手。 Interestingly, because of the way things are Jitted it might make it faster to have three distinct and complete methods rather than three that all share a common core... 有趣的是,由于事情被束之高阁,它可能会使拥有三种截然不同的完整方法的速度更快,而不是拥有共同核心的三种方法...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM