Writing in the front

This is the fourth article in the Refactoring, Tasteful Code series, which introduces you to what common wrapping techniques mean for refactoring and how they can improve your code. As you know, an important criterion for breaking up modules is to hide the internal details from the outside world, and data structures are the core secrets that cannot be exposed to the outside world, which can be handled using encapsulated records or encapsulated collections. There are more encapsulation techniques, which I’ll cover in this article…

Previously on:

  • Refactoring, Tasteful Code 01 — On the Road to Refactoring
  • Refactoring, Tasteful Code 02 — Building test Systems
  • Refactoring, Tasteful Code 03 — Common Refactoring Techniques

What is encapsulation

When we see the word encapsulation, we often ask what encapsulation is, are functions and classes encapsulation? The answer is yes, classes and functions are encapsulation. In JavaScript Advanced Programming (4th edition), page 80 says:

Functions are a core component of any language because they can encapsulate statements and execute them anywhere, at any time.

The simple way is to run the code is by putting bits of statement code in curly braces around the function, passing in arguments as the function body, and finally calling the function name.

Here’s an example:

let curDiv = document.getElementsByIdTagName("div") [0];
let p = document.create("p");
body.style.background = "green";
p.innerText = "New content added";
curDiv.appendChild(p);
Copy the code

As we can see, this code wastes resources every time it is executed and is overwritten by variables of the same name. To do this, we consider function encapsulation, which can be called at any time. As follows:

function createTag(newColor,newText){
  let curDiv = document.getElementsByIdTagName("div") [0];
  let p = document.create("p");
  body.style.background = newColor;
  p.innerText = newText;
  curDiv.appendChild(p);
}
createTag("green"."New content added")
Copy the code

After function encapsulation, the code is more reusable, which can achieve the purpose of on-demand execution and avoid global variable pollution.

Commonly used encapsulation techniques

In normal development, we often use encapsulation in code, but do not know what encapsulation is done, now we will commonly used encapsulation methods summarized as follows:

  • Encapsulation record
  • Encapsulation collection
  • Replace primitive types with objects
  • Replace temporary variables with queries
  • Derived classes
  • Inline class
  • Hiding the Delegate Relationship
  • Remove the middleman
  • Replacement algorithm

1. Encapsulate records

A common feature of most programming languages is the recordable structure, which visually organizes related data so that it can be passed as meaningful units. The drawback of a simple record structure is the need to clearly distinguish between “data stored in a record” and “data computed.”

There are two types of recording structures:

  • A valid field name needs to be declared
  • Use any field name you like. It is usually implemented by the library itself and provided in the form of classes, so these classes are also called hashes, maps, hash maps, dictionaries, associative arrays, and so on.

Typically, variables holding records are wrapped in variables * and wrapped in functions. The create class then wraps the record, replacing the value of the record variable with an instance of the class, and defining the access function GET on the class to return the original record. A new function returns an object of that class instead of the original data record. For each point of use of the record, the function call that originally returns the record can be replaced with a function call that returns the instance object.

{
  id:2021219001,
  name:"wenbo",
  height:"172cm",
  weight:"65kg"
}
Copy the code

For the above JSON data processing, you can simply encapsulate the function.

class Person{
  constructor(data){
    this._name = data.name;
    this._height = data.height;
    this._weight = data.weight;
  }
  set name(arg) {this._name = arg.name;
  }
  get name() {return this._name;
  }
  set height(arg) {this._height = arg.height;
  }
  get height() {return this._height;
  }
  set weight(arg) {this._weight = arg.weight;
  }
  get weight() {return this._weight; }}Copy the code

2. Encapsulate collections

In general, it’s a common mistake to encapsulate collections: encapsulate only access to collection variables, but still let the value function return the collection itself, which makes the members of the collection vulnerable to direct modification without the intervention of the enclosing class.

To avoid this, you can provide methods on a class to modify the collection, either by adding or deleting methods, so that changes to the collection are made through the class.

Original code:

class Person{
  get courses() {return this._courses;
  }
  set courses(aList) {this._courses = aList; }}Copy the code

Refactoring code:

class Person{
  get courses() {return this._courses.slice();
  }
  addCourse(aCourse){}
  removeCourse(aCourse){}}Copy the code

3. Replace primitive types with objects

In the early stages of development, simple data items are often used to represent simple situations, but later in the business, data items become large and difficult to manage. At this point, you can create a new class to wrap the simple data type and then the business logic.

In particular, unwrapped variables are wrapped first and a simple class is created for the data value. Class constructors should hold these data values and provide corresponding value functions and set functions for them.

4. Replace temporary variables with queries

One purpose of a temporary variable is to hold the return value of a piece of code so that it can be used later in the function. Temporary variables can allow references to previous values, which can avoid double calculation of code, and need to be further extracted into functions for reuse.

Of course, replacing temporary variables with queries only works for certain types of temporary variables (variables that are evaluated only once and are not modified later). However, if a variable is assigned multiple times in complex code, and therefore evaluated multiple times, it should be distilled into a query function.

Specifically, the variable is checked to see that it is fully evaluated before use, and that the piece of code that evaluates it gets the same value every time. If a variable is not currently read-only but can be changed to read-only, you need to modify the variable first. Extract the code segment that assigns values to variables into functions, and then use inline variables to remove temporary variables.

Original code:

class Oreder{
  constructor(quantity, item){
    this._quantity = quantity;
    this._item = item;
  }
  
  get price() {let basePrice = this._quantity * this._item.price;
    let discountFactor = 0.98;
    if(basePrice > 1000) discountFactor; - =0.03;
    returnbasePrice * discountFactor; }}Copy the code

Refactoring code:

class Oreder{
  constructor(quantity, item){
    this._quantity = quantity;
    this._item = item;
  }
  
  get basePrice() {return this._quantity * this._item.price;
  }
  
  get discountFactor() {let discountFactor = 0.98;
    if(basePrice > 1000) discountFactor -= 0.03;
    return discountFactor;
  }
  
  get price() {return tthis.basePrice * this.discountFactor; }}Copy the code

5. Derived classes

There is a convention in development commonly known as the rule that a class should have a clear abstraction, broken down into separate classes based on functional requirements. When some data and some functions are always present together, and some data often change together or even depend on each other, this indicates that the code should be subclassed.

Generally, when extracting classes, it is necessary to first clarify the code logic, separate responsibilities, and extract them into subclasses according to the business functions handled by the code.

Original code:

class Person{
  get officeAreaCode() {return this._officeAreaCode;
  }
  get officeNumber() {return this._officeNumber; }}Copy the code

Refactoring code:

class Person{
  get officeAreaCode() {return this._phoneNumber.areaCode();
  }
  get officeNumber() {return this._phoneNumber.number(); }}class PhoneNumber{
  get areaCode() {return this._areaCode;
  }
  get number() {return this._number; }}Copy the code

6. Inline classes

An inline class is a reverse refactoring of an extracted class. If a class is small and has no reason to exist on its own, it can be used as part of another important class (subclass).

In general, all newly created functions delegate directly to the in-line class for functions that create the in-line class. In addition, all reference points of public methods in the class to be inlined are modified to call corresponding delegate methods of the class to be inlined. All functions and data in the class to be inlined are defined in the inline class.

Original code:

class Person{
  get officeAreaCode() {return this._phoneNumber.areaCode();
  }
  get officeNumber() {return this._phoneNumber.number(); }}class PhoneNumber{
  get areaCode() {return this._areaCode;
  }
  get number() {return this._number; }}Copy the code

Refactoring code:

class Person{
  get officeAreaCode() {return this._officeAreaCode;
  }
  get officeNumber() {return this._officeNumber; }}Copy the code

7. Hide the delegate relationship

Encapsulation is one of the key features of good modular design, reducing the relationship between each module and the rest of the system.

Just as in some clients it is possible to get another delegate object from the fields of the service object first, the delegate relationship between them must be known by calling the functions of the delegate class to prevent the delegate class from modifying the interface and making it unusable.

// The original code
manager = aPerson.department.manager;

// Refactor code
manager = aPerson.manager;

class Person{
  get manager() {return this.department.manager
  }
}
Copy the code

8. Remove the middleman

The price of encapsulating a delegate in a hidden delegate relationship is that you have to add a simple delegate function to the server every time the client wants to use a new feature of the delegate class. However, as the delegate class becomes more and more functional, the forwarding kernel function also increases, which makes the human brain pain, so the service class becomes the worker, so why not call the delegate class directly.

Typically, the value function GET is created for the delegate class, and for each delegate function, its client is converted into successive access-function calls. It’s a reverse refactoring that hides the delegate relationship.

// The original code
manager = aPerson.manager;

class Person{
  get manager() {return this.department.manager
  }
}

// Refactor code
manager = aPerson.department.manager;
Copy the code

9. Replacement algorithms

During algorithm replacement, it is necessary to have a deep understanding of the large original function and divide it into small functions for processing, so as to ensure that the algorithm to be replaced is extracted into independent functions. In this way, it is only necessary to test the behavior of this part of the algorithm.

For each function in the delegate relationship, a simple delegate relationship is established on the service object side and the client is tuned to call only the functions provided by the service object. If no future client needs to use a delegate (delegate class), you can remove the accessor function from the service object.

Original code:

function foundPerson(people){
  for(let i = 0; j < people.length; i++){
    if(people[i] === "wenbo") {return "wenbo";
    }
    if(people[i] === "shunshun") {return "shunshun";
    }
    if(people[i] === "xiaofan") {return "xiaofan"; }}return "";
}
Copy the code

Refactoring code:

function foundPeron(people){
  const students = ["wenbo"."shunshun"."xiaofan"];
  return people.find(p= >students.includes(p) || "");
}
Copy the code

summary

This article introduces the definition of encapsulation and nine common encapsulation methods that can help make your code more tasteful, cleaner, and less smelly.

Refer to the article

Refactoring — Improving the Design of Existing Code (2nd edition)

Write in the last

Thank you for reading, I will continue to share with you more excellent articles, this article reference a large number of books and articles, if there are mistakes and mistakes, hope to correct.

More latest articles please pay attention to the author nuggets a sichuan firefly account and the public number front gravitation.