Optional Parameters handling Strategy
Often we face a
situation where we need to design an Object which expects lot of parameters
from client. Some of parameters are required and some of them are optional. In
this article we will focus on various strategies by which we can design such
objects and their pros and cons
Strategy 1. Telescopic constructors
To design such
objects, we can use chain of overloading constructors. First minimal constructor
is taking only required parameters then delegate call another constructor which
takes an optional parameter
Then this
constructor calls another which takes another optional parameter until all
optional parameters are covered. When
calling another constructor, it can pass default value for optional parameter.
Pros:
1. For
less number of parameters this is good way to design object.
2. Very
easy to implement.
Cons:
1. For
large number of parameters, it creates problems. Developer often confuse while
passing parameters.
2. If
two adjacent parameters have same datatype and developer unintentionally swap
values. Compiler not complain but it creates a genuine problem in runtime and
very hard to track.
Example:
package com.example.builder;
public class EmployeeTelescopic {
private String name;
private Integer empId;
private String company;
private Integer passport;
private String tempAddress ;
public
EmployeeTelescopic(String name,Integer
empId,String company)
{
this(name,empId,company,0);
}
public
EmployeeTelescopic(String name,Integer
empId,String company,int passport)
{
this(name,empId,company,passport,"NA");
}
public
EmployeeTelescopic(String name,Integer
empId,String company,Integer passport,String tempAddress)
{
this.name=name;
this.empId=empId;
this.company=company;
this.passport=passport;
this.tempAddress=tempAddress;
}
@Override
public String toString() {
return "EmployeeTelescopic
[name=" + name + ", empId=" + empId
+
",
company=" + company + ", passport=" + passport
+
",
tempAddress=" + tempAddress + "]";
}
public static void main(String[] args) {
EmployeeTelescopic
emp = new EmployeeTelescopic("Shamik",100,"IBM");
EmployeeTelescopic
emp1 = new EmployeeTelescopic("Akash",101,"IBM",1234,"1,bangalore");
System.out.println(emp);
System.out.println(emp1);
}
}
Strategy 2: By
getters/setters
Create a class and exposing every property through
setters/getters. Client will set the property using setter, access it via
getters.
Pros:
1.
Easy to implement.
2.
In setters you can put
validation or pass default value for optional parameters.
Cons:
1.
If object is share between
multiple thread it can be happened that one thread just create the object and
try to set the properties while other thread accesses this object, but as its
properties have not been set yet wired exception can occur.
2.
As properties are exposed
through setters/getters to make this object immutable need extra care.
Strategy 3.
Using Varargs
Create a constructor like, Employee(Object… args) ,now client can
pass variable length parameters. Constructor then checking the type of each
parameter by instanceof operator.
If type is same as bean property set value else throw IllegalArgumentException.
Pros:
1.
No such.
Cons:
1.For each
property you need to check type so it loses static type checking.
2. There is a
conditional block for each property which increase cyclomatic complexity.
Strategy 4.
Using Map
Same as varargs instead of varargs here we use a map.
Pros:
1.
No such.
Cons:
1.For each
property you need to check type so it loses static type checking.
2. There is a
conditional block for each property which increase cyclomatic complexity.
Example:
package com.example.builder;
import java.util.HashMap;
import java.util.Map;
public class EmployeeMap {
private String name;
private Integer empId;
private String company;
private Integer passport;
private String tempAddress ;
EmployeeMap(Map<String,Object>
map) throws IllegalArgumentException
{
if(!(map.containsKey("name") && map.containsKey("empId") && map.containsKey("company")))
{
throw new IllegalArgumentException("Required Parameter missing");
}
if((map.get("name")==null || map.get("empId")==null || map.get("company")==null))
{
throw new IllegalArgumentException("Required Parameter missing");
}
if(map.get("name") instanceof String)
{
this.name =(String) map.get("name") ;
}
else{
throw new IllegalArgumentException("name
Parameter type is wrong");
}
if(map.get("empId") instanceof Integer)
{
this.empId =(Integer) map.get("empId") ;
}
else{
throw new IllegalArgumentException("enpId
Parameter type is wrong");
}
if(map.get("company") instanceof String)
{
this.company =(String) map.get("company") ;
}
else{
throw new IllegalArgumentException("company
Parameter type is wrong");
}
if(map.containsKey("passport") && (map.get("passport") instanceof Integer))
{
this.passport = (Integer)map.get("passport");
}
else
{
this.passport=0;
}
if(map.containsKey("tempAddress") && (map.get("tempAddress") instanceof String))
{
this.tempAddress = (String)map.get("tempAddress");
}
else
{
this.tempAddress="NA";
}
}
@Override
public String toString() {
return "EmployeeMap
[name=" + name + ", empId=" + empId + ", company="
+
company + ",
passport=" + passport + ", tempAddress="
+
tempAddress + "]";
}
public static void main(String[] args) {
try
{
Map
map = new
HashMap<String,Object>();
map.put("name", "Shamik");
map.put("empId", 100);
map.put("company", "IBM");
EmployeeMap
emp = new EmployeeMap(map);
Map
map1 = new
HashMap<String,Object>();
map1.put("name", "Akash");
map1.put("empId", 101);
map1.put("company", "IBM");
map1.put("passport", "1234");
map1.put("tempAddress", "1,bangalore");
EmployeeMap
emp1 = new EmployeeMap(map1);
System.out.println(emp);
System.out.println(emp1);
}
catch(IllegalAccessException
ex)
{
ex.printStackTrace();
}
}
}
Strategy 5:
Null values
Here client pass optional value as null and constructor checks if
value is null then set default value for optional parameters, if parameter is required
and null ,constructor throws an
exception.
Pros:
3. For
less number of parameters this is good way to design object.
4. Very
easy to implement.
Cons:
3. For
large number of parameters, it creates problems. Developer has to pass null
value for optional parameters.
4. If
two adjacent parameters have same datatype and developer unintentionally swap
values. Compiler not complain but it creates a genuine problem in runtime and
very hard to track.
Example:
package com.example.builder;
public class EmployeeNull {
private String name;
private Integer empId;
private String company;
private Integer passport;
private String tempAddress;
public EmployeeNull(String name,Integer empId,String company,Integer passport,String tempAddress) throws
IllegalArgumentException
{
if(name ==null && empId==null && company==null)
{
throw new
IllegalArgumentException("Required Parameter
missing");
}
this.name=name;
this.empId=empId;
this.company=company;
this.passport= passport != null?passport:0;
this.tempAddress = tempAddress !=null? tempAddress:"NA";
}
@Override
public String toString() {
return "EmployeeNull
[name=" + name + ", empId=" + empId + ", company="
+
company + ",
passport=" + passport + ", tempAddress="
+
tempAddress + "]";
}
public static void main(String[] args) throws
IllegalAccessException {
EmployeeNull
emp = new EmployeeNull("Shamik",100,"IBM",null,null);
EmployeeNull
emp1 = new EmployeeNull("Akash",101,"IBM",1234,"1,blore");
System.out.println(emp);
System.out.println(emp1);
}
}
Strategy 6. Builder Pattern
Use a nested
static class which act as builder of this bean. Builder takes require
parameters as it’s constructor arguments and for each optional parameter there
will be a helper method which set the value and return Builder instance itself.
So we can invoke another helper method for parameter. Builder pattern maintains
fluent interface pattern (Channing of methods).
At last build() method return an Immutable bean object.
Pros:
1. Immutability
achieve easily so Object is thread safe by design.
2. To
set optional parameters, parameter position is not necessary as each parameter
has helper method client can invokes them. It offers fluidity by chain of
methods pattern.
Cons:
1. Complex
to implement
Example:
package com.example.builder;
public class Employee {
private String name;
private Integer empId;
private String company;
private Integer passport;
private String tempAddress ;
private Employee()
{
}
private static class EmployeeBuilder
{
private String name;
private Integer empId;
private String company;
private Integer passport;
private String tempAddress ;
public
EmployeeBuilder(String name,Integer empId,String company)
{
this.name=name;
this.empId=empId;
this.company=company;
}
public EmployeeBuilder
setPassport(Integer passport)
{
this.passport=passport;
return this;
}
public EmployeeBuilder
setTempAddress(String address)
{
this.tempAddress=address;
return this;
}
public Employee build()
{
Employee
emp = new Employee();
emp.name=this.name;
emp.empId=this.empId;
emp.company=this.company;
emp.passport=this.passport!=null?this.passport:0;
emp.tempAddress=this.tempAddress!=null?this.tempAddress:"NA";
return emp;
}
}
@Override
public String toString() {
return "Employee
[name=" + name + ", empId=" + empId + ", company="
+
company + ",
passport=" + passport + ", tempAddress="
+
tempAddress + "]";
}
public static void main(String[] args) {
Employee
emp = new
Employee.EmployeeBuilder("Shamik", 100, "IBM").build();
Employee
emp1 = new
Employee.EmployeeBuilder("Akash", 101, "IBM").setPassport(1234).setTempAddress("1,bangalore").build();
System.out.println(emp);
System.out.println(emp1);
}
}
Comparison: Please find the picture below.
Conclusion: For fewer parameters like 4 or less use
Telescopic constructor pattern
For larger number of parameters use Builder pattern.
Post a Comment