Всем привет! Это моя первая публикация, поэтому прошу строго не судить :)
Ниже будут представлены примеры с использованием JavaScript и библиотеки InversifyJS.
KISS (Keep It Simple, Stupid - Сделай это проще)
Принцип, согласно которому код должен быть максимально простым и понятным для разработчиков.
Плохой пример
function calculateSum(array) {
let sum = 0;
for (let i = 0; i < array.length; i++) {
sum += array[i];
}
return sum;
}
Хороший пример
const calculateSum = array => array.reduce((sum, curr) => sum + curr, 0);
DRY (Don't Repeat Yourself - Не повторяй себя)
Принцип, который подразумевает избегание повторения кода.
Плохой пример
// Каждый раз для нового класса, работающего со списком,
// реализуется похожий набор свойств и методов.
@injectable()
class UserStore {
list = [];
// Реализация методов и свойств для работы со списком.
add() {};
delete() {};
edit() {};
// и т.д.
}
@injectable()
class TaskStore {
list = [];
// Реализация методов и свойств для работы со списком.
add() {};
delete() {};
edit() {};
// и т.д.
}
Хороший пример
// Создаем базовый интерфейс для работы со списком.
interface ListStoreInterface<T> {
list: T[];
add(item: T): void;
delete(item: T['id']): void;
edit(item: Partial<T>): T;
// и т.д.
}
// Создаем идентификатор для интерфейса.
export const ListStoreInterface: interfaces.ServiceIdentifier<ListStoreInterface> =
Symbol('ListStoreInterface');
// Создаем базовый класс реализующий свойства и методы интерфейса.
class ListStore<T> implements ListStoreInterface<T> {
// Реализация методов и свойств для работы со списком.
}
// Выполняем привязку идентификатора к классу.
ctx.bind(ListStoreInterface).to(ListStore).inSingletonScope();
// Если в классе требуется работа со списком,
// просто выполняем инъекцию без дополнительного описания методов и свойств.
@injectable()
class UserStore {
@inject(ListStoreInterface)
listStore: ListStoreInterface;
// Код...
}
@injectable()
class TaskStore {
@inject(ListStoreInterface)
listStore: ListStoreInterface;
// Код...
}
Single Responsibility Principle (Принцип единственной ответственности)
Каждый класс или функция должны быть ответственны только за одну задачу. Это означает, они не должны быть перегружены функциональностью, которая не относится к их основной задаче.
Плохой пример
// Вся логика перемешена и размещена в одном классе.
@injectable()
export class UserService {
list = [];
// Реализация методов и свойств для работы со списком.
add(){};
delete(){};
edit(){};
// и т.д.
async getList() {
// Запрос на сервер для получения данных.
// Маппинг полей.
// и т.д.
}
}
Хороший пример
// Логика декомпозирована и вынесена в отдельные модули.
@injectable()
export class UserService {
// Модуль для работы со списком.
@inject(UserListInterface)
listStore: UserListInterface;
// Модуль маппинга полей.
@inject(UserRegisterInterface)
registerEntity: UserRegisterInterface;
// Модуль получения данных с сервера.
@inject(UserRequestGetListInterface)
requestGetList: UserRequestGetListInterface;
// Метод регистрации элемента.
register(){};
// Метод получения готового списка.
async getList() {}
}
Open/Closed Principle (Принцип открытости/закрытости)
Код должен быть открыт для расширения, но закрыт для изменения. Это означает, что если нужно добавить новую функциональность, то это должно быть сделано без изменения уже существующего кода.
Плохой пример
// Для добавления новой операции необходимо вносить изменения в основной код функции.
function calculateSum(array, operation) {
let sum = 0;
for (let i = 0; i < array.length; i++) {
if (operation === 'add') {
sum += array[i];
} else if (operation === 'subtract') { // Добавляем новую операцию.
sum -= array[i];
}
}
return sum;
}
calculateSum([1, 2, 3], 'add');
calculateSum([1, 2, 3], 'subtract');
Хороший пример
// Добавление новой операции осуществляется без изменения основного кода функции.
function calculateSum(array, calculator) {
return array.reduce(calculator, 0);
}
function add(acc, curr) {
return acc + curr;
}
function subtract(acc, curr) { // Добавляем новую операцию.
return acc - curr;
}
calculateSum([1, 2, 3], add);
calculateSum([1, 2, 3], subtract);
Liskov Substitution Principle (принцип подстановки Лисков)
Объекты должны быть заменяемыми на экземпляры их подтипов без изменения корректности программы. Это означает, что если класс B является экземпляром класса A, то любой объект типа A может быть заменен объектом типа B без нарушения логики программы.
Базовый класс
export class List {
_list = [];
setList(list) {
this._list = list;
}
get list() {
return this._list;
}
}
Плохой пример
// Базовый класс List не может быть заменен дочерним классом FilterList,
// т.к. переопределен геттер нарушающий логику программы.
export class FilterList extends List {
filter = {};
constructor() {
super();
}
setFilter(filter) {
this.filter = filter;
}
get list() {
return this._list.map(this.filter);
}
}
Хороший пример
// Базовый класс List может быть заменен дочерним классом FilterList
// без нарушения логики программы.
export class FilterList extends List {
filter = {};
constructor() {
super();
}
setFilter(filter) {
this.filter = filter;
}
get filteredList() {
return this._list.map(this.filter);
}
}
Interface Segregation Principle (Принцип разделения интерфейса)
Сущности не должны зависеть от методов, которые они не используют. Это означает, что интерфейсы должны быть максимально простыми и содержать только базовый набор методов и свойств.
Плохой пример
// Базовый интерфейс содержит свойства и методы, которые могут не потребоваться.
interface User {
id: number;
name: string;
email: string;
address: string;
phone: string;
age: number;
gender: string;
friends: User[];
addFriend(friend: User): void;
}
class User implements User {
// Код...
}
Хороший пример
// Выполнена декомпозиция интерфейса.
interface User {
id: number;
name: string;
email: string;
}
interface Profile {
address: string;
phone: string;
age: number;
gender: string;
}
interface Friendable {
friends: User[];
addFriend(friend: User): void;
}
class UserProfile implements User, Profile {
// Код...
}
class UserFriendable implements User, Friendable {
// Код...
}
Dependency Inversion Principle (Принцип инверсии зависимостей)
Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Вместо этого они должны зависеть от абстракций.
Плохой пример
// Верхнеуровневый модуль StorageManager зависит от реализации
// нижнеуровнего модуля LocalStorage.
// Если потребуется заменить LocalStorage на другое хранилище,
// верхнеуровневый модуль придется переписывать.
class StorageManager {
storage: LocalStorage;
constructor() {
this.storage = new LocalStorage();
}
setItem({key, value}) {
// Код...
this.storage.setItem({key, value});
}
getItem(key) {
// Код...
this.storage.getItem(key);
}
}
class LocalStorage {
setItem({key, value}) {
localStorage.setItem(key, value);
}
getItem(key) {
localStorage.getItem(key);
}
// и т.д.
}
Хороший пример
// Создаем базовый интерфейс хранилища.
interface StorageInterface {
set: (data) => void;
get: (data) => any;
// и т.д.
}
// Создаем одноименный идентификатор для интерфейса.
export const StorageInterface: interfaces.ServiceIdentifier<StorageInterface> =
Symbol('StorageInterface');
// Привязываем индентификатор к классу с методами описанными в интерфейсе.
ctx.bind(StorageInterface).to(LocalStorage).inSingletonScope();
class StorageManager {
// Выполняем инъекцию зависимости.
// В качестве инъектируемой сущности используется абстрактный интерфейс,
// а не конкретный класс.
@inject(StorageInterface)
storage: StorageInterface;
set(data) {
// Код...
this.storage.set(data);
}
get(data) {
// Код...
return this.storage.get(data);
}
}
// Реализуем методы описанные в интерфейсе
class LocalStorage implements StorageInterface {
set({key, value}) {
localStorage.setItem(key, value);
}
get(key) {
localStorage.getItem(key);
}
// и т.д.
}