Написал на Ангуляре первое приложение. Пока разобрался с RxJS и не наловил ошибок, приложение писал так, чтобы оно работало прямо и исправно. И мне помог этот паттерн. Прошу под кат.
Паттерн предполагает замену работы с null/undefined и изибавиться от последующей обработки таковых. https://www.carloscaballero.io/design-patterns-null-object/
Пример: аутентификация пользователя
Сразу к примеру. Получаем при логине пользователя, сохраняем в локал-сторадже, подписываемся на него в нужных компонентах.
// наша моделька
export class Passport {
constructor(
public accessToken: string,
public refreshToken: string,
public roles: string[]
) {
}
}
// Аутентифкатор
@Injectable({providedIn: 'root'})
export class Authenticator {
private passport$: Subject;
constructor(private http: HttpClient, private router: Router,) {
// загружаем юзера из localstorage
// делаем this.passport$.next(НАШ_ЮЗЕР)
}
public get passport(): Subject<Passport | undefined> {
return this.passport$;
}
Что тут не так: у нас возможно варианты, когда загрузка пользователя не прошла корректно: его еще нет, какая-то ошибка в приложении и прочее.
Это заставляет писать наш код так, что в подписчиках нужно делать фильтры и везде проставлять union-типа типы T | undefined
. А еще можно забыть и много всего интересного.
Это утомительно, чревато ошибками и куче бойлерпелейта.
Исправляем:
// модель
export class Passport {
constructor(
public accessToken: string,
public refreshToken: string,
public roles: string[]
) {
}
}
// Null Object, который будет у нас всегда,
// когда нет корректного пользователя и при разлогине
export class Anonymous extends Passport {
constructor() {
super('anon', 'anon', 'anon', []);
}
}
// Аутентификатор
@Injectable({providedIn: 'root'})
export class Authenticator {
private passport$ = new BehaviorSubject<Passport>(new Anonymous())
constructor(private http: HttpClient, private router: Router,) {
// загружаем юзера из localstorage
// если ошибка, в системе есть анонимный null object,
// который корректный тип сам по себе
}
public get passport(): BehaviorSubject<Passport> {
return this.passport$;
}
Теперь у нас всегда в аутентификаторе лежит корректный с точки зрения системы объект. Просто он без ролей, с битым некорректным токеном, но система работает прямо и корректно, можно писать более надежный и точно более постой код.
Теперь код всегда корректный, с ним мы можем писать спокойно наши интерцепторы, наши компоненты и делать HTTP-запросы на бекенд. Но тк значения не валиды, то бекенд отклонит токен, а отсутствие ролей не даст доступа к компонентам, закрытымич через Guards.
Код всегда корректен:
this.authenticator.passport
.pipe(
tap((passport: Passport) => {
// тут всегда корректный тип
})
)
Пользуйтесь :) Подход концептуальный и не является императивом.
UPD: тк статья приняла удар недовольства, то заменой этого ООП-решения может быть использование монады Maybe из ФП, а данный паттерн использовать в простых случаях с неветвистой логикой: https://functionalprogramming.medium.com/maybe-monad-in-typescript-7966d2be8623