Chapter 8. SOLID Principles

SOLID Principles are well-known Object-Oriented Design (OOD)principles summarized by Uncle Bob (Robert C. Martin). The word SOLID comes from the initials of the five principles it refers to, including Single responsibility principle, Open-closed principle, Liskov substitution principle, Interface segregation principle and Dependency inversion principle. Those principles are closely related to each other, and can be a great guidance in practice.

Here is a widely used summary of SOLID principles from Uncle Bob:

  • Single responsibility principle: A class should have one, and only one, reason to change
  • Open-closed principle: You should be able to extend a classes behavior, without modifying it
  • Liskov substitution principle: Derived classes must be substitutable for their base classes
  • Interface segregation principle: Make fine-grained interfaces that are client specific
  • Dependency inversion principle: Depend on abstractions, not on concretions

In this chapter, we will walk through them and find out how those principles can help form a design that smells nice.

But before we proceed, I want to mention that a few of the reasons why those principles exist might be related to the age in which they were raised, the languages and their building or distributing process people were working with, and even computing resources. When being applied to JavaScript and TypeScript projects nowadays, some of the details may not be necessary. Think more about what problems those principles want to prevent people from getting into, rather than the literal descriptions of how a principle should be followed.

Single responsibility principle

The single responsibility principle declares that a class should have one, and only one reason to change. And the definition of the world reason in this sentence is important.

Example

Consider a Command class that is designed to work with both command-line interface and graphical user interface:

class Command { 
  environment: Environment; 
 
  print(items: ListItem[]) { 
    let stdout = this.environment.stdout; 
   
    stdout.write('Items:
'); 
   
    for (let item of items) { 
      stdout.write(item.text + '
'); 
    } 
  } 
   
  render(items: ListItem[]) { 
    let element = <List items={items}></List>; 
    this.environment.render(element); 
  } 
   
  execute() { } 
} 

To make this actually work, execute method would need to handle both the command execution and result displaying:

class Command { 
  .. 
   
  execute() {
    
  let items = ...;

    if (this.environment.type === 'cli') {

      this.print(items);

    } else {

      this.render(items);

    }

  } 
} 

In this example, there are two reasons for changes:

  1. How a command gets executed.
  2. How the result of a command gets displayed in different environments.

Those reasons lead to changes in different dimensions and violate the single responsibility principle. This might result in a messy situation over time. A better solution is to have those two responsibilities separated and managed by the CommandEnvironment:

Example

Does this look familiar to you? Because it is a variant of the Visitor Pattern. Now it is the environment that executes a specific command and handles its result based on a concrete environment class.

Choosing an axis

You might be thinking, doesn't CommandResult violate the single responsibility principle by having the abilities to display content in a different environment? Yes, and no. When the axis of this reason is set to displaying content, it does not; but if the axis is set to displaying in a specific environment, it does. But take the overall structure into consideration, the result of a command is expected to be an output that can adapt to a different environment. And thus the reason is one-dimensional and confirms the principle.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.37.10