Share
Explore

Strategy Pattern + Abstract Factory Pattern

Study case: SharedHelper class @ Talenta Mobile Android
I had this e-book called Clean Code by Robert C. Martin a.k.a. Uncle Bob which I already had since 3 years ago and have never finished reading it because it’s e-book and reading it from my smartphone got me easily distracted by popped-up notifications and other things on my phone, so when I finally joined Mekari, I thought this is the right time to come back to the book and finish it. But I’m no where near the end of the book now, the physical book. I found something interesting in a chapter, and with the code listing I found myself guilty of doing such violation to my projects. I guess some of developers here also have written the same thing as I did, before I knew that there’s a better way to do it, with design patterns.

On the code listing 3-4 in Clean Code book, there’s this
which basically has a method to calculate payroll based on the employee type.
public Money calculatePay(Employee e) throws InvalidEmployeeType {
switch (e.type) {
case COMMISSIONED: return calculateCommissionedPay(e);
case HOURLY: return calculateHourlyPay(e);
case SALARIED: return calculateSalariedPay(e);
default: throw new InvalidEmployeeType(e.type);
}
}

Looking at that code above, we’re familiar with that scenario in our projects, right? “What’s wrong with that?” we asked. That code seems alright and neat, we have small method there and everything works fine, but, have you ever thought what if we got additional employee type? Simple, just add one more case to the switch-case statement, we’re done here. But we’re here to write a clean code with the SOLID Principle applied. The S in SOLID Principle stands for Single Responsibility Principle (SRP) which means that a method should only do one thing and should only have one reason to change. If we look back on the code above, with a scenario that we have additional employee type, then we need to change the implementation and so on with future additional employee type. So, we have violated the SRP.

What can we do to fix that?

Fret not
, my friend. We have design patterns to fix the misery.
Uncle Bob himself stated in his book that we can solve the
switch
statement problem with Abstract Factory Pattern and let not it be alone, we can surely match it with another design pattern called Strategy Pattern. Instead of fixing the code block above, I chose to look for this kind of violation in Talenta Mobile Android project, the project that I’m maintaining right now. And that’s where I found
when
statement in
SharedHelper.kt
.

Before

Here’s a chunk of code in
SharedHelper.kt
that has the
when
statement in
valueFrom
method.
fun
<
T
> valueFrom(
key
:
String
,
defaultValue
:
T
):
T
? {
when
(
defaultValue
){
is
Boolean
->{
return if
(
isSecure
) {
EncryptedPreference
.getInstance(
context
,
key
).getBoolean(
key
,
defaultValue
)
as?
T
}
else
{
sharedPreferences
.getBoolean(
key
,
defaultValue
)
as
T
}
}
is
String
->{
return if
(
isSecure
) {
EncryptedPreference
.getInstance(
context
,
key
).getString(
key
,
defaultValue
)
as?
T
}
else
{
return
sharedPreferences
.getString(
key
,
defaultValue
)
as
T
}
}
is
Int
->{
if
(
isSecure
) {
EncryptedPreference
.getInstance(
context
,
key
).getInt(
key
,
defaultValue
)
}
else
{
return
sharedPreferences
.getInt(
key
,
defaultValue
)
as
T
}
}
is
Float
->
return
sharedPreferences
.getFloat(
key
,
defaultValue
)
as
T
is
Long
->{
if
(
isSecure
) {
EncryptedPreference
.getInstance(
context
,
key
).getLong(
key
,
defaultValue
)
}
else
{
return
sharedPreferences
.getLong(
key
,
defaultValue
)
as
T
}
}
else
->
throw
Error(
"Fatal Error, unknown type value "
)
}
return null
}

Basically, the
valueFrom
method accepts 2 arguments which are a String and any object type and will return which method from which preferences will be executed based on the object type given. This scenario only questions if it’s a Boolean, String, Int, Float or Long or it’s an unknown type. So, what if in the future we can accept another object type like Double? It’s the same case with code listing 3-4 in the Clean Code book. Are we going to add more lines of code to this method while it’s already that long? Let’s do some refactoring to
valueFrom
method, so it won’t violate the SRP.

After

What I did with
valueFrom
method is refactor it with 2 design patterns; strategy pattern and abstract factory pattern.

A. Strategy Pattern

In the
when
statement, we have some branching to Boolean, String, Int, Float and Long implementation. So we have several strategy of how we want to get our preference value. Basically, they all do one thing of how to get preference value. So we can create an abstract class named
DefaultValuePreference
.

1. DefaultValuePreference

abstract class
DefaultValuePreference
{

abstract fun
<
T
> getPreference(
sharedPreferences
:
SharedPreferences
,
isSecure
:
Boolean
,
context
:
Context
,
key
:
String
,
defaultValue
:
T
):
T
?

}
In this abstract class, we have an abstract method called
getPreference
, which will be implemented differently in each class of
BooleanPreference
,
StringPreference
,
IntPreference
,
FloatPreference
and
LongPreference
according to their own requirements.

2. BooleanPreference

This class is a class with only 1 implementation method from
DefaultValuePreference
abstract, which is
getPreference
. The overridden
getPreference
method in this class only shows how we implement things for specific Boolean value.
class
BooleanPreference
: DefaultValuePreference() {
@Suppress
(
"UNCHECKED_CAST"
)
override fun
<
T
> getPreference(
sharedPreferences
:
SharedPreferences
,
isSecure
:
Boolean
,
context
:
Context
,
key
:
String
,
defaultValue
:
T
):
T
? {
return if
(
isSecure
) {
EncryptedPreference
.getInstance(
context
,
key
).getBoolean(
key
,
defaultValue
as
Boolean
)
as?
T
}
else
{
sharedPreferences
.getBoolean(
key
,
defaultValue
as
Boolean
)
as
T
}
}
}

3. StringPreference

The overridden
getPreference
method in this class only shows how we implement things for specific String value.
class
StringPreference
: DefaultValuePreference() {
@Suppress
(
"UNCHECKED_CAST"
)
override fun
<
T
> getPreference(
sharedPreferences
:
SharedPreferences
,
isSecure
:
Boolean
,
context
:
Context
,
key
:
String
,
defaultValue
:
T
):
T
? {
return if
(
isSecure
) {
EncryptedPreference
.getInstance(
context
,
key
).getString(
key
,
defaultValue
as
String
)
as?
T
}
else
{
sharedPreferences
.getString(
key
,
defaultValue
as
String
)
as
T
}
}
}

4. IntPreference

The overridden
getPreference
method in this class only shows how we implement things for specific Int value.
class
IntPreference
: DefaultValuePreference() {
@Suppress
(
"UNCHECKED_CAST"
)
override fun
<
T
> getPreference(
sharedPreferences
:
SharedPreferences
,
isSecure
:
Boolean
,
context
:
Context
,
key
:
String
,
defaultValue
:
T
):
T
? {
return if
(
isSecure
) {
EncryptedPreference
.getInstance(
context
,
key
).getInt(
key
,
defaultValue
as
Int
)
as
T
}
else
{
sharedPreferences
.getInt(
key
,
defaultValue
as
Int
)
as
T
}
}
}

5. FloatPreference

The overridden
getPreference
method in this class only shows how we implement things for specific Float value.
class
FloatPreference
: DefaultValuePreference() {
@Suppress
(
"UNCHECKED_CAST"
)
override fun
<
T
> getPreference(
sharedPreferences
:
SharedPreferences
,
isSecure
:
Boolean
,
context
:
Context
,
key
:
String
,
defaultValue
:
T
):
T
? {
return
sharedPreferences
.getFloat(
key
,
defaultValue
as
Float
)
as
T
}
}

6. LongPreference

The overridden
getPreference
method in this class only shows how we implement things for specific Long value.
class
LongPreference
: DefaultValuePreference() {
@Suppress
(
"UNCHECKED_CAST"
)
override fun
<
T
> getPreference(
sharedPreferences
:
SharedPreferences
,
isSecure
:
Boolean
,
context
:
Context
,
key
:
String
,
defaultValue
:
T
):
T
? {
return if
(
isSecure
) {
EncryptedPreference
.getInstance(
context
,
key
).getLong(
key
,
defaultValue
as
Long
)
as
T
}
else
{
sharedPreferences
.getLong(
key
,
defaultValue
as
Long
)
as
T
}
}
}

The strategy is all set up. Now it’s time for us to add another design pattern. It’s abstract factory pattern show time!

B. Abstract Factory Pattern

With abstract factory pattern, we first create an interface for the factory, so then we can make the implementation class. A factory that we’re gonna make has only one method called create.

1. DefaultValuePreferenceFactory

This interface here, is made as a factory or creator of which strategy soon to be applied, but we don’t do implementation here, only the abstract.
interface
DefaultValuePreferenceFactory
{
fun
<
T
> create(
defaultValue
:
T
):
DefaultValuePreference
?
}

2. DefaultValuePreferenceFactoryImpl

After creating the abstract factory, now we can implement the factory and its only method, create. This is where we implement the create method, which class exactly we should return when the create method is executed from the main function. This create method can return
BooleanPreference
,
StringPreference
,
IntPreference
,
FloatPreference
,
LongPreference
class or just throw an error if the argument type is unknown.
class
DefaultValuePreferenceFactoryImpl
:
DefaultValuePreferenceFactory
{
override fun
<
T
> create(
defaultValue
:
T
):
DefaultValuePreference
? {
return when
(
defaultValue
) {
is
Boolean
-> BooleanPreference()
is
String
-> StringPreference()
is
Int
-> IntPreference()
is
Float
-> FloatPreference()
is
Long
-> LongPreference()
else
->
throw
Error(
"Fatal Error, unknown type value"
)
}
}
}

C. Joined Forces on valueFrom() method

All of the things that we need are already created, now it’s time for us to implement the factory and call only one method named
getPreference
from
valueFrom
method in
SharedHelper.kt
. And it only take us 2 lines of code to do that.
fun
<
T
> valueFrom(
key
:
String
,
defaultValue
:
T
):
T
? {
val
valuePreference
= DefaultValuePreferenceFactoryImpl().create(
defaultValue
)
return
valuePreference
?.getPreference(
sharedPreferences
,
isSecure
,
context
,
key
,
defaultValue
)
}

Now we can compare the
valueFrom
method from the
Before
section to this
C
section. In the future, if we need to add another strategy of how we do
getPreference
, we can easily add another class for that implementation and modify the factory implementation, meanwhile the
valueFrom
method will not change at all. Now, each of the class’ method has only one reason to change. Hooray! We’re not violating the SRP anymore.

Strategy Pattern + Abstract Factory Pattern = Love.

Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.