Skip to main content
deleted 192 characters in body
Source Link
Denis
  • 8.5k
  • 5
  • 31
  • 76
public class Shift
{
    public string Name { get; private set; }
    public TimeSpan StartHour { get; private set; }
    public TimeSpan EndHour { get; private set; }

    public TimeSpan Duration => EndHour - StartHour;

    private Shift(string name, TimeSpan startHour, TimeSpan endHour)
    {
        Name = name;
        StartHour = startHour;
        EndHour = endHour;
    }

    private Shift()
    {
    }
}
public class Shift
{
    public string Name { get; private set; }
    public TimeSpan StartHour { get; private set; }
    public TimeSpan EndHour { get; private set; }

    public TimeSpan Duration => EndHour - StartHour;

    private Shift(string name, TimeSpan startHour, TimeSpan endHour)
    {
        Name = name;
        StartHour = startHour;
        EndHour = endHour;
    }

    private Shift()
    {
    }
}
public class Shift
{
    public string Name { get; private set; }
    public TimeSpan StartHour { get; private set; }
    public TimeSpan EndHour { get; private set; }

    public TimeSpan Duration => EndHour - StartHour;

    private Shift()
    {
    }
}
Source Link
Denis
  • 8.5k
  • 5
  • 31
  • 76

Validating Shift class through builder pattern

I have a Shift class that looks like this:

public class Shift
{
    public string Name { get; private set; }
    public TimeSpan StartHour { get; private set; }
    public TimeSpan EndHour { get; private set; }

    public TimeSpan Duration => EndHour - StartHour;

    private Shift(string name, TimeSpan startHour, TimeSpan endHour)
    {
        Name = name;
        StartHour = startHour;
        EndHour = endHour;
    }

    private Shift()
    {
    }
}

Since there are some limitations and restrictions to what StartHour && EndHour can be I needed some way to validate the input of the class and if it fails - prevent the creation of the object or at least not return it to the user, without throwing an exception because I want to allow the user to re-enter the arguments if some of them were invalid.

Than I created an interface and some derived classes to be injected during the construction of the object:

public interface IShiftValidator
{
    Exception FailedValidationException { get; }
    string FailedValidationMessage { get; }
    bool IsValid(Shift shiftToValidate);
}

public class ShiftRangeValidator : IShiftValidator
{
    public Exception FailedValidationException { get; private set; }
    public string FailedValidationMessage { get; private set; } = string.Empty;

    public bool IsValid(Shift shiftToValidate)
    {
        if (shiftToValidate.Duration.TotalMinutes <= 0)
        {
            FailedValidationException = new ArgumentOutOfRangeException(nameof(shiftToValidate.Duration));
            FailedValidationMessage = "Shift's duration cant be less than 1 minute.";
            return false;
        }
        if (shiftToValidate.Duration.TotalHours > SharedSettings.MaximumShiftHours)
        {
            FailedValidationException =
                new ArgumentOutOfRangeException(nameof(shiftToValidate.Duration));
            FailedValidationMessage = "Shift's duration cant be longer than the maximum allowed hours.";
            return false;
        }
        return true;
    }
}

After that I implemented the builder pattern as a nested class in the Shift class, so I can make the constructor of the class private and construct the object fully in the builder class:

public class ShiftBuilder
{
    private Shift _shift;

    public ShiftBuilder()
    {
        _shift = new Shift();
    }

    public ShiftBuilder WithName(string name)
    {
        _shift.Name = name;   
        return this;
    }

    public ShiftBuilder WithStartHour(TimeSpan startHour)
    {
        _shift.StartHour = startHour;
        return this;
    }

    public ShiftBuilder WithEndHour(TimeSpan endHour)
    {
        _shift.EndHour = endHour;
        return this;
    }

    public Shift Build(IEnumerable<IShiftValidator> validators)
    {
        if (validators != null)
        {
            foreach (var shiftValidator in validators)
            {
                if (!shiftValidator.IsValid(_shift))
                {
                    if (!string.IsNullOrEmpty(shiftValidator.FailedValidationMessage))
                    {
                        MessageBox.Show(shiftValidator.FailedValidationMessage);
                    }
                    throw shiftValidator.FailedValidationException;
                }
            }
        }
        Shift shift = _shift;
        _shift = null;
        return shift;
    }
}

Here's an example usage:

Shift.ShiftBuilder shiftBuilder = new Shift.ShiftBuilder();
var a = shiftBuilder.WithName("Test")
    .WithStartHour(new TimeSpan(12, 0, 0))
    .WithEndHour(new TimeSpan(24, 0, 0))
    .Build(new[] { new ShiftRangeValidator() });

var b = shiftBuilder.Build(null); // null
var c = shiftBuilder.Build(new[] {new ShiftRangeValidator()}); // crash

I have few concerns:

  1. Is it clear that you're unable to reuse the same ShiftBuilder twice?
  2. Is IShiftValidator good as it is or should I separate it in few smaller interfaces?