in

 

Jimmy Bogard

Assistant to the assistant to the regional manager

Separation of Concerns by example: Part 1

In the prelude to this series, I looked at a snippet of code that took the kitchen sink approach to concerns.  Here's what we started out with:

public class CustomerManager
{
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public static List<Customer> GetCustomers(int startRowIndex, int maximumRows)
    {
        List<Customer> customers = null;
        string key = "Customers_Customers_" + startRowIndex + "_" + maximumRows;

        if (HttpContext.Current.Cache[key] != null)
        {
            customers = (List<Customer>) HttpContext.Current.Cache[key];
        }
        else
        {
            customers =
                (
                    from
                        c in DataGateway.Context.Customers
                    orderby
                        c.CustomerID descending
                    select
                        c
                ).Skip(startRowIndex).Take(maximumRows).ToList();

            if ((customers != null) && (customers.Count > 0))
                HttpContext.Current.Cache.Insert(key, customers, null, DateTime.Now.AddDays(1), TimeSpan.Zero);
        }

        return customers;
    }
}

Before we can even think about separating the concerns out, we already have the design working against us.  I prefer a dependency inversion approach to separate concerns, as opposed to having the class or method instantiate them all at once.

When we have a static method, how can we give the dependencies to the class?  I can think of some clever ways, but clever is rarely simple.  I'd like to err on the side of simple.

Additionally, we don't want our domain services mixed in with presentation concerns, such as the DataObjectMethod attribute.  That is in place for the ObjectDataSource control, a presentation object.

So, first order of business, let's make this a plain old class, instead of a static class.

Refactoring away from static methods

Now, presentation-specific classes are just fine.  If our presentation layer needs static methods and some crazy attributes, good for them.

But I don't want those concerns bleeding down into my BLL domain layer.

To fix this, first we'll need to extract method and get all of the contents out of that method (Ctrl+Alt+M ftw):

public class CustomerManager
{
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public static List<Customer> GetCustomers(int startRowIndex, int maximumRows)
    {
        return FindAllCustomers(startRowIndex, maximumRows);
    }

    private static List<Customer> FindAllCustomers(int startRowIndex, int maximumRows)
    {
        List<Customer> customers = null;
        string key = "Customers_Customers_" + startRowIndex + "_" + maximumRows;

        if (HttpContext.Current.Cache[key] != null)
        {
            customers = (List<Customer>) HttpContext.Current.Cache[key];
        }
        else
        {
            customers =
                (
                    from
                        c in DataGateway.Context.Customers
                    orderby
                        c.CustomerID descending
                    select
                        c
                ).Skip(startRowIndex).Take(maximumRows).ToList();

            if ((customers != null) && (customers.Count > 0))
                HttpContext.Current.Cache.Insert(key, customers, null, DateTime.Now.AddDays(1), TimeSpan.Zero);
        }

        return customers;
    }
}

Now that we have a private method, we can use Move Method to move this method to a whole new class.  If this method used any other private (or public) methods, we'd have to deal with them one at a time.  For now, we'll have a new CustomerFinder application-level service class:

public class CustomerFinder
{
    public List<Customer> FindAllCustomers(int startRowIndex, int maximumRows)
    {
        List<Customer> customers = null;
        string key = "Customers_Customers_" + startRowIndex + "_" + maximumRows;

        if (HttpContext.Current.Cache[key] != null)
        {
            customers = (List<Customer>) HttpContext.Current.Cache[key];
        }
        else
        {
            customers =
                (
                    from
                        c in DataGateway.Context.Customers
                    orderby
                        c.CustomerID descending
                    select
                        c
                ).Skip(startRowIndex).Take(maximumRows).ToList();

            if ((customers != null) && (customers.Count > 0))
                HttpContext.Current.Cache.Insert(key, customers, null, DateTime.Now.AddDays(1), TimeSpan.Zero);
        }

        return customers;
    }
}

Now, we don't have any tests in place (this is a full-blown legacy app), so we're just focusing on dependency-breaking techniques.  These techniques preserve existing behavior, which is what we need when we don't have a safety net of unit tests in place.

The existing CustomerManager now needs to change, as it needs to use our new class.  No matter, we'll just instantiate a new CustomerFinder and use that:

public class CustomerManager
{
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public static List<Customer> GetCustomers(int startRowIndex, int maximumRows)
    {
        return new CustomerFinder().FindAllCustomers(startRowIndex, maximumRows);
    }
}

Now our presentation layer is just a very thin layer on top of our application and domain layer.  Which is good, as the presentation layer is the hardest layer to test (and the absolute slooooooowest).

Quick review

We want to change the CustomerManager, but it's a presentation layer class that we still need to service our user interface.  To get around this, we applied a series of refactorings to move the behavior to an application layer service, which we can now work against for future enhancements.  These refactorings included:

We did these in a way such the existing behavior of CustomerManager remained unchanged, but its responsibilities were moved to the CustomerFinder service.

Next time, we'll look at the CustomerFinder to see how we can remove some of the static dependencies (I think I saw a screencast somewhere on that one...)

Published Jun 19 2008, 10:12 PM by bogardj
Filed under:

Comments

 

Joshua Flanagan said:

This is a great idea for a series, and I have a suggestion that might make it a little more effective.

I assume the target audience is readers that are not yet used to this type of development, but are curious enough to learn. Chances are, they are not using ReSharper and references to things like Ctrl_Alt-M will be confusing. It may slow you down a bit, but I bet it would help the readers a lot if you suggest the default Visual Studio key mappings (Ctrl-R, Ctrl-M - I had to look it up) when available, or how to go about the refactoring manually if it is not available in VS.

Yes, everyone should be using a tool like ReSharper, but lets not make it a barrier to entry to understanding the practices.

June 19, 2008 11:31 PM
 

Reflective Perspective - Chris Alcock » The Morning Brew #119 said:

Pingback from  Reflective Perspective - Chris Alcock  &raquo; The Morning Brew #119

June 20, 2008 1:52 AM
 

Colin Jack said:

As Joshua said its a great idea for a series, looking forward to reading the rest.

June 20, 2008 2:41 AM
 

Dew Droplet - June 20, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Droplet - June 20, 2008 | Alvin Ashcraft's Morning Dew

June 20, 2008 10:59 AM
 

Coding in an Igloo said:

Separation of Concerns

June 20, 2008 12:02 PM
 

Arjan`s World » LINKBLOG for June 20, 2008 said:

Pingback from  Arjan`s World    &raquo; LINKBLOG for June 20, 2008

June 20, 2008 2:20 PM
 

Code Monkey Labs said:

General Separation of Concerns by Example, Part 1 : I find that examples are one of the best ways to learn a topic. As a follow up to an example of what not to do , Jimmy Bogard has written an excellent post about how separation of concerns should be

June 20, 2008 2:36 PM
 

Duncan said:

Thanks, this looks like a great example, I am looking forward to the rest of the series, I like description of the refactoring to solve the static method problem.

June 22, 2008 6:07 PM
 

DotNetKicks.com said:

You've been kicked (a good thing) - Trackback from DotNetKicks.com

June 22, 2008 6:14 PM
 

gOODiDEA.NET said:

.NET Dynamic Compilation How is my C# code converted into machine instructions Becoming a Jedi - Part

June 22, 2008 7:09 PM
 

GrabBag said:

Separation of concerns is one of the fundamental tenets of good object-oriented design. Anyone can throw

June 24, 2008 8:17 AM
 

GrabBag said:

We made quite a bit of progress separating out the concerns in Part 2, but there are still some issues

June 26, 2008 11:14 PM
 

GrabBag said:

In the last part, we finally broke out the caching and data access concerns from our original class.

July 10, 2008 7:57 AM
 

GrabBag said:

In our last example, disaster finally struck our quaint little application. A strange defect showed up

July 17, 2008 8:23 AM
 

Tim Barcz said:

In church last week we were talking about evolution when the term &quot;Irreducible Complexity&quot;

September 2, 2008 9:49 AM
 

Community Blogs said:

In church last week we were talking about evolution when the term &quot;Irreducible Complexity&quot;

September 2, 2008 10:03 AM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add

About bogardj

I'm a senior consultant with Headspring Systems in Austin, TX. My focus is using .NET technologies together with Agile methodologies. Back in 2005, I drank the Agile punch and haven't looked at a waterfall the same since.
Copyright Los Techies 2007. All rights reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems