swvitaliy/ifjs

View on GitHub
README_ru.md

Summary

Maintainability
Test Coverage
#cl-intf

## Цели проекта

* Поддержка интерфейсов.
* Поддержка приватных областей видимости, причем произвольных, не только private и protected.


## Проверка програмных интерфейсов при разработке

Модуль содержит 2 функции, служащих для этого:
* Interface - функция-конструктор, создающая интерфейс доступа к вашему объекту.
* Class - простая обертка для описания функции-конструктора, включающая
  наследование через F.prototype = new Parent(),
  список интерфейсов и объект-прототип.

Проверка объекта на принадлежность интерфейсу происходит в 2-х случаях:
* на этапе определения класса с помощью функции Class
* на этапе передачи объекта как параметра в функцию, которая описана в интерфейсе.

[Простой пример](examples/collection.js)

Для описания параметров или возвращаемого значения так можно использовать нативные js типы (кроме null и undefined).

```javacript
var ICalc = new Interface('ICalc', { sum: {args: [Number, Number], result: Number},
        dif: {args: [Number, Number], result: Number} });

var IStringConcat = new Interface('IStringConcat', { concat: {args: [String, String], result: String} });

var IMatch = new Interface('IMatch', { match: {args: [RegExp], result: Boolean} });
```

Кроме этого, при описании аргументов функции в интерфейсе (или возвращаемых функцией значений) можно использовать
функцию-конструктор, например:

```javacript
function F() {}
F.prototype.fn = function() { return '\' nruter } ()noitcnuf = nf.epytotorp.F'; }

var IFClient = new Interface('IFClient', {
  run: [F]
});

function FClient() {}

Class(FClient, null, [IFClient], {
  run: function(obj) {
    console.log(obj.fn());
  };
});

var f = new F();
var fclient = new FClient();

fclient.run(f);
```


Вы можете передавать в качестве интерфейса объект, не обрамленный в new Interface([ifname], ...).

```javacript
Class({}, null, [{ fn: [] }], { fn: function() { return 'Yes, we can!'; } });
```

Если вы передаете только один интерфейс можно опустить скобки "[...]".

```javacript
Class({}, null, { fn: { args: [], result: String } },
    { fn: function() { return '1234567890'; } });
```

Однако, в случае, если вы не обрамляете интерфейс в new Interface([ifname], ...) самостоятельно, происходит его
обрамление в данную конструкцию автоматически и имя интерфейса задается также автоматически. Это имя является просто
инкрементным числом, поэтому при ошибке вы получите что-то вроде

    object doesn't implement the "\_\_12" interface. Method "fn" was not found.

Какой именно это интерфейс остается неясным. Поэтому я бы рекомендовал самостоятельно создавать объекты-интефрейсы.

Чистота.
--------

Одной из особенностей интерфейса является его "чистота". Если вы передаете в функцию некоторый объект по интерфейсу
(здесь необходимо передавать именно по интерфейсу, те. объекту созданному как new Interface), то все поля-функции
передаваемого объекта, которые не соответствуют интерфейсу, внутри функции будут недоступны. Причем при выходе из нее
они снова становятся доступными. Также происходит проверка всех "\_\_proto\_\_" свойств у объекта и "очистка" функций
из них, что позволяет использовать "наследование" по prototype / \_\_proto\_\_.

Таким образом, внутри "клиента" вы можете использовать только то, что позволяет вам использовать интерфейс.

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

Пример с вызовом функции, которая не указана в интерфейсе

```javacript
var IPush = new Interface('IPush', { push: [String] });
var IPop = new Interface('IPop', { pop: { args:[], result:String } });

function Stack() {
  this.items = [];
}

Class(Stack, null, [IPush, IPop], {
  push: function(str) { this.items.push(str); return this; },
  pop: function() {
    if (!this.items.length) throw "empty stack";
    return this.items.pop();
  }
});

var IPusher = new Interface('IPusher', { run: [IPush] });

function Pusher() {}

Class(Pusher, null, [IPusher], {
  run: function(stack) {
    return obj.pop();
  }
});

var pusher = new Pusher();

obj.push('123');

try {
  pusher.run(obj)         // <--- ОШИБКА! Внутри pusher.run такой функции как pop в объекте obj
                          //     и всех его __proto__ свойствах не существует
} catch(e) {}

console.log(obj.pop());  // <--- Никакой ошибки нет! Метод pop вернулся на свое место!
// > 123
```

Пример с вызовом функции, которая объявлена в родительском конструкторе.

```javacript
/////////////////////
function F() {
  this.field = 'F';
}

F.prototype.fn = function() { return 'I\'m legal function';  };
F.prototype.filth = function() {};

/////////////////////
function G() {
  F.call(this);
  this.gield = 'G';
}

G.prototype = new F();
G.prototype.constructor = G;

G.prototype.gg = function() { return 'I\'m a white rabbit!'; };

/////////////////////
function H() {
  G.call(this);
  this.hield = 'H';
}

H.prototype = new G();
H.prototype.constructor = H;

H.prototype.hh = function() { return 'Who let the dogs out?!'; };

/////////////////////

var h = new H();

var I = new Interface('I', {
  fn: [],
  gg: [],
  hh: []
});

var IHClient = new Interface('IHClient', {
  run: [I]
});

function HClient() {}

Class(HClient, null, IHClient, {
  run: function(h) {
    console.log(h.fn());
    console.log(h.gg());
    console.log(h.hh());
  }
});

var hclient = new HClient();
hclient.run(h);

// > I'm legal function
// > I'm a white rabbit!
// > Who let the dogs out?!
```