How a Single Composite Context Transformed My Calculator Architecture

As software engineers, we often face the same problem over and over again: managing a growing number of dependencies across multiple classes that share similar—but slightly different—needs.

Today, I want to share a small but impactful refactor I did while developing a SaaS platform that performs complex deadline calculations across various business rules and jurisdictions. This refactor cleaned up my calculator classes and made the platform easier to maintain and extend.

The Problem: Constructor Hell & Fragile Dependency Passing

Initially, each calculator in the platform needed several dependencies injected to operate properly—things like database connection strings, audit logging contexts, repositories, and logger services.

Here’s what the constructor looked like for one of my calculators:

// C# code

MedicalMalpracticeCalculator medMalCalc =
new MedicalMalpracticeCalculator(_connectionString, _auditLogContext!, _loggerService!);

Sounds simple? Maybe, until I started adding more dependencies and more calculators:

  • The constructors became long and unwieldy.
  • Every time I added a new dependency, I had to update every calculator signature and all their call sites.
  • Unit tests bloated with repeated setup code for each dependency.
  • Code duplication crept in across calculators, violating the DRY principle.

This scenario is common in many projects—dependency management quickly becomes a tangled mess, especially as systems grow in complexity.

The Solution: Introducing CalculatorContext

I realized that all calculators basically needed the same core set of dependencies. So why not bundle them into a single composite object? Enter:

public class CalculatorContext
{
public string ConnectionString { get; }
public IAuditLogContext AuditLogContext { get; }
public ILoggerService LoggerService { get; }
public IRuleRepository RuleRepository { get; }
// More dependencies as needed…

// Constructor to instantiate all dependencies once
public CalculatorContext(string connectionString, IAuditLogContext auditLogContext, ILoggerService loggerService, IRuleRepository ruleRepository)
{
ConnectionString = connectionString;
AuditLogContext = auditLogContext;
LoggerService = loggerService;
RuleRepository = ruleRepository;
}

}

Now, the calculator constructors only accept one parameter:

MedicalMalpracticeCalculator medMalCalc =
new MedicalMalpracticeCalculator(calculatorContext);

The Magic: Base Calculator Unpacks the Context

I didn’t stop there. All calculators inherit from a base class. So I changed the base calculator constructor to accept the CalculatorContext and unpack dependencies inside it:

public abstract class BaseCalculator
{
protected readonly IAuditLogContext AuditLogContext;
protected readonly ILoggerService LoggerService;
protected readonly IRuleRepository RuleRepository;


protected BaseCalculator(CalculatorContext context)
{
AuditLogContext = context.AuditLogContext;
LoggerService = context.LoggerService;
RuleRepository = context.RuleRepository;
}
}

Because the rest of the calculator code already referenced these protected fields, no other code needed to change. This was a huge win—it meant my refactor was isolated and non-disruptive.

The Benefits: Cleaner, More Maintainable, More Scalable

This simple refactor brought immediate and long-term benefits:

  • Cleaner code: Calculator constructors are simple, uniform, and less noisy.
  • Standardized instantiation: All calculators are created the same way, reducing confusion.
  • Single source of truth: One place to add or change dependencies, minimizing duplication and errors.
  • Slimmer unit tests: Test fixtures no longer need to mock or set up dozens of parameters repeatedly.
  • Easy scaling: Adding new calculators or dependencies is now much easier.
  • Future-proof: The platform is now better prepared for dependency injection frameworks or factory patterns, should I choose to adopt them later.

Takeaway for Fellow Developers

If you’re managing multiple classes that share a common set of dependencies—especially if those dependencies grow over time—consider wrapping them in a context or configuration object.

Pair that with a well-designed base class that unpacks the dependencies and provides protected fields or properties to derived classes.

This pattern can drastically reduce constructor bloat, improve testability, and make your codebase more maintainable and scalable.

Final Thoughts

Refactoring for maintainability often feels like a chore, but it’s these little wins that compound over time and make a huge difference.

Today, my calculator classes feel lighter, clearer, and more robust—and I’m ready to tackle adding new rules and jurisdictions with confidence.

If you’ve faced similar struggles or found other neat ways to manage dependencies in your projects, I’d love to hear about them!

Thanks for reading!

About the Author

My name is Paul A. Jones Jr., and I am a software engineer and legal tech founder developing tools for professionals in law and other regulated industries. I write about systems thinking, modern workflows, and SaaS applications at PaulJonesSoftware.com. Follow me on Twitter: @PaulAJonesJr.

Posted in

2 responses to “How a Single Composite Context Transformed My Calculator Architecture”

  1. […] How a Single Composite Context Transformed My Calculator Architecture […]

    Like

  2. […] How a Single Composite Context Transformed My Calculator Architecture […]

    Like

Leave a reply to What Happens When a Client Questions Your Statute of Limitations Calculation? – Paul Jones Software Cancel reply