Here are examples that demonstrate different software development principles using functional programming in TypeScript. By focusing on pure functions, immutability, and higher-order functions, we can achieve similar goals as object-oriented approaches but with a functional paradigm.

1. SOLID Principles

Single Responsibility Principle (SRP)

Definition: A function should have one, and only one, reason to change, meaning that it should perform only one task.

Example:

type User = {
  name: string
  email: string
}
 
const createUser = (name: string, email: string): User => ({
  name,
  email,
})
 
const saveUser = (user: User): void => {
  // Save user to database
  console.log("User saved:", user)
}
 
const registerUser = (name: string, email: string): void => {
  const user = createUser(name, email)
  saveUser(user)
}
 
registerUser("John Doe", "[email protected]")

Open/Closed Principle (OCP)

Definition: Software entities should be open for extension, but closed for modification.

Example:

interface Shape {
  area: () => number
}
 
const createRectangle = (width: number, height: number): Shape => ({
  area: () => width * height,
})
 
const createCircle = (radius: number): Shape => ({
  area: () => Math.PI * radius * radius,
})
 
const calculateTotalArea = (shapes: Shape[]): number =>
  shapes.reduce((total, shape) => total + shape.area(), 0)
 
const shapes = [createRectangle(10, 20), createCircle(15)]
console.log(calculateTotalArea(shapes)) // Output: area of all shapes

Liskov Substitution Principle (LSP)

Definition: Objects should be replaceable with instances of their subtypes without altering the correctness of the program.

Example:

type Bird = {
  fly: () => void
}
 
const createDuck = (): Bird => ({
  fly: () => console.log("Duck flying"),
})
 
const createPenguin = (): Bird => ({
  fly: () => {
    throw new Error("Penguins can't fly")
  },
  swim: () => console.log("Penguin swimming"),
})
 
const makeBirdFly = (bird: Bird) => bird.fly()
 
const duck = createDuck()
makeBirdFly(duck) // Works fine
 
const penguin = createPenguin()
try {
  makeBirdFly(penguin) // Throws an error
} catch (error) {
  console.error(error.message)
}

Interface Segregation Principle (ISP)

Definition: A client should not be forced to depend on interfaces it does not use.

Example:

type Printer = {
  print: () => void
}
 
type Scanner = {
  scan: () => void
}
 
const createMultiFunctionPrinter = (): Printer & Scanner => ({
  print: () => console.log("Printing"),
  scan: () => console.log("Scanning"),
})
 
const createSimplePrinter = (): Printer => ({
  print: () => console.log("Printing"),
})

Dependency Inversion Principle (DIP)

Definition: High-level modules should not depend on low-level modules. Both should depend on abstractions.

Example:

type Database = {
  save: (data: any) => void
}
 
const createMySQLDatabase = (): Database => ({
  save: (data: any) => console.log("Saving data in MySQL database", data),
})
 
const saveUser = (database: Database, user: any): void => {
  database.save(user)
}
 
const database = createMySQLDatabase()
const user = { name: "John Doe" }
saveUser(database, user)

2. DRY (Don’t Repeat Yourself)

Definition: DRY is a principle aimed at reducing the repetition of code.

Example:

const isValidEmail = (email: string): boolean => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
 
const createUser = (email: string) => {
  if (!isValidEmail(email)) {
    throw new Error("Invalid email address")
  }
  return { email }
}
 
const sendEmail = (email: string, message: string): void => {
  if (!isValidEmail(email)) {
    throw new Error("Invalid email address")
  }
  // Logic to send email
  console.log(`Email sent to ${email}: ${message}`)
}

3. KISS (Keep It Simple, Stupid)

Definition: KISS principle states that systems work best if they are kept simple.

Example:

const factorial = (n: number): number => (n <= 1 ? 1 : n * factorial(n - 1))
 
console.log(factorial(5)) // Output: 120

4. YAGNI (You Aren’t Gonna Need It)

Definition: YAGNI principle states that a developer should not add functionality until it is necessary.

Example:

type User = {
  name: string
  email: string
}
 
const createUser = (name: string, email: string): User => ({
  name,
  email,
})
 
const saveUser = (user: User): void => {
  // Save user to database
  console.log("User saved:", user)
}

5. Separation of Concerns

Definition: Separation of concerns is a design principle for separating a computer program into distinct sections.

Example:

type User = {
  name: string
  email: string
}
 
const createUser = (name: string, email: string): User => ({
  name,
  email,
})
 
const saveUser = (user: User): void => {
  // Save user to database
  console.log("User saved:", user)
}
 
const registerUser = (name: string, email: string): void => {
  const user = createUser(name, email)
  saveUser(user)
}
 
const userController = (req: any, res: any): void => {
  const { name, email } = req.body
  registerUser(name, email)
  res.send("User registered successfully")
}
 
const getUserController = (req: any, res: any): void => {
  const userId = req.params.id
  // Simulate fetching user
  const user = createUser("John Doe", "[email protected]")
  res.json(user)
}

6. Encapsulation

Definition: Encapsulation is the bundling of data with the methods that operate on that data.

Example:

type User = {
  name: string
  email: string
}
 
const createUser = (name: string, email: string): User => {
  let _name = name
  let _email = email
 
  return {
    getName: () => _name,
    setName: (newName: string) => {
      _name = newName
    },
    getEmail: () => _email,
    setEmail: (newEmail: string) => {
      _email = newEmail
    },
  }
}
 
const user = createUser("John Doe", "[email protected]")
console.log(user.getName()) // John Doe
user.setName("Jane Doe")
console.log(user.getName()) // Jane Doe

7. Polymorphism

Definition: Polymorphism is the ability of different classes to be treated as instances of the same class through a common interface.

Example:

type Animal = {
  makeSound: () => void
}
 
const createDog = (): Animal => ({
  makeSound: () => console.log("Bark"),
})
 
const createCat = (): Animal => ({
  makeSound: () => console.log("Meow"),
})
 
const makeAnimalSound = (animal: Animal): void => {
  animal.makeSound()
}
 
const dog = createDog()
const cat = createCat()
makeAnimalSound(dog) // Bark
makeAnimalSound(cat) // Meow