0% found this document useful (0 votes)
3 views5 pages

SOLID Principles

The document outlines the SOLID principles of object-oriented design, which include the Single Responsibility Principle, Open-Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle. Each principle is defined with examples of good and bad design practices. The principles aim to create more maintainable and scalable software by promoting separation of concerns and reducing dependencies.

Uploaded by

pranjalsingh1428
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views5 pages

SOLID Principles

The document outlines the SOLID principles of object-oriented design, which include the Single Responsibility Principle, Open-Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle. Each principle is defined with examples of good and bad design practices. The principles aim to create more maintainable and scalable software by promoting separation of concerns and reducing dependencies.

Uploaded by

pranjalsingh1428
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

SOLID Principles

Obsidian Help

The most widely referenced design principles are SOLID, introduced by Robert C. Martin.
[!important]
These principles guide object-oriented design but are broadly applicable across paradigms.
Single Responsibility Principle
[!definition]
A module/struct should have only one reason to change.
[!note]
Each unit should focus on a single responsibility or concern.
struct User {
username: String,
}

impl User {
fn save(&self) {
// database logic
}

fn log_activity(&self) {
// logging logic
}
}

The bad design mixes data, persistence and logic.


struct User {
username: String,
}

struct UserRepository;

impl UserRepository {
fn save(user: &User) {
// database logic
}
}

struct Logger;

impl Logger {
fn log(message: &str) {
// logging logic
}
}

User
+username: String

uses logs via


UserRepository Logger

+save(user: User) +log(message: String)


Open-Closed Principle Obsidian Help

[!definition]
Software entities should be open for extension but closed for modification.
[!note]
You should be able to add new behaviour without changing existing code.
fn calculate_discount(user_type: &str) -> f64 {
if user_type == "regular" {
0.1
} else if user_type == "premium" {
0.2
} else {
0.0
}
}

The bad design requires modification for every new type.


trait Discount {
fn get_discount(&self) -> f64;
}

struct Regular;
struct Premium;

impl Discount for Regular {


fn get_discount(&self) -> f64 { 0.1 }
}

impl Discount for Premium {


fn get_discount(&self) -> f64 { 0.2 }
}

fn print_discount<T: Discount>(user: T) {
println!("{}", user.get_discount());
}

Checkout

+calculate(discount: Discount)

«interface»
Discount

+getDiscount() : : f64

RegularUser PremiumUser

+getDiscount() : : f64 +getDiscount() : : f64

Liskov Substitution Principle


[!definition]
Objects of a superclass should be replaceable with objects of its subclasses without breaking correctness.
[!note] Obsidian Help

Derived classes must honour the behaviour contract of the base class.
trait Bird {
fn fly(&self);
}

struct Penguin;

impl Bird for Penguin {


fn fly(&self) {
panic!("Penguins can't fly!");
}
}

This is kinda impossible to do in Rust but this breaks.


trait Bird {}

trait FlyingBird: Bird {


fn fly(&self);
}

struct Sparrow;
struct Penguin;

impl Bird for Sparrow {}


impl Bird for Penguin {}

impl FlyingBird for Sparrow {


fn fly(&self) {
println!("Flying");
}
}

«interface»
FlyingBird «interface»
Bird
+fly()

Sparrow Penguin

Interface Segregation Principle


[!definition]
Clients should not be forced to depend on interfaces they do not use.
[!note]
Prefer small, specific interfaces over large, general ones.
trait Machine {
fn print(&self);
fn scan(&self);
fn fax(&self);
}

This forces unnecessary implementation.


trait Printer {
fn print(&self);
} Obsidian Help

trait Scanner {
fn scan(&self);
}

struct SimplePrinter;

impl Printer for SimplePrinter {


fn print(&self) {
println!("Printing...");
}
}

«interface» «interface» «interface»


Fax Scanner Printer

+fax() +scan() +print()

MultiFunctionPrinter
SimplePrinter
+print()
+scan() +print()
+fax()

Dependency Inversion Principle


[!definition]
High-level modules should not depend on low-level modules. Both should depend on abstractions.
[!note]
Use interfaces/abstractions instead of concrete implementations.
struct MySQLDatabase;

impl MySQLDatabase {
fn save(&self, data: &str) {}
}

struct UserService {
db: MySQLDatabase,
}

This makes the design tightly coupled.


trait Database {
fn save(&self, data: &str);
}

struct MySQLDatabase;

impl Database for MySQLDatabase {


fn save(&self, data: &str) {
println!("Saved to MySQL");
}
}

struct UserService<T: Database> {


db: T,
}
impl<T: Database> UserService<T> { Obsidian Help
fn process(&self) {
[Link]("user data");
}
}

UserService
-db: Database
+process()

«interface»
Database

+save(data: String)

MySQLDatabase PostgresDatabase

+save(data: String) +save(data: String)

You might also like