├── .gitignore ├── README.md └── src ├── 1-single-responsibility.kt ├── 2-open-closed-principle.kt ├── 3-liskov-substitution-principle.kt ├── 4-interface-segregation-principle.kt └── 5-dependency-inversion-principle.kt /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/** 2 | out/** 3 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SOLID principles in Kotlin 2 | Examples of SOLID principles in Kotlin 3 | 4 | ## Single Responsibility 5 | * Entities should only have one reason to change 6 | 7 | [View example](src/1-single-responsibility.kt) 8 | 9 | ## Open Closed Principle 10 | * Entities should be open for extension, but closed for modification 11 | 12 | [View example](src/2-open-closed-principle.kt) 13 | 14 | ## Liskov Substitution Principle 15 | * Derived classes must be substitutable for their base classes 16 | 17 | [View example](src/3-liskov-substitution-principle.kt) 18 | 19 | ## Interface Segregation Principle 20 | * Clients should not be forced to program against interfaces they do not use 21 | 22 | [View example](src/4-interface-segregation-principle.kt) 23 | 24 | ## Dependency Inversion Principle 25 | * Program to an interface, not an implementation 26 | 27 | [View example](src/5-dependency-inversion-principle.kt) -------------------------------------------------------------------------------- /src/1-single-responsibility.kt: -------------------------------------------------------------------------------- 1 | // BAD: This class violates the Single Responsibility Principle 2 | // BadUser class performs storage actions as well as user related actions 3 | class BadUser { 4 | fun login() {} 5 | fun storeAuthToken() { 6 | // Logic for storing auth token 7 | } 8 | fun clearAuthToken() { 9 | // Logic for clearing auth token 10 | } 11 | } 12 | 13 | // BETTER: This class no longer performs storage actions, reducing the number 14 | // of reasons for this class to change 15 | interface AuthTokenStorage { 16 | fun store(token: String) 17 | fun clear() 18 | } 19 | 20 | class User(private val authStorage: AuthTokenStorage) { 21 | fun login() {} 22 | fun storeAuthToken(token: String) { 23 | authStorage.store(token) 24 | } 25 | fun clearAuthToken() { 26 | authStorage.clear() 27 | } 28 | } -------------------------------------------------------------------------------- /src/2-open-closed-principle.kt: -------------------------------------------------------------------------------- 1 | // Example of Open Closed Principle using Animals 2 | fun main() { 3 | val dog = Dog() 4 | dog.move() 5 | dog.woof() 6 | } 7 | 8 | open class Animal { 9 | fun move() { 10 | println("I am moving!") 11 | } 12 | } 13 | 14 | // Dog class is able to add the woof() functionality 15 | // but the Dog class is unable to change the existing move() functionality 16 | class Dog: Animal() { 17 | fun woof() { 18 | println("woof!") 19 | } 20 | } -------------------------------------------------------------------------------- /src/3-liskov-substitution-principle.kt: -------------------------------------------------------------------------------- 1 | // Example of the Liskov Substitution Principle 2 | 3 | // When main() runs, VehicleRepository.getAll() will return a list 4 | // of vehicles. One will be a Car subtype and another will be a 5 | // Campervan subtype. They will both be accessible under the interface 6 | // of the Vehicle object 7 | 8 | fun main() { 9 | val vehicles: List = VehicleRepository().getAll() 10 | for (vehicle in vehicles) { 11 | println(vehicle.registration) 12 | } 13 | } 14 | 15 | open class Vehicle(val registration: String) 16 | 17 | data class Car(val reg: String) : Vehicle(reg) 18 | 19 | data class Campervan(val reg: String, val numberOfBeds: Int) : Vehicle(reg) 20 | 21 | class VehicleRepository { 22 | fun getAll(): List { 23 | val vehicles: MutableList = ArrayList() 24 | vehicles.add(Car(reg = "AB66 GHJ")) 25 | vehicles.add(Campervan(reg = "CA66 MPR", numberOfBeds = 2)) 26 | return vehicles 27 | } 28 | } -------------------------------------------------------------------------------- /src/4-interface-segregation-principle.kt: -------------------------------------------------------------------------------- 1 | import kotlin.math.pow 2 | 3 | // BAD: This class violates the Interface Segregation Principle 4 | // BadShape interface forces BadSquare to calculate a radius, 5 | // of which it cannot & has no use for 6 | 7 | interface BadShape { 8 | fun radius(): Double 9 | fun area(): Double 10 | } 11 | 12 | data class BadCircle(val diameter: Double): BadShape { 13 | override fun radius() = diameter / 2 14 | override fun area() = Math.PI * radius().pow(2) 15 | } 16 | 17 | data class BadSquare(val width: Double, val height: Double): BadShape { 18 | override fun radius() = 0.0 19 | override fun area() = width * height 20 | } 21 | 22 | // GOOD: by breaking BadShape up into Shape & CircularShape, 23 | // we no longer need to force Square to implement a radius 24 | interface Shape { 25 | fun area(): Double 26 | } 27 | 28 | interface CircularShape { 29 | fun radius(): Double 30 | } 31 | 32 | data class Circle(val diameter: Double): Shape, CircularShape { 33 | override fun radius() = diameter / 2 34 | override fun area() = Math.PI * radius().pow(2) 35 | } 36 | 37 | data class Square(val width: Double, val height: Double): Shape { 38 | override fun area() = width * height 39 | } -------------------------------------------------------------------------------- /src/5-dependency-inversion-principle.kt: -------------------------------------------------------------------------------- 1 | // BAD: This class violates the Dependency Inversion Principle 2 | // by creating the BookRepository dependency inside of Library 3 | 4 | data class Book(val name: String) 5 | 6 | class BookRepository(val apiKey: String) { 7 | fun getBooks(): List { 8 | val books = ArrayList() 9 | books.add(Book("Harry Potter")) 10 | return books 11 | } 12 | } 13 | 14 | class BadLibrary { 15 | fun list() = BookRepository(BOOK_API_KEY).getBooks() 16 | 17 | companion object { 18 | const val BOOK_API_KEY = "abc123" 19 | } 20 | } 21 | 22 | // BETTER: BookRepository is now provided in the constructor of Library, 23 | // making this class much easier to test and maintain. Changes to the 24 | // constructor of BookRepository no longer affect the Library class. 25 | 26 | class Library(private val repository: BookRepository) { 27 | fun list() = repository.getBooks() 28 | } --------------------------------------------------------------------------------