Breaking if statements with pattern
Notes: updated with comment from Sean Stapleton
I re-coded SearchCategoryCriteriaType enum type as:
[Flags]
public enum SearchCategoryCriteriaType : int
{
None = 0,
Id = 1,
Name = 2,
CreatedUser = 4,
CreatedDate = 8,
All = Id | Name | CreatedUser | CreatedDate
}
And changed code with this modification. Added unit testing section for this post
Updated in 2010/06/22.
--------------------------------------------------------------------------------------------------------------------------------------------------------------
Do you meet some situations that your function have many if statements for created condition search?
I got some problems when try to coding for search function in this screen:
So I need build the Lambda Expression for this search condition. But I had to custom build this Lambda Expression. And finally, I need being able to get condition for build Lambda Expression for it. I believe that you are very hard to figure out my context. So now I will give you a example for it. Because I want to implement the search in this screen, so I must check for 16 situations (it's meaning id is not null, name is not empty, created user is not empty, created date is null, id and name is not null and empty,...). And we shall check the Search Model and get type of search for each situation, our code will be express as here:
private SearchCategoryCriteriaType GetSearchCategoryCriteriaType(CategorySearchCriteria criteriaModel)
{
SearchCategoryCriteriaType type = SearchCategoryCriteriaType.All;
if (criteriaModel.Id != null)
{
if (!string.IsNullOrEmpty(criteriaModel.Name))
{
if (!string.IsNullOrEmpty(criteriaModel.CreatedUser))
{
if (criteriaModel.CreatedDate != null)
{
type = SearchCategoryCriteriaType.All;
}
else
{
type = SearchCategoryCriteriaType.Id
| SearchCategoryCriteriaType.Name
| SearchCategoryCriteriaType.CreatedUser;
}
}
else
{
if (criteriaModel.CreatedDate != null)
{
type = SearchCategoryCriteriaType.Id
| SearchCategoryCriteriaType.Name
| SearchCategoryCriteriaType.CreatedDate;
}
else
{
type = SearchCategoryCriteriaType.Id
| SearchCategoryCriteriaType.Name;
}
}
}
else
{
if (!string.IsNullOrEmpty(criteriaModel.CreatedUser))
{
if (criteriaModel.CreatedDate != null)
{
type = SearchCategoryCriteriaType.Id
| SearchCategoryCriteriaType.CreatedUser
| SearchCategoryCriteriaType.CreatedDate;
}
else
{
type = SearchCategoryCriteriaType.Id
| SearchCategoryCriteriaType.CreatedUser;
}
}
else
{
if (criteriaModel.CreatedDate != null)
{
type = SearchCategoryCriteriaType.Id
| SearchCategoryCriteriaType.CreatedDate;
}
else
{
type = SearchCategoryCriteriaType.Id;
}
}
}
}
else if (!string.IsNullOrEmpty(criteriaModel.Name))
{
if (!string.IsNullOrEmpty(criteriaModel.CreatedUser))
{
if (criteriaModel.CreatedDate != null)
{
type = SearchCategoryCriteriaType.Name
| SearchCategoryCriteriaType.CreatedUser
| SearchCategoryCriteriaType.CreatedDate;
}
else
{
type = SearchCategoryCriteriaType.Name
| SearchCategoryCriteriaType.CreatedUser;
}
}
else
{
if (criteriaModel.CreatedDate != null)
{
type = SearchCategoryCriteriaType.Name
| SearchCategoryCriteriaType.CreatedDate;
}
else
{
type = SearchCategoryCriteriaType.Name;
}
}
}
else if (!string.IsNullOrEmpty(criteriaModel.CreatedUser))
{
if (criteriaModel.CreatedDate != null)
{
type = SearchCategoryCriteriaType.CreatedUser
| SearchCategoryCriteriaType.CreatedDate;
}
else
{
type = SearchCategoryCriteriaType.CreatedUser;
}
}
else if (criteriaModel.CreatedDate != null)
{
type = SearchCategoryCriteriaType.CreatedDate;
}
return type;
}
And as you see it is very messy code and very hard to maintain, what happen when you must add some criteria in the future?.So how are we do for decrease if statements? Now it is a place that the design pattern jump into. This snippet is many if statement so that we need break it into separating code and one code separation make one responsibility. Have you ever heard about Chain of Responsibility pattern? If you don't hear about it before, please go to my highlight link for read about it, and else you can be imagine that I will break if statement with Chain of Responsibiltity pattern. Now started to breaking it.I will show you the model first, and second is the details implementation of it.
The first step I need make is model the Chain of Criteria that I must to implementation.
I have the SearchCategoryTypeFinder is a abstract class, and 3 classes (AllSearch, NameSearch, IdSearch) are sub-class of it (certainly we have 16 classes, but i don't show it as here, you can find it in the link in the bottom of this post). And the code for it as here:
+ SearchCategoryTypeFinder:
public abstract class SearchCategoryTypeFinder
{
protected SearchCategoryTypeFinder Finder { get; private set; }
public SearchCategoryTypeFinder SetFinder(SearchCategoryTypeFinder finder)
{
Finder = finder;
return this;
}
public abstract SearchCategoryCriteriaType GetSearchCategoryType(CategorySearchCriteria model);
}
+ AllSearch:
public class AllSearch : SearchCategoryTypeFinder
{
public override SearchCategoryCriteriaType GetSearchCategoryType(CategorySearchCriteria model)
{
if (model.Id.CompareTo(null) == 0 && string.IsNullOrEmpty(model.Name)
&& string.IsNullOrEmpty(model.CreatedUser) && model.CreatedDate == null)
{
return SearchCategoryCriteriaType.All;
}
else
if (Finder != null)
return Finder.GetSearchCategoryType(model);
return SearchCategoryCriteriaType.All;
}
}
+ IdSearch:
internal class IdSearch : SearchCategoryTypeFinder
{
public override SearchCategoryCriteriaType GetSearchCategoryType(CategorySearchCriteria model)
{
if (model.Id.CompareTo(null) != 0 && string.IsNullOrEmpty(model.Name)
&& string.IsNullOrEmpty(model.CreatedUser) && model.CreatedDate == null)
{
return SearchCategoryCriteriaType.Id;
}
else
if (Finder != null)
return Finder.GetSearchCategoryType(model);
return SearchCategoryCriteriaType.All;
}
}
+ NameSearch:
internal class NameSearch : SearchCategoryTypeFinder
{
public override SearchCategoryCriteriaType GetSearchCategoryType(CategorySearchCriteria model)
{
if (model.Id.CompareTo(null) == 0 && !string.IsNullOrEmpty(model.Name)
&& string.IsNullOrEmpty(model.CreatedUser) && model.CreatedDate == null)
{
return SearchCategoryCriteriaType.Name;
}
else
if (Finder != null)
return Finder.GetSearchCategoryType(model);
return SearchCategoryCriteriaType.All;
}
}It's very easy to you, isn't it? And the second step I must implement is the Factory class (Abstract Factory patternand Factory Method pattern) for created the search instance and the Search chain builder for building the searchchain. It's model as here:I will show you the code for implement these classes now+ ISearchFinderFactorypublic interface ISearchFinderFactory
{
SearchCategoryTypeFinder CreatedAllSearch();
SearchCategoryTypeFinder CreatedIdSearch();
SearchCategoryTypeFinder CreatedNameSearch();
SearchCategoryTypeFinder CreatedCreatedUserSearch();
SearchCategoryTypeFinder CreatedCreatedDateSearch();
SearchCategoryTypeFinder CreatedIdAndNameSearch();
SearchCategoryTypeFinder CreatedIdAndCreatedUserSearch();
SearchCategoryTypeFinder CreatedIdAndCreatedDateSearch();
SearchCategoryTypeFinder CreatedNameAndCreatedUserSearch();
SearchCategoryTypeFinder CreatedNameAndCreatedDateSearch();
SearchCategoryTypeFinder CreatedCreatedUserAndCreatedDateSearch();
SearchCategoryTypeFinder CreatedIdAndNameAndCreatedUserSearch();
SearchCategoryTypeFinder CreatedIdAndNameAndCreatedDateSearch();
SearchCategoryTypeFinder CreatedIdAndCreatedUserAndCreatedDateSearch();
SearchCategoryTypeFinder CreatedNameAndCreatedUserAndCreatedDateSearch();
SearchCategoryTypeFinder CreatedIdAndNameAndCreatedUserAndCreatedDateSearch();
}+ SearchFinderFactorypublic class SearchFinderFactory : ISearchFinderFactory
{
public SearchCategoryTypeFinder CreatedAllSearch()
{
return new AllSearch();
}
public SearchCategoryTypeFinder CreatedIdSearch()
{
return new IdSearch();
}
public SearchCategoryTypeFinder CreatedNameSearch()
{
return new NameSearch();
}
public SearchCategoryTypeFinder CreatedCreatedUserSearch()
{
return new CreatedUserSearch();
}
public SearchCategoryTypeFinder CreatedCreatedDateSearch()
{
return new CreatedDateSearch();
}
public SearchCategoryTypeFinder CreatedIdAndNameSearch()
{
return new IdAndNameSearch();
}
public SearchCategoryTypeFinder CreatedIdAndCreatedUserSearch()
{
return new IdAndCreatedUserSearch();
}
public SearchCategoryTypeFinder CreatedIdAndCreatedDateSearch()
{
return new IdAndCreatedDateSearch();
}
public SearchCategoryTypeFinder CreatedNameAndCreatedUserSearch()
{
return new NameAndCreatedUserSearch();
}
public SearchCategoryTypeFinder CreatedNameAndCreatedDateSearch()
{
return new NameAndCreatedDateSearch();
}
public SearchCategoryTypeFinder CreatedCreatedUserAndCreatedDateSearch()
{
return new CreatedUserAndCreatedDateSearch();
}
public SearchCategoryTypeFinder CreatedIdAndNameAndCreatedUserSearch()
{
return new IdAndNameAndCreatedUserSearch();
}
public SearchCategoryTypeFinder CreatedIdAndNameAndCreatedDateSearch()
{
return new IdAndNameAndCreatedDateSearch();
}
public SearchCategoryTypeFinder CreatedIdAndCreatedUserAndCreatedDateSearch()
{
return new IdAndCreatedUserAndCreatedDateSearch();
}
public SearchCategoryTypeFinder CreatedNameAndCreatedUserAndCreatedDateSearch()
{
return new NameAndCreatedUserAndCreatedDateSearch();
}
public SearchCategoryTypeFinder CreatedIdAndNameAndCreatedUserAndCreatedDateSearch()
{
return new IdAndNameAndCreatedUserAndCreatedDateSearch();
}
}+ ISearchChainBuilder:public interface ISearchChainBuilder
{
SearchCategoryCriteriaType GetSearchCategoryCriteria(CategorySearchCriteria model);
}+ SearchChainBuilder:public class SearchChainBuilder : ISearchChainBuilder
{
public ISearchFinderFactory SearchFinderFactory { get; private set; }
public SearchChainBuilder() : this(IoC.GetInstance<ISearchFinderFactory>())
{
}
internal SearchChainBuilder(ISearchFinderFactory searchFinderFactory)
{
SearchFinderFactory = searchFinderFactory;
}
public SearchCategoryCriteriaType GetSearchCategoryCriteria(CategorySearchCriteria model)
{
Check.Assert(SearchFinderFactory != null, "SearchFinderFactory is null");
Check.Assert(model != null, "CategorySearchCriteria is null");// This is a pipeline that is built from abstract class
var searchCategoryType = SearchFinderFactory.CreatedAllSearch()
.SetFinder(SearchFinderFactory.CreatedIdSearch())
.SetFinder(SearchFinderFactory.CreatedNameSearch())
.SetFinder(SearchFinderFactory.CreatedCreatedUserSearch())
.SetFinder(SearchFinderFactory.CreatedCreatedDateSearch())
.SetFinder(SearchFinderFactory.CreatedIdAndNameSearch())
.SetFinder(SearchFinderFactory.CreatedIdAndCreatedUserSearch())
.SetFinder(SearchFinderFactory.CreatedIdAndCreatedDateSearch())
.SetFinder(SearchFinderFactory.CreatedNameAndCreatedUserSearch())
.SetFinder(SearchFinderFactory.CreatedNameAndCreatedDateSearch())
.SetFinder(SearchFinderFactory.CreatedCreatedUserAndCreatedDateSearch())
.SetFinder(SearchFinderFactory.CreatedIdAndNameAndCreatedUserSearch())
.SetFinder(SearchFinderFactory.CreatedIdAndNameAndCreatedDateSearch())
.SetFinder(SearchFinderFactory.CreatedIdAndCreatedUserAndCreatedDateSearch())
.SetFinder(SearchFinderFactory.CreatedNameAndCreatedUserAndCreatedDateSearch())
.SetFinder(SearchFinderFactory.CreatedIdAndNameAndCreatedUserAndCreatedDateSearch())
.GetSearchCategoryType(model);
return searchCategoryType;
}
}And finally we only use it very easy as here:public Func<ICategory, bool> CreateCategorySearchCriteria(CategorySearchCriteria criteriaModel)
{
Check.Assert(SearchBuilder != null, "SearchChainBuilder is null");
Check.Assert(criteriaModel != null, "CategorySearchCriteria is null");
// replaced with Chain of criteria
var searchCriteriaType = SearchBuilder.GetSearchCategoryCriteria(criteriaModel);
return new SearchCategoryCriteriaExpression().CreatedCriteriaExpression(criteriaModel, searchCriteriaType);
}+ Unit testing my work as:[Test]
public void GetIdAndNameAndCreatedUserAndCreatedDateWithTwoParamettersTest()
{
var funcSearch = CriteriaFactory.CreateCategorySearchCriteria(model);
var result = funcSearch(testingCategory);
Assert.IsTrue(result == false);
}and when I run it, my result is:Now your code will be easy to read and maintain. And in the future if you have additional condition,you can add it into your chain that don't make effect to your old implemention. So cool!Full of my implementation is put at http://nma.codeplex.com, you can find it in\NewsManagement_VS2010\src\NewsManagement\NMA.Infrastruture.NHibernate\CriteriaExpression\Category\SearchCategory pathHope my post is useful for you. Good bye and see you next time!