androidengineers.Book a session

Code Organization

Patterns: Factory, Builder, Strategy

article25 minMedium

Three classic patterns that appear constantly in Android architecture: Factory for decoupled object creation, Builder for complex optional configuration, and Strategy for swappable algorithms.

Factory Pattern

Decouple the caller from knowing which concrete class to instantiate.

// Interface
interface ImageLoader {
    fun load(url: String, imageView: ImageView)
}

// Concrete implementations
class GlideImageLoader : ImageLoader {
    override fun load(url: String, imageView: ImageView) {
        Glide.with(imageView).load(url).into(imageView)
    }
}

class CoilImageLoader : ImageLoader {
    override fun load(url: String, imageView: ImageView) {
        imageView.load(url)
    }
}

// Factory
object ImageLoaderFactory {
    fun create(context: Context): ImageLoader {
        return if (BuildConfig.USE_COIL) CoilImageLoader() else GlideImageLoader()
    }
}

// Caller
val loader = ImageLoaderFactory.create(context)
loader.load("https://...", imageView)

In Android: ViewModel.Factory is the canonical Android example:

class ArticleViewModel(private val id: String, private val repo: ArticleRepository) : ViewModel() {
    class Factory(private val id: String, private val repo: ArticleRepository) : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            @Suppress("UNCHECKED_CAST")
            return ArticleViewModel(id, repo) as T
        }
    }
}

Builder Pattern

Build complex objects step-by-step with optional configuration, avoiding constructors with too many parameters.

// Complex object with many optional fields
data class ApiRequest(
    val url: String,
    val method: String,
    val headers: Map<String, String>,
    val body: String?,
    val timeout: Long,
    val retryCount: Int,
    val cacheEnabled: Boolean
)

class ApiRequestBuilder(private val url: String) {
    private var method = "GET"
    private val headers = mutableMapOf<String, String>()
    private var body: String? = null
    private var timeout = 30_000L
    private var retryCount = 3
    private var cacheEnabled = true

    fun method(method: String) = apply { this.method = method }
    fun header(key: String, value: String) = apply { headers[key] = value }
    fun body(body: String) = apply { this.body = body; this.method = "POST" }
    fun timeout(ms: Long) = apply { this.timeout = ms }
    fun retries(count: Int) = apply { this.retryCount = count }
    fun noCache() = apply { this.cacheEnabled = false }
    fun build() = ApiRequest(url, method, headers.toMap(), body, timeout, retryCount, cacheEnabled)
}

// Usage — reads like English
val request = ApiRequestBuilder("https://api.example.com/articles")
    .header("Authorization", "Bearer $token")
    .timeout(5000)
    .noCache()
    .build()

In Android: OkHttp's Request.Builder, Notification's NotificationCompat.Builder, and Glide's RequestBuilder are all canonical Builder examples.

Strategy Pattern

Define a family of algorithms and make them interchangeable.

// Strategy interface
interface SortStrategy<T> {
    fun sort(items: List<T>): List<T>
}

// Concrete strategies
class ChronologicalSort : SortStrategy<Article> {
    override fun sort(items: List<Article>) = items.sortedByDescending { it.publishedAt }
}

class PopularitySort : SortStrategy<Article> {
    override fun sort(items: List<Article>) = items.sortedByDescending { it.viewCount }
}

class PersonalizedSort(private val userModel: UserModel) : SortStrategy<Article> {
    override fun sort(items: List<Article>) = items.sortedByDescending { userModel.scoreFor(it) }
}

// Context that uses the strategy
class FeedSorter(private var strategy: SortStrategy<Article> = ChronologicalSort()) {
    fun setStrategy(strategy: SortStrategy<Article>) { this.strategy = strategy }
    fun sort(articles: List<Article>) = strategy.sort(articles)
}

// In ViewModel:
class FeedViewModel(private val flags: FeatureFlags) : ViewModel() {
    private val sorter = FeedSorter(
        if (flags.isPersonalizedFeedEnabled) PersonalizedSort(userModel) else ChronologicalSort()
    )
}

Kotlin idiom: Strategies are often just lambdas in Kotlin:

typealias SortStrategy<T> = (List<T>) -> List<T>

val chronological: SortStrategy<Article> = { it.sortedByDescending { a -> a.publishedAt } }
val byPopularity: SortStrategy<Article> = { it.sortedByDescending { a -> a.viewCount } }

class FeedSorter(var strategy: SortStrategy<Article> = chronological) {
    fun sort(articles: List<Article>) = strategy(articles)
}

When to Use Each

PatternUse when
FactoryYou want to hide which concrete class is created; swapping implementations
BuilderObject has many optional parameters; construction order matters
StrategyYou have multiple algorithms for the same operation; algorithm should be swappable at runtime

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Patterns: Factory, Builder, Strategy | Android System Design | Android Engineers