0

Context

I am making a service class that essentially functions as a business datetime calculator. That is, it can perform various calculations such as what date is X business days from a given date, when is the start of the next business date, is a given date in business hours, etc.

The catch is this class has to take in various parameters that makes it quite a bit more complex. In particular, user-defined work hours and holidays are passed in when the class is initialized; any methods that perform these business datetime calculations need to take these work hours and holidays into account. Work hours are defined by a dictionary that maps days of the week to the work hours of each of those days. This means this class has to deal with "non-standard" work schedules, such as Tuesday-Thursday 14:00-01:00, Friday-Saturday 16:00-03:00. (I am defining what a "standard" and "non-standard" work schedule is based on some metric I've come up with -- it's not important for the sake of this question how they're defined.)

Standard work schedules are easy to deal with, whereas non-standard work schedules make things more complex. I've been thinking about how to deal with this, and I've come to the conclusion that the implementation of these calculations should differ between standard and non-standard work schedules. That is, for each method, if we're dealing with a standard work schedule, do X; if we're dealing with a non-standard schedule, do Y.

Question

What is the best practice for setting up this class? Here are the options I can think of:

  1. Each method should just have an if/else statement:

    if (standardWorkSchedule)
    {
        // do standard work schedule stuff
    }
    else
    {
        // do non-standard work schedule stuff
    }
    
  2. Create an interface, IBusinessDateTime, with all the methods for business datetime calculations. Then, create two distinct service classes, StandardBusinessDateTime : IBusinessDateTime and NonStandardBusinessDateTime : IBusinessDateTime, that implement this interface. StandardBusinessDateTime will implement standard work schedule logic for its methods, whereas NonStandardBusinessDateTime will implement non-standard work schedule logic. Finally, create an additional, intermediary class, BusinessDateTime : IBusinessDateTime, that takes in the work schedule and determines whether it's standard or non-standard. Everywhere that uses this service class can just call the methods of this intermediary class, and it offloads the actual work to the other two classes. If it's standard, then use StandardBusinessDateTime; if it's non-standard, then use NonStandardBusinessDateTime.

I'm not too familiar with design patterns, so it's possible that I'm describing a fairly common and basic problem, and the solutions I've proposed are commonly-used design patterns. I feel that option 2 is the better solution, but I just want to make sure. Also, I'm sure there are other and possibly better solutions -- I appreciate any help I can get with this.

Finally, I want to emphasize that I am not asking any of the following things:

  • How to implement a business datetime calculator

  • How to define standard and non-standard work schedules

  • If I should even be dealing with non-standard work schedules

  • 1
    If you want to enable yourself flexible api for your calculation method you should use Strategy pattern - generic interface as a parameter of your method. You can then pass any concrete implementation compatible with your interface. You don't need to know all possible non-standard algorithms a priori. On the other hand, if you pick algorithm based on passed parameters you should cover implementation details using Factory pattern and allow clean api as well. You can also combine both patterns if needed. – Tomasz Maciejewski Feb 2 '18 at 0:26
  • Thanks for the reply. I looked up how the strategy pattern works since I haven't heard of it before. It's definitely useful, but I don't think it's the best pattern for my case. I don't want to put the burden on whatever uses this service class to figure out what algorithm it should use. Ideally, it just calls the service class without worrying about which one to use. I'll look into the factory pattern -- I think it's what I want. – Drew Feb 2 '18 at 0:43
1

Good that you read about patterns, will help you in the future for sure.

Few comments to your solution.

1) Use static method rather than constructor. You should not have any state inside your factory so you don't need many instances of it.

2) Return concrete implementation from you factory based on params from the input

You should then be able to do something like

IBusinessDateTime businessDateTime = BusinessDateTimeFactory.getInstance(... params)
businessDateTime.calculate(params for calculation);

Why do you want it? Because now everyone in the system doesn't need to care about algorithms implementation. Whenever you realize that you want to change it you just need to change factory method, not its consumers (which rely on interface contract).

3) But then let's assume that you want to let other programmers to calculate dates on their on way. You can let them add another condition to your factory method. But sometimes better and safier solution is to let pass algorithm. You specify algorithm interface and add it to your method. Inside the method you rely on interface methods. Concrete implementation will be passed by consumer of your method.

  • Good points, thanks for adding to the solution. I'll weave them into what I have. – Drew Feb 2 '18 at 17:55
0

I looked into the strategy and factory patterns thanks to @kadiii's comment, and I have decided to implement a strategy/factory hybrid, which is essentially what option 2 is in my question. Based on what I read about the two design patterns, traditionally both of them rely on whatever application code is using them to determine which implementation is used. I don't want to put this burden on wherever this service class is used, so I leave it to BusinessDateTime to do this.

The "factory" class, BusinessDateTime, has a private field:

private IBusinessDateTime implementation;

In the constructor, I set this field based on the type of work schedule:

public BusinessDateTime(WorkSchedule schedule, moreParams...)
{    
    if (schedule.StandardWorkSchedule)
    {
        this.implementation = new StandardBusinessDateTime();
    }
    else
    {
        this.implementation = new NonStandardBusinessDateTime();
    }
    // more initialization
}

And then for each of my interface methods in BusinessDateTime, I just do

public DateTime SomeBusinessDateCalculation()
{
    return this.implementation.SomeBusinessDateCalculation();
}

There are a couple hiccups to figure out, such as passing data back and forth between BusinessDateTime and the implementation classes, but I think this is a good solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.