Showing posts with label design pattern. Show all posts
Showing posts with label design 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.



Thursday, 15 November 2012

Chain of responsibility using Spring @Autowired List

There is a way in Spring 3.1 to auto populate a typed List which is very handy when you want to push a bit the decoupling and the cleaning in your code.

To show you how it works, I will implement a simple chain of responsibility that will take care of printing some greetings for a passed User.

Let start from the (only) domain class we have, the User:
package com.marco.springchain;
public class User {

        private final String name;
        private final char gender;

        public User(String name, char gender) {
                super();
                this.name = name;
                this.gender = gender;
        }

        public String getName() {
                return name;
        }

        public char getGender() {
                return gender;
        }
}



Then we create an interface that defines the type for our command objects to be used in our chain:

package com.marco.springchain;
public interface Printer {

        void print(User user);
}



This is the generic class (the template) for a Printer implementation.
The org.springframework.core.Ordered is used to tell the AnnotationAwareOrderComparator how we want our List to be ordered.
You don't need to implement the Ordered interface and to override the getOrder method if you don't need your chain to have an execution order.
Also notice that this abstract class return Ordered.LOWEST_PRECEDENCE, this because I want some Printer commands to just run at the end of the chain and I don't care about their execution order (everything will be clearer after, I promise!).

package com.marco.springchain;
import org.springframework.core.Ordered;
public abstract class GenericPrinter implements Printer, Ordered {

        public void print(User user) {
                String prefix = "Mr";
                if (user.getGender() == 'F') {
                        prefix = "Mrs";
                }
                System.out.println(getGreeting() + " " + prefix + " " + user.getName());
        }

        protected abstract String getGreeting();

        public int getOrder() {
                return Ordered.LOWEST_PRECEDENCE;
        }
}



This is our first real Printer command. I want this to have absolute precedence in the chain, hence the order is  HIGHEST_PRECEDENCE.

package com.marco.springchain;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
@Component
public class HelloPrinter extends GenericPrinter {

        private static final String GREETING = "Hello";

        @Override
        protected String getGreeting() {
                return GREETING;
        }

        @Override
        public int getOrder() {
                return Ordered.HIGHEST_PRECEDENCE;
        }
}




WelcomePrinter to be executed as first command (After High precedence ones ).

package com.marco.springchain;
import org.springframework.stereotype.Component;
@Component
public class WelcomePrinter extends GenericPrinter {

        private static final String GREETING = "Welcome to the autowired chain";

        @Override
        protected String getGreeting() {
                return GREETING;
        }

        @Override
        public int getOrder() {
                return 1;
        }
}



GoodbyePrinter to be executed as second command

package com.marco.springchain;
import org.springframework.stereotype.Component;
@Component
public class GoodbyePrinter extends GenericPrinter {

        private static final String GREETING = "Goodbye";

        @Override
        protected String getGreeting() {
                return GREETING;
        }

        @Override
        public int getOrder() {
                return 2;
        }
}




These 2 commands need to be executed after the others, but I don't care about their specific order, so I will not override the getOrder method, leaving the GenericPrinter to return Ordered.LOWEST_PRECEDENCE for both.

package com.marco.springchain;
import org.springframework.stereotype.Component;
@Component
public class CleaningMemoryPrinter extends GenericPrinter {

        private static final String GREETING = "Cleaning memory after";

        @Override
        protected String getGreeting() {
                return GREETING;
        }
}



package com.marco.springchain;
import org.springframework.stereotype.Component;
@Component
public class CleaningSpacePrinter extends GenericPrinter {

        private static final String GREETING = "Cleaning space after";

        @Override
        protected String getGreeting() {
                return GREETING;
        }
}




This is the chain context.
Spring will scan (see the spring-config.xml) the package specified in the config file, it will see the typed (List<Printer>) list, and it will populate the list with an instance of any @Component that implements the type Printer.
To order the List we use AnnotationAwareOrderComparator.INSTANCE that use the getOrder method to re-order the List ( the object with the lowest value has highest priority (somewhat analogous to Servlet "load-on-startup" values)).

package com.marco.springchain;
import java.util.Collections;
import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.stereotype.Component;
@Component
public class PrinterChain {

        @Autowired
        private List<Printer> printers;

        @PostConstruct
        public void init() {
                Collections.sort(printers, AnnotationAwareOrderComparator.INSTANCE);
        }

        public void introduceUser(User user) {
                for (Printer printer : printers) {
                        printer.print(user);
                }
        }
}



The spring-config.xml in the src/main/resources.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
  http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd" default-lazy-init="true">

    <context:component-scan base-package="com.marco.springchain"/>
</beans>



Finally, a main class to test our chain.

package com.marco.springchain;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainTest {

        public static void main(String[] args) {
                ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
                PrinterChain printerChain = (PrinterChain) context.getBean("printerChain");
                printerChain.introduceUser(new User("Marco Castigliego", 'M'));
                printerChain.introduceUser(new User("Julie Marot", 'F'));
        }
}



OUTPUT:


Hello Mr Marco Castigliego
Welcome to the autowired chain Mr Marco Castigliego
Goodbye Mr Marco Castigliego
Cleaning space after Mr Marco Castigliego
Cleaning memory after Mr Marco Castigliego
Hello Mrs Julie Marot
Welcome to the autowired chain Mrs Julie Marot
Goodbye Mrs Julie Marot
Cleaning space after Mrs Julie Marot
Cleaning memory after Mrs Julie Marot


Hope you enjoyed the example. see ya.