Martin Fowler’s Design Pattern Domain Model in C# (With UI)
Most of the example code in Martin Fowler’s book Patterns of Enterprise Application Architecture are in Java. Here I’m trying to write the equivalent code in C#. I will also provide a simple user interface.
The examples are not a complete application. They’re stripped down to explain the principle. Here I have kept them as thus.
I have made some modifications to the code where I thought there were bugs. This is a running application and has been tested on Visual Studio 2019 (version 16.10.3).
I wanted this to be an experience such as going through a tutorial. But I haven’t explained some of the basics like how to start a new project in Visual Studio, how to add components to the user interface etc. I’m assuming that you already know some of these things. (If you don’t have a look at the web to learn what you have to learn before you start this tutorial).
This shouldn’t be taken as a guide to organizing a software project. There’re best practices I have neglected. Such as naming the components with meaningful names. I wanted only to transfer the knowledge about the pattern.
I have not described the design pattern at all here. For that you’ll have to go to the book.
Here’s what the UI looks like.
Here’s the code for the Form1.cs class.
public partial class Form1 : Form
{
Contract contractWord;
Contract contractCalc;
Contract contractDB;public Form1()
{
InitializeComponent();
}
private void btnWordProcessor_Click(object sender, EventArgs e)
{
Product word = Product.NewWordProcessor("Thinking Word");
contractWord = new Contract(word, 100.00M, DateTime.Today);
contractWord.CalculateRecognitions();
cmbContract.Items.Add("Word Processor Contract");
}private void btnSpreadSheet_Click(object sender, EventArgs e)
{
Product calc = Product.NewSpreadSheet("Thinking Calc");
contractCalc = new Contract(calc, 150.00M, DateTime.Today);
contractCalc.CalculateRecognitions();
cmbContract.Items.Add("Spread Sheet Contract");
}private void btnDatabase_Click(object sender, EventArgs e)
{
Product db = Product.NewDatabase("Thinking DB");
contractDB = new Contract(db, 200.00M, DateTime.Today);
contractDB.CalculateRecognitions();
cmbContract.Items.Add("Database Contract");
}private void btnCalculate_Click(object sender, EventArgs e)
{
string selectedIndex = cmbContract.Text;
switch (selectedIndex)
{
case "Word Processor Contract":
lblRevenue.Text = contractWord.RecognizedRevenue(dtpAsOf.Value).ToString("0.00");
break;
case "Spread Sheet Contract":
lblRevenue.Text = contractCalc.RecognizedRevenue(dtpAsOf.Value).ToString("0.00");
break;
case "Database Contract":
lblRevenue.Text = contractDB.RecognizedRevenue(dtpAsOf.Value).ToString("0.00");
break;
}
}
}
Here’s the code for the Contract class.
public class Contract
{
private Product product;
private decimal revenue;
private DateTime whenSigned;
private long id;
private ArrayList revenueRecognitions = new ArrayList();public Contract(Product product, decimal revenue, DateTime whenSigned)
{
this.product = product;
this.revenue = revenue;
this.whenSigned = whenSigned;
}public decimal GetRevenue()
{
return revenue;
}public DateTime GetWhenSigned()
{
return whenSigned;
}
public void AddRevenueRecognition(RevenueRecognition revenueRecognition)
{
revenueRecognitions.Add(revenueRecognition);
}
public decimal RecognizedRevenue(DateTime asOf)
{
decimal result = 0.0M;foreach(RevenueRecognition r in revenueRecognitions)
{
if(r.IsRecognizableBy(asOf))
{
result += r.GetAmount();
}
}
return result;
}public void CalculateRecognitions()
{
product.CalculateRevenueRecognitions(this);
}
}
Here’s the code for the Product class.
public class Product
{
private string name;
private RecognitionStrategy recognitionStrategy;public Product(string name, RecognitionStrategy recognitionStrategy)
{
this.name = name;
this.recognitionStrategy = recognitionStrategy;
}public static Product NewWordProcessor(string name)
{
return new Product(name, new CompleteRecognitionStrategy());
}
public static Product NewSpreadSheet(string name)
{
return new Product(name, new ThreeWayRecognitionStrategy(60, 90));
}public static Product NewDatabase(string name)
{
return new Product(name, new ThreeWayRecognitionStrategy(30, 60));
}public void CalculateRevenueRecognitions(Contract contract)
{
recognitionStrategy.CalculateRevenueRecognitions(contract);
}
}
Here’s the code for the RevenueRecognition class.
public class RevenueRecognition
{
private decimal amount;
private DateTime date;
public RevenueRecognition(decimal amount, DateTime date)
{
this.amount = amount;
this.date = date;
}
public decimal GetAmount()
{
return amount;
}
public bool IsRecognizableBy(DateTime asOf)
{
return asOf >= date;
}
}
Here’s the code for the RecognitionStrategy class.
public abstract class RecognitionStrategy
{
public abstract void CalculateRevenueRecognitions(Contract contract);
}
Here’s the code for the CompleteRecognitionStrategy class.
public class CompleteRecognitionStrategy : RecognitionStrategy
{
public override void CalculateRevenueRecognitions(Contract contract)
{
contract.AddRevenueRecognition(new RevenueRecognition(contract.GetRevenue(), contract.GetWhenSigned()));
}
}
Here’s the code for the ThreeWayRecognitionStrategy class.
public class ThreeWayRecognitionStrategy : RecognitionStrategy
{
private int firstRecognitionOffset;
private int secondRecognitionOffset;public ThreeWayRecognitionStrategy(int firstRecognitionOffset, int secondRecognitionOffset)
{
this.firstRecognitionOffset = firstRecognitionOffset;
this.secondRecognitionOffset = secondRecognitionOffset;
}public override void CalculateRevenueRecognitions(Contract contract)
{
decimal allocation = contract.GetRevenue() / 3;contract.AddRevenueRecognition(new RevenueRecognition(allocation, contract.GetWhenSigned()));
contract.AddRevenueRecognition(new RevenueRecognition(allocation, contract.GetWhenSigned().AddDays(firstRecognitionOffset)));
contract.AddRevenueRecognition(new RevenueRecognition(allocation, contract.GetWhenSigned().AddDays(secondRecognitionOffset)));
}
}