Javascript. Memento pattern

Memento (хранитель) - поведенческий шаблон проектирования, позволяющий, не нарушая инкапсуляцию, зафиксировать и сохранить внутреннее состояния объекта так, чтобы позднее восстановить его в это состояние.

Клиент запрашивает Memento (хранителя) у исходного объекта, когда ему необходимо сохранить состояние исходного объекта (установить контрольную точку). Исходный объект инициализирует Memento своим текущим состоянием. Клиент является "посыльным" за Memento, но только исходный объект может сохранять и извлекать информацию из Memento (Memento является "непрозрачным" для клиентов и других объектов). Если клиенту в дальнейшем нужно "откатить" состояние исходного объекта, он передает Memento обратно в исходный объект для его восстановления.
Паттерн проектирования Memento определяет трех различных участников:

Originator (хозяин) - объект, умеющий создавать хранителя, а также знающий, как восстановить свое внутреннее состояние из хранителя.

Caretaker (смотритель) - объект, который знает, почему и когда хозяин должен сохранять и восстанавливать себя.

Memento (хранитель) - "ящик на замке", который пишется и читается хозяином и за которым присматривает смотритель.


Ниже представлен пример реализации данного паттерна.

// "Хранитель"
var Memento = function (state) {
var state = state || 'undefined';
this.getState = function () {
return state;
};
};

// "Опекун"
var Caretaker = function () {

var memento;
this.getMemento = function () {
return memento;
};
this.setMemento = function (_memento) {
if (_memento instanceof Memento) {
memento = _memento;
}
};

};

// "Создатель"
var Originator = function () {

var state;
this.setState = function (_state) {
state = _state || "undefined";
};
this.getState = function () {
return state;
};
this.saveState = function () {
return new Memento(state);
};
this.restoreState = function (memento) {
if (memento instanceof Memento) {
state = memento.getState();
}
};

};

var Application = function () {
this.run = function () {
var originator = new Originator();
var caretaker = new Caretaker();
originator.setState('on');
console.log('State is ' + originator.getState());
caretaker.setMemento(originator.saveState());
originator.setState('off');
console.log('State is ' + originator.getState());
originator.restoreState(caretaker.getMemento());
console.log('State is ' + originator.getState());
};
};

var application = new Application();
application.run();

На выходе приложения получаем следующие сообщения:

State is on
State is off
State is on

Javascript. Strict mode


Как известно, в спецификации ES5 определены так называемые Strict Mode правила, которые включают в себя ограничения, учитываемые во время «компиляции» и интерпретации (выполнения скрипта). Причем правила эти декларативного характера, то есть должны соблюдаться разработчиками, но не всегда. В настоящее время имеется возможность включать режим Strict Mode непосредственно в скрипт, тем самым оговаривая необходимость жесткого соблюдения правил. Для включения данного режима достаточно прописать

"use strict";

в заголовке кода и режим включен. Стоит также отметить, что данный режим в полной мере  поддерживается не всеми браузерами (есть тесты, в которых можно оценить степень поддержки режима в своем рабочем окружении). 

Javascript. Composite pattern

Компоновщик (англ. Composite pattern) — структурный шаблон проектирования, относится к структурным паттернам, объединяет объекты в древовидную структуру для представления иерархии от частного к целому. Компоновщик позволяет клиентам обращаться к отдельным объектам и к группам объектов одинаково


var _hasProp = {}.hasOwnProperty,
_extends = function(child, parent) { 
for (var key in parent) { 
if (_hasProp.call(parent, key)) { 
child[key] = parent[key]; 
}
} 
function ctor() { 
this.constructor = child; 
} 
ctor.prototype = parent.prototype; 
child.prototype = new ctor(); 
child.__super__ = parent.prototype; 
return child; 
};

// Component
var Component = (function () {

function Component(name) {
this.name = name || 'undefined';
}
Component.prototype.add = function () {};
Component.prototype.remove = function () {};
Component.prototype.display = function () {};
return Component;

})();

// Composite
var Composite = (function (_super) {

_extends(Composite, _super);
function Composite() {
this.children = [];
}
Composite.prototype.add = function (component) {
if (component instanceof Component) {
this.children.push(component);
}
};
Composite.prototype.remove = function (component) {
if (component instanceof Component) {
var index = this.children.indexOf(component);
if (~index) {
this.children.splice(index, 1);
}
}
};
Composite.prototype.display = function () {
console.log(this.children);
};
return Composite;

})(Component);

// Leaf
var Leaf = (function (_super) {

_extends(Leaf, _super);

function Leaf() {
return Leaf.__super__.constructor.apply(this, arguments);
}

Leaf.prototype.display = function() {
console.log("Cannot add to a leaf");
};
Leaf.prototype.display = function() {
console.log("Cannot remove from a leaf");
};
Leaf.prototype.display = function() {
console.log(this.name);
};
return Leaf;

})(Component);

var root = new Composite('root');

root.add(new Leaf('Leaf A'));
root.add(new Leaf('Leaf B'));

var comp = new Composite('Composite X');

comp.add(new Leaf('Leaf XA'));
comp.add(new Leaf('Leaf XB'));
comp.add(new Leaf('Leaf XC'));
comp.add(new Leaf('Leaf XD'));

root.add(comp);
root.add(new Leaf('Leaf C'));

var leaf = new Leaf('Leaf D');

root.add(leaf);

root.remove(comp);
root.remove(leaf);

root.display();

Вот еще один пример реализации данного паттерна. Стоит только оговориться: паттерн Компоновщик ниже реализован с использованием паттерна Итератор (см. здесь).

Array.prototype.iterator = function () {
    var self = this, position = 0, undefined;
    return {
        next:function () {
            return self[position++];
        },
        hasNext: function () {
            if (position >= self.length) {
                return false;
            } 
            else {
                return true;
            }
        },
        remove: function () {
            if (position <= 0) {
                throw new Error("You can`t an item until you`ve done at least one next()");
            }
            if (self[position - 1] !== undefined) {
                for (var i = position - 1; i < (self.length - 1); i++) {
                    self[i] = self[i + 1];
                }
                self[self.length - 1] = undefined;
            }
        }
    };
};

/* abstract class MenuComponent */

var MenuComponent = function () {

    this.add = function (menuComponent) {
        throw new Error("UnsupportedOperationException");
    };
    
    this.remove = function (menuComponent) {
        throw new Error("UnsupportedOperationException");
    };
    
    this.getChild = function (i) {
        throw new Error("UnsupportedOperationException");
    };
    
    this.getName = function () {
        throw new Error("UnsupportedOperationException");
    };
    
    this.getDescription = function () {
        throw new Error("UnsupportedOperationException");
    };
    
    this.getPrice = function () {
        throw new Error("UnsupportedOperationException");
    };
    
    this.isVegetarian = function () {
        throw new Error("UnsupportedOperationException");
    };
    
    this.print = function () {
        throw new Error("UnsupportedOperationException");
    };

}

/* Menu Leaf */

var MenuItem = function (name, description, vegetarian, price) {

    var name = name,
        description = description,
        vegetarian = vegetarian,
        price = price;
    
    this.getName = function () {
        return name;
    };
    
    this.getDescription = function () {
        return description;
    };
    
    this.getPrice = function () {
        return price;
    };
    
    this.isVegetarian = function () {
        return vegetarian;
    };
    
    this.print = function () {
        var item =  " " + this.getName() + 
                    (this.isVegetarian() ? "(v)" : "") + 
                    ", " + this.getPrice() + 
                    "   -- " + this.getDescription();
        console.log(item);
    };

};

MenuItem.prototype = new MenuComponent();
MenuItem.prototype.constructor = MenuItem;

/* Menu Composite */

var Menu = function (name, description) {

    var menuComponents = [],
        name = name,
        description = description;

    this.add = function (menuComponent) {
        menuComponents.push(menuComponent);
    };
    
    this.remove = function (menuComponent) {
        var index = menuComponents.indexOf(menuComponent);
        if (index >= 0) {
            menuComponents.splice(index, 1);
        };
    };
    
    this.getChild = function (i) {
        return menuComponents[i];
    };
    
    this.getName = function () {
        return name;
    };
    
    this.getDescription = function () {
        return description;
    };
    
    this.print = function () {
        var item =  this.getName() + ", " +    this.getDescription();
        console.log(item);
        console.log("----------------------");
        
        var iterator = menuComponents.iterator();
        while (iterator.hasNext()) {
            var menuComponent = iterator.next();
            menuComponent.print();
        }
    };

};

Menu.prototype = new MenuComponent();
Menu.prototype.constructor = Menu;

/* Client */

var Waitress = function (allMenus) {

    var allMenus = allMenus;
    
    this.printMenu = function () {
        allMenus.print();
    };

};

/* test application */

var Application = function () {
    this.run = function () {
    
        var pancakeHouseMenu = new Menu("PANCAKE HOUSE MENU", "Breakfast"), 
            dinerMenu = new Menu("DINER MENU", "Lunch"), 
            cafeMenu = new Menu("CAFE MENU", "Dinner"),
            dessertMenu = new Menu("DESSERT MENU", "Dessert of course!");
            
        var allMenus = new Menu("ALL MENUS", "All menus combined");
        
        allMenus.add(pancakeHouseMenu);
        allMenus.add(dinerMenu);
        allMenus.add(cafeMenu);
        
        pancakeHouseMenu.add(new MenuItem("K&B`s Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99));
        pancakeHouseMenu.add(new MenuItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", false, 2.99));
        pancakeHouseMenu.add(new MenuItem("Blueberry Pancakes", "Pancakes made with fresh blueberries", true, 3.49));
        pancakeHouseMenu.add(new MenuItem("Waffles", "Waffles, with your choice of blueberries or strawberries", true, 3.59));
    
        dinerMenu.add(new MenuItem("Vegetarian BLT", "(Fakin) Bacon with lettuce & tomato on whle wheat", true, 2.99));
        dinerMenu.add(new MenuItem("BLT", "Bacon with lettuce & tomato on whle wheat", false, 2.99));
        dinerMenu.add(new MenuItem("Soup of the day", "Soup of the day, with a side of potato salad", false, 3.29));
        dinerMenu.add(new MenuItem("Hotdog", "A hot dog, with saurkraut, relish, onions, topped with cheese", false, 3.05));
        dinerMenu.add(new MenuItem("Pasta", "Spaghetti with Marinara Sauce, and a slice of sourdough bread", true, 3.89));
        
        dinerMenu.add(dessertMenu);        
        dessertMenu.add(new MenuItem("Apple Pie", "Apple pie with a flakey crust, topped with vanilla icecream", true, 1.59));
        
        cafeMenu.add(new MenuItem("Veggie Burger and Air Fries", "Veggie burger on a whole wheat bun, lettuce, tomato, and fries", true, 3.99));
        cafeMenu.add(new MenuItem("Soup of the day", "A cup of the soup of the day, with a side salad", false, 3.69));
        cafeMenu.add(new MenuItem("Burrito", "A large burrito, with whole pinto beans, salsa, guacamole", true, 4.29));
        
        var waitress = new Waitress(allMenus);
        
        waitress.printMenu();
    
    };
};

var application = new Application();
application.run();

На выходе имеем:

ALL MENUS, All menus combined
----------------------

PANCAKE HOUSE MENU, Breakfast
----------------------
K&B`s Pancake Breakfast(v), 2.99   -- Pancakes with scrambled eggs, and toast
Regular Pancake Breakfast, 2.99   -- Pancakes with fried eggs, sausage
Blueberry Pancakes(v), 3.49   -- Pancakes made with fresh blueberries
Waffles(v), 3.59   -- Waffles, with your choice of blueberries or strawberries

DINER MENU, Lunch
----------------------
Vegetarian BLT(v), 2.99   -- (Fakin) Bacon with lettuce & tomato on whle wheat
BLT, 2.99   -- Bacon with lettuce & tomato on whle wheat
Soup of the day, 3.29   -- Soup of the day, with a side of potato salad
Hotdog, 3.05   -- A hot dog, with saurkraut, relish, onions, topped with cheese
Pasta(v), 3.89   -- Spaghetti with Marinara Sauce, and a slice of sourdough bread

DESSERT MENU, Dessert of course!
----------------------
Apple Pie(v), 1.59   -- Apple pie with a flakey crust, topped with vanilla icecream

CAFE MENU, Dinner
----------------------
Veggie Burger and Air Fries(v), 3.99   -- Veggie burger on a whole wheat bun, lettuce, tomato, and fries
Soup of the day, 3.69   -- A cup of the soup of the day, with a side salad
Burrito(v), 4.29   -- A large burrito, with whole pinto beans, salsa, guacamole

Lexicographical string comparison (alphabetical order)

Предполагается, что любой словарь содержит слова отсортированные по определенному порядку, как правило - по алфавиту. Такого рода порядок называется лексикографическим.
Посимвольное сравнение строк происходит с учетом положения сравниваемых символов в алфавите. Понятное дело, что в данном случае под алфавитом имеется в виду таблица кодировок, например - ASCII таблица. Таким образом сравниваются их номера в алфавите (коды из таблицы кодировок). В случае, если сравниваемые символы равны, то наличие любого символа в одной из строк увеличивает свой вес по отношению к другой строке. Таким образом на выходе мы можем получить вот такой порядок:

A < AA < AAB < ABC < AD < AFF < Z < ZZZ

или на кириллице:

А < АА < ААА < ААБ < ААВ < АБ < Б < … < ЯЯЯ

Из Си нам знакома функция strcmp, которая сравнивает строки именно лексикографически (по алфавиту). В C++, например, тип данных string поддерживает операции сравнения операторами >, < и ==, в Java - compareTo, в javascript - также операторы сравнения и localeCompare.

Javascript. Working with the url structure

Для работы с текущим url документа, объект window поддерживает свойство - location. В действительности window.location - специализированный объект, позволяющий как получать, так и устанавливать всю структуру url (в этом случае - это приводит к вызову window с заданным url). Как известно, базовая структура url имеет следующий вид:

<protocol>//<hostname>:<port>/<pathname><search><hash>

Соответственно, window.location поддерживает всю эту структуру. Например, для получения хоста, необходимо обратиться к свойству этого объекта - window.location.host. Если Вы используете фреймворк jquery, тогда обратиться к этому объекту можно так - $(location).attr('host'). Далее приведу небольшой пример.

// http://www.test.ru:8080/index.php?param=1#1

var CurrentLocation = window.location;

/*

if support jquery
var CurrentLocation = $(location);

*/

console.log(CurrentLocation.href);           // http://localhost:8080/index.php?param=1#1
console.log(CurrentLocation.protocol);    // http:

console.log(CurrentLocation.host);           // www.test.ru:8080
console.log(CurrentLocation.hostname);  // www.test.ru
console.log(CurrentLocation.port);           // 8080
console.log(CurrentLocation.pathname);  // /index.php
console.log(CurrentLocation.search);       // ?param=1
console.log(CurrentLocation.hash);          // #1