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

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

主站蜘蛛池模板: 惠东县| 胶州市| 三江| 巴林右旗| 阿克苏市| 韶关市| 饶阳县| 湟源县| 庆云县| 佛坪县| 合肥市| 武鸣县| 宜兰县| 文成县| 泸溪县| 府谷县| 虞城县| 荣昌县| 贵德县| 运城市| 黑山县| 嘉善县| 邢台县| 鄂伦春自治旗| 威信县| 额敏县| 时尚| 翁牛特旗| 鄱阳县| 松溪县| 松溪县| 罗平县| 拉孜县| 肇东市| 霍林郭勒市| 肇州县| 金平| 黑水县| 龙南县| 高雄县| 河南省|