fabric - garevna/js-course GitHub Wiki

🎓 Фабричные методы

«Я думаю, что ООП без классов — это подарок человечеству от JavaScript»

Дуглас Крокфорд


Любая функция JS может вернуть объект

Если она делает это без использования ключевого слова new, то такая функция является фабрикой


🎓 ООП-объекты и структуры данных

Роберт Мартин, «Чистый код»:
Объекты предоставляют поведение и скрывают данные
Структуры данных предоставляют данные,
но не обладают сколько-нибудь значительным поведением
☕ 1️⃣
В этом примере с помощью замыкания создается объект, данные которого скрыты от непосредственного доступа и доступны только через интерфейс, представленный методом getVar
let google = ( function ( params, pin ) {
    return {
        getVar ( varName, pincode ) {
            return pin === pincode ? 
                   params [ varName ] :
                   'No access'
        }
    }
} )( {
    name: "Google",
    token: "A7fgh14-771pd-ufr147"
}, '789541' )

console.log ( google.getVar ( "token" ) )            // No access
console.log ( google.getVar ( "name", "789541" ) )   // Google
Выведем объект google в консоль:
console.log ( google )
Результат:
▼ User {name: "Google", getVar: ƒ}
  ► getVar: ƒ getVar( varName, pincode )
    name: "Google"
  ► __proto__: Object

☕ 2️⃣
В этом примере мы делаем то же самое, но используя расширение класса User
Конструктор класса создает публичное свойство name
Фабричный метод класса updateUser позволяет расширить функционал класса, создать скрытые данные, доступные через интерфейс ( метод getVar )
class User {
    constructor ( name ) {
        this.name = name || 'unknown'
    }
}
User.updateUser = function ( user, params, pin ) {
    return Object.assign ( user, {
        getVar ( varName, pincode ) {
            return pin === pincode ? 
                   params [ varName ] :
                   'No access'
        }
    })
}

let google = User.updateUser ( new User ( "Google" ), {
        token: "AfG78-1nm*15ph",
        cash: 25000
}, "789451" )

google.getVar( "token", "789451" )  // "AfG78-1nm*15ph"
google.getVar( "cash", "789451" )   // 25000
Выведем объект google в консоль:
console.log ( google )
Результат:
▼ User {name: "Google", getVar: ƒ}
  ► getVar: ƒ getVar( varName, pincode )
    name: "Google"
  ▼ __proto__:
      ▼ constructor: class User
          ► updateUser: ƒ ( user, params, pin )
            arguments: (...)
            caller: (...)
            length: 1
            name: "User"
            prototype: {constructor: ƒ}
          ► __proto__: ƒ ()
      ► __proto__: Object

🎓 Полиморфизм

Фабричные методы дают возможность расширять функциональность конструктора,
обеспечивая его полиморфизм

В следующем примере конструктор User имеет фабричный метод createNewUser,
позволяющий создавать экземпляры класса с различным набором свойств и методов

экземпляры visitor и currentUser, созданные конструктором User,
имеют различные свойства и методы

☕ 3️⃣

function User () {
    this.talk = function ( key ) {
        document.write ( `<p>${key}: <b>${this [ key ]}</b></p>` )
    }
}

User.createNewUser = function( params ) {
    var user = new this
    for ( var key in params ) 
        user [ key ] = params [ key ]
    return user
}

var visitor = User.createNewUser( {
    name: 'migrant',
    timeVisit: new Date ().toLocaleString()
})
visitor.talk ( 'name' )
visitor.talk ( 'timeVisit' )

var currentUser = User.createNewUser ({
    name: prompt( 'What is your name?' ),
    age: prompt( 'How old are you?' ),
    id: Math.round ( Math.random () * 100000000 ),
    posts: {},
    registered: new Date ().toLocaleString().split( ', ' ),
    write: function ( text ) {
        this.posts = Object.assign ( this.posts, {
            [ new Date ().toLocaleString() ] : text
        } )  
    }
})
currentUser.talk ( 'name' )
currentUser.talk ( 'registered' )
currentUser.write ( `I'm here since ${new Date ().toLocaleString()}` )
visitor
▼ User {talk: ƒ, name: "migrant", timeVisit: "01.11.2018, 13:40:41"}
    name: "migrant"
  ► talk: ƒ ( key )
    timeVisit: "01.11.2018, 13:40:41"
  ► __proto__: Object
currentUser
▼ User {talk: ƒ, name: "Nick", age: "25", id: 80661698, posts: {…}, …}
    age: "25"
    id: 80661698
    name: "Nick"
  ▼ posts:
        01.11.2018, 13:40:51: "I'm here since 01.11.2018, 13:40:51"
      ► __proto__: Object
  ► registered: (2) ["01.11.2018", "13:40:51"]
  ► talk: ƒ ( key )
  ► write: ƒ ( text )
  ► __proto__: Object
console.dir ( currentUser.__proto__.constructor )
currentUser.proto.constructor
▼ ƒ User()
    createNewUser: ƒ ( params )
    arguments: null
    caller: null
    length: 0
    name: "User"
    prototype: {constructor: ƒ}
  ► __proto__: ƒ ()

Получили перечислимый статический метод createNewUser конструктора

Сделаем то же самое с помощью класса

class User {
    constructor () {
        this.talk = function ( key ) {
            document.write ( `<p>${key}: <b>${this [ key ]}</b></p>` )
        }
    }
    static createNewUser ( params ) {
        var user = new this
        for ( var key in params ) 
            user [ key ] = params [ key ]
        return user
    }
}

В этом случае статический метод класса createNewUser
будет неперечислимым,
а в остальном все будет аналогично варианту с конструктором
Так что "под капотом" работает все то же прототипное наследование,
только с косметическими "добавками"

⚠️ **GitHub.com Fallback** ⚠️