Object-Oriented Programming vs Functional Programming
/ 6 min read /
Table of Contents 目录
What is Object-Oriented vs Functional Programming?
Programming paradigms are mainly divided into Object-Oriented Programming (OOP) and Functional Programming (FP). Understanding these two paradigms helps us choose the right approach to solve problems and improve code maintainability and extensibility.
Object-Oriented Programming
It belongs to imperative programming, which also includes procedural programming. Object-oriented programming is a paradigm centered around objects, organizing data and methods that operate on that data within objects. Its characteristics are as follows:
- Encapsulation: Bundles data and methods inside an object, exposing only necessary information externally, protecting data integrity through access control.
- Inheritance: Allows a class to reuse code from other classes, reducing duplicate code through continuous abstraction.
- Polymorphism: The same interface can have multiple implementations, and objects can take different forms based on context to improve code flexibility and extensibility.
Functional Programming
It belongs to declarative programming, which also includes logical programming, mathematical programming, and reactive programming. Functional programming is a paradigm centered around functions—for example, the often-heard phrase “functions are first-class citizens.” Its characteristics are as follows:
- Pure functions: Same input always produces the same output, no side effects, does not depend on external state.
- Immutability: Data is immutable once created; modifying data returns new data.
- Higher-order functions: Functions can be passed as arguments and returned as values.
Object-Oriented Programming Experience
Let’s use object-oriented programming to build a Todo List.
I put the code on Code Copy: https://www.codecopy.cn/post/81sxun
Since it’s an HTML file, you can run and debug it directly. Let’s look at the JavaScript section:
// Todo item class class TodoItem { constructor(id, text, completed = false) { this.id = id; this.text = text; this.completed = completed; }
toggle() { this.completed = !this.completed; } }
// Todo list management class class TodoList { constructor() { this.todos = []; this.loadFromLocalStorage(); }
addTodo(text) { const id = Date.now(); const todo = new TodoItem(id, text); this.todos.push(todo); this.saveToLocalStorage(); return todo; }
removeTodo(id) { this.todos = this.todos.filter((todo) => todo.id !== id); this.saveToLocalStorage(); }
toggleTodo(id) { const todo = this.todos.find((todo) => todo.id === id); if (todo) { todo.toggle(); this.saveToLocalStorage(); } }
saveToLocalStorage() { localStorage.setItem("todos", JSON.stringify(this.todos)); }
loadFromLocalStorage() { const stored = localStorage.getItem("todos"); if (stored) { const parsedTodos = JSON.parse(stored); this.todos = parsedTodos.map((todo) => new TodoItem(todo.id, todo.text, todo.completed)); } } }
// Todo application class class TodoApp { constructor() { this.todoList = new TodoList(); this.render(); }
addTodo() { const input = document.getElementById("todoInput"); const text = input.value.trim();
if (text) { this.todoList.addTodo(text); input.value = ""; this.render(); } }
removeTodo(id) { this.todoList.removeTodo(id); this.render(); }
toggleTodo(id) { this.todoList.toggleTodo(id); this.render(); }
render() { const todoListElement = document.getElementById("todoList"); todoListElement.innerHTML = "";
this.todoList.todos.forEach((todo) => { const li = document.createElement("li"); li.className = `todo-item ${todo.completed ? "completed" : ""}`;
const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.checked = todo.completed; checkbox.onclick = () => this.toggleTodo(todo.id);
const text = document.createElement("span"); text.textContent = todo.text;
const deleteButton = document.createElement("button"); deleteButton.className = "delete-btn"; deleteButton.textContent = "delete"; deleteButton.onclick = () => this.removeTodo(todo.id);
li.appendChild(checkbox); li.appendChild(text); li.appendChild(deleteButton); todoListElement.appendChild(li); }); } }
// Initialize the app const todoApp = new TodoApp();
// Add enter key support document.getElementById("todoInput").addEventListener("keypress", function (e) { if (e.key === "Enter") { todoApp.addTodo(); } });Here we created three classes: TodoItem class: represents a to-do item with an ID, text, and completion status. Provides a method to toggle completion status; TodoList class: manages the list of to-do items, provides methods to add, delete, and toggle completion status. Includes data persistence to local storage. TodoApp class: handles UI rendering and user interaction, including adding, deleting, and toggling to-do item completion. By abstracting functionality into three classes, each class implements a single logic, and the outer class calls inner classes—like an onion, encapsulating complex logic inside. Users don’t need to care about how it’s implemented, only how to use it.
Functional Programming Experience
Full code also here: https://www.codecopy.cn/post/81sxun
// Create a new todo item const createTodo = (text) => ({ id: Date.now(), text, completed: false, });
// Toggle todo completion status const toggleTodo = (todo) => ({ ...todo, completed: !todo.completed, });
// Filter out a specific todo by id const removeTodoById = (todos, id) => todos.filter((todo) => todo.id !== id);
// Add a new todo const addTodo = (todos, text) => [...todos, createTodo(text)];
// Toggle a specific todo by id const toggleTodoById = (todos, id) => todos.map((todo) => (todo.id === id ? toggleTodo(todo) : todo));
// Local storage operations const storage = { save: (todos) => localStorage.setItem("todos", JSON.stringify(todos)), load: () => JSON.parse(localStorage.getItem("todos") || "[]"), };
// Render function const renderTodos = (todos, handlers) => { const todoList = document.getElementById("todoList"); todoList.innerHTML = "";
const createTodoElement = (todo) => { const li = document.createElement("li"); li.className = `todo-item ${todo.completed ? "completed" : ""}`;
const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.checked = todo.completed; checkbox.onchange = () => handlers.onToggle(todo.id);
const text = document.createElement("span"); text.textContent = todo.text;
const deleteButton = document.createElement("button"); deleteButton.className = "delete-btn"; deleteButton.textContent = "delete"; deleteButton.onclick = () => handlers.onDelete(todo.id);
li.appendChild(checkbox); li.appendChild(text); li.appendChild(deleteButton); return li; };
todos.forEach((todo) => { todoList.appendChild(createTodoElement(todo)); }); };
// Application state management const createTodoApp = () => { let todos = storage.load();
const updateState = (newTodos) => { todos = newTodos; storage.save(todos); render(); };
const handlers = { onAdd: (text) => { if (text.trim()) { updateState(addTodo(todos, text)); } }, onToggle: (id) => { updateState(toggleTodoById(todos, id)); }, onDelete: (id) => { updateState(removeTodoById(todos, id)); }, };
const render = () => renderTodos(todos, handlers);
// Initial render render();
return handlers; };
// Initialize the app const app = createTodoApp();
// Event listener setup document.getElementById("addButton").addEventListener("click", () => { const input = document.getElementById("todoInput"); app.onAdd(input.value); input.value = ""; });
document.getElementById("todoInput").addEventListener("keypress", (e) => { if (e.key === "Enter") { const input = document.getElementById("todoInput"); app.onAdd(input.value); input.value = ""; } });From the code above, you can see all logic is implemented using functions. Pure functions and immutability improve code predictability and maintainability. However, compared to the OOP version, the code may be less clear.
OOP vs FP
From the code above, we can intuitively see their respective pros and cons. Let’s compare and summarize:
- Code organization: OOP is object-based, bundling data and corresponding behavior together, managing code through classes; FP revolves around functions to derive new data, with data and behavior separated.
- State management: In OOP, state is maintained internally within objects, updated through class methods, and state changes may cause side effects; in FP, state is managed by returning new data (state) from functions, emphasizing immutability.
- Code reuse: OOP achieves reuse through inheritance; FP achieves reuse through function composition.
- Use cases: OOP suits systems with clear objects and behaviors requiring complex state management; FP suits business development, concurrent programming, data transformation, and computation.
Let’s look at the programming paradigms used by well-known frontend libraries. Vue 2.x is based on OOP; React class components before React 18 were also OOP-based—each component is essentially an object. However, after React 18, React shifted to functional components and custom hooks. This change happened because reusing logic in class components was cumbersome, requiring HOCs (higher-order components), which create unnecessary component trees, causing nesting hell and making debugging difficult. The introduction of custom hooks made logic reuse in React components very simple. I think the key point is that class components didn’t actually use class features—we didn’t use encapsulation, inheritance, etc.
There’s no silver bullet in technology; every approach has its drawbacks. Only by mastering both can you choose the right coding style when facing a problem and write elegant code.