An employee’s salary is calculated from two elements – Basic
and StandardPoint
. One employee will get one Basic and one or more (or no) StandardPoint each month.
There are various StandardPoints namely – StarPerformerPoint , RecognitionPoint , ReferralPoint, BrandingPoint.
There is a functionality to calculate TotalAnnualSalary. In a year, an employee is eligible to avail a maximum of 4 standards points. Even if he gets more points only 4 will be calculated for total annual salary calculation.
I have following code. It works fine. However there is a non-efficient memory utilization. The StandardPointElement is created multiple times.
How can we optimize the object creation in this scenario?
UPDATE
Can we use something like flyweight pattern?
Wikipedia says about Flyweight pattern
A flyweight is an object that minimizes memory use by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory.
In the article Flyweight Design Pattern - C#, Shailendra Chauhan mentions the following
Extrinsic data is computed on the fly means at runtime and it is held outside of a flyweight object. Hence it can be stateful.
Why is the Flyweight Pattern practical?
Code
interface ISalaryScoreElement
{
int SalaryScore { get; }
}
public class BasicElement : ISalaryScoreElement
{
public int SalaryScore
{
get
{
return 100;
}
}
}
public class StandardPointElement : ISalaryScoreElement
{
public int SalaryScore
{
get
{
return 10;
}
}
}
Employee Class
class Employee
{
private List<string> eligibility;
public List<string> EligibleSalaryTypes
{
get
{
return eligibility;
}
}
public Employee(List<string> eligibility)
{
this.eligibility = eligibility;
}
public int GetTotalAnnualSalary()
{
int totalSalary = 0;
ISalaryScoreElement sal = null;
CalculatorFactory factory = new CalculatorFactory();
int occurenceCountForStandardPoint = 0;
foreach (string salaryType in EligibleSalaryTypes)
{
switch (salaryType)
{
case "Basic":
sal = factory.GetSalaryElement("BasicElement");
break;
case "ReferralPoint":
sal = factory.GetSalaryElement("StandardPointElement");
break;
case "BrandingPoint":
sal = factory.GetSalaryElement("StandardPointElement");
break;
case "RecognitionPoint":
sal = factory.GetSalaryElement("StandardPointElement");
break;
case "StarPerformerPoint":
sal = factory.GetSalaryElement("StandardPointElement");
break;
default:
throw new Exception("No mapping available");
}
if (sal is StandardPointElement)
{
occurenceCountForStandardPoint++;
if (occurenceCountForStandardPoint > 4)
{
//StandardPointElement can be considered a maximum of 4 times for salary calculation
continue;
}
}
totalSalary = totalSalary + sal.SalaryScore;
}
return totalSalary;
}
}
Factory
class CalculatorFactory
{
public ISalaryScoreElement GetSalaryElement(string salaryKey)
{
ISalaryScoreElement c = null;
switch (salaryKey)
{
case "BasicElement":
c = new BasicElement();
break;
case "StandardPointElement":
c = new StandardPointElement();
break;
default:
throw new Exception("Factory cannot create the object specified");
}
return c;
}
}
Client
class Program
{
static void Main(string[] args)
{
List<string> eligibleBonus = new List<string>();
//For January 2013
eligibleBonus.Add("Basic");
eligibleBonus.Add("StarPerformerPoint");
//For February 2013
eligibleBonus.Add("Basic");
eligibleBonus.Add("StarPerformerPoint");
eligibleBonus.Add("ReferralPoint");
//For March 2013
eligibleBonus.Add("Basic");
eligibleBonus.Add("BrandingPoint");
eligibleBonus.Add("RecognitionPoint");
//For April 2013
eligibleBonus.Add("Basic");
eligibleBonus.Add("BrandingPoint");
Employee e = new Employee(eligibleBonus);
int effectiveSalary = e.GetTotalAnnualSalary();
Console.WriteLine(effectiveSalary);
Console.ReadKey();
}
}
I am using flyweight pattern. [But this may not be the best solution]
REFERENCES
Flyweight Factory
class CalculatorFlyweightFactory
{
Dictionary<string, ISalaryScoreElement> calculators = new Dictionary<string, ISalaryScoreElement>();
public int TotalObjectsCreated
{
get { return calculators.Count; }
}
public ISalaryScoreElement GetSalaryElement(string salaryKey)
{
ISalaryScoreElement c = null;
if (calculators.ContainsKey(salaryKey))
{
c = calculators[salaryKey];
}
else
{
switch (salaryKey)
{
case "BasicElement":
c = new BasicElement();
calculators.Add("BasicElement", c);
break;
case "StandardPointElement":
c = new StandardPointElement();
calculators.Add("StandardPointElement", c);
break;
default:
throw new Exception("Factory cannot create the object specified");
}
}
return c;
}
}
Flyweights
interface ISalaryScoreElement
{
int SalaryScore { get; }
//Newly Added
int OccurenceCount { get; set; }
}
public class BasicElement : ISalaryScoreElement
{
public int SalaryScore
{
get
{
return 100;
}
}
public int OccurenceCount { get; set; }
}
public class StandardPointElement : ISalaryScoreElement
{
public int SalaryScore
{
get
{
return 10;
}
}
public int OccurenceCount { get; set; }
}
Function
public int GetTotalAnnualSalary()
{
int totalSalary = 0;
ISalaryScoreElement sal = null;
CalculatorFlyweightFactory flyweightFactory = new CalculatorFlyweightFactory();
foreach (string salaryType in EligibleSalaryTypes)
{
switch (salaryType)
{
case "Basic":
sal = flyweightFactory.GetSalaryElement("BasicElement");
break;
case "ReferralPoint":
sal = flyweightFactory.GetSalaryElement("StandardPointElement");
break;
case "BrandingPoint":
sal = flyweightFactory.GetSalaryElement("StandardPointElement");
break;
case "RecognitionPoint":
sal = flyweightFactory.GetSalaryElement("StandardPointElement");
break;
case "StarPerformerPoint":
sal = flyweightFactory.GetSalaryElement("StandardPointElement");
break;
default:
throw new Exception("No mapping available");
}
if (sal is StandardPointElement && sal.OccurenceCount >= 4)
{
//StandardPointElement can be considered a maximum of 2 times for salary calculation
continue;
}
sal.OccurenceCount = sal.OccurenceCount + 1;
totalSalary = totalSalary + sal.SalaryScore;
}
return totalSalary;
}