The introduction of the OOP paradigm popularized key programming concepts such as Inheritance, Polymorphism, Abstraction, and Encapsulation. OOP quickly became a widely accepted programming paradigm with implementation in several languages such as Java, C , C#, JavaScript, and more. The OOP system became more complex over time, but its software remained resistant to change. To improve software extensibility and reduce code rigidity, Robert C. Martin (a.k.a Uncle Bob) introduced the SOLID principles in the early 2000s.
SOLID is an acronym that consists of a set of principles — single responsibility principle, open-closed principle, Liskov substitution principle, interface segregation principle, and dependency inversion principle — that helps software engineers design and write maintainable, scalable, and flexible code. Its aim? To improve the quality of software developed following the Object Oriented Programming (OOP) paradigm.
In this article, we will delve into all of SOLID’s principles and illustrate how they are implemented using one of the most popular web programming languages, JavaScript.
Single Responsibility Principle (SRP)
The first letter in SOLID represents the single responsibility principle. This principle suggests that a class or module should perform just one role.
Simply put, a class should have a single responsibility or a single reason to change. If a class handles more than one functionality, updating one functionality without affecting the others becomes tricky. The subsequent complications could result in a fault in software performance. To avoid these kinds of problems, we should do our best to write modular software in which concerns are separated.
If a class has too many responsibilities or functionalities, it becomes a headache to modify. By using the single responsibility principle, we can write code that is modular, easier to maintain, and less error-prone. Take, for instance, a person model:
class Person { constructor(name, age, height, country){ this.name = name this.age = age this.height = height this.country = country } getPersonCountry(){ console.log(this.country) } greetPerson(){ console.log("Hi " + this.name) } static calculateAge(dob) { const today = new Date(); const birthDate = new Date(dob); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff <p>The code above appears to be fine, right? Not quite. The sample code violates the single responsibility principle. Instead of being the only model from which other instances of a Person can be created, the Person class also has other responsibilities such as calculateAge, greetPerson and getPersonCountry. </p> <p>These extra responsibilities handled by the Person class make it difficult to change just one aspect of the code. For example, if you attempted to refactor the calculateAge, you might also be forced to refactor the Person model. Depending on how compact and complex our code base is, it could be difficult to reconfigure the code without causing errors. </p> <p>Let’s try and revise the mistake. We can separate the responsibilities into different classes, like so:<br> </p><pre class="brush:php;toolbar:false">class Person { constructor(name, age, height, country){ this.name = name this.age = age this.height = height this.country = country } getPersonCountry(){ console.log(this.country) } greetPerson(){ console.log("Hi " + this.name) } static calculateAge(dob) { const today = new Date(); const birthDate = new Date(dob); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff <p>As you can see from the sample code above, we’ve separated our responsibilities. The Person class is now a model with which we can create a new person object. And the PersonUtils class has just one responsibility — to calculate the age of a person. The PersonService class handles greetings and shows us each person’s country. </p> <p>If we want, we can still reduce this process more. Following the SRP, we want to decouple the responsibility of a class to the barest minimum so that when there is an issue, refactoring and debugging can be done without much hassle. </p> <p>By dividing functionality into separate classes, we’re adhering to the single responsibility principle and ensuring each class is responsible for a specific aspect of the application. </p> <p>Before we move on to the next principle, it should be noted that adhering to the SRP doesn’t mean that each class should <em>strictly</em> contain a single method or functionality. </p> <p>However, adhering to the single responsibility principle means we should be intentional about assigning functionalities to classes. Everything a class carries out should be closely related in every sense. We must be careful not to have several classes scattered everywhere, and we should, by all means, avoid bloated classes in our code base.</p> <h2> Open-Closed Principle (OCP) </h2> <p>The open-closed principle states that software components (classes, functions, modules, etc.) should be open to extension and closed to modification. I know what you’re thinking — yes, this idea of might seem contradictory at first. But the OCP is simply asking that the software is designed in a way that allows for extension without necessarily modifying the source code. </p> <p>The OCP is crucial for maintaining large code bases, as this guideline is what allows you to introduce new features with little to no risk of breaking the code. Instead of modifying the existing classes or modules when new requirements arise, you should extend the relevant classes by adding new components. As you do this, be sure to check that the new component doesn’t introduce any bugs to the system. </p> <p>The OC principle can be achieved in JavaScript using the ES6 class Inheritance feature. </p> <p>The following code snippets illustrate how to implement the Open-Closed principle in JavaScript, using the aforementioned ES6 class keyword:<br> </p> <pre class="brush:php;toolbar:false">class Person { constructor(name, dateOfBirth, height, country){ this.name = name this.dateOfBirth = dateOfBirth this.height = height this.country = country } } class PersonUtils { static calculateAge(dob) { const today = new Date(); const birthDate = new Date(dob); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if(monthDiff <p>The code above works fine, but it’s limited to calculating only the area of a rectangle. Now imagine that there is a new requirement to calculate. Let’s say, for instance, that we need to calculate the area of a circle. We would have to modify the shapeProcessor class to cater to that. However, following the JavaScript ES6 standard, we can extend this functionality to account for areas of new shapes without necessarily modifying the shapeProcessor class. </p> <p>We can do that like so:<br> </p><pre class="brush:php;toolbar:false">class Person { constructor(name, age, height, country){ this.name = name this.age = age this.height = height this.country = country } getPersonCountry(){ console.log(this.country) } greetPerson(){ console.log("Hi " + this.name) } static calculateAge(dob) { const today = new Date(); const birthDate = new Date(dob); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff <p>In the above code snippet, we extended the functionality of the Shape class by using the extends keyword. In each subclass, we override the implementation of the area() method. Following this principle, we can add more shapes and process areas without needing to modify the functionality of the ShapeProcessor class.</p> <h3> Why Is the OCP Important? </h3>
- Reduce bugs: OCP helps to avoid bugs in a large code base by avoiding system modification.
- Encourages software adaptability: OCP also improves the ease with which new features can be added to software without breaking or altering the source code.
- Testing new features: OCP promotes code extension over modification, making it easier for new features to be tested as a unit without affecting the entire codebase.
The Liskov Substitution principle
The Liskov substitution principle states that an object of a subclass should be able to replace an object of a superclass without breaking the code. Let’s break down how that works with an example: if L is a subclass of P, then an object of L should replace an object of P without breaking the system. This just means that a subclass should be able to override a superclass method in a way that does not break the system.
In practice, the Liskov substitution principle ensures that the following conditions are adhered to:
- A subclass should override methods of the parent class without breaking the code
- A subclass should not deviate from the behavior of the parent class, meaning subclasses can only add functionality but cannot alter or remove the parent class functionality
- The code that works with the instances of the parent class should work with the instances of the subclasses without needing to know that the class has changed
It’s time to illustrate the Liskov substitution principle with JavaScript code samples. Take a look:
class Person { constructor(name, dateOfBirth, height, country){ this.name = name this.dateOfBirth = dateOfBirth this.height = height this.country = country } } class PersonUtils { static calculateAge(dob) { const today = new Date(); const birthDate = new Date(dob); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if(monthDiff <p>In the code snippet above, we created two subclasses (Bicycle and Car) and one superclass (Vehicle). For the purposes of this article, we implemented a single method (OnEngine) for the superclass. </p> <p>One of the core conditions for the LSP is that subclasses should override the parent classes' functionality without breaking the code. Keeping that in mind, let’s see how the code snippet we just saw violates the Liskov substitution principle. In reality, a Car has an engine and can turn ON an engine but a bicycle technically doesn’t have an engine and therefore cannot turn ON an engine. So, a Bicycle cannot override the OnEngine method in the Vehicle class without breaking the code. </p><p>We’ve now identified the section of the code that violates the Liskov substitution principle. The Car class can override the OnEngine functionality in the superclass and implement it in such a way that differentiates it from other vehicles (like an airplane, for example) and the code will not break. The Car class satisfies the Liskov substitution principle. </p> <p>In the code snippet below, we’ll illustrate how to structure the code to be in compliance with the Liskov substitution principle:<br> </p> <pre class="brush:php;toolbar:false">class Person { constructor(name, age, height, country){ this.name = name this.age = age this.height = height this.country = country } getPersonCountry(){ console.log(this.country) } greetPerson(){ console.log("Hi " + this.name) } static calculateAge(dob) { const today = new Date(); const birthDate = new Date(dob); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff <p>Here is a basic example of a Vehicle class with a general functionality, move. It is a general belief that all vehicles move; they just move via different mechanisms. One way we’re going to illustrate LSP is to override the move() method and implement it in a way that depicts how a particular vehicle, for instance, a Car would move. </p> <p>To do so, we’re going to create a Car class that extends the Vehicle class and overrides the move method to suit the movement of a car, like so:<br> </p> <pre class="brush:php;toolbar:false">class Person { constructor(name, dateOfBirth, height, country){ this.name = name this.dateOfBirth = dateOfBirth this.height = height this.country = country } } class PersonUtils { static calculateAge(dob) { const today = new Date(); const birthDate = new Date(dob); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if(monthDiff <p>We can still implement the move method in another sub-vehicle class—for instance—an airplane. </p> <p>Here’s how we would do that:<br> </p> <pre class="brush:php;toolbar:false">class Rectangle { constructor(width, height) { this.width = width; this.height = height; } area() { return this.width * this.height; } } class ShapeProcessor { calculateArea(shape) { if (shape instanceof Rectangle) { return shape.area(); } } } const rectangle = new Rectangle(10, 20); const shapeProcessor = new ShapeProcessor(); console.log(shapeProcessor.calculateArea(rectangle));
In these two examples above, we illustrated key concepts such as inheritance and method overriding.
N.B: A programming feature that allows subclasses to implement a method already defined in the parent class is called method overriding.
Let’s do some housekeeping and put everything together, like so:
class Shape { area() { console.log("Override method area in subclass"); } } class Rectangle extends Shape { constructor(width, height) { super(); this.width = width; this.height = height; } area() { return this.width * this.height; } } class Circle extends Shape { constructor(radius) { super(); this.radius = radius; } area() { return Math.PI * this.radius * this.radius; } } class ShapeProcessor { calculateArea(shape) { return shape.area(); } } const rectangle = new Rectangle(20, 10); const circle = new Circle(2); const shapeProcessor = new ShapeProcessor(); console.log(shapeProcessor.calculateArea(rectangle)); console.log(shapeProcessor.calculateArea(circle));
Now, we have 2 subclasses inheriting and overriding a single functionality from the parent class and implementing it according to their requirements. This new implementation does not break the code.
Interface Segregation Principle (ISP)
The interface segregation principle states that no client should be forced to depend on an interface it doesn’t use. It wants us to create smaller, more specific interfaces that are relevant to the particular clients, rather than having a large, monolithic interface that forces clients to implement methods they don’t need.
Keeping our interfaces compact makes code bases easier to debug, maintain, test, and extend. Without the ISP, a change in one part of a large interface could force changes in unrelated parts of the codebase, causing us to carry out code refactoring which in most cases depending on the size of the code base can be a difficult task.
JavaScript, unlike C-based programming languages like Java, does not have built-in support for interfaces. However, there are techniques with which interfaces are implemented in JavaScript.
Interfaces are a set of method signatures that a class must implement.
In JavaScript, you define an interface as an object with names of method and function signatures, like so:
class Person { constructor(name, age, height, country){ this.name = name this.age = age this.height = height this.country = country } getPersonCountry(){ console.log(this.country) } greetPerson(){ console.log("Hi " + this.name) } static calculateAge(dob) { const today = new Date(); const birthDate = new Date(dob); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff <p>To implement an interface in JavaScript, create a class and ensure that it contains methods with the same names and signatures that are specified in the interface:<br> </p> <pre class="brush:php;toolbar:false">class Person { constructor(name, dateOfBirth, height, country){ this.name = name this.dateOfBirth = dateOfBirth this.height = height this.country = country } } class PersonUtils { static calculateAge(dob) { const today = new Date(); const birthDate = new Date(dob); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if(monthDiff <p>Now we’ve figured out how to create and use interfaces in JavaScript. The next thing we need to do is illustrate how to segregate interfaces in JavaScript so that we can see how it all fits together and makes code easier to maintain. </p> <p>In the following example, we’ll use a printer to illustrate the interface segregation principle. </p> <p>Assuming we have a printer, scanner, and fax, let’s create an interface defining these objects’ functions:<br> </p> <pre class="brush:php;toolbar:false">class Rectangle { constructor(width, height) { this.width = width; this.height = height; } area() { return this.width * this.height; } } class ShapeProcessor { calculateArea(shape) { if (shape instanceof Rectangle) { return shape.area(); } } } const rectangle = new Rectangle(10, 20); const shapeProcessor = new ShapeProcessor(); console.log(shapeProcessor.calculateArea(rectangle));
In the code above, we created a list of separated or segregated interfaces against having one large interface that defines all of these functionalities. By breaking these functionalities into smaller bits and more specific interfaces, we’re allowing different clients to implement just the methods they need and keeping all the other bits out.
In the next step, we’ll create classes that implement these interfaces. Following the interface segregation principle, each class will only implement the methods that it needs.
If we want to implement a basic printer that can only print documents, we can just implement the print() method through the printerInterface, like so:
class Shape { area() { console.log("Override method area in subclass"); } } class Rectangle extends Shape { constructor(width, height) { super(); this.width = width; this.height = height; } area() { return this.width * this.height; } } class Circle extends Shape { constructor(radius) { super(); this.radius = radius; } area() { return Math.PI * this.radius * this.radius; } } class ShapeProcessor { calculateArea(shape) { return shape.area(); } } const rectangle = new Rectangle(20, 10); const circle = new Circle(2); const shapeProcessor = new ShapeProcessor(); console.log(shapeProcessor.calculateArea(rectangle)); console.log(shapeProcessor.calculateArea(circle));
This class only implements the PrinterInterface. It doesn't implement scan or fax method. By following the interface segregation principle, the client — in this case, the Printer class — has reduced its complexity and improved the performance of a software.
Dependency Inversion Principle (DIP)
Now for our last principle: the dependency inversion principle. This principle says that higher-level modules (business logic) should rely on abstraction rather than relying directly on lower-level modules (concretion). It helps us to reduce code dependencies and offers developers the flexibility to modify and expand applications at higher levels without encountering complications.
Why does the dependency inversion principle favor abstraction over direct dependencies? That’s because the introduction of abstractions reduces the potential impacts of changes, improves testability (mocking abstractions instead of concrete implementations), and achieves a higher degree of flexibility in your code. This rule makes it easier to extend software components through a modular approach and also helps us to modify low-level components without affecting high-level logic.
Adhering to the DIP makes code easier to maintain, extend, and scale, thereby stopping bugs that might occur because of changes in the code. It recommends that developers use loose coupling instead of tight coupling between classes. Generally, by embracing a mindset that prioritizes abstractions over direct dependencies, teams will gain the agility to adapt and add new functionalities or change old components without causing ripple disruptions. In JavaScript, we’re able to implement DIP using the dependency injection approach, like so:
class Person { constructor(name, age, height, country){ this.name = name this.age = age this.height = height this.country = country } getPersonCountry(){ console.log(this.country) } greetPerson(){ console.log("Hi " + this.name) } static calculateAge(dob) { const today = new Date(); const birthDate = new Date(dob); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff <p>In the basic example above, the Application class is the high-level module that depends on the database abstraction. We created two database classes: MySQLDatabase, and MongoDBDatabase. The databases are low-level modules, and their instances are injected into the Application runtime without modifying the Application itself.</p> <h2> Conclusion </h2> <p>The SOLID principle is a fundamental building block for scalable, maintainable, and robust software design. This set of principles helps developers write clean, modular, and adaptable code. </p> <p>The SOLID principle promotes cohesive functionality, extensibility without modification, object substitution, interface separation, and abstraction over concrete dependencies. Be sure to integrate the SOLID principles into your code to prevent bugs to reap all their benefits.</p> <hr> <h2> LogRocket: Debug JavaScript errors more easily by understanding the context </h2> <p>Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.</p> <p>LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.</p> <p><img src="/static/imghwm/default1.png" data-src="https://img.php.cn/upload/article/000/000/000/173538566094173.jpg?x-oss-process=image/resize,p_40" class="lazy" alt="SOLID principles for JavaScript"></p> <p>LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!</p> <p>Try it for free.</p>
The above is the detailed content of SOLID principles for JavaScript. For more information, please follow other related articles on the PHP Chinese website!

The main difference between Python and JavaScript is the type system and application scenarios. 1. Python uses dynamic types, suitable for scientific computing and data analysis. 2. JavaScript adopts weak types and is widely used in front-end and full-stack development. The two have their own advantages in asynchronous programming and performance optimization, and should be decided according to project requirements when choosing.

Whether to choose Python or JavaScript depends on the project type: 1) Choose Python for data science and automation tasks; 2) Choose JavaScript for front-end and full-stack development. Python is favored for its powerful library in data processing and automation, while JavaScript is indispensable for its advantages in web interaction and full-stack development.

Python and JavaScript each have their own advantages, and the choice depends on project needs and personal preferences. 1. Python is easy to learn, with concise syntax, suitable for data science and back-end development, but has a slow execution speed. 2. JavaScript is everywhere in front-end development and has strong asynchronous programming capabilities. Node.js makes it suitable for full-stack development, but the syntax may be complex and error-prone.

JavaScriptisnotbuiltonCorC ;it'saninterpretedlanguagethatrunsonenginesoftenwritteninC .1)JavaScriptwasdesignedasalightweight,interpretedlanguageforwebbrowsers.2)EnginesevolvedfromsimpleinterpreterstoJITcompilers,typicallyinC ,improvingperformance.

JavaScript can be used for front-end and back-end development. The front-end enhances the user experience through DOM operations, and the back-end handles server tasks through Node.js. 1. Front-end example: Change the content of the web page text. 2. Backend example: Create a Node.js server.

Choosing Python or JavaScript should be based on career development, learning curve and ecosystem: 1) Career development: Python is suitable for data science and back-end development, while JavaScript is suitable for front-end and full-stack development. 2) Learning curve: Python syntax is concise and suitable for beginners; JavaScript syntax is flexible. 3) Ecosystem: Python has rich scientific computing libraries, and JavaScript has a powerful front-end framework.

The power of the JavaScript framework lies in simplifying development, improving user experience and application performance. When choosing a framework, consider: 1. Project size and complexity, 2. Team experience, 3. Ecosystem and community support.

Introduction I know you may find it strange, what exactly does JavaScript, C and browser have to do? They seem to be unrelated, but in fact, they play a very important role in modern web development. Today we will discuss the close connection between these three. Through this article, you will learn how JavaScript runs in the browser, the role of C in the browser engine, and how they work together to drive rendering and interaction of web pages. We all know the relationship between JavaScript and browser. JavaScript is the core language of front-end development. It runs directly in the browser, making web pages vivid and interesting. Have you ever wondered why JavaScr


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),

Atom editor mac version download
The most popular open source editor

Dreamweaver Mac version
Visual web development tools

SublimeText3 Linux new version
SublimeText3 Linux latest version

EditPlus Chinese cracked version
Small size, syntax highlighting, does not support code prompt function
