Toto
Toto

Reputation: 7719

How to avoid expanding a Request class when adding new consumers?

I have a system where multiple consumers (e.g., pricing calculators) process requests based on specific constraints. Each consumer has different requirements, meaning they only need a subset of the available data. For example:

Eg:

public interface IRequestProcessor
{
    double ComputeResult(RequestData request);
}

class ConsumerA : IRequestProcessor
{
    public double ComputeResult(RequestData request)
        => 10 * request.SomeValue + request.BaseCost;
}

class ConsumerB : IRequestProcessor
{
    public double ComputeResult(RequestData request)
        => (request.SomeCondition ? 10 : 0) * request.SomeValue + request.BaseCost;
}

class ConsumerC : IRequestProcessor
{
    public double ComputeResult(RequestData request)
        => request.HasDetail ? 1.5 * request.BaseCost : request.BaseCost;
}

class ConsumerD : IRequestProcessor
{
    public double ComputeResult(RequestData request)
        => request.DueDate < DateTime.Now.AddMonths(6) 
            ? -1 
            : request.HasDetail ? 1.5 * request.BaseCost : request.BaseCost;
}

And the RequestData class:

class RequestData
{
    public double BaseCost { get; }    // Used by all Consumers
    public double SomeValue { get; }   // Used by Consumer A & B
    public bool SomeCondition { get; } // Used by Consumer B
    public bool HasDetail { get; }     // Used by Consumer C & D
    public DateTime DueDate { get; }   // Used by Consumer D
}

Each time that I add a new consumer, I need to modify RequestData to introduce new fields. This leads to:

  1. Growing complexity : The RequestData class keeps expanding.
  2. Unused data : Most consumers only require a subset of the fields, making it inefficient.

==> How can I modelize this system so that it fill a better design ?

Although my current implementation is in C#, I welcome any general design pattern or architectural approach to solve this issue.

Upvotes: 0

Views: 56

Answers (1)

David Osborne
David Osborne

Reputation: 6801

I like Jeremey Lakeman's suggestion of generics.

You could also try consumer-specific derivatives:

abstract class RequestData {
    public double BaseCost { get; }    // Used by all Consumers
}

class ConsumerARequestData : RequestData{
    public double SomeValue { get; }   // Used by Consumer A & B
}

class ConsumerBRequestData : ConsumerARequest {
    public bool SomeCondition { get; } // Used by Consumer B
}

class ConsumerCRequestData : RequestData {
    public bool HasDetail { get; }     // Used by Consumer C & D
}

class ConsumerDRequestData : ConsumerCRequestData {
    public DateTime DueDate { get; }   // Used by Consumer D
}

However, the cost of this approach is that you have to cast to the specific type in each implementation:

class ConsumerD : IRequestProcessor
{
    public double ComputeResult(RequestData request) {
        var requestData = (ConsumerDRequestData) request;

        // ...
    }
}

Upvotes: 0

Related Questions