Принцип «открыто-закрыто» – объяснение концепции архитектуры SOLID

Принцип «открыто-закрыто» (OCP) является одним из 5 принципов проектирования SOLID. Его популяризировал американский ученый по информатике и преподаватель Роберт С. Мартин (он же Дядя Боб) в статье, опубликованной в 2000 году.

Остальные 4 принципа дизайна SOLID:

  • Принцип единой ответственности (SRP)
  • Принцип замены Лескова (LSP)
  • Принцип сегрегации интерфейса (ISP)
  • Принцип инверсии зависимостей (DIP).

Если вы хотите узнать все эти принципы, вы можете прочитать мой учебник по принципам SOLID здесь.

В этой статье я покажу вам, что означает открытый-закрытый принцип (OCP), почему вы должны использовать его и его реализацию в JavaScript.

Что мы покроем

Что такое принцип «открыто-закрыто»?

Принцип «открыто-закрыто» утверждает, что программные сущности (классы, модули, функции и т.п.) должны быть открыты для расширения, но закрыты для модификации.

Вам, вероятно, интересно, почему это утверждение звучит как противоречие. К примеру, почему что-то открывается и закрывается одновременно?

Что ж, в стране разработки ПО возможно, чтобы элемент открывался для расширения и закрывался для модификации. Это означает, что вы или члены вашей команды должны иметь возможность добавлять новые функции в существующую систему программного обеспечения, не изменяя существующий код.

Принцип «открыто-закрыто» побуждает разработчиков программного обеспечения сосредотачиваться на том, что необходимо, когда приходит время добавлять новые функции.

Если вы хотите добавить новую функциональность к своему существующему коду и вам нужно изменить его, прежде чем добавить новую функциональность, тогда вы не соблюдаете принцип «открыто-закрыто».

Почему следует использовать принцип «открыто-закрыто»?

Вот несколько причин, почему следует использовать принцип «открыто-закрыто».

  • Вам не нужно заново изобретать велосипед: как указано в принципе, код, над которым работаете вы и ваша команда, закрыт для расширения. Это означает, что если вы соблюдаете принцип «открыто-закрыто», вам не нужно изобретать колесо (и строить все заново), когда вы хотите добавить новые функции.

  • Вы сосредотачиваетесь на том, что необходимо: как указано в OCP, ваш код закрыт для модификации. Это означает, что вы можете добавлять новые функции, не слишком редактируя существующий код или вообще не редактируя его. Это может помочь вам и членам вашей команды сосредоточиться на том, что необходимо, когда придет время внедрения новых функций.

  • Вы можете избежать ошибок: поскольку вам не нужно редактировать существующий код перед добавлением новых функций, вы можете избежать ввода ненужных ошибок.

Ваш код более пригоден для обслуживания, тестирования и гибкости.: соблюдение OCP сделает вашу кодовую базу слабо связанной. Благодаря этому код становится более гибким и подходящим для обслуживания. И если вы хотите, вы можете успешно протестировать каждый класс.

Как реализовать принцип «открыто-закрыто» в JavaScript

Первый пример открыто-закрытого принципа, который я покажу, это класс, использующий переключатель или несколько операторов if. Это потому, что в коде, подобном этому, существует очень реальная возможность сменить класс с помощью операторов switch или if. И это нарушает принцип «открыто-закрыто» в процессе.

class Footballer {
  constructor(name, age, role) {
    this.name = name;
    this.age = age;
    this.role = role;
  }

  getFootballerRole() {
    switch (this.role) {
      case 'goalkeeper':
        console.log(`The footballer, ${this.name} is a goalkeeper`);
        break;
      case 'defender':
        console.log(`The footballer, ${this.name} is a defender`);
        break;
      case 'midfielder':
        console.log(`The footballer, ${this.name} is a midfielder`);
        break;
      case 'forward':
        console.log(`The footballer, ${this.name} plays in the forward line`);
        break;
      default:
        throw new Error(`Unsupported animal type: ${this.type}`);
    }
  }
}

const kante = new Footballer('Ngolo Kante', 31, 'midfielder');
const hazard = new Footballer('Eden Hazard', 32, 'forward');

kante.getFootballerRole(); // The footballer, Ngolo Kante is a midfielder
hazard.getFootballerRole(); // The footballer, Eden Hazard plays in the forward line

Есть больше футбольных ролей, таких как вингер, опорный полузащитник. Итак, в коде выше, что, по-вашему, случилось бы, если бы вам пришлось добавить другую роль футболиста на поле? Вам придется сменить switch заявление. Это нарушает принцип «открыто-закрыто», поскольку вам нужно изменить существующий код.

Чтобы устранить нарушения, нужно создать отдельную роль class использовать метод, который получает роль игрока от супер class. Но это не кончается. Затем вам нужно создать другой class для каждой роли, расширяющей класс, получающий роль игрока.

Это будет иметь больше смысла в коде:

class Footballer {
  constructor(name, age, role) {
    this.name = name;
    this.age = age;
    this.role = role;
  }

  getRole() {
    return this.role.getRole();
  }
}

// PlayerRole class uses the getRole method
class PlayerRole {
  getRole() {}
}

// Sub classes for different roles extend the PlayerRole class
class GoalkeeperRole extends PlayerRole {
  getRole() {
    return 'goalkeeper';
  }
}

class DefenderRole extends PlayerRole {
  getRole() {
    return 'defender';
  }
}

class MidfieldRole extends PlayerRole {
  getRole() {
    return 'midfielder';
  }
}

class ForwardRole extends PlayerRole {
  getRole() {
    return 'forward';
  }
}

// Putting all of them together
const hazard = new Footballer('Hazard', 32, new ForwardRole());
console.log(`${hazard.name} plays in the ${hazard.getRole()} line`); // Hazard plays in the forward line

const kante = new Footballer('Ngolo Kante', 31, new MidfieldRole());
console.log(`${kante.name} is the best ${kante.getRole()} in the world!`); //Ngolo Kante is the best midfielder in the world!

Вот еще один пример, использующий функции вместо классов JavaScript:

function calculatePrice(price, discount) {
  if (discount === '10%') {
    return price * 0.9;
  } else if (discount === '20%') {
    return price * 0.8;
  } else if (discount === '30%') {
    return price * 0.7;
  } else {
    throw new Error('Invalid discount');
  }
}

const discountedPrice = calculatePrice(100, '10%');
console.log(`Your discounted price is ${discountedPrice}`); //  The discount you get is 90

Приведенный выше код нарушает принцип «открыто-закрыто», потому что вам нужно добавить другой if…else заявление, если вы хотите добавить новую скидку.

Чтобы исправить это, вы можете получить все ваши скидки на объект и использовать их в функции следующим образом:

const discounts = {
  '10%': 0.9,
  '20%': 0.8,
  '30%': 0.7,
};

function calculatePrice(price, discountType) {
  const discount = discounts[discountType];
  if (discount === undefined) {
    throw new Error('Invalid discount');
  }
  return price * discount;
}

const discountedPrice = calculatePrice(100, '30%');
console.log(`Your discounted price is $${discountedPrice}`);

Теперь, если вы хотите добавить новые скидки, вам нужно добавить только объект скидки, а не существующую функцию, которая вычисляет скидку.

Вывод

В этой статье вы узнали о принципе «открыто-закрыто», его преимуществах и способах его реализации.

Чтобы выполнить реализацию, мы использовали классы JavaScript, чтобы проиллюстрировать, как можно это сделать с помощью объектно-ориентированного программирования JavaScript. Сначала я показал, как код нарушает принцип и как его рефакторить, чтобы этого не было.

Поскольку функция является программной сущностью, мы также рассмотрели, как можно реализовать принцип «открыто-закрыто» с помощью функций JavaScript.

Продолжайте кодировать!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *