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.