├── DIP ├── 1 │ ├── overall.txt │ ├── someservice.js │ └── someservice.spec.js ├── 2 │ ├── someservice.js │ └── someservice.spec.js ├── 3 │ ├── http.mock.ts │ ├── http.ts │ ├── mocha.ts │ ├── someservice.spec.ts │ └── someservice.ts └── 4 │ ├── http.mock.ts │ ├── http.ts │ ├── mocha.ts │ ├── someservice.spec.ts │ └── someservice.ts ├── ISP ├── 1 │ ├── lodash.js │ └── simple.js ├── 2 │ ├── lodash.js │ └── simple.js └── 3 │ └── simple.js ├── LSP ├── 1 │ ├── rectangle.js │ └── squere.js └── 2 │ └── shape.js ├── OCP ├── 1 │ ├── icons.js │ └── switch.js └── 2 │ ├── AddTodoReducer.js │ ├── BaseReducer.js │ └── ReducerPool.js ├── README.md ├── SRP ├── 1 │ ├── employe-component.html │ ├── employe-component.js │ ├── employe-service.js │ └── wrong.js └── 2 │ ├── employe-component.html │ ├── employe-component.js │ ├── employe-service.js │ └── relationships.js └── jsconfig.json /DIP/1/overall.txt: -------------------------------------------------------------------------------- 1 | Dependency Injection 2 | Dependency Invertion 3 | Invertion of Control 4 | Decoupling 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | Decoupling (избавление от зависимостей) 35 | - Invertion of Control (принцип) 36 | -> ( Dependency Injection ) (дизайн паттерн) 37 | - Dependency Invertion (принцип) 38 | -> ( Interfaces ) -------------------------------------------------------------------------------- /DIP/1/someservice.js: -------------------------------------------------------------------------------- 1 | import { Http } from 'http'; 2 | 3 | export class SomeService { 4 | constructor() { 5 | this.http = new Http(); 6 | } 7 | getUsers() { 8 | return this.http.get('users').then(function(resp){ 9 | return resp.json(); 10 | }); 11 | } 12 | } -------------------------------------------------------------------------------- /DIP/1/someservice.spec.js: -------------------------------------------------------------------------------- 1 | import { SomeService } from './someservice'; 2 | 3 | describe('SomeService - ', function() { 4 | it('shoul be able to get users', function() { 5 | var service = new SomeService(); 6 | 7 | expect(service.getUsers()).to.be(['User1']); 8 | }) 9 | }) -------------------------------------------------------------------------------- /DIP/2/someservice.js: -------------------------------------------------------------------------------- 1 | export class SomeService { 2 | constructor(http) { 3 | this.http = http; 4 | } 5 | getUsers() { 6 | return this.http.get('users').then(function(resp){ 7 | return resp.json(); 8 | }); 9 | } 10 | } -------------------------------------------------------------------------------- /DIP/2/someservice.spec.js: -------------------------------------------------------------------------------- 1 | import { SomeService } from './http'; 2 | import { MockHttp } from './http.mock.js'; 3 | 4 | describe('SomeService - ', function() { 5 | it('shoul be able to get users', function() { 6 | var service = new SomeService( 7 | new MockHttp(['User1']) 8 | ); 9 | 10 | expect(service.getUsers()).to.be(['User1']); 11 | }) 12 | }) -------------------------------------------------------------------------------- /DIP/3/http.mock.ts: -------------------------------------------------------------------------------- 1 | export class MockHttp { 2 | constructor(d: any) { 3 | } 4 | get(url: string): any { 5 | // .. 6 | } 7 | } -------------------------------------------------------------------------------- /DIP/3/http.ts: -------------------------------------------------------------------------------- 1 | export class Http { 2 | get(url: string): any { 3 | // .. 4 | } 5 | } -------------------------------------------------------------------------------- /DIP/3/mocha.ts: -------------------------------------------------------------------------------- 1 | export function describe(a: any, b: any) { 2 | 3 | } 4 | 5 | export function it(a: any, b: any) { 6 | 7 | } 8 | 9 | export function expect(a: any): any { 10 | 11 | } -------------------------------------------------------------------------------- /DIP/3/someservice.spec.ts: -------------------------------------------------------------------------------- 1 | import { SomeService } from './someservice'; 2 | import { MockHttp } from './http.mock'; 3 | import { describe, it, expect } from './mocha'; 4 | 5 | describe('SomeService - ', function() { 6 | it('shoul be able to get users', function() { 7 | var service = new SomeService( 8 | new MockHttp(['User1']) // error! 9 | ); 10 | 11 | expect(service.getUsers()).to.be(['User1']); 12 | }) 13 | }) -------------------------------------------------------------------------------- /DIP/3/someservice.ts: -------------------------------------------------------------------------------- 1 | import { Http } from './http' 2 | 3 | export class SomeService { 4 | http: Http; 5 | constructor(http: Http) { 6 | this.http = http; 7 | } 8 | getUsers() { 9 | return this.http.get('users').then(function(resp){ 10 | return resp.json(); 11 | }); 12 | } 13 | } -------------------------------------------------------------------------------- /DIP/4/http.mock.ts: -------------------------------------------------------------------------------- 1 | import { IHttp } from './http'; 2 | 3 | export class MockHttp implements IHttp { 4 | constructor(d: any) { 5 | } 6 | get(url: string): any { 7 | // .. 8 | } 9 | } -------------------------------------------------------------------------------- /DIP/4/http.ts: -------------------------------------------------------------------------------- 1 | export class Http implements IHttp { 2 | get(url: string): any { 3 | // .. 4 | } 5 | } 6 | export interface IHttp { 7 | get(url: string): any; 8 | } -------------------------------------------------------------------------------- /DIP/4/mocha.ts: -------------------------------------------------------------------------------- 1 | export function describe(a: any, b: any) { 2 | 3 | } 4 | 5 | export function it(a: any, b: any) { 6 | 7 | } 8 | 9 | export function expect(a: any): any { 10 | 11 | } -------------------------------------------------------------------------------- /DIP/4/someservice.spec.ts: -------------------------------------------------------------------------------- 1 | import { SomeService } from './someservice'; 2 | import { MockHttp } from './http.mock'; 3 | import { describe, it, expect } from './mocha'; 4 | 5 | describe('SomeService - ', function() { 6 | it('shoul be able to get users', function() { 7 | var service = new SomeService( 8 | new MockHttp(['User1']) // Ok! 9 | ); 10 | 11 | expect(service.getUsers()).to.be(['User1']); 12 | }) 13 | }) -------------------------------------------------------------------------------- /DIP/4/someservice.ts: -------------------------------------------------------------------------------- 1 | import { IHttp } from './http' 2 | 3 | export class SomeService { 4 | http: IHttp; 5 | constructor(http: IHttp) { 6 | this.http = http; 7 | } 8 | getUsers() { 9 | return this.http.get('users').then(function(resp){ 10 | return resp.json(); 11 | }); 12 | } 13 | } -------------------------------------------------------------------------------- /ISP/1/lodash.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | 3 | _.chain([1, 2, 3]) 4 | .map(x => [x, x*2]) 5 | .flatten() 6 | .sort() 7 | .value(); 8 | 9 | 10 | //https://medium.com/making-internets/why-using-chain-is-a-mistake-9bc1f80d51ba -------------------------------------------------------------------------------- /ISP/1/simple.js: -------------------------------------------------------------------------------- 1 | class User { 2 | constructor() { 3 | this.other = new GodObject(); 4 | } 5 | doSomething() { 6 | var result = []; 7 | 8 | var a = this.other.getA([1, 2]); 9 | var b = this.other.getB('a', 'c', 'e'); 10 | 11 | result.push(this.other.doSomething(a, b, 1, 3)); 12 | 13 | var c = this.other.getC([1, 2]); 14 | var e = this.other.getE('a', 'c', 'e'); 15 | 16 | result.push(c + e); 17 | 18 | return result; 19 | } 20 | } -------------------------------------------------------------------------------- /ISP/2/lodash.js: -------------------------------------------------------------------------------- 1 | import map from "lodash/fp/map"; 2 | import flatten from "lodash/fp/flatten"; 3 | import sortBy from "lodash/fp/sortBy"; 4 | import flow from "lodash/fp/flow"; 5 | 6 | flow( 7 | map(x => [x, x*2]), 8 | flatten, 9 | sortBy(x => x) 10 | )([1,2,3]); 11 | 12 | // https://github.com/lodash/babel-plugin-lodash 13 | // https://medium.com/making-internets/why-using-chain-is-a-mistake-9bc1f80d51ba -------------------------------------------------------------------------------- /ISP/2/simple.js: -------------------------------------------------------------------------------- 1 | class User { 2 | constructor() { 3 | this.other = new GodObject(); 4 | } 5 | doSomething() { 6 | var a = this.other.getA([1, 2]); 7 | var b = this.other.getB('a', 'c', 'e'); 8 | 9 | return this.other.doSomething(a, b, 1, 3); 10 | } 11 | doSomethingElse() { 12 | var c = this.other.getC([1, 2]); 13 | var e = this.other.getE('a', 'c', 'e'); 14 | 15 | return c + e; 16 | } 17 | } -------------------------------------------------------------------------------- /ISP/3/simple.js: -------------------------------------------------------------------------------- 1 | class User { 2 | constructor() { 3 | this.other = new GodObject(); 4 | } 5 | } 6 | class SimpleUser extends User { 7 | doSomething() { 8 | var a = this.other.getA([1, 2]); 9 | var b = this.other.getB('a', 'c', 'e'); 10 | 11 | return this.other.doSomething(a, b, 1, 3); 12 | } 13 | } 14 | class AdvancedUser extends User { 15 | doSomething() { 16 | var c = this.other.getC([1, 2]); 17 | var e = this.other.getE('a', 'c', 'e'); 18 | 19 | return c + e; 20 | } 21 | } -------------------------------------------------------------------------------- /LSP/1/rectangle.js: -------------------------------------------------------------------------------- 1 | class Rectangle { 2 | setWidth(w) { 3 | this.w = w; 4 | } 5 | setHeight(h) { 6 | this.h = h; 7 | } 8 | getArea() { 9 | return this.w * this.h; 10 | } 11 | } 12 | 13 | class Square extends Rectangle { 14 | setSize(size) { 15 | this.size = size; 16 | } 17 | getArea() { 18 | return this.size * this.size; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LSP/1/squere.js: -------------------------------------------------------------------------------- 1 | class Square { 2 | getWidth() { 3 | return 100; 4 | } 5 | getHeith() { 6 | return getWidth(); 7 | } 8 | } 9 | 10 | class Rectangle extends Square { 11 | getWidth() { 12 | return 200; 13 | } 14 | getHeith() { 15 | return 100; 16 | } 17 | } 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | function foo(shape) { 31 | var size = shape.getWidth() * shape.getWidth(); 32 | } -------------------------------------------------------------------------------- /LSP/2/shape.js: -------------------------------------------------------------------------------- 1 | class Geometry { 2 | getArea() { 3 | throw new Error('Not implemented'); 4 | } 5 | } 6 | class SquareGeometry extends Geometry { 7 | constructor() { 8 | this.size = 0; 9 | } 10 | getArea() { 11 | return this.size * this.size; 12 | } 13 | } 14 | class RectangleGeometry extends Geometry { 15 | constructor() { 16 | this.width = 0; 17 | this.height = 0; 18 | } 19 | getArea() { 20 | return this.width * this.height; 21 | } 22 | } 23 | 24 | class Square { 25 | constructor() { 26 | this.geometry = new SquareGeometry(); 27 | } 28 | } 29 | class Rectangle { 30 | constructor() { 31 | this.geometry = new RectangleGeometry(); 32 | } 33 | } 34 | 35 | var s = new Square(); 36 | s.geometry.size = 10; 37 | console.log(s.geometry.getArea()); 38 | 39 | var r = new Rectangle(); 40 | r.geometry.width = 100; 41 | r.geometry.height = 20; 42 | console.log(s.geometry.getArea()) -------------------------------------------------------------------------------- /OCP/1/icons.js: -------------------------------------------------------------------------------- 1 | { 2 | device: 'phone', 3 | os: 'mac', 4 | version: '9' 5 | } 6 | 7 | 8 | 0001000 -> pc windows 9 | 0001100 -> pc windows 10 10 | 1001110 -> mac macOS 11 -------------------------------------------------------------------------------- /OCP/1/switch.js: -------------------------------------------------------------------------------- 1 | import { ADD_TODO, DELETE_TODO, EDIT_TODO, COMPLETE_TODO, COMPLETE_ALL, CLEAR_COMPLETED } from '../constants/ActionTypes' 2 | 3 | const initialState = [ 4 | { 5 | text: 'Use Redux', 6 | completed: false, 7 | id: 0 8 | } 9 | ] 10 | 11 | export default function todos(state = initialState, action) { 12 | switch (action.type) { 13 | case ADD_TODO: 14 | return [ 15 | { 16 | id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1, 17 | completed: false, 18 | text: action.text 19 | }, 20 | ...state 21 | ] 22 | 23 | case DELETE_TODO: 24 | return state.filter(todo => 25 | todo.id !== action.id 26 | ) 27 | 28 | case EDIT_TODO: 29 | return state.map(todo => 30 | todo.id === action.id ? 31 | Object.assign({}, todo, { text: action.text }) : 32 | todo 33 | ) 34 | 35 | case COMPLETE_TODO: 36 | return state.map(todo => 37 | todo.id === action.id ? 38 | Object.assign({}, todo, { completed: !todo.completed }) : 39 | todo 40 | ) 41 | 42 | case COMPLETE_ALL: 43 | const areAllMarked = state.every(todo => todo.completed) 44 | return state.map(todo => Object.assign({}, todo, { 45 | completed: !areAllMarked 46 | })) 47 | 48 | case CLEAR_COMPLETED: 49 | return state.filter(todo => todo.completed === false) 50 | 51 | default: 52 | return state 53 | } 54 | } -------------------------------------------------------------------------------- /OCP/2/AddTodoReducer.js: -------------------------------------------------------------------------------- 1 | import { BaseReducer } from './BaseReducer' 2 | import { ReducerPool } from './ReducerPool' 3 | import { ADD_TODO } from '../constants/TodoActionTypes' 4 | 5 | export class AddTodoReducer extends BaseReducer { 6 | getNewState(state, action) { 7 | if (action.type == ADD_TODO) { 8 | return [ 9 | { 10 | id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1, 11 | completed: false, 12 | text: action.text 13 | }, 14 | ...state 15 | ] 16 | } else { 17 | return state; 18 | } 19 | } 20 | } 21 | 22 | ReducerPool.register(new AddTodoReducer()); -------------------------------------------------------------------------------- /OCP/2/BaseReducer.js: -------------------------------------------------------------------------------- 1 | export class BaseReducer { 2 | getNewState() { 3 | throw new Error('must be overriten') 4 | } 5 | } -------------------------------------------------------------------------------- /OCP/2/ReducerPool.js: -------------------------------------------------------------------------------- 1 | export class ReducerPool { 2 | constructor() { 3 | this.reducers = []; 4 | } 5 | 6 | registerReducer(reducer) { 7 | reducers.push(reducer); 8 | } 9 | 10 | getNewState(state, action) { 11 | return reducers.reduce((state, reducer) => { 12 | return reducer.getNewState(state, action); 13 | }, state); 14 | } 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SOLID-JavaScript-CodeSamples 2 | Code samples for SOLID JavaScript talk 3 | 4 | In case if something is not clear - create pull request 5 | -------------------------------------------------------------------------------- /SRP/1/employe-component.html: -------------------------------------------------------------------------------- 1 |

hello world

-------------------------------------------------------------------------------- /SRP/1/employe-component.js: -------------------------------------------------------------------------------- 1 | import { Component, ViewChildren } from '@angular/core'; 2 | import { DI } from 'DI'; 3 | 4 | @Component({ 5 | selector: 'employee', 6 | templateUrl: 'employee.component.html', 7 | queries: { 8 | manager: new ViewChildren('manager') 9 | } 10 | }) 11 | export class EmployeeComponent { 12 | constructor() { 13 | this.employeeService = DI.inject('employeeService') 14 | } 15 | onEmployeeSubmit(employee) { 16 | this.employeeService.setProfile() 17 | .subscribe(() => this.manager.show()) 18 | } 19 | } -------------------------------------------------------------------------------- /SRP/1/employe-service.js: -------------------------------------------------------------------------------- 1 | import { DI } from 'DI'; 2 | 3 | export class EmployeeService { 4 | constructor() { } 5 | setProfile() { 6 | return {} 7 | } 8 | } 9 | 10 | DI.register(EmployeeService).asKey('employeeService') -------------------------------------------------------------------------------- /SRP/1/wrong.js: -------------------------------------------------------------------------------- 1 | class DoSomething { 2 | doA() { 3 | 4 | } 5 | doB() { 6 | 7 | } 8 | } 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | class DoSomethingA { 25 | doA() { 26 | 27 | } 28 | } 29 | 30 | class DoSomethingB { 31 | doB() { 32 | 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /SRP/2/employe-component.html: -------------------------------------------------------------------------------- 1 |

hello world

-------------------------------------------------------------------------------- /SRP/2/employe-component.js: -------------------------------------------------------------------------------- 1 | import { Component, ViewChildren, EventEmitter } from '@angular/core'; 2 | import { DI } from 'DI'; 3 | 4 | @Component({ 5 | selector: 'employee', 6 | templateUrl: 'employee.component.html', 7 | outputs: [ 8 | 'submit' 9 | ] 10 | }) 11 | export class EmployeeComponent { 12 | constructor() { 13 | DI.register(this).asKey('employee'); 14 | this.submit = new EventEmitter(); 15 | } 16 | onEmployeeSubmit(employee) { 17 | this.submit.next(employee) 18 | } 19 | } -------------------------------------------------------------------------------- /SRP/2/employe-service.js: -------------------------------------------------------------------------------- 1 | import { DI } from 'DI'; 2 | 3 | export class EmployeeService { 4 | constructor() { } 5 | setProfile() { 6 | return {} 7 | } 8 | } 9 | 10 | DI.register(EmployeeService).asKey('employeeService') -------------------------------------------------------------------------------- /SRP/2/relationships.js: -------------------------------------------------------------------------------- 1 | import { DI } from 'DI'; 2 | 3 | export class Relationships { 4 | constructor() { 5 | this.employee = DI.inject('employee'); 6 | this.employeeService = DI.inject('employeeService'); 7 | this.manager = DI.inject('manager'); 8 | 9 | this.employee.on('submit', this.onEmployeeSubmit.bind(this)) 10 | } 11 | onEmployeeSubmit(employee) { 12 | this.employeeService.setProfile(employee) 13 | .subscribe(this.onEmployeeSaved) 14 | } 15 | onEmployeeSaved() { 16 | this.manager.show(); 17 | } 18 | } 19 | 20 | // https://en.wikipedia.org/wiki/DCI -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=759670 3 | // for the documentation about the jsconfig.json format 4 | "compilerOptions": { 5 | "target": "es6", 6 | "module": "es2015", 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true 9 | }, 10 | "exclude": [ 11 | "node_modules", 12 | "bower_components", 13 | "jspm_packages", 14 | "tmp", 15 | "temp" 16 | ] 17 | } 18 | --------------------------------------------------------------------------------