开发者

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).

If I have a class that performs some action in every method, let's pretending doing a try/catch and some other stuff:

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();
        });
    }

开发者_如何学Go    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. Think of adding validation, there would be the need to say if(!this.Validate(entity))... in each method.

Is this too difficult to maintain/ugly/bad design?


Using lambda expressions usually reduces readability. 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.


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.

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. 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. 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.). 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. So in your example at the end you mention perhaps using 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.

public interface IExample { bool Validate(); void Action(); }

Now entity implements IExample, ServiceRequest now takes an IExample as its parameter and bob's your uncle.

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...

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜