Skip to main content
replaced http://programmers.stackexchange.com/ with https://softwareengineering.stackexchange.com/
Source Link
public UserConfig(UserConfig_Fieldable uc_f) { //transfer try { sName = uc_f.getName(); } catch(NullPointerException rx) { throw new NullPointerException("uc_f"); } iAge = uc_f.getAge(); sFavColor = uc_f.getFavoriteColor(); //validate (should really pre-compile the patterns...) try { if(!Pattern.compile("\\\w+").matcher(sName).matches()) { throw new IllegalArgumentException("uc_f.getName() (\"" + sName + "\") may not be empty, and must contain only letters digits and underscores."); } } catch(NullPointerException rx) { throw new NullPointerException("uc_f.getName()"); } if(iAge 

Documenting Builders

This section is applicable to both Bloch Builders and Blind Builders. It demonstrates how I document the classes in this design, making setters (in the builder) and their getters (in the ToBeBuilt class) directly cross-referenced to each other--with a single mouse-click, and without the user needing to know where those functions actually reside--and without the developer having to document anything redundantly.

Getters: In the ToBeBuilt classes only

Getters are documented only in the ToBeBuilt class. The equivalent getters both in the *_Fieldable and *_Cfg classes are ignored. I don't document them at all.

/** <P>The user's age.</P> @return An int representing the user's age. @see UserConfig_Cfg#age(int) @see getName() **/ public int getAge() { return iAge; }

The first @see is a link to its setter, which is in the builder class.

Setters: In the builder-class

The setter is documented as if it is in the ToBeBuilt class, and also as if it does the validation (which really is done by the ToBeBuilt's constructor). The asterisk ("*") is a visual clue indicating that the link's target is in another class.

/** <P>Set the user's age.</P> @param i_age May not be less than zero. Get with {@code UserConfig#getName() getName()}*. @see #favoriteColor(String) **/ public UserConfig_Cfg age(int i_age) { iAge = i_age; return this; }

Further information

Putting it all together: The full source of the Blind Builder example, with complete documentation

UserConfig.java

import java.util.regex.Pattern; /** <P>Information about a user -- <I>[builder: UserConfig_Cfg]</I></P> <P>Validation of all fields occurs in this classes constructor. However, each validation requirement is document only in the builder's setter functions.</P> <P>{@code java xbn.z.xmpl.lang.builder.finalv.UserConfig}</P> **/ public class UserConfig { public static final void main(String[] igno_red) { UserConfig uc = new UserConfig_Cfg("Kermit").age(50).favoriteColor("green").build(); System.out.println(uc); } private final String sName ; private final int iAge ; private final String sFavColor; /** <P>Create a new instance. This sets and validates all fields.</P> @param uc_f May not be {@code null}. **/ public UserConfig(UserConfig_Fieldable uc_f) { //transfer try { sName = uc_f.getName(); } catch(NullPointerException rx) { throw new NullPointerException("uc_f"); } iAge = uc_f.getAge(); sFavColor = uc_f.getFavoriteColor(); //validate try { if(!Pattern.compile("\\\w+").matcher(sName).matches()) { throw new IllegalArgumentException("uc_f.getName() (\"" + sName + "\") may not be empty, and must contain only letters digits and underscores."); } } catch(NullPointerException rx) { throw new NullPointerException("uc_f.getName()"); } if(iAge < 0) { throw new IllegalArgumentException("uc_f.getAge() (" + iAge + ") is less than zero."); } try { if(!Pattern.compile("(?:red|blue|green|hot pink)").matcher(sFavColor).matches()) { throw new IllegalArgumentException("uc_f.getFavoriteColor() (\"" + uc_f.getFavoriteColor() + "\") is not red, blue, green, or hot pink."); } } catch(NullPointerException rx) { throw new NullPointerException("uc_f.getFavoriteColor()"); } } //getters...START /** <P>The user's name.</P> @return A non-{@code null}, non-empty string. @see UserConfig_Cfg#UserConfig_Cfg(String) @see #getAge() @see #getFavoriteColor() **/ public String getName() { return sName; } /** <P>The user's age.</P> @return A number greater-than-or-equal-to zero. @see UserConfig_Cfg#age(int) @see #getName() **/ public int getAge() { return iAge; } /** <P>The user's favorite color.</P> @return A non-{@code null}, non-empty string. @see UserConfig_Cfg#age(int) @see #getName() **/ public String getFavoriteColor() { return sFavColor; } //getters...END public String toString() { return "getName()=" + getName() + ", getAge()=" + getAge() + ", getFavoriteColor()=" + getFavoriteColor(); } }

UserConfig_Fieldable.java

/** <P>Required by the {@link UserConfig} {@code UserConfig#UserConfig(UserConfig_Fieldable) constructor}.</P> **/ public interface UserConfig_Fieldable { String getName(); int getAge(); String getFavoriteColor(); }

UserConfig_Cfg.java

import java.util.regex.Pattern; /** <P>Builder for {@link UserConfig}.</P> <P>Validation of all fields occurs in the <CODE>UserConfig</CODE> constructor. However, each validation requirement is document only in this classes setter functions.</P> **/ public class UserConfig_Cfg implements UserConfig_Fieldable { public String sName ; public int iAge ; public String sFavColor; /** <P>Create a new instance with the user's name.</P> @param s_name May not be {@code null} or empty, and must contain only letters, digits, and underscores. Get with {@code UserConfig#getName() getName()}{@code ()}*. **/ public UserConfig_Cfg(String s_name) { sName = s_name; } //self-returning setters...START /** <P>Set the user's age.</P> @param i_age May not be less than zero. Get with {@code UserConfig#getName() getName()}{@code ()}*. @see #favoriteColor(String) **/ public UserConfig_Cfg age(int i_age) { iAge = i_age; return this; } /** <P>Set the user's favorite color.</P> @param s_color Must be {@code "red"}, {@code "blue"}, {@code green}, or {@code "hot pink"}. Get with {@code UserConfig#getName() getName()}{@code ()}*. @see #age(int) **/ public UserConfig_Cfg favoriteColor(String s_color) { sFavColor = s_color; return this; } //self-returning setters...END //getters...START public String getName() { return sName; } public int getAge() { return iAge; } public String getFavoriteColor() { return sFavColor; } //getters...END /** <P>Build the UserConfig, as configured.</P> @return <CODE>(new {@link UserConfig#UserConfig(UserConfig_Fieldable) UserConfig}(this))</CODE> **/ public UserConfig build() { return (new UserConfig(this)); } }
public UserConfig(UserConfig_Fieldable uc_f) { //transfer try { sName = uc_f.getName(); } catch(NullPointerException rx) { throw new NullPointerException("uc_f"); } iAge = uc_f.getAge(); sFavColor = uc_f.getFavoriteColor(); //validate (should really pre-compile the patterns...) try { if(!Pattern.compile("\\\w+").matcher(sName).matches()) { throw new IllegalArgumentException("uc_f.getName() (\"" + sName + "\") may not be empty, and must contain only letters digits and underscores."); } } catch(NullPointerException rx) { throw new NullPointerException("uc_f.getName()"); } if(iAge 

Documenting Builders

This section is applicable to both Bloch Builders and Blind Builders. It demonstrates how I document the classes in this design, making setters (in the builder) and their getters (in the ToBeBuilt class) directly cross-referenced to each other--with a single mouse-click, and without the user needing to know where those functions actually reside--and without the developer having to document anything redundantly.

Getters: In the ToBeBuilt classes only

Getters are documented only in the ToBeBuilt class. The equivalent getters both in the *_Fieldable and *_Cfg classes are ignored. I don't document them at all.

/** <P>The user's age.</P> @return An int representing the user's age. @see UserConfig_Cfg#age(int) @see getName() **/ public int getAge() { return iAge; }

The first @see is a link to its setter, which is in the builder class.

Setters: In the builder-class

The setter is documented as if it is in the ToBeBuilt class, and also as if it does the validation (which really is done by the ToBeBuilt's constructor). The asterisk ("*") is a visual clue indicating that the link's target is in another class.

/** <P>Set the user's age.</P> @param i_age May not be less than zero. Get with {@code UserConfig#getName() getName()}*. @see #favoriteColor(String) **/ public UserConfig_Cfg age(int i_age) { iAge = i_age; return this; }

Further information

Putting it all together: The full source of the Blind Builder example, with complete documentation

UserConfig.java

import java.util.regex.Pattern; /** <P>Information about a user -- <I>[builder: UserConfig_Cfg]</I></P> <P>Validation of all fields occurs in this classes constructor. However, each validation requirement is document only in the builder's setter functions.</P> <P>{@code java xbn.z.xmpl.lang.builder.finalv.UserConfig}</P> **/ public class UserConfig { public static final void main(String[] igno_red) { UserConfig uc = new UserConfig_Cfg("Kermit").age(50).favoriteColor("green").build(); System.out.println(uc); } private final String sName ; private final int iAge ; private final String sFavColor; /** <P>Create a new instance. This sets and validates all fields.</P> @param uc_f May not be {@code null}. **/ public UserConfig(UserConfig_Fieldable uc_f) { //transfer try { sName = uc_f.getName(); } catch(NullPointerException rx) { throw new NullPointerException("uc_f"); } iAge = uc_f.getAge(); sFavColor = uc_f.getFavoriteColor(); //validate try { if(!Pattern.compile("\\\w+").matcher(sName).matches()) { throw new IllegalArgumentException("uc_f.getName() (\"" + sName + "\") may not be empty, and must contain only letters digits and underscores."); } } catch(NullPointerException rx) { throw new NullPointerException("uc_f.getName()"); } if(iAge < 0) { throw new IllegalArgumentException("uc_f.getAge() (" + iAge + ") is less than zero."); } try { if(!Pattern.compile("(?:red|blue|green|hot pink)").matcher(sFavColor).matches()) { throw new IllegalArgumentException("uc_f.getFavoriteColor() (\"" + uc_f.getFavoriteColor() + "\") is not red, blue, green, or hot pink."); } } catch(NullPointerException rx) { throw new NullPointerException("uc_f.getFavoriteColor()"); } } //getters...START /** <P>The user's name.</P> @return A non-{@code null}, non-empty string. @see UserConfig_Cfg#UserConfig_Cfg(String) @see #getAge() @see #getFavoriteColor() **/ public String getName() { return sName; } /** <P>The user's age.</P> @return A number greater-than-or-equal-to zero. @see UserConfig_Cfg#age(int) @see #getName() **/ public int getAge() { return iAge; } /** <P>The user's favorite color.</P> @return A non-{@code null}, non-empty string. @see UserConfig_Cfg#age(int) @see #getName() **/ public String getFavoriteColor() { return sFavColor; } //getters...END public String toString() { return "getName()=" + getName() + ", getAge()=" + getAge() + ", getFavoriteColor()=" + getFavoriteColor(); } }

UserConfig_Fieldable.java

/** <P>Required by the {@link UserConfig} {@code UserConfig#UserConfig(UserConfig_Fieldable) constructor}.</P> **/ public interface UserConfig_Fieldable { String getName(); int getAge(); String getFavoriteColor(); }

UserConfig_Cfg.java

import java.util.regex.Pattern; /** <P>Builder for {@link UserConfig}.</P> <P>Validation of all fields occurs in the <CODE>UserConfig</CODE> constructor. However, each validation requirement is document only in this classes setter functions.</P> **/ public class UserConfig_Cfg implements UserConfig_Fieldable { public String sName ; public int iAge ; public String sFavColor; /** <P>Create a new instance with the user's name.</P> @param s_name May not be {@code null} or empty, and must contain only letters, digits, and underscores. Get with {@code UserConfig#getName() getName()}{@code ()}*. **/ public UserConfig_Cfg(String s_name) { sName = s_name; } //self-returning setters...START /** <P>Set the user's age.</P> @param i_age May not be less than zero. Get with {@code UserConfig#getName() getName()}{@code ()}*. @see #favoriteColor(String) **/ public UserConfig_Cfg age(int i_age) { iAge = i_age; return this; } /** <P>Set the user's favorite color.</P> @param s_color Must be {@code "red"}, {@code "blue"}, {@code green}, or {@code "hot pink"}. Get with {@code UserConfig#getName() getName()}{@code ()}*. @see #age(int) **/ public UserConfig_Cfg favoriteColor(String s_color) { sFavColor = s_color; return this; } //self-returning setters...END //getters...START public String getName() { return sName; } public int getAge() { return iAge; } public String getFavoriteColor() { return sFavColor; } //getters...END /** <P>Build the UserConfig, as configured.</P> @return <CODE>(new {@link UserConfig#UserConfig(UserConfig_Fieldable) UserConfig}(this))</CODE> **/ public UserConfig build() { return (new UserConfig(this)); } }
public UserConfig(UserConfig_Fieldable uc_f) { //transfer try { sName = uc_f.getName(); } catch(NullPointerException rx) { throw new NullPointerException("uc_f"); } iAge = uc_f.getAge(); sFavColor = uc_f.getFavoriteColor(); //validate (should really pre-compile the patterns...) try { if(!Pattern.compile("\\\w+").matcher(sName).matches()) { throw new IllegalArgumentException("uc_f.getName() (\"" + sName + "\") may not be empty, and must contain only letters digits and underscores."); } } catch(NullPointerException rx) { throw new NullPointerException("uc_f.getName()"); } if(iAge 

Documenting Builders

This section is applicable to both Bloch Builders and Blind Builders. It demonstrates how I document the classes in this design, making setters (in the builder) and their getters (in the ToBeBuilt class) directly cross-referenced to each other--with a single mouse-click, and without the user needing to know where those functions actually reside--and without the developer having to document anything redundantly.

Getters: In the ToBeBuilt classes only

Getters are documented only in the ToBeBuilt class. The equivalent getters both in the *_Fieldable and *_Cfg classes are ignored. I don't document them at all.

/** <P>The user's age.</P> @return An int representing the user's age. @see UserConfig_Cfg#age(int) @see getName() **/ public int getAge() { return iAge; }

The first @see is a link to its setter, which is in the builder class.

Setters: In the builder-class

The setter is documented as if it is in the ToBeBuilt class, and also as if it does the validation (which really is done by the ToBeBuilt's constructor). The asterisk ("*") is a visual clue indicating that the link's target is in another class.

/** <P>Set the user's age.</P> @param i_age May not be less than zero. Get with {@code UserConfig#getName() getName()}*. @see #favoriteColor(String) **/ public UserConfig_Cfg age(int i_age) { iAge = i_age; return this; }

Further information

Putting it all together: The full source of the Blind Builder example, with complete documentation

UserConfig.java

import java.util.regex.Pattern; /** <P>Information about a user -- <I>[builder: UserConfig_Cfg]</I></P> <P>Validation of all fields occurs in this classes constructor. However, each validation requirement is document only in the builder's setter functions.</P> <P>{@code java xbn.z.xmpl.lang.builder.finalv.UserConfig}</P> **/ public class UserConfig { public static final void main(String[] igno_red) { UserConfig uc = new UserConfig_Cfg("Kermit").age(50).favoriteColor("green").build(); System.out.println(uc); } private final String sName ; private final int iAge ; private final String sFavColor; /** <P>Create a new instance. This sets and validates all fields.</P> @param uc_f May not be {@code null}. **/ public UserConfig(UserConfig_Fieldable uc_f) { //transfer try { sName = uc_f.getName(); } catch(NullPointerException rx) { throw new NullPointerException("uc_f"); } iAge = uc_f.getAge(); sFavColor = uc_f.getFavoriteColor(); //validate try { if(!Pattern.compile("\\\w+").matcher(sName).matches()) { throw new IllegalArgumentException("uc_f.getName() (\"" + sName + "\") may not be empty, and must contain only letters digits and underscores."); } } catch(NullPointerException rx) { throw new NullPointerException("uc_f.getName()"); } if(iAge < 0) { throw new IllegalArgumentException("uc_f.getAge() (" + iAge + ") is less than zero."); } try { if(!Pattern.compile("(?:red|blue|green|hot pink)").matcher(sFavColor).matches()) { throw new IllegalArgumentException("uc_f.getFavoriteColor() (\"" + uc_f.getFavoriteColor() + "\") is not red, blue, green, or hot pink."); } } catch(NullPointerException rx) { throw new NullPointerException("uc_f.getFavoriteColor()"); } } //getters...START /** <P>The user's name.</P> @return A non-{@code null}, non-empty string. @see UserConfig_Cfg#UserConfig_Cfg(String) @see #getAge() @see #getFavoriteColor() **/ public String getName() { return sName; } /** <P>The user's age.</P> @return A number greater-than-or-equal-to zero. @see UserConfig_Cfg#age(int) @see #getName() **/ public int getAge() { return iAge; } /** <P>The user's favorite color.</P> @return A non-{@code null}, non-empty string. @see UserConfig_Cfg#age(int) @see #getName() **/ public String getFavoriteColor() { return sFavColor; } //getters...END public String toString() { return "getName()=" + getName() + ", getAge()=" + getAge() + ", getFavoriteColor()=" + getFavoriteColor(); } }

UserConfig_Fieldable.java

/** <P>Required by the {@link UserConfig} {@code UserConfig#UserConfig(UserConfig_Fieldable) constructor}.</P> **/ public interface UserConfig_Fieldable { String getName(); int getAge(); String getFavoriteColor(); }

UserConfig_Cfg.java

import java.util.regex.Pattern; /** <P>Builder for {@link UserConfig}.</P> <P>Validation of all fields occurs in the <CODE>UserConfig</CODE> constructor. However, each validation requirement is document only in this classes setter functions.</P> **/ public class UserConfig_Cfg implements UserConfig_Fieldable { public String sName ; public int iAge ; public String sFavColor; /** <P>Create a new instance with the user's name.</P> @param s_name May not be {@code null} or empty, and must contain only letters, digits, and underscores. Get with {@code UserConfig#getName() getName()}{@code ()}*. **/ public UserConfig_Cfg(String s_name) { sName = s_name; } //self-returning setters...START /** <P>Set the user's age.</P> @param i_age May not be less than zero. Get with {@code UserConfig#getName() getName()}{@code ()}*. @see #favoriteColor(String) **/ public UserConfig_Cfg age(int i_age) { iAge = i_age; return this; } /** <P>Set the user's favorite color.</P> @param s_color Must be {@code "red"}, {@code "blue"}, {@code green}, or {@code "hot pink"}. Get with {@code UserConfig#getName() getName()}{@code ()}*. @see #age(int) **/ public UserConfig_Cfg favoriteColor(String s_color) { sFavColor = s_color; return this; } //self-returning setters...END //getters...START public String getName() { return sName; } public int getAge() { return iAge; } public String getFavoriteColor() { return sFavColor; } //getters...END /** <P>Build the UserConfig, as configured.</P> @return <CODE>(new {@link UserConfig#UserConfig(UserConfig_Fieldable) UserConfig}(this))</CODE> **/ public UserConfig build() { return (new UserConfig(this)); } }
Added parethesized comment about the disadvantage of using the main constructor also as the copy-constructor.
Source Link
aliteralmind
  • 669
  • 1
  • 6
  • 12

As noted by one smart commenter (who inexplicably deleted their answer), if the ToBeBuilt class also implements its Fieldable, its one-and-only constructor can be used as both its primary and copy constructor (a disadvantage is that fields are always validated, even though it is known that the fields in the original `ToBeBuilt` are valid).

As noted by one smart commenter (who inexplicably deleted their answer), if the ToBeBuilt class also implements its Fieldable, its one-and-only constructor can be used as both its primary and copy constructor.

As noted by one smart commenter (who inexplicably deleted their answer), if the ToBeBuilt class also implements its Fieldable, its one-and-only constructor can be used as both its primary and copy constructor (a disadvantage is that fields are always validated, even though it is known that the fields in the original `ToBeBuilt` are valid).

added 10 characters in body
Source Link
aliteralmind
  • 669
  • 1
  • 6
  • 12

I have created what, for me, is a big improvement over Josh Bloch's Builder Pattern. Not to say in any way that it is "better", just that in a my personal development environmentvery specific situation, it does provide me some advantages--the biggest being that it decouples the builder from its to-be-built class.

I have created what, for me, is a big improvement over Josh Bloch's Builder Pattern. Not to say in any way that it is "better", just that in my personal development environment, it does provide me some advantages--the biggest being that it decouples the builder from its to-be-built class.

I have created what, for me, is a big improvement over Josh Bloch's Builder Pattern. Not to say in any way that it is "better", just that in a very specific situation, it does provide some advantages--the biggest being that it decouples the builder from its to-be-built class.

Added "Fieldable interface optional" and "copy constructor" comments
Source Link
aliteralmind
  • 669
  • 1
  • 6
  • 12
Loading
added 4 characters in body
Source Link
aliteralmind
  • 669
  • 1
  • 6
  • 12
Loading
Source Link
aliteralmind
  • 669
  • 1
  • 6
  • 12
Loading