ООП. Паттерни Модуль та Фабрика.

Фронтенд розробка JavaScript. -> ООП. Паттерни Модуль та Фабрика.

ООП. Паттерни Модуль та Фабрика.

Завдання.

Створити на сторінці емуляцію кількох банкоматів.

Банкомат буде блоком з такими полями

  • Номер картки;

  • пін код;

  • Сума поповнення.

Кнопки

  • показати баланс;

  • поповнити рахунок.

Шаблон сторінки.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <style>
    </style>
    <title>Bankomet</title>
  </head>
  <body>
    <h1>Bankomet</h1>
    <div id="root"></div>
    <script src="jquery.min.js" ></script>
    <script src="app.js" ></script>
  </body>
</html>

UML діаграма класу банкомату.

uml

Будуємо початкові класи.

class Card {

    constructor(number,pin,account){
        this.number = number; 
        this.pin = pin;
        this.account = account;
    }
}
class Bankomat {
    cards = [];

    constructor(cards){
        this.cards = cards;
    }

    check() {

    }

    replanish() {
        console.log('replanish money');
    }

    show() {
        console.log('show money');
    }

    display() {
        const tpl = `
        <div>
             <img src="bankomat.png" width="200" />
             <p>
                <input id="cnum" type="text" placeholder="Номер карты" />
             </p>
             <p>
                <input id="cpin" type="text" placeholder="ПИН" />
             </p>
             <p>
                <input id="csum" type="text" placeholder="Сумма" />
             </p>
             <p>
                <button id="show-button">Show balance</button>
                <button id="repl-button">Add money</button>
             </p>
        </div>
        `
        $("#root").append(tpl);
        $('#show-button').on('click',()=> {this.show()});
        $('#repl-button').on('click',()=> {this.replanish()});        
    }
}

var bankomat = new Bankomat();
bankomat.display();

factory

Генеруємо набір карток і перемальовуємо input в select з вибором картки.

class Bankomat {
    cards = [];

...

display() {
    const tpl = `
    <div>
         <img src="bankomat.png" width="200" />
         <p>
            <select>
                ${this.cards.map((el) => `<option>${el.number}</option>`)}
            </select>

....

const card1 = new Card('123123','1',0);
const card2 = new Card('223123','2',1);
const card3 = new Card('323123','3',2);
const bankomat = new Bankomat([card1,card2,card3]);
bankomat.display();

Цей підхід незручний тим, що у клієнтському коді ми змушені дбати про створення карт.

Логічніше цю процедуру делегувати банкомату у вигляді фабричного методу.

Скрізь, де ви зустрінете слова Фабрика, Фабричний метод, Абстрактна фабрика - мають на увазі логіка створення нових об’єктів. Іноді вона містить умовні висловлювання. Наприклад, якщо ми в одному банкоматі генеруємо одні типи карток (Mastercard), а в іншому - інші. Тоді в ці фабрики й закладаються такі умови.

Створюємо фабричний метод.

class Bankomat {
    cards = [];

    constructor(){
        this.cardFabrik();
    }

    cardFabrik(){
        const card1 = new Card('123123','1',0);
        const card2 = new Card('223123','2',1);
        const card3 = new Card('323123','3',2);
        this.cards = [card1,card2,card3];
    }

...

При цьому спрощується клієнтський код.

var bankomat = New Bankomat();
bankomat.display();

Наповнимо функцію перевірки пін коду.

class Bankomat {
    ....

    check(cardNumber,pin) {
        let isCorrect = false;
        this.cards.forEach((el) => {
            if(cardNumber === el.number){  
                if(el.pin === pin) {
                    isCorrect = true;
                }
            }
        })
        return isCorrect;
    }

Викличемо її при поповненні балансу.

replanish() {
    const pin = $('#cpin').val();
    const cardNumber = $('#cnum').val();
    if(!this.check(cardNumber,pin)) {
        alert('Wrong PIN!');
    } else {
        console.log('replanish money');
    }

}

factory

Тут виникає така проблема.

З клієнтського коду можна змінити пін-код і “зламати” карту.

bankomat.cards[0].pin = 'fake';

Так само як і її номер!

factory

Що робить наш код класу карти вразливим та небезпечним для несумлінних програмістів.

Тому необхідно захистити дані карти та зробити їх приватними.

На превеликий жаль ES6 немає такої підтримки.

Але ми можемо скористатися прихованою змінною в конструкторі та повертати її окремою функцією.

class Card {

    constructor(number,pin,account){
        ...
        var _pin = pin;
        this.getPin = function() { return _pin };
    }
}

Під час перевірки явно викликаємо функцію getPin.

check(cardNumber,pin) {
    let isCorrect = false;
    this.cards.forEach((el) => {
        if(cardNumber === el.number){
            if(el.getPin() === pin) {
                isCorrect = true;
            }
        }
    })
    return isCorrect;
}

Пробуємо поміняти пін і переконуємось у неможливості.

bankomat.cards[0]._pin = 'fake';

Якби ми використовували ES5 і писали на функціях, того ж результату можна було досягти, повернувши об’єкт із зовнішньої функції. А на цей об’єкт покласти методи, що повертають приватні (захищені) дані.

Таким чином pin був би зашитий у замикання з локальною областю видимості.

let card = (function(){
    let pin = 123;

    return {
        getPin: function(){
            return pin;
        }
    }
})();

card.getPin();

Цей прийом називається Модуль.