SOLID
Принцип единственной ответственности
Класс должен быть ответственен лишь за что-то одно. Если класс отвечает за решение нескольких задач, его подсистемы, реализующие решение этих задач, оказываются связанными друг с другом. Изменения в одной такой подсистеме ведут к изменениям в другой.
Инкапсуляция в программировании — это принцип, согласно которому внутреннее устройство сущностей нужно объединять в специальной «оболочке» и скрывать от вмешательств извне. Доступ к объектам возможен через специальные открытые методы, а напрямую обратиться к их содержимому нельзя.
Инкапсуляцию также описывают как принцип разделения логики и поведения. Логика — то, как что-то устроено внутри. Поведение — то, как оно взаимодействует с другими сущностями. Разделение этих двух понятий упрощает взаимодействие объектов в коде.
class News {
constructor(title, text) {
this.title = title
this.text = text
this.modified = false
}
update(text) {
this.text = text
this.modified = true
}
}
class NewsPrinter {
constructor(news) {
this.news = news
}
html() {
return `
<div class="news">
<h1>${this.news.title}</h1>
<p>${this.news.text}</p>
</div>
`
}
json() {
return JSON.stringify({
title: this.news.title,
text: this.news.text,
modified: this.news.modified
})
}
}
const printer = new NewsPrinter(
new News('News title', 'News text')
)
console.log(printer.html())
/*
<div class="news">
<h1>News title</h1>
<p>News text</p>
</div>
*/
console.log(printer.json())
/*
{"title":"News title","text":"News text","modified":false}
*/
Принцип открытости/закрытости
Программные сущности (классы, модули, функции) должны быть открыты для расширения, но не для модификации.
class Shape {
area() {
throw new Error('Area method should be implemented!')
}
}
class Square extends Shape {
constructor(size) {
super()
this.size = size
}
area() {
return this.size ** 2
}
}
class Circle extends Shape {
constructor(radius) {
super()
this.radius = radius
}
area() {
return (this.radius ** 2) * Math.PI
}
}
class AreaCalculator {
constructor(shapes = []) {
this.shapes = shapes
}
sum() {
return this.shapes.reduce((acc, shape) => {
acc += shape.area()
return acc
}, 0)
}
}
const calc = new AreaCalculator([
new Square(2),
new Circle(5)
])
console.log(calc.sum()) // 82.53981633974483
Принцип подстановки Барбары Лисков
Необходимо, чтобы подклассы могли бы служить заменой для своих суперклассов.
class Person {}
class Member extends Person {
access() {
console.log('You have an access.')
}
}
class Guest extends Person {
isGuest = true
}
class Frontend extends Member {
canCreateFrontend() {}
}
class Backend extends Member {
canCreateBackend() {}
}
class PersonFromDifferentCompany extends Guest {
access() {
throw new Error('You haven\'t access!')
}
}
function openSecretDoor(member) {
member.access()
}
openSecretDoor(new Frontend()) // You have an access.
openSecretDoor(new Backend()) // You have an access.
openSecretDoor(new PersonFromDifferentCompany()) // Uncaught Error: You haven't access!
Принцип разделения интерфейсов
Создавайте узкоспециализированные интерфейсы, предназначенные для конкретного клиента. Клиенты не должны зависеть от интерфейсов, которые они не используют.
class Animal {
constructor(name) {
this.name = name
}
}
const walker = {
walk() {
console.log(`${this.name} can walk.`)
}
}
const swimmer = {
swim() {
console.log(`${this.name} can swim.`)
}
}
const flier = {
fly() {
console.log(`${this.name} can fly.`)
}
}
class Dog extends Animal {}
class Eagle extends Animal {}
class Whale extends Animal {}
Object.assign(Dog.prototype, walker, swimmer)
Object.assign(Eagle.prototype, walker, flier)
Object.assign(Whale.prototype, swimmer)
const dog = new Dog('Leo')
dog.walk() // Leo can walk.
dog.swim() // Leo can swim.
const eagle = new Eagle('Vu')
eagle.walk() // Vu can walk.
eagle.fly() // Vu can fly.
const whale = new Whale('Bubba')
whale.swim() // Bubba can swim.
Принцип инверсии зависимостей
Система должна конструироваться на основе абстракций “сверху вниз”: не абстракции должны формироваться на основе деталей, а детали должны формироваться на основе абстракций
class Fetch {
request(url) {
// return fetch(url).then(response => response.json())
return Promise.resolve('Data from fetch.')
}
}
class LocalStorage {
get() {
const dataFromLocalStorage = 'Data from LocalStorage.'
return dataFromLocalStorage
}
}
class FetchClient {
constructor() {
this.fetch = new Fetch()
}
clientGet() {
return this.fetch.request()
}
}
class LocalStorageClient {
constructor() {
this.localStorage = new LocalStorage()
}
clientGet() {
return this.localStorage.get()
}
}
class Database {
constructor(client) {
this.client = client
}
getData() {
return this.client.clientGet()
}
}
const db = new Database(new FetchClient())
console.log(db.getData()) // Promise {<fulfilled>: "Data from fetch."}
const localStorage = new Database(new LocalStorageClient())
console.log(localStorage.getData()) // Data from LocalStorage.