
Object-Oriented Programming in Practice: Teaching Design, Not Just Classes, with Kotlin
Object-Oriented Programming (OOP) is often introduced through classes, attributes, and inheritance. While these elements are important, they represent only the surface of object orientation. True OOP is about design, behavior, and responsibility, not about syntax. This article presents a more technical view of OOP, using Kotlin as the implementation language, focusing on how object-oriented principles can be taught and applied in a way that reflects real-world software design.
Publicado em 27 de janeiro de 2026 às 15:51
Objects as Behavioral Units
In practical OOP, an object should represent a unit of behavior, not merely a data container. Objects are responsible for making decisions and enforcing invariants.
In Kotlin, this concept becomes clearer due to concise class definitions and expressive functions:
class BankAccount(
private val owner: String,
private var balance: BigDecimal
) {
fun deposit(amount: BigDecimal) {
require(amount > BigDecimal.ZERO)
balance += amount
}
fun withdraw(amount: BigDecimal) {
require(amount > BigDecimal.ZERO)
require(balance >= amount)
balance -= amount
}
}
Here, the object controls its own state. There is no external manipulation of balance, which reinforces encapsulation and responsibility.
Encapsulation Beyond Access Modifiers
Encapsulation is often misunderstood as simply using private fields. In reality, it is about protecting business rules.
Kotlin’s design encourages this by making immutability and restricted visibility easy to apply. When invariants are enforced inside the object, misuse becomes difficult by default.
Composition Over Inheritance
Inheritance is frequently overused in introductory courses. In professional systems, composition is often more flexible and maintainable.
Kotlin’s support for interfaces and delegation makes composition a natural choice:
interface PaymentProcessor {
fun process(amount: BigDecimal)
}
class CreditCardProcessor : PaymentProcessor {
override fun process(amount: BigDecimal) { /* ... */ }
}
class PaymentService(
private val processor: PaymentProcessor
) {
fun pay(amount: BigDecimal) {
processor.process(amount)
}
}
This approach reduces coupling and improves testability.
Abstraction as a Design Tool
Abstraction should not exist for its own sake. Each abstraction must solve a concrete problem: reducing complexity, isolating change, or expressing intent.
Kotlin’s interfaces and sealed classes allow precise abstractions without unnecessary hierarchy:
sealed class OrderStatus {
object Created : OrderStatus()
object Paid : OrderStatus()
object Shipped : OrderStatus()
}
This models a closed set of states clearly and safely.
OOP and Clean Architecture
OOP becomes truly valuable when combined with architectural thinking. Objects should align with use cases and domain rules, not frameworks or infrastructure.
Kotlin integrates well with architectural styles such as Clean Architecture, allowing domain models to remain expressive and framework-agnostic.
Teaching OOP Through Constraints
A key teaching strategy is introducing constraints:
No public setters
No anemic domain models
Explicit responsibilities per class
Kotlin supports these constraints naturally, helping students learn disciplined design instead of accidental complexity.
Conclusion
Object-Oriented Programming is not about memorizing principles or writing class diagrams. It is about designing systems that are cohesive, expressive, and resilient to change.
Kotlin, with its expressive syntax and modern features, provides an excellent environment for teaching and practicing OOP as it is applied in real-world software engineering.