S.O.L.I.D. with examples
S)ingle Responsibility Principle
A class should have one, and only one, reason to change. A class should have a single responsibility within the software.
Bad Example:
1class User {
2 fun save() {
3 // Logic to save user to the database
4 }
5
6 fun sendEmail() {
7 // Logic to send email to the user
8 }
9}
Good Example:
1class User {
2 fun save() {
3 // Logic to save user to the database
4 }
5}
6
7class EmailService {
8 fun sendEmail(user: User) {
9 // Logic to send email to the user
10 }
11}
O)pen Closed Principle
You should be able to extend a class’s behavior without modifying it. Objects should be open for extension but closed for modification.
Bad Example:
1class Rectangle(val width: Double, val height: Double) {
2 fun area(): Double {
3 return width * height
4 }
5}
6
7class Circle(val radius: Double) {
8 fun area(): Double {
9 return Math.PI * radius * radius
10 }
11}
Good Example:
1interface Shape {
2 fun area(): Double
3}
4
5class Rectangle(val width: Double, val height: Double) : Shape {
6 override fun area(): Double {
7 return width * height
8 }
9}
10
11class Circle(val radius: Double) : Shape {
12 override fun area(): Double {
13 return Math.PI * radius * radius
14 }
15}
L)iskov Substitution Principle
Derived classes should be substitutable for their base classes. The Liskov Substitution Principle was introduced by Barbara Liskov in 1987: “If for every object o1 of type S there is an object o2 of type T such that for all programs P, the behavior of P is unchanged when o1 is substituted by o2, then S is a subtype of T.”
Bad Example:
1open class Bird {
2 open fun fly() {
3 println("Flying")
4 }
5}
6
7class Ostrich : Bird() {
8 override fun fly() {
9 throw Exception("Ostriches can't fly")
10 }
11}
Good Example:
1open class Bird {
2 open fun makeSound() {
3 println("Chirp")
4 }
5}
6
7class Sparrow : Bird() {
8 override fun makeSound() {
9 println("Chirp chirp")
10 }
11}
12
13class Ostrich : Bird() {
14 override fun makeSound() {
15 println("Hiss")
16 }
17}
I)nterface Segregation Principle
Make interfaces that are client-specific. A class should not be forced to implement interfaces and methods that will not be used.
Bad Example:
1interface Machine {
2 fun print()
3 fun scan()
4 fun fax()
5}
6
7class Printer : Machine {
8 override fun print() {
9 // Logic to print
10 }
11
12 override fun scan() {
13 throw UnsupportedOperationException("Printer cannot scan")
14 }
15
16 override fun fax() {
17 throw UnsupportedOperationException("Printer cannot fax")
18 }
19}
Good Example:
1interface Printer {
2 fun print()
3}
4
5interface Scanner {
6 fun scan()
7}
8
9class SimplePrinter : Printer {
10 override fun print() {
11 // Logic to print
12 }
13}
14
15class MultiFunctionPrinter : Printer, Scanner {
16 override fun print() {
17 // Logic to print
18 }
19
20 override fun scan() {
21 // Logic to scan
22 }
23}
D)ependency Inversion Principle
Depend on abstractions, not on concretions. A high-level module should not depend on low-level modules; both should depend on abstractions.
Bad Example:
1class EmailService {
2 fun sendEmail(message: String) {
3 // Logic to send email
4 }
5}
6
7class Notification {
8 private val emailService = EmailService()
9
10 fun notify(message: String) {
11 emailService.sendEmail(message)
12 }
13}
Good Example:
1interface MessageSender {
2 fun send(message: String)
3}
4
5class EmailService : MessageSender {
6 override fun send(message: String) {
7 // Logic to send email
8 }
9}
10
11class Notification(private val sender: MessageSender) {
12 fun notify(message: String) {
13 sender.send(message)
14 }
15}
16
17// Usage
18val emailService = EmailService()
19val notification = Notification(emailService)
20notification.notify("Hello, World!")