Интерфейс описывает контракт — структуру объекта или класса:
interface User {
id: number
name: string
email: string
age?: number // опциональное поле
readonly createdAt: string // только чтение
}
const user: User = {
id: 1,
name: 'Алексей',
email: 'alex@mail.ru',
createdAt: '2024-01-01',
}Интерфейс может расширять другой, добавляя новые поля:
interface Animal {
name: string
age: number
}
interface Dog extends Animal {
breed: string
bark(): void
}
interface GuideDog extends Dog {
owner: string
isWorking: boolean
}
const buddy: GuideDog = {
name: 'Buddy',
age: 3,
breed: 'Labrador',
bark() { console.log('Woof!') },
owner: 'Алексей',
isWorking: true,
}Класс может реализовывать интерфейс — обязан иметь все его методы и свойства:
interface Printable {
print(): void
toString(): string
}
class Invoice implements Printable {
constructor(private amount: number, private client: string) {}
print() {
console.log(`Invoice for ${this.client}: ${this.amount}₽`)
}
toString() {
return `Invoice(${this.client}, ${this.amount})`
}
}Позволяют описать объект с динамическими ключами:
interface StringMap {
[key: string]: string // любой строковый ключ → строковое значение
}
interface NumberMap {
[key: string]: number
}
const translations: StringMap = {
hello: 'привет',
world: 'мир',
// можно добавлять любые ключи
}| Возможность | interface | type |
|---|---|---|
| Объектный тип | ✓ | ✓ |
| extends / implements | ✓ | с & (intersection) |
| Declaration merging | ✓ | ✗ |
| Union типы | ✗ | ✓ |
| Mapped types | ✗ | ✓ |
Declaration merging — можно дополнить интерфейс в другом месте кода (используется в библиотеках):
// В одном файле:
interface Request {
url: string
}
// В другом файле (расширяем):
interface Request {
user?: User // добавляем поле
}
// Итоговый Request = { url: string; user?: User }Правило выбора: interface для объектов которые могут наследоваться или реализовываться классами; type для union, intersection, примитивных псевдонимов и сложных mapped types.
Паттерн Repository — InMemoryUserRepo и InMemoryProductRepo реализуют единый "интерфейс" с методами getById, getAll, save, delete
// TypeScript позволяет явно объявить interface Repository<T>.
// В JavaScript симулируем контракт через документацию и runtime-проверки.
//
// interface Repository<T> {
// getById(id: number): T | undefined
// getAll(): T[]
// save(item: T): T
// delete(id: number): boolean
// }
class InMemoryUserRepo {
#store = new Map()
#nextId = 1
save(user) {
if (!user || typeof user.name !== 'string') {
throw new TypeError('User должен иметь поле name: string')
}
if (user.id) {
// update
this.#store.set(user.id, { ...user })
return this.#store.get(user.id)
}
// create
const newUser = { ...user, id: this.#nextId++ }
this.#store.set(newUser.id, newUser)
return newUser
}
getById(id) {
return this.#store.get(id)
}
getAll() {
return Array.from(this.#store.values())
}
delete(id) {
return this.#store.delete(id)
}
// Дополнительный метод (сверх интерфейса)
findByEmail(email) {
return this.getAll().find(u => u.email === email)
}
}
class InMemoryProductRepo {
#store = new Map()
#nextId = 1
save(product) {
if (!product || typeof product.name !== 'string' || typeof product.price !== 'number') {
throw new TypeError('Product должен иметь name: string и price: number')
}
if (product.id) {
this.#store.set(product.id, { ...product })
return this.#store.get(product.id)
}
const newProduct = { ...product, id: this.#nextId++ }
this.#store.set(newProduct.id, newProduct)
return newProduct
}
getById(id) {
return this.#store.get(id)
}
getAll() {
return Array.from(this.#store.values())
}
delete(id) {
return this.#store.delete(id)
}
// Дополнительный метод
findByMaxPrice(maxPrice) {
return this.getAll().filter(p => p.price <= maxPrice)
}
}
// Функция работающая с ЛЮБЫМ репозиторием через общий "интерфейс"
// В TypeScript: function printAll<T>(repo: Repository<T>): void
function printAll(repo, label) {
const items = repo.getAll()
console.log(`${label} (${items.length} шт.):`)
items.forEach(item => console.log(' ', JSON.stringify(item)))
}
// --- Демонстрация ---
const users = new InMemoryUserRepo()
const products = new InMemoryProductRepo()
console.log('=== Users Repository ===')
const alice = users.save({ name: 'Алиса', email: 'alice@mail.ru' })
const bob = users.save({ name: 'Боб', email: 'bob@mail.ru' })
console.log('Создано:', alice, bob)
users.save({ ...alice, name: 'Алиса Обновлённая' })
printAll(users, 'Пользователи')
console.log('findByEmail:', users.findByEmail('bob@mail.ru'))
users.delete(bob.id)
printAll(users, 'После удаления Боба')
console.log('\n=== Products Repository ===')
products.save({ name: 'Ноутбук', price: 75000 })
products.save({ name: 'Мышь', price: 1500 })
products.save({ name: 'Клавиатура', price: 3000 })
printAll(products, 'Продукты')
console.log('До 5000₽:', products.findByMaxPrice(5000))Интерфейс описывает контракт — структуру объекта или класса:
interface User {
id: number
name: string
email: string
age?: number // опциональное поле
readonly createdAt: string // только чтение
}
const user: User = {
id: 1,
name: 'Алексей',
email: 'alex@mail.ru',
createdAt: '2024-01-01',
}Интерфейс может расширять другой, добавляя новые поля:
interface Animal {
name: string
age: number
}
interface Dog extends Animal {
breed: string
bark(): void
}
interface GuideDog extends Dog {
owner: string
isWorking: boolean
}
const buddy: GuideDog = {
name: 'Buddy',
age: 3,
breed: 'Labrador',
bark() { console.log('Woof!') },
owner: 'Алексей',
isWorking: true,
}Класс может реализовывать интерфейс — обязан иметь все его методы и свойства:
interface Printable {
print(): void
toString(): string
}
class Invoice implements Printable {
constructor(private amount: number, private client: string) {}
print() {
console.log(`Invoice for ${this.client}: ${this.amount}₽`)
}
toString() {
return `Invoice(${this.client}, ${this.amount})`
}
}Позволяют описать объект с динамическими ключами:
interface StringMap {
[key: string]: string // любой строковый ключ → строковое значение
}
interface NumberMap {
[key: string]: number
}
const translations: StringMap = {
hello: 'привет',
world: 'мир',
// можно добавлять любые ключи
}| Возможность | interface | type |
|---|---|---|
| Объектный тип | ✓ | ✓ |
| extends / implements | ✓ | с & (intersection) |
| Declaration merging | ✓ | ✗ |
| Union типы | ✗ | ✓ |
| Mapped types | ✗ | ✓ |
Declaration merging — можно дополнить интерфейс в другом месте кода (используется в библиотеках):
// В одном файле:
interface Request {
url: string
}
// В другом файле (расширяем):
interface Request {
user?: User // добавляем поле
}
// Итоговый Request = { url: string; user?: User }Правило выбора: interface для объектов которые могут наследоваться или реализовываться классами; type для union, intersection, примитивных псевдонимов и сложных mapped types.
Паттерн Repository — InMemoryUserRepo и InMemoryProductRepo реализуют единый "интерфейс" с методами getById, getAll, save, delete
// TypeScript позволяет явно объявить interface Repository<T>.
// В JavaScript симулируем контракт через документацию и runtime-проверки.
//
// interface Repository<T> {
// getById(id: number): T | undefined
// getAll(): T[]
// save(item: T): T
// delete(id: number): boolean
// }
class InMemoryUserRepo {
#store = new Map()
#nextId = 1
save(user) {
if (!user || typeof user.name !== 'string') {
throw new TypeError('User должен иметь поле name: string')
}
if (user.id) {
// update
this.#store.set(user.id, { ...user })
return this.#store.get(user.id)
}
// create
const newUser = { ...user, id: this.#nextId++ }
this.#store.set(newUser.id, newUser)
return newUser
}
getById(id) {
return this.#store.get(id)
}
getAll() {
return Array.from(this.#store.values())
}
delete(id) {
return this.#store.delete(id)
}
// Дополнительный метод (сверх интерфейса)
findByEmail(email) {
return this.getAll().find(u => u.email === email)
}
}
class InMemoryProductRepo {
#store = new Map()
#nextId = 1
save(product) {
if (!product || typeof product.name !== 'string' || typeof product.price !== 'number') {
throw new TypeError('Product должен иметь name: string и price: number')
}
if (product.id) {
this.#store.set(product.id, { ...product })
return this.#store.get(product.id)
}
const newProduct = { ...product, id: this.#nextId++ }
this.#store.set(newProduct.id, newProduct)
return newProduct
}
getById(id) {
return this.#store.get(id)
}
getAll() {
return Array.from(this.#store.values())
}
delete(id) {
return this.#store.delete(id)
}
// Дополнительный метод
findByMaxPrice(maxPrice) {
return this.getAll().filter(p => p.price <= maxPrice)
}
}
// Функция работающая с ЛЮБЫМ репозиторием через общий "интерфейс"
// В TypeScript: function printAll<T>(repo: Repository<T>): void
function printAll(repo, label) {
const items = repo.getAll()
console.log(`${label} (${items.length} шт.):`)
items.forEach(item => console.log(' ', JSON.stringify(item)))
}
// --- Демонстрация ---
const users = new InMemoryUserRepo()
const products = new InMemoryProductRepo()
console.log('=== Users Repository ===')
const alice = users.save({ name: 'Алиса', email: 'alice@mail.ru' })
const bob = users.save({ name: 'Боб', email: 'bob@mail.ru' })
console.log('Создано:', alice, bob)
users.save({ ...alice, name: 'Алиса Обновлённая' })
printAll(users, 'Пользователи')
console.log('findByEmail:', users.findByEmail('bob@mail.ru'))
users.delete(bob.id)
printAll(users, 'После удаления Боба')
console.log('\n=== Products Repository ===')
products.save({ name: 'Ноутбук', price: 75000 })
products.save({ name: 'Мышь', price: 1500 })
products.save({ name: 'Клавиатура', price: 3000 })
printAll(products, 'Продукты')
console.log('До 5000₽:', products.findByMaxPrice(5000))Реализуй интерфейс `Serializable` с методами `serialize(): string` и статическим `deserialize(json: string)`. Напиши класс `Config` реализующий этот интерфейс. Класс хранит настройки (Record<string, unknown>) и поддерживает методы `get(key: string): unknown` и `set(key: string, value: unknown): this`.
Объяви `interface Serializable` с методом `serialize(): string`. В `Config implements Serializable` объяви `private data: ConfigData = {}`. В `serialize()` верни `JSON.stringify(this.data)`. В `static deserialize` используй `JSON.parse(json) as ConfigData`.