Абстрактный класс — это класс, от которого нельзя создать экземпляр напрямую. Он служит шаблоном, определяя общую структуру для своих подклассов.
abstract class Animal {
abstract makeSound(): string // абстрактный метод — без тела
// Обычный метод с реализацией — наследуется
describe(): string {
return `Я животное, издаю звук: ${this.makeSound()}`
}
}
// const a = new Animal() // Ошибка: Cannot create an instance of an abstract class
class Dog extends Animal {
makeSound(): string { // ОБЯЗАТЕЛЬНО реализовать
return 'Гав!'
}
}
class Cat extends Animal {
makeSound(): string {
return 'Мяу!'
}
}
const dog = new Dog()
console.log(dog.describe()) // 'Я животное, издаю звук: Гав!'Абстрактными могут быть не только методы, но и свойства:
abstract class Shape {
abstract readonly name: string // абстрактное свойство
abstract area(): number // абстрактный метод
abstract perimeter(): number
// Готовый метод — общая логика для всех фигур
toString(): string {
return `${this.name}: площадь=${this.area().toFixed(2)}`
}
}
class Circle extends Shape {
readonly name = 'Круг'
constructor(private radius: number) { super() }
area() { return Math.PI * this.radius ** 2 }
perimeter() { return 2 * Math.PI * this.radius }
}| Критерий | abstract class | interface |
|---|---|---|
| Реализация методов | Да | Нет (только сигнатуры) |
| Поля с состоянием | Да | Нет |
| Конструктор | Да | Нет |
| Наследование | extends (один) | implements (несколько) |
| Компилируется в JS | Да (остаётся классом) | Нет (стирается) |
| Модификаторы доступа | Да | Нет |
// Interface — только форма данных/контракт
interface Serializable {
serialize(): string
deserialize(data: string): this
}
// Abstract class — общая логика + обязательные точки расширения
abstract class BaseRepository<T> {
protected items: T[] = []
findAll(): T[] { return [...this.items] }
findById(id: number): T | undefined {
return this.items.find((item: any) => item.id === id)
}
// Подкласс обязан реализовать эти методы
abstract validate(item: T): boolean
abstract create(data: Partial<T>): T
}Используйте абстрактный класс, когда:
1. **Нужна общая логика** — часть методов одинакова для всех подклассов
2. **Есть состояние** — подклассы разделяют общие поля
3. **Шаблонный метод** — алгоритм зафиксирован, детали варьируются
abstract class DataProcessor {
// Шаблонный метод — алгоритм зафиксирован
process(data: string[]): string[] {
const filtered = this.filter(data) // шаг 1 — переопределяется
const mapped = this.transform(filtered) // шаг 2 — переопределяется
return this.sort(mapped) // шаг 3 — общая реализация
}
abstract filter(data: string[]): string[]
abstract transform(data: string[]): string[]
protected sort(data: string[]): string[] {
return [...data].sort()
}
}**Правило**: если вам нужно описать лишь форму объекта — используйте interface. Если нужна общая реализация с обязательными точками расширения — abstract class.
Шаблонный метод через абстрактный класс: иерархия логгеров с общим форматированием
// В TypeScript это было бы abstract class Logger
// В JS эмулируем: конструктор бросает ошибку если вызван напрямую
class Logger {
constructor(level = 'INFO') {
if (new.target === Logger) {
throw new Error('Logger — абстрактный класс')
}
this.level = level
}
// "Абстрактные" методы — подкласс обязан реализовать
formatMessage(message) {
throw new Error(`${this.constructor.name} не реализовал formatMessage()`)
}
writeOutput(text) {
throw new Error(`${this.constructor.name} не реализовал writeOutput()`)
}
// Шаблонный метод — алгоритм зафиксирован
log(message) {
const timestamp = new Date().toISOString().substring(11, 19)
const formatted = this.formatMessage(message)
const output = `[${timestamp}] [${this.level}] ${formatted}`
this.writeOutput(output)
return output
}
warn(message) {
const original = this.level
this.level = 'WARN'
const result = this.log(message)
this.level = original
return result
}
error(message) {
const original = this.level
this.level = 'ERROR'
const result = this.log(message)
this.level = original
return result
}
}
// ConsoleLogger — выводит в консоль с цветами (эмуляция через префиксы)
class ConsoleLogger extends Logger {
constructor() { super('INFO') }
formatMessage(message) {
return String(message)
}
writeOutput(text) {
console.log(text)
}
}
// PrefixLogger — добавляет префикс к каждому сообщению
class PrefixLogger extends Logger {
constructor(prefix) {
super('INFO')
this.prefix = prefix
}
formatMessage(message) {
return `[${this.prefix}] ${message}`
}
writeOutput(text) {
console.log(text)
}
}
// BufferLogger — накапливает сообщения в буфер
class BufferLogger extends Logger {
constructor() {
super('INFO')
this.buffer = []
}
formatMessage(message) {
return String(message)
}
writeOutput(text) {
this.buffer.push(text)
}
getBuffer() {
return [...this.buffer]
}
flush() {
const lines = this.buffer.join('\n')
this.buffer = []
return lines
}
}
// --- Демонстрация ---
console.log('=== ConsoleLogger ===')
const logger = new ConsoleLogger()
logger.log('Приложение запущено')
logger.warn('Память заканчивается')
logger.error('Соединение с БД потеряно')
console.log('\n=== PrefixLogger ===')
const dbLogger = new PrefixLogger('DB')
dbLogger.log('Подключение установлено')
dbLogger.error('Запрос завершился с ошибкой')
console.log('\n=== BufferLogger ===')
const buf = new BufferLogger()
buf.log('Событие 1')
buf.log('Событие 2')
buf.warn('Предупреждение')
console.log('Буфер накопил:', buf.getBuffer().length, 'сообщений')
console.log('Содержимое буфера:')
console.log(buf.flush())
console.log('\n=== Попытка создать Logger напрямую ===')
try {
new Logger()
} catch (e) {
console.log('Ошибка:', e.message)
}Абстрактный класс — это класс, от которого нельзя создать экземпляр напрямую. Он служит шаблоном, определяя общую структуру для своих подклассов.
abstract class Animal {
abstract makeSound(): string // абстрактный метод — без тела
// Обычный метод с реализацией — наследуется
describe(): string {
return `Я животное, издаю звук: ${this.makeSound()}`
}
}
// const a = new Animal() // Ошибка: Cannot create an instance of an abstract class
class Dog extends Animal {
makeSound(): string { // ОБЯЗАТЕЛЬНО реализовать
return 'Гав!'
}
}
class Cat extends Animal {
makeSound(): string {
return 'Мяу!'
}
}
const dog = new Dog()
console.log(dog.describe()) // 'Я животное, издаю звук: Гав!'Абстрактными могут быть не только методы, но и свойства:
abstract class Shape {
abstract readonly name: string // абстрактное свойство
abstract area(): number // абстрактный метод
abstract perimeter(): number
// Готовый метод — общая логика для всех фигур
toString(): string {
return `${this.name}: площадь=${this.area().toFixed(2)}`
}
}
class Circle extends Shape {
readonly name = 'Круг'
constructor(private radius: number) { super() }
area() { return Math.PI * this.radius ** 2 }
perimeter() { return 2 * Math.PI * this.radius }
}| Критерий | abstract class | interface |
|---|---|---|
| Реализация методов | Да | Нет (только сигнатуры) |
| Поля с состоянием | Да | Нет |
| Конструктор | Да | Нет |
| Наследование | extends (один) | implements (несколько) |
| Компилируется в JS | Да (остаётся классом) | Нет (стирается) |
| Модификаторы доступа | Да | Нет |
// Interface — только форма данных/контракт
interface Serializable {
serialize(): string
deserialize(data: string): this
}
// Abstract class — общая логика + обязательные точки расширения
abstract class BaseRepository<T> {
protected items: T[] = []
findAll(): T[] { return [...this.items] }
findById(id: number): T | undefined {
return this.items.find((item: any) => item.id === id)
}
// Подкласс обязан реализовать эти методы
abstract validate(item: T): boolean
abstract create(data: Partial<T>): T
}Используйте абстрактный класс, когда:
1. **Нужна общая логика** — часть методов одинакова для всех подклассов
2. **Есть состояние** — подклассы разделяют общие поля
3. **Шаблонный метод** — алгоритм зафиксирован, детали варьируются
abstract class DataProcessor {
// Шаблонный метод — алгоритм зафиксирован
process(data: string[]): string[] {
const filtered = this.filter(data) // шаг 1 — переопределяется
const mapped = this.transform(filtered) // шаг 2 — переопределяется
return this.sort(mapped) // шаг 3 — общая реализация
}
abstract filter(data: string[]): string[]
abstract transform(data: string[]): string[]
protected sort(data: string[]): string[] {
return [...data].sort()
}
}**Правило**: если вам нужно описать лишь форму объекта — используйте interface. Если нужна общая реализация с обязательными точками расширения — abstract class.
Шаблонный метод через абстрактный класс: иерархия логгеров с общим форматированием
// В TypeScript это было бы abstract class Logger
// В JS эмулируем: конструктор бросает ошибку если вызван напрямую
class Logger {
constructor(level = 'INFO') {
if (new.target === Logger) {
throw new Error('Logger — абстрактный класс')
}
this.level = level
}
// "Абстрактные" методы — подкласс обязан реализовать
formatMessage(message) {
throw new Error(`${this.constructor.name} не реализовал formatMessage()`)
}
writeOutput(text) {
throw new Error(`${this.constructor.name} не реализовал writeOutput()`)
}
// Шаблонный метод — алгоритм зафиксирован
log(message) {
const timestamp = new Date().toISOString().substring(11, 19)
const formatted = this.formatMessage(message)
const output = `[${timestamp}] [${this.level}] ${formatted}`
this.writeOutput(output)
return output
}
warn(message) {
const original = this.level
this.level = 'WARN'
const result = this.log(message)
this.level = original
return result
}
error(message) {
const original = this.level
this.level = 'ERROR'
const result = this.log(message)
this.level = original
return result
}
}
// ConsoleLogger — выводит в консоль с цветами (эмуляция через префиксы)
class ConsoleLogger extends Logger {
constructor() { super('INFO') }
formatMessage(message) {
return String(message)
}
writeOutput(text) {
console.log(text)
}
}
// PrefixLogger — добавляет префикс к каждому сообщению
class PrefixLogger extends Logger {
constructor(prefix) {
super('INFO')
this.prefix = prefix
}
formatMessage(message) {
return `[${this.prefix}] ${message}`
}
writeOutput(text) {
console.log(text)
}
}
// BufferLogger — накапливает сообщения в буфер
class BufferLogger extends Logger {
constructor() {
super('INFO')
this.buffer = []
}
formatMessage(message) {
return String(message)
}
writeOutput(text) {
this.buffer.push(text)
}
getBuffer() {
return [...this.buffer]
}
flush() {
const lines = this.buffer.join('\n')
this.buffer = []
return lines
}
}
// --- Демонстрация ---
console.log('=== ConsoleLogger ===')
const logger = new ConsoleLogger()
logger.log('Приложение запущено')
logger.warn('Память заканчивается')
logger.error('Соединение с БД потеряно')
console.log('\n=== PrefixLogger ===')
const dbLogger = new PrefixLogger('DB')
dbLogger.log('Подключение установлено')
dbLogger.error('Запрос завершился с ошибкой')
console.log('\n=== BufferLogger ===')
const buf = new BufferLogger()
buf.log('Событие 1')
buf.log('Событие 2')
buf.warn('Предупреждение')
console.log('Буфер накопил:', buf.getBuffer().length, 'сообщений')
console.log('Содержимое буфера:')
console.log(buf.flush())
console.log('\n=== Попытка создать Logger напрямую ===')
try {
new Logger()
} catch (e) {
console.log('Ошибка:', e.message)
}Реализуй абстрактный класс `Vehicle` (через обычный класс с проверкой new.target) с "абстрактными" методами `getFuelType()` и `getMaxSpeed()`, которые бросают ошибку если не переопределены. Добавь готовый метод `describe()`, возвращающий строку вида "Toyota: максимальная скорость 180 км/ч, топливо: бензин". Создай подкласс `Car` (параметры: brand, maxSpeed) с топливом "бензин", и `ElectricCar` (параметры: brand, maxSpeed) с топливом "электричество".
В Vehicle: getFuelType и getMaxSpeed бросают new Error(`${this.constructor.name} не реализовал метод`). В describe() вызывай this.getFuelType() и this.getMaxSpeed(). В Car и ElectricCar: сохрани maxSpeed через this.maxSpeed = maxSpeed в конструкторе.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке