I was working with a customer a couple of months ago where they were repeating code again and again, for every business entity, so that each business entity had the same base set of persistance methods:
- RetrieveById
- Save
- CreateNew
Before you ask, there lots of reasons why it had to be done this way and was done this way, none of which I'm really supposed to talk about, but suffice it to say that we all occasionally run into a situation where we want a bunch of classes to have the same static methods without recoding this every time and then needing to fix bugs in all of them when we find something. There's a way to use generics to get this effect, this “inheritance” however there is no overriding as such.
The example I'm using below is based around the idea of data persistance, one entity is a Desk and another is a Student. Each has its own PersistanceManager class. I'm not saying this is a great approach to take, but it is easy enough to explain with this that you should see how it is relevant to your own scenarios.
// This interface ensures that the class is able to be populated from a provided datarow and includes an Id property.
public interface IRowPopulatable {
Int32 Id
{
get;
set;
}
void PopulateFrom(DataRow row);
}
// This class represents a Student, though it is mostly empty for purposes of this article.
public class Student : IRowPopulatable
{
protected Int32 _id;
public Int32 Id
{
get { return _id;}
set { _id = value; }
}
// The PopulateFrom method in this class is empty as it doesn't matter relative to the article
public void PopulateFrom(DataRow row)
{
return;
}// PopulateFrom
}// Student
// This class represents a Desk, though it is mostly empty for purposes of this article, it has some differences from the Student.
public class Desk : IRowPopulatable
{
protected string _styleName;
protected Int32 _id;
public Int32 Id
{
get { return _id; }
set { _id = value; }
}// Property ID
public string StyleName
{
get { return _styleName; }
set { _styleName = value; }
} // Property StyleName
public virtual void PopulateFrom(DataRow row)
{
return;
}// PopulateFrom
public Desk() {
_id = -1;
_styleName = string.Empty;
}// Desk Constructor
}// Desk
// This is the main important class, the generic class. It uses a generic T, requires that the type T implement the interface IRowPopulatable simply to allow the static RetrieveById class to be able to be implemented once, generically. The new() requirement is because of the T result = new T().
public class PersistanceManager where T : IRowPopulatable, new()
{
public static T RetrieveById(Int32 id) {
// Defensive check of the id parameter not provided for simplicity
DataTable dt = new DataTable();
// Fake Data Retrieval populating dt with a datatable
T result = new T();
result.PopulateFrom(row);
return result;
}// RetrieveById
public static void Save(T entity)
{
}// Save
}// PersistanceManager
// This class is an example of a PersistanceManager that focuses exclusively on Desk. Note that it has no code other than a default constructor, which is not needed but is provided to illustrate that there is no code missing. The DeskPersistanceManager automatically has a Save and a RetrieveById static method.
public class DeskPersistanceManager : PersistanceManager
{
// The DeskPersistanceManager now has a RetrieveById method that returns
// a desk with no addition code
public DeskPersistanceManager()
{
}// DeskPersistanceManager
}// DeskPersistanceManager
// This class is an example of a PersistanceManager that focuses exclusively on Student. Note that it has no code other than a default constructor, which is not needed but is provided to illustrate that there is no code missing. The StudentPersistanceManager automatically has a Save and a RetrieveById static method. However as you would expect with generics, StudentPersistanceManager and DeskPersistanceManager do not have a common parent from which they inherit.
public class StudentPersistanceManager : PersistanceManager
{
// The StudentPersistanceManager now has a RetrieveById method that returns
// a student with no addition code
public StudentPersistanceManager()
{
}// StudentPersistanceManager
public static new void Save(Student sampleStudent)
{
// Do my own thing
return;
}
}// StudentPersistanceManager
With all of this code providing the interface, the two entities, the generic 'base' PersistanceManager and the 2 entity-specific PersistanceManagers we can now do the following
class Program
{
static void Main(string[] args)
{
Desk sampleDesk = DeskPersistanceManager.RetrieveById(5);
Student sampleStudent = StudentPersistanceManager.RetrieveById(3);
}// Main
}// Program
When you look at the Intelli-Sense for DeskPersistManager and StudentPersistanceManager you see the RetrievebyId which is specific to them, i.e. one returning Desk and the other Student respectively. Both have also a Save method, as expected.
The purpose of this technique is to allow you to have a means for avoiding recoding static methods again and again between classes for shared purposes. It does not allow you to have a defined interface, or anything, that would then allow you to iterate over multiple classes using them polymorphically as some common parent because they don't have a common parent. Remember, in .NET List and List do not have a common parent of List, they are effective ListInt and ListString classes with no ancestors.
As always, let me know what you think. driss @ zouak . com
PS: Extending
Want your own static Save method in StudentPersistanceManager or need to change something? For example:
public static new void Save(Student sampleStudent) {
// Trivial but effective
return ;
}
As you would expect, if you don't add the New or you add any additional Save method, you will get a warning that you are hiding the Save method from the PersistanceManager. However, part of the point is to standardize your approach so use this with caution.