I'm implementing a Fluent Builder Interface and instead of having null, empty or whitespace checks, I force the developer to fill in the following mandatory fields: firstName
, prefix
and facultyNumber
. The other two fields middleName
and lastName
are optional.
When you try the snippet below, it will force you to fill in the fields. The issue is when it comes to IPrefixSelectionStage
, it lets you add .WithMiddleName
twice. Is there a way I can prevent that?
var asd = FluentAccountBuilder
.Create()
.WithFirstName("Иван")
.WithMiddleName("Симеонов")
.WithMiddleName("Asd") // shouldn't let me do that, even tho it's optional
.WithLastName("Петков")
.WithPrefix("cs")
.WithFacultyNumber("196300")
.Build();
Snippet
public record Account(
string FirstName,
string? MiddleName,
string? LastName,
string Username,
string Password);
public sealed class FluentAccountBuilder :
IFirstNameSelectionStage,
IPrefixSelectionStage,
IFacultyNumberSelectionStage,
IBuildInitializerStage
{
private static readonly Regex FacultyNumberRegex = new("^[0-9]{6}$", RegexOptions.Compiled);
private string _firstName = string.Empty;
private string? _middleName;
private string? _lastName;
private string _prefix = string.Empty;
private string _facultyNumber = string.Empty;
private FluentAccountBuilder()
{
}
public static IFirstNameSelectionStage Create()
{
return new FluentAccountBuilder();
}
public IPrefixSelectionStage WithFirstName(string firstName)
{
if (string.IsNullOrWhiteSpace(firstName))
{
throw new ArgumentOutOfRangeException(nameof(firstName), "Please provide a valid FirstName");
}
_firstName = firstName;
return this;
}
public IPrefixSelectionStage WithMiddleName(string middleName)
{
_middleName = middleName;
return this;
}
public IPrefixSelectionStage WithLastName(string lastName)
{
_lastName = lastName;
return this;
}
public IFacultyNumberSelectionStage WithPrefix(string prefix)
{
_prefix = prefix;
return this;
}
public IBuildInitializerStage WithFacultyNumber(string facultyNumber)
{
if (!FacultyNumberRegex.IsMatch(facultyNumber))
{
throw new ArgumentOutOfRangeException(nameof(facultyNumber), "Faculty number must be 6-digits long");
}
_facultyNumber = facultyNumber;
return this;
}
public Account Build()
{
// Transliterate name
var transliteratedFirstName = Transliteration.CyrillicToLatin(_firstName);
var transliteratedMiddleName = Transliteration.CyrillicToLatin(_middleName ?? string.Empty);
var transliteratedLastName = Transliteration.CyrillicToLatin(_lastName ?? string.Empty);
// Generate username and password
// [cs/se][year-of-study][name-initials]
var yearOfStudy = _facultyNumber[..2];
var firstLetterOfFirstName = transliteratedFirstName.ToLowerInvariant()[..1];
var firstLetterOfMiddleName = transliteratedMiddleName.Length > 0
? transliteratedMiddleName.ToLowerInvariant()[..1]
: string.Empty;
var firstLetterOfLastName = transliteratedLastName.Length > 0
? transliteratedLastName.ToLowerInvariant()[..1]
: string.Empty;
var username = $"{_prefix}{yearOfStudy}{firstLetterOfFirstName}{firstLetterOfMiddleName}{firstLetterOfLastName}";
var password = _facultyNumber;
return new Account(_firstName, _middleName, _lastName, username, password);
}
}
public interface IFirstNameSelectionStage
{
IPrefixSelectionStage WithFirstName(string firstName);
}
public interface IPrefixSelectionStage
{
IPrefixSelectionStage WithMiddleName(string middleName);
IPrefixSelectionStage WithLastName(string lastName);
IFacultyNumberSelectionStage WithPrefix(string prefix);
}
public interface IFacultyNumberSelectionStage
{
IBuildInitializerStage WithFacultyNumber(string facultyNumber);
}
public interface IBuildInitializerStage
{
Account Build();
}