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.
New ideas pass through three periods:
1) It can’t be done.
2) It probably can be done, but it’s not worth doing.
3) I knew it was a good idea all along!
- Arthur C. Clarke
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; } }
package com.marco.springchain; public interface Printer { void print(User user); }
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; } }
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; } }
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; } }
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; } }
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; } }
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); } } }
<?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>
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')); } }
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
<queue name="phaseQueueFromEngine"> <entry name="/queue/phaseQueueFromEngine"/> </queue>
<acceptor name="in-vm"> <factory-class>org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory</factory-class> </acceptor> <connector name="in-vm"> <factory-class>org.hornetq.core.remoting.impl.invm.InVMConnectorFactory</factory-class> </connector>
<acceptor name="netty"> <factory-class>org.hornetq.integration.transports.netty.NettyAcceptorFactory</factory-class> <param key="host" value="${host:localhost}"/> <param key="port" value="${port:5445}"/> </acceptor>
<connector name="remote-engine-connector"> <factory-class> org.hornetq.integration.transports.netty.NettyConnectorFactory</factory-class> <param key="host" value="172.x.x.62"/> <param key="port" value="5445"/> </connector>
package com.marco.test;
package com.marco.test;
Contents |
/** "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(); } }
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; } } } }