Irbisa · Cheatsheet
OOP, SOLID & Design Patterns
OOP and SOLID in Kotlin, DRY, KISS and YAGNI, coupling and cohesion, dependency injection, GoF patterns and where Android and its libraries use them, anti-patterns
What do DRY, KISS and YAGNI mean, and when does applying DRY make code worse?
// DRY: one rule, one home; the UI hint is derived from it, not copiedobject PasswordRule { const val MIN_LENGTH = 8 fun isValid(password: String) = password.length >= MIN_LENGTH val hint = "At least $MIN_LENGTH characters"}…What are coupling and cohesion, and why do you aim for low coupling and high cohesion?
What is the Observer pattern, and which forms of it do you use on Android?
fun interface CartListener { fun onCartChanged(count: Int)} class Cart { private val listeners = mutableListOf<CartListener>()…What is the Strategy pattern, and how do you write one in Kotlin?
// a stateless one-method strategy: a function type is enoughtypealias Discount = (subtotalCents: Long) -> Long val noDiscount: Discount = { it }val blackFriday: Discount = { it * 70 / 100 }fun coupon(offCents: Long): Discount = { it - minOf(offCents, it / 5) } // at most 20% off…What does this code print, and which SOLID principle does
Squarebreak?open class Rectangle { open var width = 0 open var height = 0 val area get() = width * height} class Square : Rectangle() { override var width: Int get() = super.width set(value) { super.width = value; super.height = value } override var height: Int get() = super.height set(value) { super.width = value; super.height = value }} fun resize(r: Rectangle) { r.width = 5; r.height = 4; println(r.area) }fun main() { resize(Rectangle()); resize(Square()) }How does Interface Segregation apply to Android listeners and repositories, and do default methods fix a fat interface?
// fat: a screen that only shows the profile also depends on uploads and account deletioninterface UserRepository { suspend fun profile(id: Long): Profile suspend fun updateName(id: Long, name: String) suspend fun uploadAvatar(id: Long, jpeg: ByteArray) suspend fun deleteAccount(id: Long)…Should every class have an interface? When is an interface with a single implementation worth it?
What is the difference between a factory function, Factory Method and Abstract Factory, and where does Android use each?
// factory function: named like the type, validates and hides the implementation classinterface Session { val userId: Long }private class ActiveSession(override val userId: Long) : Session fun Session(userId: Long): Session { require(userId > 0) { "invalid user id: $userId" }…Named and default arguments replace most Builders in Kotlin. When is a Builder still the right choice, and why do OkHttp and Retrofit keep one?
class HttpConfig private constructor( val baseUrl: String, val timeoutMs: Long, val headers: Map<String, String>,) { fun newBuilder() = Builder(this) // derive a variant…Find the bug: image requests to a third-party CDN carry the user's access token.
val base = OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) val apiClient = base .addInterceptor(AuthInterceptor(tokens)) .build() // meant to be: the same timeouts, without authval cdnClient = base.build()Adapter, Decorator, Proxy and Facade all wrap another object. How do you tell them apart, and is
RecyclerView.Adaptera real Adapter?interface ImageSource { suspend fun load(url: String): ByteArray} // Adapter: a callback SDK made to fit the interface the app expectsclass SdkImageSource(private val sdk: LegacySdk) : ImageSource {…What are Flyweight and Object Pool, and why do
Drawable.mutate()andMessage.obtain()exist?// Flyweight: drawables from one resource share a ConstantState until mutate()val star = ContextCompat.getDrawable(context, R.drawable.ic_star)!!val dimmed = ContextCompat.getDrawable(context, R.drawable.ic_star)!! dimmed.alpha = 80 // wrong: star and every other ic_star fade toodimmed.mutate().alpha = 80 // right: dimmed gets its own copy of the state first…How does Chain of Responsibility work, and how would you build one in Kotlin, for example to route deep links?
sealed interface Route { data class Product(val id: String) : Route data class Order(val id: Long) : Route data object Home : Route}…What is the Template Method pattern, why must an
Activitycallsuper.onCreate(), and what replaced overriding in newer Android APIs?sealed interface SyncResult { data object Skipped : SyncResult data class Done(val count: Int) : SyncResult} // the template: the order of steps is fixed in the base class…What is the Command pattern, and how would you use it to implement undo in an editor?
interface EditCommand { fun execute(doc: StringBuilder) fun undo(doc: StringBuilder)} class Insert(private val at: Int, private val text: String) : EditCommand {…How do you write a state machine in Kotlin with sealed classes, and how does the State pattern differ from Strategy?
sealed interface Upload { data object Idle : Upload data class Sending(val file: String, val attempt: Int) : Upload data class Done(val url: String) : Upload data class Failed(val file: String, val reason: String) : Upload}…Open/Closed in Kotlin: when is a sealed hierarchy with
whena better design than a polymorphic interface, and why does Kotlin rarely need Visitor?// open to new TYPES: another module adds a provider without touching this codeinterface PaymentProvider { val id: String fun feeCents(amountCents: Long): Long}…Every screen in your app extends a
BaseActivitythat is five levels deep. What goes wrong, and how do you refactor it into composition?// before: every screen inherits these checks, in super-call orderopen class BaseActivity : AppCompatActivity() { override fun onResume() { super.onResume() if (!session.isLoggedIn) startActivity(Intent(this, LoginActivity::class.java)) analytics.screen(javaClass.simpleName)…What anti-patterns do you look for when you inherit an Android codebase, and in what order do you refactor them?