官术网_书友最值得收藏!

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();

主站蜘蛛池模板: 福安市| 本溪| 松潘县| 老河口市| 五峰| 张家港市| 内丘县| 隆林| 丽江市| 长顺县| 靖安县| 乐都县| 连云港市| 新绛县| 斗六市| 无锡市| 宁化县| 涞源县| 清徐县| 台中县| 台北县| 乃东县| 花莲市| 神池县| 宁强县| 石泉县| 莱芜市| 蓝山县| 赞皇县| 文水县| 龙游县| 台州市| 资中县| 宾川县| 许昌市| 遂溪县| 鄄城县| 汤原县| 巴东县| 汪清县| 临清市|