HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Alternative to the Josh Bloch Builder pattern in C#

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
blochthealternativebuilderjoshpattern

Problem

The Josh Bloch Builder Pattern (here after JBBuilder) introduced me to the idea of using a nested class to aid in construction. In Java the JBBuilder pattern is aimed at fixing the telescoping constructor problem. That problem occurs in java mostly because java doesn't have named and optional arguments the way C# does. Because the C# has them I'm wondering how good an idea the JBBuilder is in C#. I'm trying to solve a different problem with a structure similar to the JBBuilder.

I will also use a nested class to build. But rather than following the JBBuilder pattern and simulating optional arguments I'll use the nested class to let me simulate constructors with different names.

These simulated constructors will be much like static factory methods but without them being static. I'm avoiding static factory methods because they can't be passed around dependency injection style. I want whatever decides which method to call to not have to know which concrete method this is. So I'm hanging them off a stateless instance that could have any implementation.

These simulated constructors will give an input string different meanings and choose an implementation of a Strategy Pattern to wrap that string with different Validate() behavior.

Using differently named 'constructors' allows us to deal with the fact that input in both cases is the same type: string. One is a regular expression. The other is wildcard pattern. The meaning of the string is decided by the method used from the Build object.

The different names avoid a single constructor being forced to do logic or accept a flag to understand the strings meaning and select an implementation or choosing a constructor based on the type passed in. The new pattern presented below is a hack that allows constructors to have different names without resorting to static factory methods. It gives the string different types that then polymorphically decide the Validate() behavior.

My questions:

  • Is the new pa

Solution

Why create a class to wrap the validation strategy at all? A common pattern with immutable objects is to have an empty or seed value to start construction from.

public interface IValidationStrategy
{
    bool Validate(string pStringToValidate);
}

public class RegexValidationStrategy : IValidationStrategy
{
    private Regex regEx;

    public RegexValidationStrategy(Regex regEx)
    {
        this.regEx = regEx;
    }

    public bool Validate(string stringToValidate)
    {
        return regEx.IsMatch(stringToValidate);
    }
}

public class StringValidator
{
    private class EmptyValidator : IValidationStrategy
    {
        public bool Validate(string input) => true;
    }

    public static readonly IValidationStrategy Empty = new EmptyValidator();
}

public static class RegexValidationStrategyExtensions
{
    public static IValidationStrategy Regex(this IValidationStrategy strategy, string pattern)
    {
        return new CompositeValidationStrategy(strategy, new RegexValidationStrategy(new Regex(pattern)));
    }
}

public class CompositeValidationStrategy : IValidationStrategy
{
    private readonly IValidationStrategy left;
    private readonly IValidationStrategy right;

    public CompositeValidationStrategy(IValidationStrategy left, IValidationStrategy right)
    {
        this.left = left;
        this.right = right;
    }

    public bool Validate(string input)
    {
        return left.Validate(input) && right.Validate(input);
    }
}


Then your test:

StringValidator
    .Empty
    .Regex(@"^\d+$")
    .Validate("55") // true


You can also apply more than 1 rule:

StringValidator
    .Empty
    .Regex(@"^\d+$")
    .Regex("5{2,}")
    .Validate("1255") // true


By having separate classes and using extension methods to construct instances, you make it trivially easy to add more validators - create a type that implements the strategy and add an extension method to instantiate the type. You could add additional extension methods to IValidationStrategy to make composition easier and avoid leaking the knowledge of CompositeValidationStrategy.

Code Snippets

public interface IValidationStrategy
{
    bool Validate(string pStringToValidate);
}

public class RegexValidationStrategy : IValidationStrategy
{
    private Regex regEx;

    public RegexValidationStrategy(Regex regEx)
    {
        this.regEx = regEx;
    }

    public bool Validate(string stringToValidate)
    {
        return regEx.IsMatch(stringToValidate);
    }
}

public class StringValidator
{
    private class EmptyValidator : IValidationStrategy
    {
        public bool Validate(string input) => true;
    }

    public static readonly IValidationStrategy Empty = new EmptyValidator();
}

public static class RegexValidationStrategyExtensions
{
    public static IValidationStrategy Regex(this IValidationStrategy strategy, string pattern)
    {
        return new CompositeValidationStrategy(strategy, new RegexValidationStrategy(new Regex(pattern)));
    }
}

public class CompositeValidationStrategy : IValidationStrategy
{
    private readonly IValidationStrategy left;
    private readonly IValidationStrategy right;

    public CompositeValidationStrategy(IValidationStrategy left, IValidationStrategy right)
    {
        this.left = left;
        this.right = right;
    }

    public bool Validate(string input)
    {
        return left.Validate(input) && right.Validate(input);
    }
}
StringValidator
    .Empty
    .Regex(@"^\d+$")
    .Validate("55") // true
StringValidator
    .Empty
    .Regex(@"^\d+$")
    .Regex("5{2,}")
    .Validate("1255") // true

Context

StackExchange Code Review Q#145447, answer score: 3

Revisions (0)

No revisions yet.