Introduction
This is a simple policy based design sample. They are File Name Policy classes that I use the policy based analysis to design policies. In policy based design, the most important thing is analysis. At first, we must define the policy combined roles and implement them in the policy host classes. Then we must decompose the classes to small policy classes.
C# does not support multiple inheritance, but the multiple inheritance is the key technique in policy based design, so we must use interface to solve it.
What is Policy Based Design?
We can find some explanation on Wiki. And one day, I got a feel about it.
I love to eat food, so I thought about the policy recipe of cuisine. Maybe I can define a role to make a cuisine as follows:
Cuisine = Food + Flavor + Cooking
- The cuisine is the host combinable role. Food, Flavor and Cooking are policy groups.
- Food: Beef, Pork, Chicken, Spaghetti ...
- Flavor: Acid, Sweet, Hot, Curry ...
- Cooking: Roast, Cook, Fry, Stew ...
- Cuisine(Steak) = Food(Beef) + Flavor(None) + Cooking(Roast)
- Cuisine(Roast chicken) = Food(Chicken) + Flavor(Curry) + Cooking(Roast)
The recipe of cuisine is a combination of food, flavor and cooking.
A different policy can combine a different cuisine.
I think this explanation is easiest to understand.
Background
I loved the template in C++ and I love the design patterns with Object-Oriented Programming. So I found a book called Modern C++ Design.
After I studied policy based design in this book, I loved it and tried to implement it in C#.
Class Diagrams
The base interface is CBINamePolicy
, it just has a property Name
.
All of FileNamePolicy
classes will implement these interfaces including CBIFileNamePolicy
, CBIFilenameExtensionPolicy
and CBIFileAnmeControl
.
These are policy host classes. The base class is CBFileName
implemented.
These classes implement the CBIFileNamePolicy
interface.
These classes implement CBIFilenameExtensionPolicy
interface.
These classes implement CBIFileNameControlPolicy
and CBILargeFileSplitPolicy
interface.
Using the Code
public interface CBINamePolicy
{
string Name { get; set; }
}
public interface CBIFileNamePolicy : CBINamePolicy
{
}
public class CBFileNamePolicy : CBIFileNamePolicy
{
public string Name { get; set; }
}
public class CBFullFileNamePolicy : CBIFileNamePolicy
{
string m_name;
public string Name
{
get
{
return CBGeneral.GetFullPath(m_name);
}
set
{
m_name = value;
}
}
}
public class CBDateNowNamePolicy : CBIFileNamePolicy
{
#region CBINamePolicy Members
public string Name
{
get
{
return DateTime.Now.ToString("yyyyMMdd");
}
set
{
throw new NotSupportedException
("CBDateNowNamePolicy.Name is a readonly property.");
}
}
#endregion
}
public class CBPreDateNowNamePolicy : CBIFileNamePolicy
{
string m_name;
#region CBINamePolicy Members
public string Name
{
get
{
return m_name + DateTime.Now.ToString("yyyyMMdd");
}
set
{
m_name = value;
}
}
#endregion
}
public class CBFullDateNowNamePolicy : CBIFileNamePolicy
{
#region CBINamePolicy Members
public string Name
{
get
{
return CBGeneral.GetFullPath(DateTime.Now.ToString("yyyyMMdd"));
}
set
{
throw new NotSupportedException
("CBFullPathDateNowNamePolicy.Name is a readonly property.");
}
}
#endregion
}
public class CBFullPreDateNowNamePolicy : CBIFileNamePolicy
{
string m_name;
#region CBINamePolicy Members
public string Name
{
get
{
return CBGeneral.GetFullPath(m_name + DateTime.Now.ToString("yyyyMMdd"));
}
set
{
m_name = value;
}
}
#endregion
}
In these classes, the Name
property will get a string
for file name.
public interface CBIFilenameExtensionPolicy : CBIFileNamePolicy
{
}
public class CBTextExtensionPolicy : CBIFilenameExtensionPolicy
{
#region CBIFilenameExtensionPolicy Members
public string Name
{
get
{
return "txt";
}
set
{
throw new NotImplementedException();
}
}
#endregion
}
public class CBXmlExtensionPolicy : CBIFilenameExtensionPolicy
{
#region CBIFilenameExtensionPolicy Members
public string Name
{
get
{
return "xml";
}
set
{
throw new NotImplementedException();
}
}
#endregion
}
public class CBLogExtensionPolicy : CBIFilenameExtensionPolicy
{
#region CBIFilenameExtensionPolicy Members
public string Name
{
get
{
return "log";
}
set
{
throw new NotImplementedException();
}
}
#endregion
}
public class CBIniExtensionPolicy : CBIFilenameExtensionPolicy
{
#region CBIFilenameExtensionPolicy Members
public string Name
{
get
{
return "ini";
}
set
{
throw new NotImplementedException();
}
}
#endregion
}
These are readonly classes, just return extension name.
public interface CBIFileNameControlPolicy : CBIFileNamePolicy
{
void Control();
}
public interface CBIFileSplitSizePolicy
{
long MaxSplitSize { get; set; }
}
public interface CBILargeFileSplitPolicy
{
int Index { get; set; }
}
public class CBFileNumSplitPolicy : CBILargeFileSplitPolicy, CBIFileNameControlPolicy
{
public CBFileNumSplitPolicy()
{
Index = 1;
}
public int Index { get; set; }
public virtual string Name
{
get
{
return Index.ToString("00");
}
set
{
throw new NotImplementedException();
}
}
public void Control()
{
Index++;
}
}
public class CBFileTextSplitPolicy : CBFileNumSplitPolicy
{
public CBFileTextSplitPolicy() : base()
{
Index = 0;
}
public override string Name
{
get
{
int ch1 = (Index / 26) + 0x41;
int ch2 = (Index % 26) + 0x41;
return string.Format("{0}{1}",Convert.ToChar(ch1), Convert.ToChar(ch2));
}
set
{
throw new NotImplementedException();
}
}
}
These are file names split with split size property.
Sometimes the log file will split with file size, so I use this policy to control file name.
public abstract class CBFileName : CBIFileNamePolicy, CBIFileSplitSizePolicy
{
const long DEFAULT_SPLIT_SIZE = 1024 * 1024 * 5;
public virtual string Name { get; set; }
public long MaxSplitSize { get; set; }
public CBFileName()
{
MaxSplitSize = DEFAULT_SPLIT_SIZE;
}
}
The CBFileName
is an abstract
class and just has two properties:
public sealed class CBFileName<TNamePolicy> : CBFileName
where TNamePolicy : CBIFileNamePolicy,new()
{
TNamePolicy _policy;
public CBFileName()
: base()
{
_policy = new TNamePolicy();
}
public override string Name
{
get
{
return _policy.Name;
}
set
{
_policy.Name = value;
}
}
}
This is first policy host class.
It accesses _policy.Name
only.
public sealed class CBFileName<TNamePolicy, TNameExtensionPolicy> : CBFileName
where TNamePolicy : CBIFileNamePolicy, new()
where TNameExtensionPolicy : CBIFilenameExtensionPolicy, new()
{
TNamePolicy _policy1;
TNameExtensionPolicy _policy2;
public CBFileName()
: base()
{
_policy1 = new TNamePolicy();
_policy2 = new TNameExtensionPolicy();
}
public override string Name
{
get
{
return _policy1.Name + "." + _policy2.Name;
}
set
{
_policy1.Name = value;
}
}
}
This policy host class has two generic arguments.
In this role, CBFileName.Name
will return _policy1.Name + "." _policy2.Name
.
The _policy1
is CBIFileNamePolicy
, _policy2
is CBIFilenameExtensionPolicy
.
CBFileName name1 =
new CBFileName<CBFileNamePolicy, CBTextExtensionPolicy>();
name1.Name = "Test1";
Console.WriteLine(name1.Name);
We can use it like this sample.
The Name
property will get Test1.txt.
public sealed class CBFileName<TNamePolicy, TNameControlPolicy,
TNameExtensionPolicy> : CBFileName, CBIFileNameControlPolicy
where TNamePolicy : CBIFileNamePolicy, new()
where TNameControlPolicy : CBIFileNameControlPolicy, new()
where TNameExtensionPolicy : CBIFilenameExtensionPolicy, new()
{
TNamePolicy _policy1;
TNameControlPolicy _policy2;
TNameExtensionPolicy _policy3;
public CBFileName()
: base()
{
_policy1 = new TNamePolicy();
_policy2 = new TNameControlPolicy();
_policy3 = new TNameExtensionPolicy();
}
string FileName
{
get
{
return _policy1.Name + "_" + _policy2.Name + "." + _policy3.Name;
}
}
public override string Name
{
get
{
Control();
return FileName;
}
set
{
_policy1.Name = value;
}
}
public void Control()
{
while (true)
{
FileInfo info = new FileInfo(FileName);
if (info.Exists && info.Length > MaxSplitSize)
{
_policy2.Control();
}
else
{
break;
}
}
}
}
This policy host class has three generic arguments.
In this role, CBFileName.Name
will return_policy1.Name + "_" _policy2.Name + "." _policy3.Name
.
The
_policy1
is
CBIFileNamePolicy
,
_policy2
is
CBIFileNameControlPolicy
,
_policy3
is
CBIFilenameExtensionPolicy
.
The implemented control will get file name with MaxSplitSize
.
CBFileName nameNormal =
new CBFileName<CBFullPreDateNowNamePolicy,
CBFileNumSplitPolicy, CBLogExtensionPolicy>();
nameNormal.Name = "Log\\Event_Normal_";
Console.WriteLine(nameNormal.Name);
In this sample, the Name
property is: D:\CloudDevelop\PolicySample\PolicySample\bin\Debug\Log\Event_Normal_20111003_01.log
References
History
- V1.1 Added explanation about policy based design
- V1.0 First done