SOLID: principer, 5 OOP-regler och tillämpning i utveckling

Författare: IT Sectr Publicerad: 2026-05-11 Lästid: 10 min

SOLID — fem principer för objektorienterad programmering, formulerade av Robert C. Martin (Uncle Bob) i början av 2000-talet. Enligt DigitalOcean, 2024, står SOLID för Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation och Dependency Inversion. Dessa principer utgör grunden för Clean Architecture och tillämpas i Android-utveckling (MVP, MVVM, Clean Architecture) och iOS (VIPER, TCA).

Huvudpunkter

  • SOLID — akronym för fem OOP-principer: SRP, OCP, LSP, ISP, DIP, formulerade av Robert C. Martin för att skapa flexibel och underhållbar kod.
  • SRP (Single Responsibility) — varje klass har en anledning att ändras, ett ansvar per modul.
  • OCP (Open-Closed) — klasser är öppna för utökning men stängda för modifiering, realiseras genom arv och polymorfism.
  • LSP (Liskov Substitution) — objekt av underklasser måste kunna ersätta objekt av basklassen utan att ändra programmets korrekthet.
  • ISP (Interface Segregation) — klienter bör inte vara beroende av gränssnitt de inte använder, gränssnitt ska vara smala och specifika.
  • DIP (Dependency Inversion) — moduler på hög nivå är inte beroende av moduler på låg nivå, båda är beroende av abstraktioner.

Vad är SOLID? Översikt över fem principer

SOLID — ett mnemoniskt akronym som betecknar fem principer för objektorienterad design. Termen introducerades av Robert C. Martin i artikeln “Design Principles and Design Patterns” (2000) och populariserades senare i boken “Agile Software Development: Principles, Patterns, and Practices” (2002). SOLID är inte ett ramverk eller bibliotek — det är en samling praxis som gör koden mindre kopplad, mer testbar och lättare att ändra.

Enligt Clean Coder Blog, 2014, löser varje SOLID-princip ett specifikt designproblem: SRP bekämpar Gud-klasser, OCP — kaskadförändringar, LSP — felaktigt arv, ISP — tjocka gränssnitt, DIP — tät koppling. Tillsammans utgör de grunden för Clean Architecture, som används i Android-projekt med MVP, MVVM och MVI.

SRP: Single Responsibility Principle

Single Responsibility Principle (SRP) — principen om ett enda ansvar. Formulering: “En klass bör bara ha en anledning att ändras.” Detta innebär att varje modul eller klass är ansvarig för exakt en funktionalitet eller en domänentitet. Om en klass hanterar både användare och e-postutskick — har den två anledningar att ändras, vilket bryter mot SRP.

Enligt Robert C. Martin, 2002, är SRP den viktigaste och samtidigt den mest brutna principen. Inom mobil utveckling bryts SRP ofta i Activity/Fragment, som kombinerar UI-logik, navigering, nätverksarbete och affärslogik. Lösning — separera varje lager i en separat klass: ViewModel för UI-logik, Repository för data, NavController för navigering.

SRP-exempel: uppdelning av UserManager

Betrakta klassen UserManager, som laddar profilen, sparar inställningar och skickar e-post. Detta är tre olika ansvarsområden, varje bör separeras i en egen klass: UserProfileRepository (laddning), UserSettingsStorage (lagring) och EmailService (sändning). Klientkod (ViewModel) använder alla tre genom Dependency Injection, och varje klass testas enkelt isolerat och ändras utan att påverka de andra.

kotlin
// ❌ SRP-brott: Activity vet om nätverk, DB och UI
class ProfileActivity : AppCompatActivity() {
    fun loadProfile() {
        api.getUser() // Nätverksanrop
        db.saveUser()    // Arbeta med DB
        updateUI()         // UI-uppdatering
    }
}

// ✅ SRP följs: lager separerade
class ProfileViewModel : ViewModel() {
    private val repo = UserRepository()
    fun loadProfile() { repo.getUser() }
}

Tecken på SRP-brott: klassen innehåller över 200 rader, har metoder från olika domäner, ändras ofta av olika anledningar. För Android-utveckling är regeln enkel: Activity ansvarar bara för skärmens livscykel, ViewModel — för UI-tillstånd, Repository — för datakällor.

SRP och mikrotjänstarkitektur

SRP-principen gäller inte bara klasser utan även arkitektur på tjänstenivå. Varje mikrotjänst ansvarar för en domänentitet: UserService — bara användare, PaymentService — bara betalningar, NotificationService — bara notifieringar. Detta möjliggör oberoende skalning, driftsättning och testning av tjänster. I en mobilapp manifesteras SRP på mikrotjänstnivå i uppdelningen av API-klienter per domän.

OCP: Open-Closed Principle

Open-Closed Principle (OCP) — principen om öppenhet/slutenhet. Klasser bör vara öppna för utökning (nytt beteende kan läggas till) och stängda för modifiering (befintlig kod ändras inte). Uppnås genom polymorfism, abstrakta klasser och gränssnitt. Istället för att lägga till if-else i en befintlig metod skapas en ny implementering av gränssnittet.

Enligt Clean Coder Blog, 2014, är OCP mest effektivt i kombination med Strategy-mönstret. Till exempel, om en app stöder olika betalningsmetoder (Google Pay, Apple Pay, PayPal), behöver man inte lägga till switch-case i betalningsprocessorn. Varje betalningsmetod implementerar det gemensamma gränssnittet PaymentGateway, och ett nytt betalningssystem läggs till som en ny klass utan att ändra befintlig kod.

kotlin
// ✅ OCP: öppen för utökning, stängd för modifiering
interface PaymentGateway {
    fun processPayment(amount: Double): Boolean
}

class GooglePayGateway : PaymentGateway {
    override fun processPayment(amount: Double) = true
}

// Nytt betalningssystem — utan att ändra befintlig kod
class ApplePayGateway : PaymentGateway {
    override fun processPayment(amount: Double) = true
}

LSP: Liskov Substitution Principle

Liskov Substitution Principle (LSP) — Barbara Liskovs substitutionsprincip. Om S är en undertyp av T, då kan objekt av T ersättas med objekt av S utan att programmets egenskaper ändras. Formellt: en funktion som använder basklassen bör fungera korrekt med vilken underklass som helst. Om underklassen kastar ett undantag där basklassen inte gör det — är LSP brutet.

Enligt Robert C. Martin, 2002, är LSP den svåraste SOLID-principen att förstå. Klassiskt exempel på brott — klassen Square (fyrkant) som ärver från Rectangle (rektangel). Om setWidth för Square ställer in både bredd och höjd, får klientkod som förväntar sig Rectangle-beteende ett oväntat resultat. Inom mobil utveckling bryts LSP ofta vid arv av ViewModel, när barn-ViewModel lägger till obligatoriska beroenden.

kotlin
// ❌ LSP-brott: Square bryter Rectangle-beteendet
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) }
}

ISP: Interface Segregation Principle

Interface Segregation Principle (ISP) — principen om gränssnittssegregering. Klienter bör inte vara beroende av gränssnitt de inte använder. Istället för ett “tjockt” gränssnitt skapas flera smala, specialiserade gränssnitt. Om en klass implementerar ett gränssnitt men en del av metoderna kastar UnsupportedOperationException eller förblir tomma — är detta ett tydligt tecken på ISP-brott.

Enligt DigitalOcean, 2024, är ISP särskilt relevant inom mobil utveckling vid design av ViewModel och Repository. Istället för ett UserRepository-gränssnitt med alla CRUD-metoder är det bättre att skapa QueryUserRepository (endast läsning) och CommandUserRepository (skrivning). Då är den läsande klienten (UI-element) bara beroende av Query-gränssnittet och vet inte om skrivmetoderna.

kotlin
// ❌ Tjockt gränssnitt — klient tvingas implementera onödiga metoder
interface UserOperations {
    fun getUser(id: String): User
    fun saveUser(user: User)
    fun deleteUser(id: String)
    fun exportUsers(): File
}

// ✅ ISP: separerade gränssnitt
interface UserReader { fun getUser(id: String): User }
interface UserWriter { fun saveUser(user: User) }
interface UserDeleter { fun deleteUser(id: String) }

DIP: Dependency Inversion Principle

Dependency Inversion Principle (DIP) — principen om beroendeinversion. Moduler på hög nivå bör inte vara beroende av moduler på låg nivå. Båda nivåerna bör vara beroende av abstraktioner (gränssnitt). Abstraktioner bör inte vara beroende av detaljer — detaljer är beroende av abstraktioner. Detta är inte samma sak som “Dependency Injection” (DI), även om DI är ett vanligt sätt att implementera DIP.

Enligt Robert C. Martin, 2019, är DIP grunden för Clean Architecture. ViewModel (hög nivå) bör inte direkt skapa en instans av RetrofitApi (detalj). Istället är ViewModel beroende av UserRepository-gränssnittet, och den konkreta implementeringen UserRepositoryImpl med Retrofit skickas via konstruktorn. I Android implementeras DIP via Hilt/Dagger eller Koin: alla beroenden tillhandahålls via DI-behållaren.

kotlin
// ✅ DIP: Modul är beroende av abstraktion, inte av detalj
class UserRepositoryImpl(
    private val api: UserApi,   // Beroende av gränssnitt
    private val db: UserDao     // Beroende av gränssnitt
) : UserRepository {

    override suspend fun getUser(id: String): User {
        return api.fetchUser(id)
    }
}

// Hilt DI: detaljer kopplas via DI-modul
@Module
object NetworkModule {
    @Provides
    fun provideUserApi(retrofit: Retrofit): UserApi =
        retrofit.create(UserApi::class.java)
}

Tillämpning av SOLID i mobil utveckling

SOLID i mobil utveckling tillämpas på alla nivåer: från applikationsarkitektur till enskilda klasser. I Android-projekt delar Clean Architecture upp koden i tre lager: domain (affärslogik — oberoende av ramverk), data (förråd, API, DB) och presentation (UI, ViewModel). Domänlagret använder SOLID-principerna: use case (SRP), förrådsgränssnitt (DIP), entitetsklasser (OCP + LSP).

Enligt Android Developers Guide, 2025, manifesteras SRP i Android i uppdelningen av ViewModel, Repository och Mapper. OCP — vid tillägg av nya datakällor via DataSource-gränssnittet. LSP — i enhetlig hantering av Result från olika förråd. ISP — i CQRS-metoden (separering av Read/Write-förråd). DIP — via Hilt/Koin för beroendeinjektion.

PrincipProblem utan denLösning i mobilprojekt
SRPActivity med 1000+ raderViewModel + UseCase + Repository
OCPswitch-case efter betalningstypStrategy: PaymentGateway-gränssnitt
LSPFel vid byte av BaseViewModelKontroll av underklassers kontrakt
ISPUnsupportedOperationExceptionReader / Writer-separering
DIPViewModel skapar Retrofit manuelltHilt / Koin DI-behållare

Vanliga misstag vid tillämpning av SOLID

SOLID-misstag är oftast relaterade till överdriven komplicering av kod. Första — bokstavlig följning av principerna utan hänsyn till sammanhanget. Att dela upp en UserService-klass i 10 gränssnitt och 15 klasser för “rent” ISP är överkonstruktion. SOLID är ett verktyg, inte ett mål. Andra misstaget — förväxling mellan SRP och “en metod = ett ansvar”. En klass kan ha flera metoder, om alla tillhör samma ansvarsområde.

Enligt Simple Thread, 2024, tredje misstaget — ignorering av LSP vid arv av ViewModel i Android. Om bas-ViewModel förväntar sig LiveData och barnet använder StateFlow — får klientkod som prenumererar på LiveData inga uppdateringar. Fjärde — brott mot DIP för testningens skull: RepositoryImpl skapar direkt en instans av OkHttpClient, vilket omöjliggör enhetstestning.

Gyllene medelvägen: tillämpa SOLID när det löser ett verkligt problem (frekventa ändringar, testsvårigheter, duplicering). För enkla CRUD-skärmar är strikt följning av alla fem principer överdrivet. För affärslogik, finansiella beräkningar och API-interaktioner är SOLID obligatoriskt.

Sambandet mellan SOLID och Clean Architecture

Clean Architecture (Robert C. Martin, 2012) — direkt tillämpning av SOLID på applikationslager nivå. SRP definierar use case-gränser (varje use case — en klass). OCP realiseras via förrådsgränssnitt (Data-lagret kan ändras utan att ändra Domain). ISP ger uppdelning av Use Case i input/output boundary. DIP — riktningen av beroenden inåt Domain-lagret. LSP garanterar att varje förrådsimplementering är utbytbar utan att bryta use case.

Vanliga frågor

Vad är SOLID med enkla ord?

SOLID — fem regler för att skriva kod så att den är lätt att ändra, testa och förstå. Varje bokstav är en princip: skriv inte stora klasser (SRP), ändra inte befintlig kod — lägg till ny (OCP), bryt inte beteendet hos arvtagare (LSP) och andra.

Vilken SOLID-princip är viktigast?

SRP (Single Responsibility) anses viktigast eftersom dess brott leder till Gud-klasser — enorma klasser som är svåra att testa och ändra. Utan DIP (Dependency Inversion) förblir koden dock tätt kopplad, vilket också är kritiskt.

Är SOLID obligatoriskt för mobil utveckling?

Inte obligatoriskt, men mycket rekommenderat för kommersiella projekt med lång livscykel. För enkla appar (en skärm, ingen affärslogik) kan SOLID vara överdrivet. För projekt med 50+ skärmar och 3+ utvecklare är SOLID det nödvändiga minimumet.

Vad händer om jag inte följer SOLID?

Konsekvenser: klasser blir “tjocka” (1000+ rader), en ändring på ett ställe bryter tre andra, det är omöjligt att skriva enhetstester, att lägga till en ny funktion tar veckor istället för dagar. Med tiden förvandlas koden till “Big Ball of Mud” — trasslig och skör.

Hur kontrollerar jag om SOLID följs i projektet?

Tecken på efterlevnad: varje klass har mindre än 200 rader, en funktionsändring påverkar inte 5+ filer, tester skrivs utan att mocka 10 beroenden, en ny utvecklare förstår strukturen på en dag. Verktyg som SonarQube och detekt hjälper att upptäcka SRP- och DIP-brott.

Sammanfattning

  • SOLID — fem OOP-principer (SRP, OCP, LSP, ISP, DIP) för flexibel och underhållbar kod
  • SRP — varje entitet ansvarar för en uppgift, löser problemet med Gud-klasser
  • OCP — utökning genom polymorfism, inte modifiering av befintlig kod
  • LSP — arvtagare bör inte bryta basklassens beteende
  • ISP — smala gränssnitt istället för universell “schweizisk armékniv”
  • DIP — beroende av abstraktioner, injektion via Hilt/Koin i Android
  • SOLID är obligatoriskt för Clean Architecture och kommersiella mobilprojekt

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också