Search code examples
c#.netdesign-patternsstatic-factory

The proper usage of a static factory method for creating a DTO object with predefined values


Let's assume we have to create complex DTO object with some predefined (default) values. This object is used for serialization, and a serializer requires a parameterless constructor. To do so, I'd like to use a static factory method, but I have some doubts about the proper usage of this approach.

Please consider the following two examples:

public class Foo
{
    public void DoSomething()
    {
        // the first way of creating the object
        var addressDtoFirstWay = AddressDtoFirstWay
            .CreateWithPredefinedValues();
        addressDtoFirstWay.StreetName = "Street";
        addressDtoFirstWay.HouseNumber = 100;
        addressDtoFirstWay.PostalCode = "1000";

        // the second way of creating the object
        var addressDtoSecondWay = AddressDtoSecondWay
            .CreateWithPredefinedValues("Street", 100, null, "1000");
    }
}

public class AddressDtoFirstWay
{
    public string RecipientName { get; set; }
    public string StreetName { get; set; }
    public int HouseNumber { get; set; }
    public int? FlatNumber { get; set; }
    public string PostalCode { get; set; }
    public string City { get; set; }
    public string CountryName { get; set; }

    public static AddressDtoFirstWay CreateWithPredefinedValues()
    {
        return new AddressDtoFirstWay
        {
            RecipientName = "John Doe",
            City = "City",
            CountryName = "Country"
        };
    }
}

public class AddressDtoSecondWay
{
    public string RecipientName { get; set; }
    public string StreetName { get; set; }
    public int HouseNumber { get; set; }
    public int? FlatNumber { get; set; }
    public string PostalCode { get; set; }
    public string City { get; set; }
    public string CountryName { get; set; }

    public static AddressDtoSecondWay CreateWithPredefinedValues(
        string streetName,
        int houseNumber,
        int? flatNumber,
        string postalCode)
    {
        return new AddressDtoSecondWay
        {
            RecipientName = "John Doe",
            StreetName = streetName,
            HouseNumber = houseNumber,
            FlatNumber = flatNumber,
            PostalCode = postalCode,
            City = "City",
            CountryName = "Country"
        };
    }
}

In the first example, the factory method initializes only the predefined fields - a user has to initialize the rest of them after object creation. The second example initializes the predefined fields, also the fields which are required, but as a downside, a user has to fill nullable (not required in this case, but required in the other) field flatNumber.

I see both the advantages and disadvantages of these two solutions, but I'm considering which one is more preferred and why. Maybe some other approach will be even better. I'm open to any suggestions, but I want to notice, that the problem it's not that complex that the builder pattern will be applicable.


Solution

  • Assuming that you

    • want methods to create DTOs with certain defaults
    • want a default constructor and don't want to force use of the factory methods

    Perhaps the answer is to separate the two. Let your DTO do its own thing:

    public class AddressDto
    {
        public string RecipientName { get; set; }
        public string StreetName { get; set; }
        public int HouseNumber { get; set; }
        public int? FlatNumber { get; set; }
        public string PostalCode { get; set; }
        public string City { get; set; }
        public string CountryName { get; set; }
    }
    

    ...and don't clutter it with various default options. Over time you could find that you need different defaults for various scenarios. I could see that becoming a little bit messy.

    Then, take those exact same static methods and put them in their own class:

    public static class AddressDtoFactory
    {
        public static AddressDto CreateWithPredefinedValues()
        {
            return new AddressDto
            {
                RecipientName = "John Doe",
                City = "City",
                CountryName = "Country"
            };
        }
    }
    

    I'd lean toward the "first way" in which the factory method only populates the default values. The reason is that it's a little burdensome to have to pass every single property as a parameter, especially if they're not getting validated. Plus you'd want to update that constructor every time you added a property.

    Another option would be an extension class like this:

    public static class AddressDtoExtensions
    {
        public static AddressDto PopulatePredefinedValues(
            this AddressDto dto)
        {
            dto.RecipientName = dto.RecipientName ?? "John Doe";
            dto.City = dto.City ?? "City";
            dto.CountryName = dto.CountryName ?? "Country";
            return dto;
        }
    }
    

    That lets you do something like this:

    var dto = new AddressDto
    {
        HouseNumber = 5,
        PostalCode = "12345"
    }.PopulatePredefinedValues();
    

    It gives you both - you can use both property initialization and add your defaults. It might also be a little bit friendlier if you decide to use Automapper or something like that.