Lesson 3 gave you the basics of classes. Professional Android code uses the full object-oriented toolbox, and interviewers ask about it constantly. This lesson walks through each tool with a small example and says when to use it.
Classes, constructors and properties
A class has a primary constructor in its header and may have secondary constructors. Writing val or var in the header makes the parameter a property. init blocks run when an object is created. Properties can have custom getters, and a private set hides who may change them.
class User(val name: String, var age: Int = 18) {
val isAdult: Boolean get() = age >= 18
init {
require(name.isNotBlank()) { "name is empty" }
}
constructor(name: String, birthYear: Int, now: Int) : this(name, now - birthYear)
}
Inheritance, abstraction and polymorphism
Kotlin classes are final by default. Mark a class open to allow subclasses, and mark members open to allow overriding. An abstract class cannot be created itself and may hold shared code. An interface lists functions (and may give default bodies); a class can implement many interfaces but extend only one class. Polymorphism means code that works with the parent type accepts any child. Prefer interfaces for what something *can do*, and inheritance for a true is-a relationship.
interface Shape { fun area(): Double }
class Circle(private val r: Double) : Shape {
override fun area() = Math.PI * r * r
}
class Square(private val side: Double) : Shape {
override fun area() = side * side
}
fun totalArea(shapes: List<Shape>) = shapes.sumOf { it.area() }
Enums, sealed classes and objects
An enum class is a fixed set of named values. A sealed class or interface is a closed family of types, and a when over it must cover every case or the compiler complains. Android developers use sealed types for UI state and for results. An object is a class with exactly one instance (a singleton). A companion object holds members that belong to the class itself, like Java statics.
sealed interface Result<out T> {
data class Success<T>(val value: T) : Result<T>
data class Failure(val message: String) : Result<Nothing>
data object Loading : Result<Nothing>
}
fun show(r: Result<String>) = when (r) {
is Result.Success -> "OK: " + r.value
is Result.Failure -> "Error: " + r.message
Result.Loading -> "Loading"
}
Nested classes, generics and delegation
A nested class cannot see its outer object. An inner class can. Generics let one class or function work with many types while staying type-safe: class Box<T>(val item: T). You can restrict the type with <T : Comparable<T>>. Delegation with by lets a class pass interface work to another object, and by lazy or Delegates.observable delegate a property. Delegation is Kotlin's answer to "prefer composition over inheritance".
class Logger : Printer by ConsolePrinter() // forwards Printer calls
val expensive: String by lazy { loadBigThing() } // computed once, on first use
Visibility modifiers
public(default): visible everywhere.private: inside the class or file only.protected: the class and its subclasses.internal: inside the same module (a Gradle module). Useful in multi-module apps.
SOLID and common patterns
- Single responsibility: a class has one reason to change. A ViewModel holds screen state; a repository gets data.
- Open/closed: add behaviour by adding a new class, not by editing a long
when. - Liskov substitution: a child must work wherever the parent is expected.
- Interface segregation: small interfaces, not one huge one.
- Dependency inversion: depend on an interface; let Hilt supply the real class. This is how you swap a fake repository into a test.
Patterns you will meet: Singleton (object), Factory (a function or companion that builds objects), Builder (Kotlin named arguments and apply replace most builders), Observer (Flow and LiveData) and Strategy (pass a lambda or an interface). Do not force a pattern. Use one when it removes real duplication.
Your task
- Create a sealed interface
PaymentMethodwith Card, Upi and Cash subclasses, and awhenthat returns a fee for each. - Write an interface
Notifierwith two implementations and a function that takes the interface. - Make a generic
Stack<T>class with push, pop and peek. - Add a
private setproperty and show that outside code cannot change it.
How to check it worked
- The compiler warns when you forget a sealed case.
- You can explain open, abstract, interface and sealed in one sentence each.
- You can name the five SOLID principles with an Android example.