We will design a rules engine in Java and utilize design patterns. Let us examine the business case and the hierarchy.
Example of a Simplified Decision Tree:
-
Line of Business: Engineering
-
Location: North America
-
Job Code: Software Engineer
-
Job Grade: Senior
- Salary Range: $120,000 – $150,000
-
Job Grade: Mid-level
- Salary Range: $90,000 – $120,000
-
Job Grade: Senior
-
Job Code: Product Manager
-
Job Grade: Senior
- Salary Range: $130,000 – $160,000
-
Job Grade: Senior
-
Job Code: Software Engineer
-
Location: Europe
-
Job Code: Software Engineer
-
Job Grade: Senior
- Salary Range: €100,000 – €130,000
-
Job Grade: Mid-level
- Salary Range: €70,000 – €100,000
-
Job Grade: Senior
-
Job Code: Software Engineer
-
Location: Asia
-
Job Code: Software Engineer
-
Job Grade: Senior
- Salary Range: $80,000 – $110,000
-
Job Grade: Senior
-
Job Code: Software Engineer
-
Location: North America
-
Line of Business: Marketing
-
Location: North America
-
Job Code: Marketing Specialist
-
Job Grade: Senior
- Salary Range: $90,000 – $120,000
-
Job Grade: Mid-level
- Salary Range: $60,000 – $90,000
-
Job Grade: Senior
-
Job Code: Marketing Specialist
-
Location: Europe
-
Job Code: Marketing Specialist
-
Job Grade: Senior
- Salary Range: €70,000 – €100,000
-
Job Grade: Senior
-
Job Code: Marketing Specialist
-
Location: North America
To implement this example in Java using POJO classes, we’ll create a few classes:
-
SalaryRange
– Represents the salary range withmin
andmax
values. -
SalaryOffer
– Represents an offer made for a specific location, job code, and job grade. -
SalaryDeterminationService
– Contains the logic to determine if a salary offer falls within the specified salary range based on the provided criteria.
Step 1: Create the POJO Classes
1. SalaryRange.java
public class SalaryRange {
private double minSalary;
private double maxSalary;
private String location;
private String jobCode;
private String jobGrade;
// Constructor
public SalaryRange(double minSalary, double maxSalary, String location, String jobCode, String jobGrade) {
this.minSalary = minSalary;
this.maxSalary = maxSalary;
this.location = location;
this.jobCode = jobCode;
this.jobGrade = jobGrade;
}
// Getters and Setters
public double getMinSalary() {
return minSalary;
}
public void setMinSalary(double minSalary) {
this.minSalary = minSalary;
}
public double getMaxSalary() {
return maxSalary;
}
public void setMaxSalary(double maxSalary) {
this.maxSalary = maxSalary;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getJobCode() {
return jobCode;
}
public void setJobCode(String jobCode) {
this.jobCode = jobCode;
}
public String getJobGrade() {
return jobGrade;
}
public void setJobGrade(String jobGrade) {
this.jobGrade = jobGrade;
}
@Override
public String toString() {
return "SalaryRange [minSalary=" + minSalary + ", maxSalary=" + maxSalary +
", location=" + location + ", jobCode=" + jobCode + ", jobGrade=" + jobGrade + "]";
}
}
2. SalaryOffer.java
public class SalaryOffer {
private double offeredSalary;
private String location;
private String jobCode;
private String jobGrade;
// Constructor
public SalaryOffer(double offeredSalary, String location, String jobCode, String jobGrade) {
this.offeredSalary = offeredSalary;
this.location = location;
this.jobCode = jobCode;
this.jobGrade = jobGrade;
}
// Getters and Setters
public double getOfferedSalary() {
return offeredSalary;
}
public void setOfferedSalary(double offeredSalary) {
this.offeredSalary = offeredSalary;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getJobCode() {
return jobCode;
}
public void setJobCode(String jobCode) {
this.jobCode = jobCode;
}
public String getJobGrade() {
return jobGrade;
}
public void setJobGrade(String jobGrade) {
this.jobGrade = jobGrade;
}
@Override
public String toString() {
return "SalaryOffer [offeredSalary=" + offeredSalary +
", location=" + location + ", jobCode=" + jobCode + ", jobGrade=" + jobGrade + "]";
}
}
Step 2: Create the Service Class
SalaryDeterminationService.java
public class SalaryDeterminationService {
// Method to check if the salary offer is within the range
public boolean isOfferWithinRange(SalaryRange range, SalaryOffer offer) {
if (offer.getLocation().equalsIgnoreCase(range.getLocation()) &&
offer.getJobCode().equalsIgnoreCase(range.getJobCode()) &&
offer.getJobGrade().equalsIgnoreCase(range.getJobGrade())) {
double offeredSalary = offer.getOfferedSalary();
return offeredSalary >= range.getMinSalary() && offeredSalary <= range.getMaxSalary();
}
return false;
}
// Method to determine and adjust the offer if needed
public double determineSalaryOffer(SalaryRange range, SalaryOffer offer) {
if (isOfferWithinRange(range, offer)) {
return offer.getOfferedSalary(); // Offer is within range, return as is
} else {
// Adjust the salary to the minimum of the range if it's below
if (offer.getOfferedSalary() < range.getMinSalary()) {
return range.getMinSalary();
}
// Adjust the salary to the maximum of the range if it's above
if (offer.getOfferedSalary() > range.getMaxSalary()) {
return range.getMaxSalary();
}
}
return offer.getOfferedSalary(); // Return original if no adjustment is needed
}
}
Step 3: Testing the Implementation
Main.java
public class Main {
public static void main(String[] args) {
// Define a salary range
SalaryRange range = new SalaryRange(60000, 80000, "New York", "ENG", "Senior");
// Create a salary offer
SalaryOffer offer = new SalaryOffer(75000, "New York", "ENG", "Senior");
// Service to determine if the offer is within range
SalaryDeterminationService service = new SalaryDeterminationService();
// Check if the offer is within the salary range
boolean isWithinRange = service.isOfferWithinRange(range, offer);
System.out.println("Is offer within range? " + isWithinRange);
// Determine the final salary offer
double finalSalary = service.determineSalaryOffer(range, offer);
System.out.println("Final Salary Offer: " + finalSalary);
}
}
Output
If you run the above code, the output would be:
Is offer within range? true
Final Salary Offer: 75000.0
Explanation
- SalaryRange Class: Represents the range of salaries for a specific location, job code, and job grade.
- SalaryOffer Class: Represents a salary offer made for a specific job.
- SalaryDeterminationService: This service checks if a salary offer is within the specified range and adjusts it if necessary.
-
The
isOfferWithinRange
method checks if the offer’s salary falls within the range for the given location, job code, and job grade. -
The
determineSalaryOffer
method ensures the offered salary is adjusted to the minimum or maximum of the range if it is outside the specified bounds.
This simple example shows how you can encapsulate salary range rules and logic in Java objects and services, providing a foundation for more complex business logic or integration with rule engines like Drools.
To implement specific overrides for HR employees in California (e.g., a 10% increase) while maintaining clean and manageable code as more exceptions arise, you can use the Strategy Pattern combined with Method Chaining. This allows you to define different adjustment strategies that can be applied based on various conditions, making it easy to add more rules or exceptions in the future.
Steps to Implement the Strategy Pattern with Method Chaining
- Define an Interface for Salary Adjustment Strategies: Create an interface that defines the method for applying salary adjustments.
- Implement Different Strategy Classes: Create classes that implement the interface for each adjustment rule (e.g., bilingual bonus, California HR adjustment).
- Modify the SalaryAdjustmentService: Allow it to accept and apply different strategies in a maintainable way.
- Update the Main Class: Use the new strategy-based service to apply the adjustments.
Step 1: Define the Salary Adjustment Strategy Interface
Create an interface for salary adjustment strategies.
SalaryAdjustmentStrategy.java
public interface SalaryAdjustmentStrategy {
void applyAdjustment(SalaryOffer salaryOffer, SalaryRange salaryRange);
}
Step 2: Implement the Specific Adjustment Strategies
Now, implement the strategies for the bilingual increase and the California HR adjustment.
BilingualBonusAdjustment.java
public class BilingualBonusAdjustment implements SalaryAdjustmentStrategy {
@Override
public void applyAdjustment(SalaryOffer salaryOffer, SalaryRange salaryRange) {
if ("HR".equalsIgnoreCase(salaryOffer.getLineOfBusiness()) && salaryOffer.isBilingual()) {
double adjustedSalary = salaryOffer.getOfferedSalary() * 1.07; // 7% increase
System.out.println("Applying 7% bilingual increase for HR. Adjusted Salary: " + adjustedSalary);
salaryOffer.setOfferedSalary(adjustedSalary);
}
}
}
CaliforniaHREmployeeAdjustment.java
public class CaliforniaHREmployeeAdjustment implements SalaryAdjustmentStrategy {
@Override
public void applyAdjustment(SalaryOffer salaryOffer, SalaryRange salaryRange) {
if ("HR".equalsIgnoreCase(salaryOffer.getLineOfBusiness()) &&
"California".equalsIgnoreCase(salaryOffer.getLocation())) {
double adjustedSalary = salaryOffer.getOfferedSalary() * 1.10; // 10% increase
System.out.println("Applying 10% increase for California HR employee. Adjusted Salary: " + adjustedSalary);
salaryOffer.setOfferedSalary(adjustedSalary);
}
}
}
Step 3: Modify the SalaryAdjustmentService
Modify the service class to accept a list of strategies and apply them.
SalaryAdjustmentService.java
import java.util.ArrayList;
import java.util.List;
public class SalaryAdjustmentService {
private SalaryOffer salaryOffer;
private SalaryRange salaryRange;
private List<SalaryAdjustmentStrategy> strategies;
public SalaryAdjustmentService(SalaryOffer salaryOffer, SalaryRange salaryRange) {
this.salaryOffer = salaryOffer;
this.salaryRange = salaryRange;
this.strategies = new ArrayList<>();
}
// Method to add a strategy
public SalaryAdjustmentService addStrategy(SalaryAdjustmentStrategy strategy) {
strategies.add(strategy);
return this;
}
// Method to apply all strategies
public SalaryAdjustmentService applyAdjustments() {
for (SalaryAdjustmentStrategy strategy : strategies) {
strategy.applyAdjustment(salaryOffer, salaryRange);
}
ensureSalaryWithinRange(); // Ensure salary is within range after adjustments
return this;
}
// Method to ensure salary is within the range
private void ensureSalaryWithinRange() {
if (salaryOffer.getOfferedSalary() < salaryRange.getMinSalary()) {
System.out.println("Offer is below the minimum range. Adjusting to minimum.");
salaryOffer.setOfferedSalary(salaryRange.getMinSalary());
} else if (salaryOffer.getOfferedSalary() > salaryRange.getMaxSalary()) {
System.out.println("Offer is above the maximum range. Adjusting to maximum.");
salaryOffer.setOfferedSalary(salaryRange.getMaxSalary());
} else {
System.out.println("Offer is within the range.");
}
}
// Method to return the final adjusted offer
public SalaryOffer getFinalOffer() {
return salaryOffer;
}
}
Step 4: Update the Main Class
In the Main
class, you can now add the different strategies and apply them.
Main.java
public class Main {
public static void main(String[] args) {
// Define a salary range
SalaryRange range = new SalaryRange(60000, 80000, "New York", "ENG", "Senior");
// Create a salary offer for a bilingual HR employee in California
SalaryOffer offer = new SalaryOffer(75000, "California", "ENG", "Senior", true, "HR");
// Use the SalaryAdjustmentService with strategy pattern and method chaining
SalaryOffer finalOffer = new SalaryAdjustmentService(offer, range)
.addStrategy(new BilingualBonusAdjustment()) // Add bilingual bonus strategy
.addStrategy(new CaliforniaHREmployeeAdjustment()) // Add California HR adjustment strategy
.applyAdjustments() // Apply all adjustments
.getFinalOffer(); // Get the final adjusted offer
// Output the final salary offer
System.out.println("Final Salary Offer: " + finalOffer.getOfferedSalary());
}
}
Explanation of the Strategy Pattern Implementation
- SalaryAdjustmentStrategy Interface: This interface defines the contract for any salary adjustment strategy, allowing for easy extension in the future.
-
Specific Strategies: Each adjustment logic (e.g., bilingual bonus, California HR adjustment) is encapsulated in its own class, making it easy to manage and extend. New rules can be added by creating new classes that implement the
SalaryAdjustmentStrategy
. - SalaryAdjustmentService: This service now maintains a list of strategies and applies each one in turn. It also ensures that the final salary remains within the defined range.
- Main Class: The main class demonstrates how to create an offer and apply multiple adjustment strategies in a clean and maintainable manner.
Output
Running the Main
class with the given input will produce output similar to:
Applying 7% bilingual increase for HR. Adjusted Salary: 80250.0
Applying 10% increase for California HR employee. Adjusted Salary: 88275.0
Offer is above the maximum range. Adjusting to maximum.
Final Salary Offer: 80000.0
Benefits of This Approach
- Maintainability: New adjustment rules can be added as new classes without changing existing code, promoting the Open/Closed Principle.
-
Flexibility: Strategies can be easily combined or reordered by changing the order of
addStrategy
calls. - Separation of Concerns: Each adjustment logic is encapsulated in its own class, making it clear and easy to understand.
This design can handle multiple exceptions as they arise in the future, keeping the code clean and maintainable.
To ensure that the California rule (10% increase for HR employees in California) does not get overwritten by the default HR rule (7% increase for bilingual HR employees), you can implement a prioritization mechanism within the adjustment strategies. Here’s how you can structure it:
- Check for Specific Conditions First: Always check the more specific rules (like the California rule) before the more general rules (like the bilingual bonus). This way, if the conditions for the California adjustment are met, the adjustment is applied first.
- Flagging Adjustments: You can also use a flag or a condition to indicate if an adjustment has already been applied. If an adjustment is made, subsequent adjustments can check this flag to determine whether they should apply.
-
Order of Strategy Execution: The order in which strategies are added to the
SalaryAdjustmentService
can be controlled to ensure that specific strategies are executed before more general ones.
Implementation Steps
1. Update the Salary Adjustment Strategies
Add logic in the applyAdjustment
methods to prevent further adjustments if a specific adjustment has already been applied.
CaliforniaHREmployeeAdjustment.java
public class CaliforniaHREmployeeAdjustment implements SalaryAdjustmentStrategy {
@Override
public void applyAdjustment(SalaryOffer salaryOffer, SalaryRange salaryRange) {
if ("HR".equalsIgnoreCase(salaryOffer.getLineOfBusiness()) &&
"California".equalsIgnoreCase(salaryOffer.getLocation())) {
double adjustedSalary = salaryOffer.getOfferedSalary() * 1.10; // 10% increase
System.out.println("Applying 10% increase for California HR employee. Adjusted Salary: " + adjustedSalary);
salaryOffer.setOfferedSalary(adjustedSalary);
// Indicate that this specific adjustment has been applied
salaryOffer.setAdjustmentApplied(true); // Add a flag to indicate adjustment
}
}
}
BilingualBonusAdjustment.java
public class BilingualBonusAdjustment implements SalaryAdjustmentStrategy {
@Override
public void applyAdjustment(SalaryOffer salaryOffer, SalaryRange salaryRange) {
// Only apply if no other adjustment has been made
if (!salaryOffer.isAdjustmentApplied() &&
"HR".equalsIgnoreCase(salaryOffer.getLineOfBusiness()) && salaryOffer.isBilingual()) {
double adjustedSalary = salaryOffer.getOfferedSalary() * 1.07; // 7% increase
System.out.println("Applying 7% bilingual increase for HR. Adjusted Salary: " + adjustedSalary);
salaryOffer.setOfferedSalary(adjustedSalary);
salaryOffer.setAdjustmentApplied(true); // Indicate that this adjustment has been applied
}
}
}
2. Update the SalaryOffer Class
You’ll need to add a flag in the SalaryOffer
class to track if an adjustment has been applied.
SalaryOffer.java
public class SalaryOffer {
private double offeredSalary;
private String location;
private String jobCode;
private String jobGrade;
private boolean isBilingual;
private String lineOfBusiness;
private boolean adjustmentApplied; // New flag to indicate if an adjustment has been made
// Constructor
public SalaryOffer(double offeredSalary, String location, String jobCode, String jobGrade, boolean isBilingual, String lineOfBusiness) {
this.offeredSalary = offeredSalary;
this.location = location;
this.jobCode = jobCode;
this.jobGrade = jobGrade;
this.isBilingual = isBilingual;
this.lineOfBusiness = lineOfBusiness;
this.adjustmentApplied = false; // Initialize to false
}
// Getters and Setters...
public boolean isAdjustmentApplied() {
return adjustmentApplied;
}
public void setAdjustmentApplied(boolean adjustmentApplied) {
this.adjustmentApplied = adjustmentApplied;
}
@Override
public String toString() {
return "SalaryOffer [offeredSalary=" + offeredSalary +
", location=" + location + ", jobCode=" + jobCode +
", jobGrade=" + jobGrade + ", isBilingual=" + isBilingual +
", lineOfBusiness=" + lineOfBusiness + "]";
}
}
3. Update the Main Class to Demonstrate the Logic
In the Main
class, you can add strategies to ensure the correct order is applied:
Main.java
public class Main {
public static void main(String[] args) {
// Define a salary range
SalaryRange range = new SalaryRange(60000, 80000, "California", "ENG", "Senior");
// Create a salary offer for a bilingual HR employee in California
SalaryOffer offer = new SalaryOffer(75000, "California", "ENG", "Senior", true, "HR");
// Use the SalaryAdjustmentService with strategy pattern and method chaining
SalaryOffer finalOffer = new SalaryAdjustmentService(offer, range)
.addStrategy(new CaliforniaHREmployeeAdjustment()) // Add California HR adjustment strategy first
.addStrategy(new BilingualBonusAdjustment()) // Then add bilingual bonus strategy
.applyAdjustments() // Apply all adjustments
.getFinalOffer(); // Get the final adjusted offer
// Output the final salary offer
System.out.println("Final Salary Offer: " + finalOffer.getOfferedSalary());
}
}
How This Ensures Correct Behavior
-
Priority of Rules: By adding the
CaliforniaHREmployeeAdjustment
strategy first in theSalaryAdjustmentService
, it is guaranteed to be executed before theBilingualBonusAdjustment
. Thus, if the California condition is met, that adjustment will be applied before checking for the bilingual adjustment. -
Adjustment Flag: The
adjustmentApplied
flag in theSalaryOffer
class prevents subsequent adjustments from being applied if a more specific adjustment (like the California HR adjustment) has already been made. This ensures that once a specific adjustment is applied, other adjustments do not overwrite it.
Example Output
When you run this implementation with a salary offer for a bilingual HR employee in California, you might see the following output:
Applying 10% increase for California HR employee. Adjusted Salary: 82500.0
Offer is within the range.
Final Salary Offer: 82500.0
This approach allows you to maintain a clean and extensible system that easily accommodates additional rules or exceptions without fear of unintended overwriting of more specific adjustments.
To handle complex rule sets with specific overrides for different lines of business (LOBs) like Financial and HR, you can use a combination of the Strategy Pattern and Factory Pattern. This approach allows you to define LOB-specific rules and overrides in a modular way, and dynamically apply only the rules relevant to a particular LOB.
Key Concepts:
- Strategy Pattern: Allows you to define a family of algorithms (in this case, salary adjustments) and make them interchangeable. Each LOB can have its own set of strategies.
- Factory Pattern: Used to create the appropriate set of strategies for a given LOB. This ensures that only the relevant rules are applied based on the LOB.
- Chaining and Priority Management: Ensures that more specific rules (like the manager weekend rule) are applied after more general rules (like the weekend rule).
Implementation Steps:
- Define the Salary Adjustment Strategy Interface: This remains the same as before.
-
Create Specific Strategy Classes for Financial LOB:
- A class for the weekend adjustment.
- A class for the manager weekend adjustment.
- Create a Factory to Generate the Correct Set of Strategies: This factory will take the LOB as input and return the appropriate set of strategies.
- Integrate with the Salary Adjustment Service: Use the factory to apply only the relevant strategies for the given LOB.
- Use the Factory and Strategies in the Main Class: Apply the correct rules based on the LOB.
Step 1: Salary Adjustment Strategy Interface
This remains unchanged:
public interface SalaryAdjustmentStrategy {
void applyAdjustment(SalaryOffer salaryOffer, SalaryRange salaryRange);
}
Step 2: Create Financial LOB-Specific Strategies
WeekendBonusAdjustment.java
public class WeekendBonusAdjustment implements SalaryAdjustmentStrategy {
@Override
public void applyAdjustment(SalaryOffer salaryOffer, SalaryRange salaryRange) {
if ("FIN".equalsIgnoreCase(salaryOffer.getLineOfBusiness()) && salaryOffer.worksOnWeekends()) {
double adjustedSalary = salaryOffer.getOfferedSalary() * 1.05; // 5% increase
System.out.println("Applying 5% weekend bonus for Financial. Adjusted Salary: " + adjustedSalary);
salaryOffer.setOfferedSalary(adjustedSalary);
salaryOffer.setAdjustmentApplied(true);
}
}
}
ManagerWeekendBonusAdjustment.java
public class ManagerWeekendBonusAdjustment implements SalaryAdjustmentStrategy {
@Override
public void applyAdjustment(SalaryOffer salaryOffer, SalaryRange salaryRange) {
if ("FIN".equalsIgnoreCase(salaryOffer.getLineOfBusiness()) &&
salaryOffer.worksOnWeekends() && salaryOffer.isManager()) {
double adjustedSalary = salaryOffer.getOfferedSalary() * 1.08; // 8% increase
System.out.println("Applying 8% weekend bonus for Financial Managers. Adjusted Salary: " + adjustedSalary);
salaryOffer.setOfferedSalary(adjustedSalary);
salaryOffer.setAdjustmentApplied(true);
}
}
}
Step 3: Create a Factory to Generate the Correct Set of Strategies
SalaryAdjustmentStrategyFactory.java
import java.util.ArrayList;
import java.util.List;
public class SalaryAdjustmentStrategyFactory {
public static List<SalaryAdjustmentStrategy> getStrategiesForLOB(String lob) {
List<SalaryAdjustmentStrategy> strategies = new ArrayList<>();
switch (lob.toUpperCase()) {
case "HR":
strategies.add(new CaliforniaHREmployeeAdjustment());
strategies.add(new BilingualBonusAdjustment());
break;
case "FIN":
strategies.add(new ManagerWeekendBonusAdjustment()); // Specific manager rule first
strategies.add(new WeekendBonusAdjustment());
break;
// Add other LOB-specific strategies here...
}
return strategies;
}
}
Step 4: Integrate with the Salary Adjustment Service
Modify the SalaryAdjustmentService
to accept a list of strategies directly.
SalaryAdjustmentService.java
import java.util.List;
public class SalaryAdjustmentService {
private SalaryOffer salaryOffer;
private SalaryRange salaryRange;
private List<SalaryAdjustmentStrategy> strategies;
public SalaryAdjustmentService(SalaryOffer salaryOffer, SalaryRange salaryRange, List<SalaryAdjustmentStrategy> strategies) {
this.salaryOffer = salaryOffer;
this.salaryRange = salaryRange;
this.strategies = strategies;
}
// Method to apply all strategies
public SalaryAdjustmentService applyAdjustments() {
for (SalaryAdjustmentStrategy strategy : strategies) {
strategy.applyAdjustment(salaryOffer, salaryRange);
}
ensureSalaryWithinRange(); // Ensure salary is within range after adjustments
return this;
}
// Method to ensure salary is within the range
private void ensureSalaryWithinRange() {
if (salaryOffer.getOfferedSalary() < salaryRange.getMinSalary()) {
System.out.println("Offer is below the minimum range. Adjusting to minimum.");
salaryOffer.setOfferedSalary(salaryRange.getMinSalary());
} else if (salaryOffer.getOfferedSalary() > salaryRange.getMaxSalary()) {
System.out.println("Offer is above the maximum range. Adjusting to maximum.");
salaryOffer.setOfferedSalary(salaryRange.getMaxSalary());
} else {
System.out.println("Offer is within the range.");
}
}
// Method to return the final adjusted offer
public SalaryOffer getFinalOffer() {
return salaryOffer;
}
}
Step 5: Use the Factory and Strategies in the Main Class
In the Main
class, you can now apply the relevant rules based on the LOB.
Main.java
public class Main {
public static void main(String[] args) {
// Define a salary range
SalaryRange range = new SalaryRange(60000, 80000, "California", "ENG", "Senior");
// Create a salary offer for a Financial Manager working on weekends
SalaryOffer offer = new SalaryOffer(75000, "California", "ENG", "Senior", false, "FIN", true, true);
// Get the appropriate strategies for the Financial LOB
List<SalaryAdjustmentStrategy> strategies = SalaryAdjustmentStrategyFactory.getStrategiesForLOB(offer.getLineOfBusiness());
// Use the SalaryAdjustmentService with the appropriate strategies
SalaryOffer finalOffer = new SalaryAdjustmentService(offer, range, strategies)
.applyAdjustments() // Apply all adjustments
.getFinalOffer(); // Get the final adjusted offer
// Output the final salary offer
System.out.println("Final Salary Offer: " + finalOffer.getOfferedSalary());
}
}
Explanation of the Implementation
-
Factory Pattern: The
SalaryAdjustmentStrategyFactory
is responsible for creating and returning the appropriate set of strategies based on the LOB. This ensures that only the relevant rules are applied, keeping the system efficient and maintainable. - Chaining and Prioritization: Strategies are added in a specific order in the factory, ensuring that more specific rules (like the manager weekend bonus) are applied before more general rules (like the weekend bonus).
- Extendability: New rules for other LOBs can be easily added by implementing new strategies and updating the factory.
- Maintainability: This approach keeps the codebase clean and modular, allowing easy updates as new rules and exceptions are introduced.
Example Output
When you run this implementation with a salary offer for a Financial Manager working on weekends, the output might look like this:
Applying 8% weekend bonus for Financial Managers. Adjusted Salary: 81000.0
Offer is within the range.
Final Salary Offer: 81000.0
This implementation ensures that each LOB’s rules are applied correctly and that more specific rules take precedence over general rules, maintaining a clean and extensible code structure.
Precedence Management
To ensure that the manager’s adjustment takes precedence and is not overwritten by the general employee adjustment, you can follow these strategies:
-
Order of Execution: Ensure that the manager adjustment strategy is executed before the general employee adjustment. This can be managed by the order in which strategies are applied in the
SalaryAdjustmentService
. - Conditional Logic in the Strategies: Implement conditional checks within the general employee adjustment strategy to ensure it does not apply its adjustment if a more specific (like the manager) adjustment has already been applied.
-
Adjustment Applied Flag: Use the
adjustmentApplied
flag within theSalaryOffer
class to track if a specific adjustment has been applied. The general employee adjustment should check this flag before applying any changes.
Updated Strategy Classes
Here’s how you can implement these strategies:
1. ManagerWeekendBonusAdjustment.java
This strategy applies the manager-specific adjustment and sets the adjustmentApplied
flag to true
.
public class ManagerWeekendBonusAdjustment implements SalaryAdjustmentStrategy {
@Override
public void applyAdjustment(SalaryOffer salaryOffer, SalaryRange salaryRange) {
if ("FIN".equalsIgnoreCase(salaryOffer.getLineOfBusiness()) &&
salaryOffer.worksOnWeekends() && salaryOffer.isManager()) {
double adjustedSalary = salaryOffer.getOfferedSalary() * 1.08; // 8% increase
System.out.println("Applying 8% weekend bonus for Financial Managers. Adjusted Salary: " + adjustedSalary);
salaryOffer.setOfferedSalary(adjustedSalary);
salaryOffer.setAdjustmentApplied(true); // Flag that this adjustment has been applied
}
}
}
2. WeekendBonusAdjustment.java
This strategy applies the general employee adjustment, but only if no other adjustment (like the manager adjustment) has already been applied.
public class WeekendBonusAdjustment implements SalaryAdjustmentStrategy {
@Override
public void applyAdjustment(SalaryOffer salaryOffer, SalaryRange salaryRange) {
// Only apply if no other adjustment has been made
if (!salaryOffer.isAdjustmentApplied() &&
"FIN".equalsIgnoreCase(salaryOffer.getLineOfBusiness()) && salaryOffer.worksOnWeekends()) {
double adjustedSalary = salaryOffer.getOfferedSalary() * 1.05; // 5% increase
System.out.println("Applying 5% weekend bonus for Financial employees. Adjusted Salary: " + adjustedSalary);
salaryOffer.setOfferedSalary(adjustedSalary);
salaryOffer.setAdjustmentApplied(true);
}
}
}
Factory Pattern: Control the Order of Strategies
In the factory that generates the strategies, make sure the manager-specific strategy is added before the general employee strategy:
public class SalaryAdjustmentStrategyFactory {
public static List<SalaryAdjustmentStrategy> getStrategiesForLOB(String lob) {
List<SalaryAdjustmentStrategy> strategies = new ArrayList<>();
switch (lob.toUpperCase()) {
case "HR":
strategies.add(new CaliforniaHREmployeeAdjustment());
strategies.add(new BilingualBonusAdjustment());
break;
case "FIN":
strategies.add(new ManagerWeekendBonusAdjustment()); // Add this first
strategies.add(new WeekendBonusAdjustment()); // Then this
break;
// Add other LOB-specific strategies here...
}
return strategies;
}
}
How It Works
-
Priority through Order: By placing the
ManagerWeekendBonusAdjustment
strategy first in the list, it is executed before theWeekendBonusAdjustment
. This ensures that if the employee is a manager, the manager-specific adjustment is applied first. -
Conditional Logic with Flags: The
WeekendBonusAdjustment
checks theadjustmentApplied
flag. If the manager adjustment has already been applied, the general employee adjustment will not be executed.
Example Usage
Here’s how you would use these strategies in the Main
class:
public class Main {
public static void main(String[] args) {
// Define a salary range
SalaryRange range = new SalaryRange(60000, 80000, "California", "ENG", "Senior");
// Create a salary offer for a Financial Manager working on weekends
SalaryOffer offer = new SalaryOffer(75000, "California", "ENG", "Senior", false, "FIN", true, true);
// Get the appropriate strategies for the Financial LOB
List<SalaryAdjustmentStrategy> strategies = SalaryAdjustmentStrategyFactory.getStrategiesForLOB(offer.getLineOfBusiness());
// Use the SalaryAdjustmentService with the appropriate strategies
SalaryOffer finalOffer = new SalaryAdjustmentService(offer, range, strategies)
.applyAdjustments() // Apply all adjustments
.getFinalOffer(); // Get the final adjusted offer
// Output the final salary offer
System.out.println("Final Salary Offer: " + finalOffer.getOfferedSalary());
}
}
Expected Output
When running the above code, you might see something like this:
Applying 8% weekend bonus for Financial Managers. Adjusted Salary: 81000.0
Offer is within the range.
Final Salary Offer: 81000.0
This output confirms that the manager-specific adjustment was applied first, and the general employee adjustment was skipped due to the adjustmentApplied
flag being set. This ensures that more specific rules always take precedence over general rules, maintaining the intended logic in a clean and maintainable way.