├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main └── java └── com └── theboreddev ├── carwash ├── Car.java ├── CarWashClient.java ├── CarWashStep.java ├── DryStep.java ├── InitialWashStep.java ├── OldStyleCarWashClient.java ├── PolishStep.java ├── RinseStep.java ├── SoapStep.java └── WashState.java ├── customers ├── Customer.java ├── CustomerClient.java └── OldCustomer.java ├── prices ├── BasicDeliveryPriceCalculator.java ├── BusinessDeliveryPriceCalculator.java ├── DeliveryPriceCalculator.java ├── DeliveryPriceCalculatorClient.java ├── Item.java ├── OldStylePlan.java ├── Plan.java ├── PremiumDeliveryPriceCalculator.java └── PriceCalculatorFactory.java └── vehicles ├── AbstractVehicle.java ├── Bus.java ├── Car.java ├── OldStyleVehicleFactory.java ├── Plane.java ├── Truck.java ├── Vehicle.java ├── VehicleClient.java ├── VehicleColor.java └── VehicleType.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build 3 | .gradle 4 | *.iml 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # functional-patterns 2 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group 'org.example' 6 | version '1.0-SNAPSHOT' 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | testCompile group: 'junit', name: 'junit', version: '4.12' 14 | } 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theboreddev/functional-patterns/612283ab57bee427300b95687f635247ca540a6b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jul 04 09:55:22 BST 2020 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'functional-patterns' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/carwash/Car.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.carwash; 2 | 3 | public class Car { 4 | private WashState washState; 5 | 6 | public Car() { 7 | this.washState = WashState.INITIAL; 8 | System.out.println("Car state transitioned to " + washState); 9 | } 10 | 11 | public Car updateState(WashState state) { 12 | System.out.println("Car state transitioned to " + state); 13 | this.washState = state; 14 | return this; 15 | } 16 | 17 | public WashState washState() { 18 | return washState; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/carwash/CarWashClient.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.carwash; 2 | 3 | import java.util.function.Function; 4 | 5 | public class CarWashClient { 6 | 7 | public static void main(String[] args) { 8 | 9 | final Car car = new Car(); 10 | Function initial = c -> new Car(); 11 | 12 | final Function chain = initial 13 | .andThen(c1 -> c1.updateState(WashState.INITIAL_WASH)) 14 | .andThen(c2 -> c2.updateState(WashState.SOAP)) 15 | .andThen(c3 -> c3.updateState(WashState.RINSED)) 16 | .andThen(c4 -> c4.updateState(WashState.POLISHED)) 17 | .andThen(c5 -> c5.updateState(WashState.DRIED)); 18 | 19 | final Car finalCar = chain.apply(car); 20 | 21 | System.out.println("Final car state is " + finalCar.washState()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/carwash/CarWashStep.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.carwash; 2 | 3 | 4 | public abstract class CarWashStep { 5 | protected CarWashStep nextStep; 6 | 7 | public CarWashStep andThen(CarWashStep nextStep) { 8 | this.nextStep = nextStep; 9 | return nextStep; 10 | } 11 | abstract Car applyTo(Car car); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/carwash/DryStep.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.carwash; 2 | 3 | public class DryStep extends CarWashStep { 4 | @Override 5 | Car applyTo(Car car) { 6 | final Car newCar = car.updateState(WashState.DRIED); 7 | if (nextStep != null) { 8 | return nextStep.applyTo(newCar); 9 | } 10 | return newCar; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/carwash/InitialWashStep.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.carwash; 2 | 3 | public class InitialWashStep extends CarWashStep { 4 | 5 | @Override 6 | public Car applyTo(Car car) { 7 | final Car newCar = car.updateState(WashState.INITIAL_WASH); 8 | if (nextStep != null) { 9 | return nextStep.applyTo(newCar); 10 | } 11 | return newCar; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/carwash/OldStyleCarWashClient.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.carwash; 2 | 3 | public class OldStyleCarWashClient { 4 | 5 | public static void main(String[] args) { 6 | 7 | final Car car = new Car(); 8 | 9 | final CarWashStep chain = new InitialWashStep(); 10 | 11 | chain.andThen(new SoapStep()) 12 | .andThen(new RinseStep()) 13 | .andThen(new PolishStep()) 14 | .andThen(new DryStep()); 15 | 16 | final Car finalCar = chain.applyTo(car); 17 | 18 | System.out.println("Final car state is " + finalCar.washState()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/carwash/PolishStep.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.carwash; 2 | 3 | public class PolishStep extends CarWashStep { 4 | @Override 5 | Car applyTo(Car car) { 6 | final Car newCar = car.updateState(WashState.POLISHED); 7 | if (nextStep != null) { 8 | return nextStep.applyTo(newCar); 9 | } 10 | return newCar; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/carwash/RinseStep.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.carwash; 2 | 3 | public class RinseStep extends CarWashStep { 4 | @Override 5 | Car applyTo(Car car) { 6 | final Car newCar = car.updateState(WashState.RINSED); 7 | if (nextStep != null) { 8 | return nextStep.applyTo(newCar); 9 | } 10 | return newCar; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/carwash/SoapStep.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.carwash; 2 | 3 | public class SoapStep extends CarWashStep { 4 | 5 | @Override 6 | public Car applyTo(Car car) { 7 | final Car newCar = car.updateState(WashState.SOAP); 8 | if (nextStep != null) { 9 | return nextStep.applyTo(newCar); 10 | } 11 | return newCar; 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/carwash/WashState.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.carwash; 2 | 3 | public enum WashState { 4 | INITIAL, 5 | INITIAL_WASH, 6 | SOAP, 7 | RINSED, 8 | POLISHED, 9 | DRIED 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/customers/Customer.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.customers; 2 | 3 | import java.util.Optional; 4 | import java.util.function.Consumer; 5 | 6 | public class Customer { 7 | private final String firstName; 8 | private final String surname; 9 | private final Integer age; 10 | private final String address; 11 | private final String phoneNumber; 12 | private final Sex sex; 13 | 14 | private Customer(Builder builder) { 15 | this.firstName = builder.firstName; 16 | this.surname = builder.surname; 17 | this.age = builder.age; 18 | this.address = builder.address; 19 | this.phoneNumber = builder.phoneNumber; 20 | this.sex = builder.sex; 21 | } 22 | 23 | static class Builder { 24 | private final String firstName; 25 | private final String surname; 26 | public Integer age; 27 | public String address; 28 | public String phoneNumber; 29 | public Sex sex; 30 | 31 | public Builder(String firstName, String surname) { 32 | this.firstName = firstName; 33 | this.surname = surname; 34 | } 35 | 36 | public Builder with(Consumer consumer) { 37 | consumer.accept(this); 38 | return this; 39 | } 40 | 41 | public Customer build() { 42 | return new Customer(this); 43 | } 44 | } 45 | 46 | public String getFirstName() { 47 | return firstName; 48 | } 49 | 50 | public String getSurname() { 51 | return surname; 52 | } 53 | 54 | public Optional getAge() { 55 | return Optional.ofNullable(age); 56 | } 57 | 58 | public Optional getAddress() { 59 | return Optional.ofNullable(address); 60 | } 61 | 62 | public Optional getPhoneNumber() { 63 | return Optional.ofNullable(phoneNumber); 64 | } 65 | 66 | public Optional getSex() { 67 | return Optional.ofNullable(sex); 68 | } 69 | 70 | static enum Sex { 71 | FEMALE, 72 | MALE 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return "Customer{" + 78 | "firstName='" + firstName + '\'' + 79 | ", surname='" + surname + '\'' + 80 | ", age=" + age + 81 | ", address='" + address + '\'' + 82 | ", phoneNumber='" + phoneNumber + '\'' + 83 | ", sex=" + sex + 84 | '}'; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/customers/CustomerClient.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.customers; 2 | 3 | public class CustomerClient { 4 | 5 | public static void main(String[] args) { 6 | 7 | final Customer newCustomer = new Customer.Builder("John", "Smith") 8 | .with(builder -> { 9 | builder.age = 33; 10 | builder.address = "13, Some Street. AB XYZ"; 11 | builder.phoneNumber = "0123456789"; 12 | builder.sex = Customer.Sex.MALE; 13 | }) 14 | .build(); 15 | 16 | System.out.println(newCustomer); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/customers/OldCustomer.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.customers; 2 | 3 | import java.util.Optional; 4 | 5 | public class OldCustomer { 6 | private final String firstName; 7 | private final String surname; 8 | private final Integer age; 9 | private final String address; 10 | private final String phoneNumber; 11 | private final Sex sex; 12 | 13 | private OldCustomer(Builder builder) { 14 | this.firstName = builder.firstName; 15 | this.surname = builder.surname; 16 | this.age = builder.age; 17 | this.address = builder.address; 18 | this.phoneNumber = builder.phoneNumber; 19 | this.sex = builder.sex; 20 | } 21 | 22 | public static Builder builderOf(String firstName, String surname) { 23 | return new Builder(firstName, surname); 24 | } 25 | 26 | static class Builder { 27 | private final String firstName; 28 | private final String surname; 29 | private Integer age; 30 | private String address; 31 | private String phoneNumber; 32 | private Sex sex; 33 | 34 | private Builder(String firstName, String surname) { 35 | this.firstName = firstName; 36 | this.surname = surname; 37 | } 38 | 39 | public Builder withAge(Integer age) { 40 | this.age = age; 41 | return this; 42 | } 43 | 44 | public Builder withAddress(String address) { 45 | this.address = address; 46 | return this; 47 | } 48 | 49 | public Builder withPhoneNumber(String phoneNumber) { 50 | this.phoneNumber = phoneNumber; 51 | return this; 52 | } 53 | 54 | public Builder withSex(Sex sex) { 55 | this.sex = sex; 56 | return this; 57 | } 58 | 59 | public OldCustomer build() { 60 | return new OldCustomer(this); 61 | } 62 | } 63 | 64 | public String getFirstName() { 65 | return firstName; 66 | } 67 | 68 | public String getSurname() { 69 | return surname; 70 | } 71 | 72 | public Optional getAge() { 73 | return Optional.ofNullable(age); 74 | } 75 | 76 | public Optional getAddress() { 77 | return Optional.ofNullable(address); 78 | } 79 | 80 | public Optional getPhoneNumber() { 81 | return Optional.ofNullable(phoneNumber); 82 | } 83 | 84 | public Optional getSex() { 85 | return Optional.ofNullable(sex); 86 | } 87 | 88 | static enum Sex { 89 | FEMALE, 90 | MALE 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/prices/BasicDeliveryPriceCalculator.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.prices; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class BasicDeliveryPriceCalculator implements DeliveryPriceCalculator { 6 | @Override 7 | public BigDecimal priceFor(Item item) { 8 | return item.price().multiply(new BigDecimal("0.025")).add(new BigDecimal("1.0")); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/prices/BusinessDeliveryPriceCalculator.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.prices; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class BusinessDeliveryPriceCalculator implements DeliveryPriceCalculator { 6 | @Override 7 | public BigDecimal priceFor(Item item) { 8 | return new BigDecimal("1.0"); 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/prices/DeliveryPriceCalculator.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.prices; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public interface DeliveryPriceCalculator { 6 | BigDecimal priceFor(Item item); 7 | } 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/prices/DeliveryPriceCalculatorClient.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.prices; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class DeliveryPriceCalculatorClient { 6 | 7 | public static void main(String[] args) { 8 | 9 | final PriceCalculatorFactory factory = new PriceCalculatorFactory(); 10 | 11 | // Old style 12 | final Item newItem = new Item(1L, new BigDecimal("12.99")); 13 | final DeliveryPriceCalculator priceCalculator = factory.priceCalculatorFor(OldStylePlan.BASIC); 14 | System.out.println("Delivery price is " + priceCalculator.priceFor(newItem)); 15 | 16 | //Functional style 17 | final Item item = new Item(1L, new BigDecimal("12.99")); 18 | System.out.println("Delivery price is " + Plan.BASIC.deliveryPrice.apply(item)); 19 | System.out.println("Delivery price is " + Plan.PREMIUM.deliveryPrice.apply(item)); 20 | System.out.println("Delivery price is " + Plan.BUSINESS.deliveryPrice.apply(item)); 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/prices/Item.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.prices; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class Item { 6 | private final long id; 7 | private final BigDecimal price; 8 | 9 | public Item(long id, BigDecimal price) { 10 | this.id = id; 11 | this.price = price; 12 | } 13 | 14 | public long id() { 15 | return id; 16 | } 17 | 18 | public BigDecimal price() { 19 | return price; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/prices/OldStylePlan.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.prices; 2 | 3 | public enum OldStylePlan { 4 | BASIC, 5 | PREMIUM, 6 | BUSINESS 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/prices/Plan.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.prices; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.function.Function; 5 | 6 | public enum Plan { 7 | BASIC(deliveryPriceWithPercentageSurplus("0.025")), 8 | PREMIUM(deliveryPriceWithPercentageSurplus("0.015")), 9 | BUSINESS(deliveryPriceWithPercentageSurplus("0.0")); 10 | 11 | private static Function deliveryPriceWithPercentageSurplus(String percentageSurplus) { 12 | return item -> item.price().multiply(new BigDecimal(percentageSurplus)).add(new BigDecimal("1.0")); 13 | } 14 | 15 | public final Function deliveryPrice; 16 | 17 | Plan(Function deliveryPrice) { 18 | this.deliveryPrice = deliveryPrice; 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/prices/PremiumDeliveryPriceCalculator.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.prices; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class PremiumDeliveryPriceCalculator implements DeliveryPriceCalculator { 6 | @Override 7 | public BigDecimal priceFor(Item item) { 8 | return item.price().multiply(new BigDecimal("0.015")).add(new BigDecimal("1.0")); 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/prices/PriceCalculatorFactory.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.prices; 2 | 3 | public class PriceCalculatorFactory { 4 | 5 | public DeliveryPriceCalculator priceCalculatorFor(OldStylePlan plan) { 6 | if (plan.equals(OldStylePlan.BASIC)) { 7 | return new BasicDeliveryPriceCalculator(); 8 | } else if (plan.equals(OldStylePlan.PREMIUM)) { 9 | return new PremiumDeliveryPriceCalculator(); 10 | } else if (plan.equals(OldStylePlan.BUSINESS)) { 11 | return new BusinessDeliveryPriceCalculator(); 12 | } 13 | throw new IllegalArgumentException("No support for plan " + plan); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/vehicles/AbstractVehicle.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.vehicles; 2 | 3 | public abstract class AbstractVehicle implements Vehicle { 4 | 5 | public void start() { 6 | preStartCheck(); 7 | System.out.println(String.format("%s starting...", this.getClass().getSimpleName())); 8 | } 9 | 10 | protected abstract void preStartCheck(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/vehicles/Bus.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.vehicles; 2 | 3 | public class Bus implements Vehicle { 4 | private final VehicleColor color; 5 | 6 | public Bus(VehicleColor color) { 7 | this.color = color; 8 | } 9 | 10 | public VehicleColor color() { 11 | return color; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/vehicles/Car.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.vehicles; 2 | 3 | public class Car implements Vehicle { 4 | private final VehicleColor color; 5 | 6 | public Car(VehicleColor color) { 7 | this.color = color; 8 | } 9 | 10 | public VehicleColor color() { 11 | return color; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/vehicles/OldStyleVehicleFactory.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.vehicles; 2 | 3 | public class OldStyleVehicleFactory { 4 | /* 5 | Issues: 6 | 1 - if conditions to check type leads to duplication most of the times 7 | 2 - Adding a new type of vehicle implies adding a new conditional 8 | 3 - We have to throw IllegalArgumentException if there's no match for type 9 | 4 - Harder to read 10 | */ 11 | 12 | public static Vehicle instanceOfType(VehicleType type, 13 | VehicleColor color) { 14 | if (type.equals(VehicleType.CAR)) { 15 | return new Car(color); 16 | } else if (type.equals(VehicleType.BUS)) { 17 | return new Bus(color); 18 | } else if (type.equals(VehicleType.TRUCK)) { 19 | return new Truck(color); 20 | } 21 | throw new IllegalArgumentException("No support for type " + type); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/vehicles/Plane.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.vehicles; 2 | 3 | import com.theboreddev.vehicles.AbstractVehicle; 4 | 5 | public class Plane extends AbstractVehicle { 6 | @Override 7 | protected void preStartCheck() { 8 | System.out.println("Checking that doors are properly closed!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/vehicles/Truck.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.vehicles; 2 | 3 | public class Truck implements Vehicle { 4 | private final VehicleColor color; 5 | 6 | public Truck(VehicleColor color) { 7 | this.color = color; 8 | } 9 | 10 | public VehicleColor color() { 11 | return color; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/vehicles/Vehicle.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.vehicles; 2 | 3 | import java.util.function.Consumer; 4 | 5 | public interface Vehicle { 6 | default void start(Consumer preStartChecks) { 7 | preStartChecks.accept(null); 8 | System.out.println(String.format("%s starting...", this.getClass().getSimpleName())); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/vehicles/VehicleClient.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.vehicles; 2 | 3 | public class VehicleClient { 4 | 5 | public static void main(String[] args) { 6 | final Vehicle redCar = VehicleType.CAR.factory.apply(VehicleColor.RED); 7 | final Vehicle redCar2 = OldStyleVehicleFactory.instanceOfType(VehicleType.CAR, VehicleColor.RED); 8 | 9 | final Vehicle blackTruck = VehicleType.TRUCK.factory.apply(VehicleColor.BLACK); 10 | 11 | final Vehicle blueBus = VehicleType.BUS.factory.apply(VehicleColor.BLUE); 12 | blueBus.start(nil -> { 13 | System.out.println("Check if every new passenger has paid for their tickets"); 14 | System.out.println("Check if every passenger is seated"); 15 | }); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/vehicles/VehicleColor.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.vehicles; 2 | 3 | public enum VehicleColor { 4 | RED, 5 | BLUE, 6 | BLACK, 7 | WHITE 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/theboreddev/vehicles/VehicleType.java: -------------------------------------------------------------------------------- 1 | package com.theboreddev.vehicles; 2 | 3 | import java.util.function.Function; 4 | 5 | public enum VehicleType { 6 | BUS(Bus::new), 7 | TRUCK(Truck::new), 8 | CAR(Car::new); 9 | 10 | public final Function factory; 11 | 12 | VehicleType(Function factory) { 13 | this.factory = factory; 14 | } 15 | } 16 | 17 | --------------------------------------------------------------------------------