Open-closed principle

The open-closed principle declares that you should be able to extend a class' behavior, without modifying it. This principle is raised by Bertrand Meyer in 1988:

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

A program depends on all the entities it uses, that means changing the already-being-used part of those entities may just crash the entire program. So the idea of the open-closed principle is straightforward: we'd better have entities that never change in any way other than extending itself.

That means once a test is written and passing, ideally, it should never be changed for newly added features (and it needs to keep passing, of course). Again, ideally.

Example

Consider an API hub that handles HTTP requests to and responses from the server. We are going to have several files written as modules, including http-client.ts, hub.ts and app.ts (but we won't actually write http-client.ts in this example, you will need to use some imagination).

Save the code below as file hub.ts.

import { HttpClient, HttpResponse } from './http-client'; 
 
export function update(): Promise<HttpResponse> { 
  let client = new HttpClient(); 
   
  return client.get('/api/update'); 
} 

And save the code below as file app.ts.

import Hub from './hub'; 
 
Hub 
  .update() 
  .then(response => JSON.stringify(response.text)) 
  .then(result => { 
    console.log(result); 
}); 

Bravely done! Now we have app.ts badly coupled with http-client.ts. And if we want to adapt this API hub to something like WebSocket, BANG.

So how can we create entities that are open for extension, but closed for modification? The key is a stable abstraction that adapts. Consider the storage and client example we took with Adapter Pattern in Chapter 4, Structural Design Patterns we had a Storage interface that isolates implementation of database operations from the client. And assuming that the interface is well-designed to meet upcoming feature requirements, it is possible that it will never change or just need to be extended during the life cycle of the program.

Abstraction in JavaScript and TypeScript

Guess what, our beloved JavaScript does not have an interface, and it is dynamically typed. We were not even able to actually write an interface. However, we could still write down documentation about the abstraction and create new concrete implementations just by obeying that description.

But TypeScript offers interface, and we can certainly take advantage of it. Consider the CommandResult class in the previous section. We were writing it as a concrete class, but it may have subclasses that override the print or render method for customized output. However, the type system in TypeScript cares only about the shape of a type. That means, while you are declaring an entity with type CommandResult, the entity does not need to be an instance of CommandResult: any object with a compatible type (namely has methods print and render with proper signatures in this case) will do the job.

For example, the following code is valid:

let environment: Environment; 
 
let command: Command = { 
  environment, 
  print(items) { }, 
  render(items) { }, 
  execute() { } 
}; 

Refactor earlier

I double stressed that the open-closed principle can only be perfectly followed under ideal scenarios. That can be a result of two reasons:

  1. Not all entities in a system can be open to extension and closed to modification at the same time. There will always be changes that need to break the closure of existing entities to complete their functionalities. When we are designing the interfaces, we need different strategies for creating stable closures for different foreseeable situations. But this requires notable experience and no one can do it perfectly.
  2. None of us is too good at designing a program that lasts long and stays healthy forever. Even with thorough consideration, abstractions designed at the beginning can be choppy facing the changing requirements.

So when we are expecting the entities to be closed for modification, it does not mean that we should just stand there and watch it being closed. Instead, when things are still under control, we should refactor and keep the abstraction in the status of being open to extension and closed to modification at the time point of refactoring.

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

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