- Refactoring TypeScript
- James Hickey
- 521字
- 2021-06-11 13:05:51
Null Object Pattern
Empty Collections
Imagine you work for a company that writes software for dealing with legal cases.
As you are working on a feature, you discover some code:
const legalCases: LegalCase[] = await fetchCasesFromAPI();
for (const legalCase of legalCases) {
if(legalCase.documents != null) {
uploadDocuments(legalCase.documents);
}
}
Remember that we should be wary of null checks? What if some other part of the code forgot to check for a null array?
The Null Object pattern can help: you create an object that represents an empty or null object.
Fixing It Up
Let's look at the fetchCasesFromAPI() method. We'll apply a version of this pattern that's a very common practice in JavaScript and TypeScript when dealing with arrays:
const fetchCasesFromAPI = async function() {
const legalCases: LegalCase[] = await $http.get('legal-cases/');
for (const legalCase of legalCases) {
// Null Object Pattern
legalCase.documents = legalCase.documents || [];
}
return legalCases;
}
Instead of leaving empty arrays/collections as null, we are assigning them an actual empty array.
Now, no one else will need to make a null check!
But what about the entire legal case collection itself? What if the API returns null?
const fetchCasesFromAPI = async function() {
const legalCasesFromAPI: LegalCase[] = await $http.get('legal-cases/');
// Null Object Pattern
const legalCases = legalCasesFromAPI || [];
for (const legalCase of legalCases) {
// Null Object Pattern
legalCase.documents = legalCase.documents || [];
}
return legalCases;
}
Cool!
Now, we've made sure that everyone who uses this method doesn't need to be worried about checking for nulls.
Take 2
Other languages, such as C#, Java, and so on, won't allow you to assign a mere empty array to a collection due to rules about strong typing.
In those cases, you can use something like this version of the Null Object pattern:
class EmptyArray<T> {
static create<T>() {
return new Array<T>()
}
}
// Use it like this:
const myEmptyArray: string[] = EmptyArray.create<string>();
What About Objects?
Imagine that you are working on a video game. In it, some levels might have a boss.
When checking whether the current level has a boss, you might see something like this:
if(currentLevel.boss != null) {
currentLevel.boss.fight(player);
}
We might find other places that do this null check:
if(currentLevel.boss != null) {
currentLevel.completed = currentLevel.boss.isDead();
}
If we introduce a null object, then we can simply remove all these null checks.
First, we need an interface to represent our Boss:
interface IBoss {
fight(player: Player);
isDead();
}
Then, we can create our concrete boss class:
class Boss implements IBoss {
fight(player: Player) {
// Do some logic and return a bool.
}
isDead() {
// Return whether boss is dead depending on how the fight went.
}
}
Next, we'll create an implementation of the IBoss interface that represents a null Boss:
class NullBoss implements IBoss {
fight(player: Player) {
// Player always wins.
}
isDead() {
return true;
}
}
NullBoss will automatically allow the player to win, and we can remove all our null checks!
In the following code example, if the boss is an instance of NullBoss or Boss, no extra checks need to be made:
currentLevel.boss.fight(player);
currentLevel.boss.isDead();
- Beginning Java Data Structures and Algorithms
- 青少年美育趣味課堂:XMind思維導(dǎo)圖制作
- C語(yǔ)言最佳實(shí)踐
- 征服RIA
- Mastering JavaScript Design Patterns(Second Edition)
- 深入分布式緩存:從原理到實(shí)踐
- Java程序設(shè)計(jì)案例教程
- Spring Boot+Vue全棧開(kāi)發(fā)實(shí)戰(zhàn)
- 虛擬現(xiàn)實(shí)建模與編程(SketchUp+OSG開(kāi)發(fā)技術(shù))
- Three.js Essentials
- Java程序設(shè)計(jì)(項(xiàng)目教學(xué)版)
- Microsoft Hyper-V PowerShell Automation
- Learning ArcGIS Geodatabases
- 編寫高質(zhì)量代碼:改善JavaScript程序的188個(gè)建議
- 深入理解TypeScript