This evening I continued my journey with the C# Generics. I refactored what I described in my post “DataAccessLayer.FindAll(PublishedBy(Author))” to be able to use the Design Pattern Interpreter.
The result in the ASPX code behind file is the following:
AndSpec spec = new AndSpec(
new AndSpec(
new PublishedSpec(),
new BeforeDateSpec(DateTime.Parse("01/01/2005"))
),
new OrSpec(
new AuthorSpec("Mathieu Kempé"),
new AuthorSpec("Laurent Kempé")
)
);
DataAccessor<Article> articles = new DataAccessor<Article>("GetArticles");
GridView3.DataSource = articles.FindAll(Matching(spec));
GridView3.DataBind();
With the unique Matching Predicate, replacing all the other Predicate:
protected Predicate<Article> Matching(Spec spec)
{
return delegate(Article a)
{
return spec.isSatisfiedBy(a);
};
}
To achieve this I first added two more properties to my Entity, Article. I did not added new constructors because those two properties are not mandatory:
private DateTime datePublished;
public DateTime DatePublished
{
get { return datePublished; }
set { datePublished = value; }
}
private DateTime dateModified;
public DateTime DateModified
{
get { return dateModified; }
set { dateModified = value; }
}
Then I modified my Data Access Layer to have it a bit more generic, starting with the interface :
interface IDataAccess<T>
{
List<T> GetAll();
List<T> FindAll(Predicate<T> match);
}
I removed the method T Get(Guid uuid)
and T Get(string uuid)
because that can be easily expressed with a Predicate.
I made a Generic implementation of my Data Access through a DataAccessor class:
namespace TechHeadBrothers.Portal.DAL
{
public class DataAccessor<T> : IDataAccess<T> where T : new()
{
List<T> list = null;
public DataAccessor(string sp)
{
readFromDatabase(sp);
}
#region Protected Methods
protected void readFromDatabase(string sp)
{
<font color="green">// Create Instance of Connection and Command Object
SqlConnection myConnection =
new SqlConnection(ConfigurationManager.ConnectionStrings["TechHeadBrothers"].ConnectionString);
SqlDataAdapter myCommand = new SqlDataAdapter(sp, myConnection);
<font color="green">// Mark the Command as a SPROC
myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
<font color="green">// Create and Fill the DataSet
DataSet ds = new DataSet();
myCommand.Fill(ds);
myConnection.Close();
this.list = DataAdapterFactory.createAdapter<T>().Adapt(ds.Tables[0]);
}
#endregion
#region IDataAccess<T> Members
public List<T> GetAll()
{
return list;
}
public List<T> FindAll(Predicate<T> match)
{
return list.FindAll(match);
}
#endregion
}
}
I had to implement an Adapter to convert the data from the Database representation to the Entity :
abstract class DataAdapter<T>
{
public List<T> Adapt(DataTable table)
{
List<T> list = new List<T>(table.Rows.Count);
foreach (DataRow row in table.Rows)
{
list.Add(this.Adapt(row));
}
return list;
}
public abstract T Adapt(DataRow row);
}
And a Factory :
class DataAdapterFactory
{
public static DataAdapter<T> createAdapter<T>() where T : new()
{
string name = new T().GetType().Name.ToLower();
if ( name == "article")
return new ArticleAdapter() as DataAdapter<T>;
else if ( name == "author")
return new AuthorAdapter() as DataAdapter<T>;
return null;
}
}
Here is the concrete implementation for the Article Entity Adapter:
class ArticleAdapter : DataAdapter<Article>
{
public override Article Adapt(DataRow row)
{
Article article = new Article((string)row["Title"],
(string)row["Description"],
(string)row["Author"],
(bool)row["isPublished"],
(Guid)row["uuid"]);
if (row["DatePublished"] != DBNull.Value)
{
article.DatePublished = (DateTime)row["DatePublished"];
}
if (row["DateModified"] != DBNull.Value)
{
article.DateModified = (DateTime)row["DateModified"];
}
return article;
}
}
I made also a Business Layer generic class, but has it is just for the moment a wrapper around the DataAccessor, I will not show it.
And finally the Interpreter Design Pattern:
public abstract class Spec
{
public abstract bool isSatisfiedBy(Article article);
}
With the different concrete Specifications:
public class PublishedSpec : Spec
{
public PublishedSpec()
{
}
public override bool isSatisfiedBy(Article article)
{
return (article.isPublished == true);
}
}
public class BeforeDateSpec : Spec
{
private DateTime date;
public DateTime Date
{
get { return date; }
set { date = value; }
}
public BeforeDateSpec(DateTime date)
{
this.Date = date;
}
public override bool isSatisfiedBy(Article article)
{
return (article.DatePublished < this.Date);
}
}
public class AuthorSpec : Spec
{
private string author;
public string Author
{
get { return author;}
set { author = value; }
}
public AuthorSpec (string author)
{
this.Author = author;
}
public override bool isSatisfiedBy(Article article)
{
return (article.Author == this.Author);
}
}
public class NotSpec : Spec
{
private Spec specToNegate;
public NotSpec(Spec specToNegate)
{
this.specToNegate = specToNegate;
}
public override bool isSatisfiedBy(Article article)
{
return !specToNegate.isSatisfiedBy(article);
}
}
public class AndSpec : Spec
{
private Spec augend;
public Spec Augend
{
get { return augend;}
}
private Spec addend;
public Spec Addend
{
get { return addend;}
}
public AndSpec (Spec augend, Spec addend)
{
this.augend = augend;
this.addend = addend;
}
public override bool isSatisfiedBy(Article article)
{
return Augend.isSatisfiedBy(article) && Addend.isSatisfiedBy(article);
}
}
public class OrSpec : Spec
{
private Spec augend;
public Spec Augend
{
get { return augend; }
}
private Spec addend;
public Spec Addend
{
get { return addend; }
}
public OrSpec(Spec augend, Spec addend)
{
this.augend = augend;
this.addend = addend;
}
public override bool isSatisfiedBy(Article article)
{
return Augend.isSatisfiedBy(article) || Addend.isSatisfiedBy(article);
}
}