Showing posts with label Step Builder Pattern. Show all posts
Showing posts with label Step Builder Pattern. Show all posts

Friday, 10 May 2013

Building smart Builders

When building an API, you should always think about who is going to use it.
When the API is simply and clear to use, then the users are happy. When the users are happy then everyone is happy too :).

But great usability is not always easy to achieve. 

There are patterns that help on this, on this post I will focus on the classic builder pattern and how you can enhance it with the step builder pattern in order to build objects with a no brain interface, easy to use, impossible to get wrong.

So lets start painting some context, we have 2 domain objects representing a user configuration to connect to some remote or local server. When remote credentials are required, when local no.

package com.marco.sbp;
public class UserConfiguration {
        private final String name;
        private ServerDetails serverDetails;

        public UserConfiguration(String name) {
                this.name = name;
        }

        public void setServerDetails(ServerDetails serverDetails) {
                this.serverDetails = serverDetails;
        }

        public String getName() {
                return name;
        }

        public ServerDetails getServerDetails() {
                return serverDetails;
        }
}



package com.marco.sbp;
public class ServerDetails {

        private final String host;
        private String user;
        private String password;

        public ServerDetails(String host) {
                this.host = host;
        }

        public void setUser(String user) {
                this.user = user;
        }

        public void setPassword(String password) {
                this.password = password;
        }

        public String getHost() {
                return host;
        }

        public String getUser() {
                return user;
        }

        public String getPassword() {
                return password;
        }
}


We want to abstract the construction of the objects above using 2 different techniques, the classic builder pattern and the step builder pattern.

The classic builder pattern is pretty straightforward, it works masking the creation of the UserConfiguration and the ServerDetails using properly named methods like onLocalHost, onRemoteHost, etc.
package com.marco.sbp.builder;
import com.marco.sbp.ServerDetails;
import com.marco.sbp.UserConfiguration;
public class ClassicBuilder {
        
        private String name;
        private String host;
        private String user;
        private String password;

        
        public ClassicBuilder(String name){
                this.name = name;
        }
        
        public ClassicBuilder onLocalHost(){
                this.host = "localhost";
                return this;
        }
        
        public ClassicBuilder onRemoteHost(String remoteHost){
                this.host = remoteHost;
                return this;
        }
        
        
        public ClassicBuilder credentials(String user, String password){
                this.user = user;
                this.password = password;
                return this;
        }
        
        public UserConfiguration build(){
                UserConfiguration userConfiguration = new UserConfiguration(name);
                ServerDetails serverDetails = new ServerDetails(host);
                serverDetails.setUser(user);
                serverDetails.setPassword(password);                    
                userConfiguration.setServerDetails(serverDetails);
                return userConfiguration;
        }
}



The step builder pattern is still using smart names to construct the object, but it's exposing these methods only when needed using interfaces and proper encapsulation.
package com.marco.sbp.builder;
import com.marco.sbp.ServerDetails;
import com.marco.sbp.UserConfiguration;

/** "Step Builder" */
public class StepBuilder {
        public static NameStep newBuilder() {
                return new Steps();
        }

        private StepBuilder() {
        }

        public static interface NameStep {
                /**
                 * @param name
                 *            unique identifier for this User Configuration
                 * @return ServerStep
                 */
                ServerStep name(String name);
        }       

        public static interface ServerStep {
                /**
                 * The hostname of the server where the User Configuration file is stored will be set to "localhost".
                 * 
                 * @return BuildStep
                 */
                public BuildStep onLocalhost();

                /**
                 * The hostname of the server where the User Configuration file is stored.
                 * 
                 * @return CredentialsStep
                 */
                public CredentialsStep onRemotehost(String host);
        }

        public static interface CredentialsStep {
                /**
                 * Username required to connect to remote machine Password required to connect to remote machine
                 * 
                 * @return BuildStep
                 */
                public BuildStep credentials(String user, String password);
        }

        public static interface BuildStep {
                /**
                 * @return an instance of a UserConfiguration based on the parameters passed during the creation.
                 */
                public UserConfiguration build();
        }

        private static class Steps implements NameStep, ServerStep, CredentialsStep, BuildStep {

                private String name;
                private String host;
                private String user;
                private String password;

                public BuildStep onLocalhost() {
                        this.host = "localhost";
                        return this;
                }
                
                public ServerStep name(String name) {
                        this.name = name;
                        return null;
                }

                public CredentialsStep onRemotehost(String host) {
                        this.host = host;
                        return this;
                }

                public BuildStep credentials(String user, String password) {
                        this.user = user;
                        this.password = password;
                        return this;
                }

                public UserConfiguration build() {
                        UserConfiguration userConfiguration = new UserConfiguration(name);
                        ServerDetails serverDetails = new ServerDetails(host);
                        serverDetails.setUser(user);
                        serverDetails.setPassword(password);                    
                        userConfiguration.setServerDetails(serverDetails);
                        return userConfiguration;
                }

                
        }
}



Lets see now what is the user experience with both of our builders.

The classic builder  will be constructed using the name of the user configuration, then it will expose all of its methods leaving the user a bit too free to choose what's next.



For example, a not careful user could end up with a UserConfiguration set with localhost where no authentication is required, still passing user and password.
This is confusing and it can lead to run-time exceptions.

These are some of the possible combinations of UserConfigurations that the user can end up with, some are correct, lots are wrong:


A complete different story is with the step builder, here only one step at the time is exposed:

If the credentials are not needed they will not be exposed and the build() method is offered only when the state of the object is sure to be coherent and complete:


Only 2 possible UserConfigurations can be built with this pattern, and both make sense and are clear to the user.

 Conclusion

The step builder pattern is not the replacement of the classic Bloch one, sometimes you want to force the user to fill some parameter before advancing with the creation, in this case the step builder is doing the job, otherwise when a more open approach is required than the classic builder is your guy.





Monday, 19 November 2012

Step builder pattern on youtube

Djorgje Popovic just published an article on the Step builder pattern which includes a nice video explaining the how and the why of this design.

So thanks to Djorgje and enjoy the video.



Monday, 8 October 2012

Step Builder Pattern - WIKI-Definition


Definition


The step builder pattern is an extension of the builder pattern that fully guides the user through the creation of the object with no chances of confusion. [1]

Contents


The intent of the Step Builder design pattern is to separate the construction of a complex object from its representation by creating defined steps and exposing to the user only selected methods per time.

Advantages of using the Step Builder over the Builder pattern

    • The user will see only one or few selected methods per time during the Object creation.
    • Based on the user choice different paths can be taken during the Object creation.
    • The final build step will be available only at the end, after all mandatory steps are completed, returning an Object in a consistent state.
    • The user experience will be greatly improved by the fact that he will only see the next step methods available.

    Examples

    Java

     /** "Product" */
     class UserConfiguration {
        private final String name;
        private final String filePath;
        private ServerDetails serverDetails;
     
        public UserConfiguration(String name, String filePath){
           this.name = name;
           this.filePath = filePath;
        }
        public void setServerDetails(ServerDetails serverDetails)     { this.serverDetails = serverDetails; }
     
        //all the getters
        ...
     }
     
    class ServerDetails {
        private final String host;
        private String user;
        private String password;
     
        public ServerDetails(String host){
           this.host = host;
        }
        public void setUser(String user)     { this.user = user; }
        public void setPassword(String password)     { this.password = password; }
     
        //all the getters and isLocalhost() method
        ...
     }
     
     
     
     /** "Step Builder" */
    public class UserConfigurationBuilder {
       public static NameStep newBuilder() {
          return new Steps();
       }  
       private UserConfigurationBuilder() {}
     
       public static interface NameStep {
          /**
           * @param name unique identifier for this User Configuration
           * @return FileStep
           */
          FileStep name(String name);
       }
     
       public static interface FileStep {
          /** 
           * @param filePath absolute path of where the User Configuration exists.
           * @return ServerStep
           */
          ServerStep filePath(String name);
       }
     
       public static interface ServerStep {
          /**
           * The hostname of the server where the User Configuration file is stored will be set to "localhost".
           * @return BuildStep
           */
          public BuildStep onLocalhost();
     
          /**
           * The hostname of the server where the User Configuration file is stored.
           * @return CredentialsStep
           */
          public CredentialsStep onRemotehost(String host);
       }
     
       public static interface CredentialsStep {
          /**
            * Username required to connect to remote machine
            * Password required to connect to remote machine
            * @return BuildStep
            */
           public BuildStep credentials(String user, String password);
       }
     
       public static interface BuildStep {
          /** 
           * @return an instance of a UserConfiguration based on the parameters passed during the creation.
           */
          public Profile build();
       }
     
       private static class Steps implements NameStep, FileStep, ServerStep, CredentialsStep, BuildStep {
     
          private String name;
          private String host;
          private String user;
          private String password;
          private String filePath;
     
          @Override
          public FileStep name(String name) {
             this.name = name;
            return this;
          }
     
          @Override
          public ServerStep filePath(String filePath) {
             this.filePath = filePath;
             return this;
          }
     
          @Override
          public BuildStep onLocalhost() {
             this.host = "localhost";
             return this;
          }
     
          @Override
          public CredentialsStep onRemotehost(String host) {
             this.host = host;
             return this;
          }
     
          @Override
          public BuildStep credentials(String user, String password) {
             this.user = user;
             this.password = password;
             return this;
          }
     
          @Override
          public UserConfiguration build() {
             UserConfiguration userConfiguration = new UserConfiguration(name, filePath);
             ServerDetails serverDetails = new ServerDetails(host);
             if (!serverDetails.isLocalhost()) {
                serverDetails.setUser(user);
                serverDetails.setPassword(password);
             }
             userConfiguration.setServerDetails(serverDetails);
             return userConfiguration;
          }
       }
    }
     
     
     /** A user creating a configuration. */
     class StepBuilderExample {
        public static void main(String[] args) {
            // A local user configuration
            UserConfiguration localUserConfiguration = UserConfigurationBuilder
                                                  .newBuilder()
                                                  .name("aLocalConfiguration")
                                                  .filePath("/opt/conf/user.txt")
                                                  .onLocalhost()
                                                  .build();  
            // A remote user configuration
            UserConfiguration remoteUserConfiguration = UserConfigurationBuilder
                                                  .newBuilder()
                                                  .name("aRemoteConfiguration")
                                                  .filePath("/opt/conf/user.txt")
                                                  .onRemotehost("172.x.x.x")
                                                  .credentials("user","password")
                                                  .build();  
        }
     }

    C#

    namespace StepBuilder
    {
        class Program
        {
            static void Main(string[] args)
            {
    
    
    
             UserConfiguration localUserConfiguration = UserConfigurationBuilder                        .CreateNewBuilder()
                            .SetName(@"aLocalConfiguration")
                            .SetFilePath(@"/opt/conf/user.txt")
                            .OnLocalhost()
                            .Build();
    UserConfiguration remoteUserConfiguration = UserConfigurationBuilder .CreateNewBuilder() .SetName(@"aRemoteConfiguration") .SetFilePath(@"/opt/conf/user.txt") .OnRemoteHost("172.x.x.x") .SetCredentials("user", "password") .Build(); } } public class UserConfiguration { public UserConfiguration(string name, string filePath) { Name = name; FilePath = filePath; } private ServerDetails _serverDetails; public string Name { get; private set; } public string FilePath { get; private set; } public void SetServerDetails(ServerDetails serverDetails) { _serverDetails = serverDetails; } } public class ServerDetails { public ServerDetails(string host) { Host = host; } public string Host { get; private set; } public string User { get; set; } public string Password { get; set; } public bool IsLocalhost { get { return Host == "localhost"; } } } public class UserConfigurationBuilder { public static INameStep CreateNewBuilder() { return new Steps(); } private UserConfigurationBuilder() { } public interface INameStep { /// <param name="name">Unique identifier for this User Configuration</param> IFileStep SetName(string name); } public interface IFileStep { /// <param name="filePath">Absolute path of where the User Configuration exists</param> IServerStep SetFilePath(string filePath); } public interface IServerStep { /// <summary> /// The hostname of the server where the User Configuration file is store will be set to "localhost". /// </summary> IBuildStep OnLocalhost(); /// <param name="host">The hostname of the server where the User Configuration is stored</param> ICredentialsStep OnRemoteHost(string host); } public interface IBuildStep { /// <summary> /// Returns an instance of a UserConfiguration based on the parameters passed during the creation. /// </summary> UserConfiguration Build(); } public interface ICredentialsStep { /// <param name="user">Username required to connect to remote machine</param> /// /// <param name="password">Password required to connect to remote machine</param> IBuildStep SetCredentials(string user, string password); } private class Steps : INameStep, IFileStep, IServerStep, IBuildStep, ICredentialsStep { private string _name; private string _host; private string _user; private string _password; private string _filePath; public IFileStep SetName(string name) { _name = name; return this; } public IServerStep SetFilePath(string filePath) { _filePath = filePath; return this; } public IBuildStep OnLocalhost() { _host = "localhost"; return this; } public ICredentialsStep OnRemoteHost(string host) { _host = host; return this; } public UserConfiguration Build() { UserConfiguration userConfiguration = new UserConfiguration(_name, _filePath); ServerDetails serverDetails = new ServerDetails(_host); if (!serverDetails.IsLocalhost) { serverDetails.Password = _password; serverDetails.User = _user; } userConfiguration.SetServerDetails(serverDetails); return userConfiguration; } public IBuildStep SetCredentials(string user, string password) { _user = user; _password = password; return this; } } } }

    Saturday, 28 July 2012

    Step Builder pattern

    Recently I wanted to implement a builder in my Grep4j API, but, as already happened in the past with the builder or other creational patterns, I was not completely satisfied with the result. A Builder pattern is a design you implement when you want to separate the creation of an object from its representation. For example, let say you have a Java Panino object composed of few ingredients:
    
    
    package com.stepbuilder.bar;
    import java.util.List;
    public class Panino {
    
     private final String name;
     private String breadType;
     private String fish;
     private String cheese;
     private String meat;
     private List vegetables;
    
     public Panino(String name) {
      this.name = name;
     }
    
     public String getBreadType() {
      return breadType;
     }
    
     public void setBreadType(String breadType) {
      this.breadType = breadType;
     }
    
     public String getFish() {
      return fish;
     }
    
     public void setFish(String fish) {
      this.fish = fish;
     }
    
     public String getCheese() {
      return cheese;
     }
    
     public void setCheese(String cheese) {
      this.cheese = cheese;
     }
    
     public String getMeat() {
      return meat;
     }
    
     public void setMeat(String meat) {
      this.meat = meat;
     }
    
     public List getVegetables() {
      return vegetables;
     }
    
     public void setVegetables(List vegetables) {
      this.vegetables = vegetables;
     }
    
     public String getName() {
      return name;
     }
    
     @Override
     public String toString() {
      return "Panino [name=" + name + ", breadType=" + breadType + ", fish="
        + fish + ", cheese=" + cheese + ", meat=" + meat
        + ", vegetables=" + vegetables + "]";
     }
     
     
    }
    Now, in order to create a Panino you can write your Builder class that, more or less, will look like the following.
    
    
    package com.stepbuilder.bar;
    import java.util.ArrayList;
    import java.util.List;
    public class PaninoBuilder {
     
     private String name;
     private String breadType;
     private String fish;
     private String cheese;
     private String meat;
     private List vegetables = new ArrayList();
    
     public PaninoBuilder paninoCalled(String name){
      this.name = name;
      return this;
     }
     
     public PaninoBuilder breadType(String breadType){
      this.breadType = breadType;
      return this;
     }
     
     public PaninoBuilder withFish(String fish){
      this.fish = fish;
      return this;
     }
     
     public PaninoBuilder withCheese(String cheese){
      this.cheese = cheese;
      return this;
     }
     
     public PaninoBuilder withMeat(String meat){
      this.meat = meat;
      return this;
     }
     
     public PaninoBuilder withVegetable(String vegetable){  
      vegetables.add(vegetable);
      return this;
     }
     
     public Panino build(){  
      Panino panino = new Panino(name);
      panino.setBreadType(breadType);
      panino.setCheese(cheese);
      panino.setFish(fish);
      panino.setMeat(meat);
      panino.setVegetables(vegetables);
      return panino;
     }
     }
    A user will be then able to nicely build a Panino using this builder.
    
    
    package com.stepbuilder.bar.client;
    import com.stepbuilder.bar.Panino;
    import com.stepbuilder.bar.PaninoBuilder;
    public class Bar {
    
     public static void main(String[] args) {
      Panino marcoPanino = new PaninoBuilder().paninoCalled("marcoPanino")
        .breadType("baguette").withCheese("gorgonzola").withMeat("ham")
        .withVegetable("tomatos").build();
    
      System.out.println(marcoPanino);
     }
    }
    So far so good.
    But what I don't like of the traditional Builder pattern is the following:


    • It does not really guide the user through the creation.
    • A user can always call the build method in any moment, even without the needed information. 
    • There is no way to guide the user from a creation path instead of another based on conditions. 
    • There is always the risk to leave your object in an inconsistent state.
    • All methods are always available, leaving the responsibility of which to use and when to use to the user who did not write the api.

    For instance, in the Panino example, a user could write something like this:
    
    
    package com.stepbuilder.bar.client;
    import com.stepbuilder.bar.Panino;
    import com.stepbuilder.bar.PaninoBuilder;
    public class Bar {
    
     public static void main(String[] args) {
      Panino marcoPanino = new PaninoBuilder().paninoCalled("marcoPanino")
        .withCheese("gorgonzola").build();
    
      // or
      marcoPanino = new PaninoBuilder().withCheese("gorgonzola").build();
      // or
      marcoPanino = new PaninoBuilder().withMeat("ham").build();
      // or
      marcoPanino = new PaninoBuilder().build();
     }
    }
    All the above panino instances are wrong, and the user will not know until runtime when he will use the Panino object.
    You can put a validation in the build method of course, but still a user will be not able to recover from a badly builder usage.
    You could also put default values around all the required properties, but then the readability of the code will be lost ( new PaninoBuilder().build(); what are you building here?) and often you really need some input from the user (a user and password for a connection for example).

    So here is my solution called Step builder, an extension of the Builder patter that fully guides the user through the creation of the object with no chances of confusion.


    package com.stepbuilder.bar;
    import java.util.ArrayList;
    import java.util.List;
    public class PaninoStepBuilder {
    
            public static FirstNameStep newBuilder() {
                    return new Steps();
            }
    
            private PaninoStepBuilder() {
            }
    
            /**
             * First Builder Step in charge of the Panino name. 
             * Next Step available : BreadTypeStep
             */
            public static interface FirstNameStep {
                    BreadTypeStep paninoCalled(String name);
            }
    
            /**
             * This step is in charge of the BreadType. 
             * Next Step available : MainFillingStep
             */
            public static interface BreadTypeStep {
                    MainFillingStep breadType(String breadType);
            }
    
            /**
             * This step is in charge of setting the main filling (meat or fish). 
             * Meat choice : Next Step available : CheeseStep 
             * Fish choice : Next Step available : VegetableStep
             */
            public static interface MainFillingStep {
                    CheeseStep meat(String meat);
    
                    VegetableStep fish(String fish);
            }
    
            /**
             * This step is in charge of the cheese. 
             * Next Step available : VegetableStep
             */
            public static interface CheeseStep {
                    VegetableStep noCheesePlease();
    
                    VegetableStep withCheese(String cheese);
            }
    
            /**
             * This step is in charge of vegetables. 
             * Next Step available : BuildStep
             */
            public static interface VegetableStep {
                    BuildStep noMoreVegetablesPlease();
    
                    BuildStep noVegetablesPlease();
    
                    VegetableStep addVegetable(String vegetable);
            }
    
            /**
             * This is the final step in charge of building the Panino Object.
             * Validation should be here.
             */
            public static interface BuildStep {
                    Panino build();
            }
    
            private static class Steps implements FirstNameStep, BreadTypeStep, MainFillingStep, CheeseStep, VegetableStep, BuildStep {
    
                    private String name;
                    private String breadType;
                    private String meat;
                    private String fish;
                    private String cheese;
                    private final List<String> vegetables = new ArrayList<String>();
    
                    public BreadTypeStep paninoCalled(String name) {
                            this.name = name;
                            return this;
                    }
    
                    public MainFillingStep breadType(String breadType) {
                            this.breadType = breadType;
                            return this;
                    }
    
                    public CheeseStep meat(String meat) {
                            this.meat = meat;
                            return this;
                    }
    
                    public VegetableStep fish(String fish) {
                            this.fish = fish;
                            return this;
                    }
    
                    public BuildStep noMoreVegetablesPlease() {
                            return this;
                    }
    
                    public BuildStep noVegetablesPlease() {
                            return this;
                    }
    
                    public VegetableStep addVegetable(String vegetable) {
                            this.vegetables.add(vegetable);
                            return this;
                    }
    
                    public VegetableStep noCheesePlease() {
                            return this;
                    }
    
                    public VegetableStep withCheese(String cheese) {
                            this.cheese = cheese;
                            return this;
                    }
    
                    public Panino build() {
                            Panino panino = new Panino(name);
                            panino.setBreadType(breadType);
                            if (fish != null) {
                                    panino.setFish(fish);
                            } else {
                                    panino.setMeat(meat);
                            }
                            if (cheese != null) {
                                    panino.setCheese(cheese);
                            }
                            if (!vegetables.isEmpty()) {
                                    panino.setVegetables(vegetables);
                            }
                            return panino;
                    }
    
            }
    }


    
    
    The concept is simple:

    1. Write creational steps inner classes or interfaces where each method knows what can be displayed next.
    2. Implement all your steps interfaces in an inner static class.
    3. Last step is the BuildStep, in charge of creating the object you need to build.

    The user experience will be much more improved by the fact that he will only see the next step methods available, NO build method until is the right time to build the object.
    
    
    package com.stepbuilder.bar.client;
    import com.stepbuilder.bar.Panino;
    import com.stepbuilder.bar.PaninoBuilder;
    import com.stepbuilder.bar.PaninoStepBuilder;
    public class Bar {
    
     public static void main(String[] args) {
      Panino solePanino = PaninoStepBuilder.newBuilder()
            .paninoCalled("sole panino")
            .breadType("baguette")
            .fish("sole")
            .addVegetable("tomato")
            .addVegetable("lettece")
            .noMoreVegetablesPlease()
            .build();
    
      
     }
    }
    The user will not be able to call the method breadType(String breadType) before calling the paninoCalled(String name) method, and so on.
    Plus, using this pattern, if the user choose a fish panino, I will not give him the possibility to add cheese (i'm the chef, I know how to prepare a panino).
    In the end, I will have  a consistent Object and user will find extremely easy to use the API because he will have very few and selected choices per time.

    We could have different panino traditional builders, FishPaninoBuilder, MeatPaninoBuilder, etc, but still we would face the inconsistent problems and the user will still need to understand exactly what was your idea behind the builder.
    With the Step Builder the user will know without any doubt what input is required, making his and your life easier.