LATITUDE 40
  • Home
  • About Us
    • Who We Are
    • How We Work
    • Our Quality Standards
    • How We Forecast ROI
  • Solutions
    • Custom Software
    • Explore Solutions
  • Successes
    • Case Studies
    • Testimonials
  • Insights
    • Blog
    • ROI Guides
  • Contact

Latitude 40 blog

Clean Code: The Strategy Pattern in C#

6/30/2025

0 Comments

 

Introduction

In software development, writing code is only half the job. The real challenge (and the real cost) comes later, when that code needs to change.

That's why maintainability is one of the most important qualities of a healthy codebase. Clean, modular code reduces bugs, accelerates onboarding, and supports business agility.

One pattern I find myself using frequently is the Strategy Pattern. It's a simple but powerful way to eliminate brittle if/else logic and replace it with clean, extensible design. In this post, I'll walk through a simple yet realistic example of different order pricing models and show how the Strategy Pattern helps us write code that's easier to understand, test, and evolve.

Setting the Stage: Dynamic Pricing Strategy

Let's say you're building a custom order system for a horticulture company. Different clients get different pricing strategies:
  • Retail customers pay full price.
  • Retail loyalty customers get a percentage discount.
  • Wholesale customers get a volume discount based on order volume.

Before: The Classic If/Else Trap


public class PricingService(IWholesaleDiscountRepository wholesaleRepo,
    LoyaltyProgramSettings loyaltyProgramSettings)
{
    public decimal CalculatePrice(Customer customer, Order order)
    {
        if (customer.Type == CustomerType.Retail)
        {
            return order.BasePrice;
        }
        else if (customer.Type == CustomerType.Loyalty)
        {
            return order.BasePrice * (1 - loyaltyProgramSettings.DiscountRate);
        }
        else if (customer.Type == CustomerType.Wholesale)
        {
            var tiers = wholesaleRepo.GetDiscountTiers()
                                      .OrderByDescending(t => t.MinQuantity);

            var discount = tiers.FirstOrDefault(
                t => order.Quantity >= t.MinQuantity)?.DiscountRate ?? 0m;

            return order.BasePrice * (1 - discount);
        }

        throw new InvalidOperationException("Unknown customer type");
    }
}

What's Wrong?

  • The PricingService is doing too much with its branching logic and different calculations. Real-life scenarios will likely be much more complex too (like wholesale volume discounts are probably based on seasonal order volume, not a single order's volume).
  • Depending on the complexity of your real-world scenario, you may want to apply the Open/Closed Principal, and that is impossible in this example.
  • The logic is not modular - you can't reuse or test pricing strategies independently.

After: Strategy Pattern

Let's refactor this using the Strategy Pattern.

Step 1: Define the Strategy Interface


public interface IPricingStrategy
{
    decimal CalculatePrice(Order order);
}

Step 2: Implement Strategies


public class RetailPricingStrategy : IPricingStrategy
{
    public decimal CalculatePrice(Order order) => order.BasePrice;
}

public class LoyaltyPricingStrategy(LoyaltyProgramSettings loyaltyProgramSettings)
    : IPricingStrategy
{
    public decimal CalculatePrice(Order order)
        => order.BasePrice * (1 - loyaltyProgramSettings.DiscountRate);
}

public class WholesalePricingStrategy(IWholesaleDiscountRepository discountRepo)
    : IPricingStrategy
{
    public decimal CalculatePrice(Order order)
    {
        var tier = discountRepo.GetDiscountTiers()
                        .OrderByDescending(t => t.MinQuantity)
                        .FirstOrDefault(t => order.Quantity >= t.MinQuantity);

        var discountRate = tier?.DiscountRate ?? 0m;
        return order.BasePrice * (1 - discountRate);
    }
}

Step 3: Strategy Factory


public interface IPricingStrategyFactory
{
    IPricingStrategy GetStrategy(Customer customer);
}

public class PricingStrategyFactory(IServiceProvider provider)
    : IPricingStrategyFactory
{
    public IPricingStrategy GetStrategy(Customer customer) => customer.Type switch
    {
        CustomerType.Retail
            => provider.GetRequiredService(),
        CustomerType.Wholesale
            => provider.GetRequiredService(),
        CustomerType.Loyalty
            => provider.GetRequiredService(),
        _ => throw new NotSupportedException("Unknown customer type")
    };
}

Step 4: Application Code


public class OrderProcessor(IPricingStrategyFactory pricingStrategyFactory)
{
    public void ProcessOrder(Customer customer, Order order)
    {
        var strategy = pricingStrategyFactory.GetStrategy(customer);
        var price = strategy.CalculatePrice(order);
        ...
    }
}

Conclusion

The Strategy Pattern isn't just a design tool. It's a way to build systems that are easier to understand, extend, and maintain.

In the example above, we started with a data-driven pricing model that worked, but was tightly coupled and procedural. By refactoring with the Strategy Pattern, we separated concerns and made each pricing rule modular.

This kind of architecture pays off in real-world scenarios:
  • New pricing models can be added without touching existing logic.
  • Each pricing model can be independently unit tested and verified easier.
  • Business rules could potentially be loaded from configuration or external sources.
  • Developers onboard faster and changes are easier/faster, because the code is easier to reason about.

These aren't just technical wins, they're business wins. Clean code reduces risk, accelerates delivery, and supports long-term agility.

About Latitude 40

Latitude 40 integrates experienced on-shore software development professionals into your organization, forming collaborative teams with or without your existing developers. Together, we identify needs, create tailored software solutions, and instill best practices that drive continuous improvement and ensure agility.

Need help modernizing your codebase or designing maintainable systems? Let's talk.

About the Author

Andrew Anderson is the President of Latitude 40 and a seasoned software architect with over two decades of experience in development and Agile delivery. He's worked globally as a developer, analyst, and coach, and is passionate about helping teams build software that is both powerful and maintainable.
View my profile on LinkedIn
0 Comments

Your comment will be posted after it is approved.


Leave a Reply.

    Categories

    All
    Agile
    Claris
    Clean Code
    Custom Vs. Off The Shelf
    Forecasting ROI
    On-shoring
    Technical
    Tech Strategy

    RSS Feed

Copyright © 2025 Latitude 40 Consulting, Inc.  All rights reserved.
Latitude 40® is a trademark of Latitude 40 Consulting, Inc. All other trademarks are the property of their respective owners.
Picture
11001 W. 120th Ave. ​Suite 400
Broomfield, CO 80021
303-544-2191
CONTACT US
privacy policy
terms of service
blog index
customer login
  • Home
  • About Us
    • Who We Are
    • How We Work
    • Our Quality Standards
    • How We Forecast ROI
  • Solutions
    • Custom Software
    • Explore Solutions
  • Successes
    • Case Studies
    • Testimonials
  • Insights
    • Blog
    • ROI Guides
  • Contact