Адаптер
Суть паттерна "Адаптер" и его реализация на TypeScript
Адаптер — паттерн, который позволяет однообразно использовать различные объекты, предоставляющие данные.
Зачем это нужно?
- Есть уже работающее приложение, сервисы которого работают с одним источником данных и необходимо поддержать новый источник с другим интерфейсом данных.
- На начальном этапе разработки известно какие данные будут приходить, но неизвестно в каком формате, а начинать работу нужно уже сейчас.
Задача. Происходит объединение с другой компанией и необходимо интегрировать в существующие сервисы вашего портала работников этой компании. Мигрировать данные сотрудников поглащаемой компании в вашу базу данных не представляется возможным.
Проблема. Ваши сервисы умеют работать с одним форматом данных. Но у новых сотрудников он другой. Что делать?
Код
Общим для приложения является класс работника Employee, который реализует интерфейс EmployeeClass и предоставляет данные в определенном формате.
type Group = 'admin' | 'personnel'
type Status = 'active' | 'suspended' | 'inactive'
interface EmployeeData {
accountStatus: Status
groups: Group[]
departmentId: number
}
interface EmployeeClass {
groups: Group[]
department: number
status: Status
}
class Employee implements EmployeeClass {
private data: EmployeeData
constructor(data: EmployeeData) {
this.data = data
}
get groups() {
return this.data.groups
}
get department() {
return this.data.departmentId
}
get status() {
return this.data.accountStatus
}
}
К примеру, вот так испольует класс работника сервис отпусков, чтобы проверить, может ли текущий сотрудник редактировать записи с отпусками в отделе:
class VacationsService {
private departmentId: number
constructor(id: number) {
this.departmentId = id
}
// .. какие-то другие методы
public isUserCanEditDepartmentVacations(user: EmployeeClass) {
if (user.status !== 'active') return false
if (user.groups.includes('admin')) {
return true
}
return (
user.department === this.departmentId &&
user.groups.includes('personnel')
)
}
}
В реальном приложении подобных сервисов, заточенных под интерфейс EmployeeClass, может быть очень много.
Новый источник данных
При объединении компаний появляется еще один источник данных, с которыми, в свою очередь, работают сервисы, доставшиеся в наследство.
interface AnotherEmployeeData {
isActive: boolean
isAdmin: boolean
isPersonnel: boolean
departmentId: number
}
class AnotherEmployee {
private data: AnotherEmployeeData
constructor(data: AnotherEmployeeData) {
this.data = data
}
get isAdmin() {
return this.data.isAdmin
}
get isPersonnel() {
return this.data.isPersonnel
}
get isActive() {
return this.data.isActive
}
get department() {
return this.data.departmentId
}
}
Пишем Адаптер
Чтобы использовать данные из класса AnotherEmployee в существующих сервисах, напишем адаптер. Он будет принимать в конструктор экземпляр класса AnotherEmployee и используя его "под капотом", реализовывать привычный интерфейс EmployeeClass.
class AnotherEmployeeAdapter implements EmployeeClass {
private anotherSourceEmployee: AnotherEmployee
constructor(anotherSourceEmployee: AnotherEmployee) {
this.anotherSourceEmployee = anotherSourceEmployee
}
get groups() {
const groups: Group[] = []
if (this.anotherSourceEmployee.isAdmin) {
groups.push('admin')
}
if (this.anotherSourceEmployee.isPersonnel) {
groups.push('personnel')
}
return groups
}
get status() {
return this.anotherSourceEmployee.isActive ? 'active' : 'inactive'
}
get department() {
return this.anotherSourceEmployee.department
}
}
Теперь мы можем использовать данные из нового источника, не меняя существующих методов у существующих сервисов.
const baseUser = new Employee({
groups: ['admin'],
departmentId: 42,
accountStatus: 'active',
})
const anotherUser = new AnotherEmployee({
isActive: true,
isAdmin: false,
isPersonnel: true,
departmentId: 44,
})
const vacationsService = new VacationsService(50);
// используем привычный источник данных
const basicUserPermissionResult = vacationsService.isUserCanEditDepartmentVacations(baseUser)
// и так же используем новый источник данных с адаптером
const anotherUserAdapter = new AnotherEmployeeAdapter(anotherUser)
const anotherUserPermissionResult = vacationsService.isUserCanEditDepartmentVacations(anotherUserAdapter)
Комментарии