SOLID — vijf principes van objectgeoriënteerd programmeren, geformuleerd door Robert C. Martin (Uncle Bob) in het begin van de jaren 2000. Volgens DigitalOcean, 2024, staat SOLID voor Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation en Dependency Inversion. Deze principes vormen de basis van Clean Architecture en worden toegepast in Android-ontwikkeling (MVP, MVVM, Clean Architecture) en iOS (VIPER, TCA).
Belangrijkste
SOLID — een mnemonisch acroniem dat vijf principes van objectgeoriënteerd ontwerp aanduidt. De term werd geïntroduceerd door Robert C. Martin in het artikel „Design Principles and Design Patterns” (2000) en later gepopulariseerd in het boek „Agile Software Development: Principles, Patterns, and Practices” (2002). SOLID is geen framework of bibliotheek — het is een verzameling praktijken die code minder gekoppeld, beter testbaar en gemakkelijker te wijzigen maken.
Volgens Clean Coder Blog, 2014, lost elk SOLID-principe een specifiek ontwerpprobleem op: SRP bestrijdt God-klassen, OCP — cascaderende wijzigingen, LSP — onjuiste overerving, ISP — dikke interfaces, DIP — strakke koppeling. Samen vormen ze de fundering van Clean Architecture, die wordt gebruikt in Android-projecten met MVP, MVVM en MVI.
Single Responsibility Principle (SRP) — het principe van enkele verantwoordelijkheid. Formulering: „Een klasse mag slechts één reden hebben om te wijzigen.” Dit betekent dat elke module of klasse verantwoordelijk is voor precies één functionaliteit of één domeinentiteit. Als een klasse zowel gebruikers als het verzenden van e-mail beheert — heeft deze twee redenen om te wijzigen, wat SRP schendt.
Volgens Robert C. Martin, 2002, is SRP het belangrijkste en tegelijkertijd het meest geschonden principe. In mobiele ontwikkeling wordt SRP vaak geschonden in Activity/Fragment, waarbij UI-logica, navigatie, netwerkwerk en bedrijfslogica worden gecombineerd. Oplossing — elke laag in een aparte klasse onderbrengen: ViewModel voor UI-logica, Repository voor gegevens, NavController voor navigatie.
Beschouw de klasse UserManager, die het profiel laadt, instellingen opslaat en e-mails verzendt. Dit zijn drie verschillende verantwoordelijkheden, elk moet in een aparte klasse worden ondergebracht: UserProfileRepository (laden), UserSettingsStorage (opslaan) en EmailService (verzenden). Clientcode (ViewModel) gebruikt alle drie via Dependency Injection, en elke klasse wordt gemakkelijk geïsoleerd getest en gewijzigd zonder invloed op de andere.
// ❌ SRP-schending: Activity weet van netwerk, DB en UI
class ProfileActivity : AppCompatActivity() {
fun loadProfile() {
api.getUser() // Netwerkoproep
db.saveUser() // Werken met DB
updateUI() // UI-update
}
}
// ✅ SRP nageleefd: lagen gescheiden
class ProfileViewModel : ViewModel() {
private val repo = UserRepository()
fun loadProfile() { repo.getUser() }
}
Tekenen van SRP-schending: de klasse bevat meer dan 200 regels, heeft methoden uit verschillende domeinen, verandert vaak om verschillende redenen. Voor Android-ontwikkeling is de regel eenvoudig: Activity is alleen verantwoordelijk voor de levenscyclus van het scherm, ViewModel — voor de UI-status, Repository — voor gegevensbronnen.
Het SRP-principe is niet alleen van toepassing op klassen, maar ook op architectuur op serviceniveau. Elke microservice is verantwoordelijk voor één domeinentiteit: UserService — alleen gebruikers, PaymentService — alleen betalingen, NotificationService — alleen meldingen. Dit maakt het mogelijk om services onafhankelijk te schalen, implementeren en testen. In een mobiele app manifesteert SRP zich op microserviceniveau in de verdeling van API-cliënten per domein.
Open-Closed Principle (OCP) — het open/gesloten principe. Klassen moeten open zijn voor uitbreiding (nieuw gedrag kan worden toegevoegd) en gesloten voor wijziging (bestaande code verandert niet). Dit wordt bereikt door polymorfisme, abstracte klassen en interfaces. In plaats van if-else toe te voegen aan een bestaande methode, wordt een nieuwe implementatie van de interface gemaakt.
Volgens Clean Coder Blog, 2014, is OCP het meest effectief in combinatie met het Strategy-patroon. Als een app bijvoorbeeld verschillende betaalmethoden ondersteunt (Google Pay, Apple Pay, PayPal), hoeft er geen switch-case te worden toegevoegd aan de betalingsprocessor. Elke betaalmethode implementeert de gemeenschappelijke PaymentGateway-interface, en een nieuw betalingssysteem wordt toegevoegd als een nieuwe klasse zonder bestaande code te wijzigen.
// ✅ OCP: open voor uitbreiding, gesloten voor wijziging
interface PaymentGateway {
fun processPayment(amount: Double): Boolean
}
class GooglePayGateway : PaymentGateway {
override fun processPayment(amount: Double) = true
}
// Nieuw betalingssysteem — zonder bestaande code te wijzigen
class ApplePayGateway : PaymentGateway {
override fun processPayment(amount: Double) = true
}
Liskov Substitution Principle (LSP) — het substitutieprincipe van Barbara Liskov. Als S een subtype is van T, dan kunnen objecten van T worden vervangen door objecten van S zonder de eigenschappen van het programma te wijzigen. Formeel: een functie die de basisklasse gebruikt, moet correct werken met elke subklasse ervan. Als een subklasse een uitzondering gooit waar de basisklasse dat niet doet — is LSP geschonden.
Volgens Robert C. Martin, 2002, is LSP het moeilijkst te begrijpen SOLID-principe. Klassiek voorbeeld van schending — de klasse Square (vierkant) die overerft van Rectangle (rechthoek). Als setWidth voor Square zowel de breedte als de hoogte instelt, krijgt clientcode die het gedrag van Rectangle verwacht een onverwacht resultaat. In mobiele ontwikkeling wordt LSP vaak geschonden bij overerving van ViewModel, wanneer de child-ViewModel verplichte afhankelijkheden toevoegt.
// ❌ LSP-schending: Square verbreekt het gedrag van Rectangle
open class Rectangle(open var width: Int, open var height: Int)
class Square(side: Int) : Rectangle(side, side) {
override var width
get() = super.width
set(value) { super.setBoth(value, value) }
}
Interface Segregation Principle (ISP) — het principe van interfacesegregatie. Clients mogen niet afhankelijk zijn van interfaces die ze niet gebruiken. In plaats van één „dikke” interface worden meerdere smalle, gespecialiseerde interfaces gemaakt. Als een klasse een interface implementeert maar een deel van de methoden UnsupportedOperationException gooit of leeg laat — is dit een duidelijk teken van ISP-schending.
Volgens DigitalOcean, 2024, is ISP vooral relevant in mobiele ontwikkeling bij het ontwerpen van ViewModel en Repository. In plaats van één UserRepository-interface met alle CRUD-methoden, is het beter om QueryUserRepository (alleen lezen) en CommandUserRepository (schrijven) te creëren. Dan is de lezende client (UI-element) alleen afhankelijk van de Query-interface en weet niets van de schrijfmethode.
// ❌ Dikke interface — client gedwongen onnodige methoden te implementeren
interface UserOperations {
fun getUser(id: String): User
fun saveUser(user: User)
fun deleteUser(id: String)
fun exportUsers(): File
}
// ✅ ISP: gescheiden interfaces
interface UserReader { fun getUser(id: String): User }
interface UserWriter { fun saveUser(user: User) }
interface UserDeleter { fun deleteUser(id: String) }
Dependency Inversion Principle (DIP) — het principe van afhankelijkheidsinversie. Modules van hoog niveau mogen niet afhankelijk zijn van modules van laag niveau. Beide niveaus moeten afhankelijk zijn van abstracties (interfaces). Abstracties mogen niet afhankelijk zijn van details — details zijn afhankelijk van abstracties. Dit is niet hetzelfde als „Dependency Injection” (DI), hoewel DI een veelgebruikte manier is om DIP te implementeren.
Volgens Robert C. Martin, 2019, is DIP de basis van Clean Architecture. ViewModel (hoog niveau) mag niet direct een instantie van RetrofitApi (detail) creëren. In plaats daarvan is ViewModel afhankelijk van de UserRepository-interface, en wordt de concrete implementatie UserRepositoryImpl met Retrofit via de constructor doorgegeven. In Android wordt DIP geïmplementeerd via Hilt/Dagger of Koin: alle afhankelijkheden worden geleverd via de DI-container.
// ✅ DIP: Module hangt af van abstractie, niet van detail
class UserRepositoryImpl(
private val api: UserApi, // Afhankelijk van interface
private val db: UserDao // Afhankelijk van interface
) : UserRepository {
override suspend fun getUser(id: String): User {
return api.fetchUser(id)
}
}
// Hilt DI: details worden verbonden via DI-module
@Module
object NetworkModule {
@Provides
fun provideUserApi(retrofit: Retrofit): UserApi =
retrofit.create(UserApi::class.java)
}
SOLID in mobiele ontwikkeling wordt op alle niveaus toegepast: van applicatiearchitectuur tot individuele klassen. In Android-projecten verdeelt Clean Architecture de code in drie lagen: domain (bedrijfslogica — onafhankelijk van frameworks), data (repositories, API, DB) en presentation (UI, ViewModel). De domain-laag gebruikt SOLID-principes: use case (SRP), repository-interfaces (DIP), entity-klassen (OCP + LSP).
Volgens Android Developers Guide, 2025, manifesteert SRP zich in Android in de verdeling van ViewModel, Repository en Mapper. OCP — bij het toevoegen van nieuwe gegevensbronnen via de DataSource-interface. LSP — in de uniforme verwerking van Result van verschillende repositories. ISP — in de CQRS-benadering (scheiding van Read/Write-repositories). DIP — via Hilt/Koin voor afhankelijkheidsinjectie.
| Principe | Probleem zonder | Oplossing in mobiel project |
|---|---|---|
| SRP | Activity van 1000+ regels | ViewModel + UseCase + Repository |
| OCP | switch-case per betalingstype | Strategy: PaymentGateway-interface |
| LSP | Fout bij vervangen BaseViewModel | Contractcontrole van subklassen |
| ISP | UnsupportedOperationException | Reader / Writer-scheiding |
| DIP | ViewModel maakt handmatig Retrofit | Hilt / Koin DI-container |
SOLID-fouten houden meestal verband met overmatige complicatie van code. De eerste — letterlijk de principes volgen zonder rekening te houden met de context. Het verdelen van één UserService-klasse in 10 interfaces en 15 klassen voor „schoon” ISP is overengineering. SOLID is een hulpmiddel, geen doel. De tweede fout — verwarring tussen SRP en „één methode = één verantwoordelijkheid”. Een klasse kan meerdere methoden hebben, als ze allemaal tot hetzelfde verantwoordelijkheidsgebied behoren.
Volgens Simple Thread, 2024, is de derde fout — het negeren van LSP bij overerving van ViewModel in Android. Als de basis-ViewModel LiveData verwacht en de child StateFlow gebruikt — ontvangt clientcode die is geabonneerd op LiveData geen updates. De vierde — schending van DIP omwille van testen: RepositoryImpl maakt direct een instantie van OkHttpClient, wat unittesten onmogelijk maakt.
Gulden middenweg: pas SOLID toe wanneer het een reële oplost (frequente wijzigingen, moeilijk testen, duplicatie). Voor eenvoudige CRUD-schermen is strikte naleving van alle vijf principes overdreven. Voor bedrijfslogica, financiële berekeningen en API-interacties is SOLID verplicht.
Clean Architecture (Robert C. Martin, 2012) — directe toepassing van SOLID op het niveau van applicatielagen. SRP bepaalt de grenzen van use case (elke use case — één klasse). OCP wordt gerealiseerd via repository-interfaces (de Data-laag kan veranderen zonder de Domain-laag te wijzigen). ISP biedt de scheiding van Use Case in input/output boundary. DIP — de richting van afhankelijkheden naar binnen de Domain-laag. LSP garandeert dat elke repository-implementatie vervangbaar is zonder de use case te breken.
Veelgestelde vragen
SOLID — vijf regels voor het schrijven van code zodat deze gemakkelijk te wijzigen, testen en begrijpen is. Elke letter is één principe: schrijf geen grote klassen (SRP), wijzig bestaande code niet — voeg nieuwe toe (OCP), breek het gedrag van overervers niet (LSP) en andere.
SRP (Single Responsibility) wordt als het belangrijkste beschouwd omdat de schending ervan leidt tot God-klassen — enorme klassen die moeilijk te testen en wijzigen zijn. Zonder DIP (Dependency Inversion) blijft code echter strak gekoppeld, wat ook kritisch is.
Niet verplicht, maar zeer aanbevolen voor commerciële projecten met een lange levenscyclus. Voor eenvoudige apps (één scherm, geen bedrijfslogica) kan SOLID overdreven zijn. Voor projecten met 50+ schermen en 3+ ontwikkelaars is SOLID het noodzakelijke minimum.
Gevolgen: klassen worden „dik” (1000+ regels), een wijziging op één plaats breekt drie andere, het is onmogelijk om unittesten te schrijven, het toevoegen van een nieuwe functie duurt weken in plaats van dagen. Na verloop van tijd verandert code in „Big Ball of Mud” — verward en breekbaar.
Tekenen van naleving: elke klasse heeft minder dan 200 regels, een functiewijziging raakt niet 5+ bestanden, tests worden geschreven zonder 10 afhankelijkheden te mocken, een nieuwe ontwikkelaar begrijpt de structuur binnen een dag. Tools zoals SonarQube en detekt helpen bij het opsporen van SRP- en DIP-schendingen.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook