Принцип открытости/закрытости (open/closed principle, OCP) гласит: объекты, или сущности, должны быть открыты для расширения, но закрыты для модификации. Иначе говоря, программные сущности должны быть расширяемы без изменения их основной реализации. В объектно-ориентированном программировании этот принцип применяется путем создания новых классов, расширяющих исходный класс и переопределяющих его методы вместо того, чтобы модифицировать исходный класс напрямую.
В функциональном программировании это достигается за счет использования функций-оберток, когда можно вызвать исходную функцию и применить к ней новую функциональность, не изменяя саму исходную функцию.
Шаблон “декоратор” также является полезным инструментом для соблюдения этого принципа проектирования. С помощью декораторов можно прикрепить к объектам новые задачи или поведение, не изменяя их исходный код, и тем самым сохранить их закрытыми для модификации и открытыми для расширения.
В приведенном ниже примере расширим метод updateAvatar, включив в него валидацию, что позволит предотвратить загрузку расширений, не относящихся к изображениям. Начнем с модификации метода updateAvatar.
public async updateAvatar(id: number, file: any) {
try {
// Получение расширения файла
const fileExtension = this.fileService.getFileExtension(file);
// Проверка того, является ли формат JPG. Если нет - выбрасывается ошибка
if (["jpg", "png"].includes(fileExtension.toLowerCase())) {
throw new Error('Unsupported avatar format. Only image is allowed.');
}
const avatarPath = await this.fileService.upload(file);
await this.update(id, {avatar: avatarPath});
console.log('Avatar updated successfully.');
} catch (error) {
console.error('Avatar update failed:', error);
}
}
Как видно, приведенный код нарушает принцип открытости/закрытости (являющийся вторым принципом SOLID), поскольку требует модификации кода для добавления дополнительных расширений.
Теперь выполним рефакторинг этого кода, чтобы он соответствовал OCP.
- Создадим валидатор файлов, которому можно передать файл и ожидаемое расширение.
- Созданный валидатор будет проверять, соответствует ли расширение файла ожидаемому расширению, прежде чем разрешить выполнение операции.
Начнем с добавления новых методов в класс File.
class FileService {
public async upload(file: any, extension: string): Promise<string> {
const s3 = new AWS.S3();
const params = {
Bucket: 'my-bucket',
Key: `avatars/${Date.now()}.${extension}`,
Body: file,
};
const uploadResult = await s3.upload(params).promise();
return uploadResult.Location;
}
public validate(file: any, supportedFormats: string[]): boolean {
const fileExtension = this.getFileExtension(file);
if (supportedFormats.includes(fileExtension.toLowerCase()))
return true;
throw new Error("File extension not allowed!");
}
public getFileExtension(file: any): string {
const fileName = file.name || '';
const parts = fileName.split('.');
if (parts.length > 1)
return parts[parts.length - 1];
return '';
}
}
Как видно, прежний метод загрузки файла не был изменен, а просто добавлены новые методы в класс File.
Вторым шагом будет добавление метода валидации в функцию updateAvatar.
public async updateAvatar(id:number, file:any) {
try {
this.fileService.validate(file, ["jpg"]);
const avatarPath = await this.fileService.upload(file);
await this.update(id, {avatar: avatarPath});
} catch (error) {
console.error('Avatar update failed:', error);
}
}
А что, если будет больше поддерживаемых форматов? Опасения совершенно справедливы. В таком случае придется модифицировать данный метод. Итак, проведем еще один рефакторинг кода. Отделим поддерживаемые форматы изображений от метода updateAvatar и перенесем их в глобальные переменные или в конфигурацию приложения, где будем управлять только константами, а не логикой.
// config.js
export const SUPPORTED_IMAGE_FORMATS = ["jpg", "png", "jpeg", "svg", "webp"];
Теперь можно использовать эти константы в методе updateAvatar.
this.fileService.validate(file, SUPPORTED_IMAGE_FORMATS);
Примечание: мы не стали напрямую импортировать эту константу в метод validate, чтобы сохранить возможность повторного использования. Таким образом метод validate можно применять для видео, документов и других расширений.
Читайте также:
- Чистый код работает медленно, но он все равно нужен
- Принципы SOLID в инженерии данных. Часть 3
- Как научиться не только писать код, но и быть хорошим программистом
Читайте нас в Telegram, VK и Дзен
Перевод статьи Reza Erami: Open-Closed Principle: Extending Your Code Without Modification