Подтвердить что ты не робот

Я пытаюсь подражать интерфейсу/абстрактному классу в неправильной практике javascript

Я пишу код в Node.js, который выскочил на меня, как лучше структурированный с использованием шаблона стратегии. Исходя из .Net, я бы создал интерфейс, остальные были основаны на нем и перешли оттуда, в JavaScript это не так понятно.

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

Мой базовый класс

QueryStrategy = function () {

};

QueryStrategy.prototype.create = function(){
throw new Error("Not Implemented");
}

module.exports = QueryStrategy;

Реализация 1

var util = require('util');
var QueryStrategy = require('./queryStrategy');

SelectQueryStrategy = function (query) {
    this.parameters = query.parameters || [];
    this.entity = query.entity || '';
};

util.inherits(SelectQueryStrategy, QueryStrategy);

SelectQueryStrategy.prototype.create = function () {
    var self = this,
        params = self.parameters,
        paramList = self.parameters.length >= 1 ? '' : '*';

    for (var i = 0; i < params.length; i++) {
        var suffix = i === (params.length - 1) ? '' : ', ';
        paramList = paramList + params[i].key + suffix;
    }

    return util.format("SELECT %s FROM %s", paramList, self.entity);
};

module.exports = SelectQueryStrategy;

В настоящее время база не имеет общих функций или свойств. Общие функции придут, свойства, из-за прототипного поиска цепи я не видел смысла добавления "общих" свойств, поскольку они перезаписываются созданными экземплярами (пожалуйста, если это неправильно, сообщите мне).

Является ли это приемлемым подходом или я должен игнорировать наследование в этом случае. Там может быть какая-то проверка типа, и было бы проще, если бы вы могли сделать вывод этого типа (т.е. Все они должны быть типа QueryStrategy), но я не хочу, чтобы мой .Net-предрассудок занял здесь.

Альтернативный подход

Во-первых, я не пытаюсь продавать книги здесь, я просто много читаю о предмете с книгами, которые у меня есть, но хочу отдать должное там, где это необходимо.

Основываясь на нескольких комментариях, правильно сказать, что вывод типа на самом деле не имеет никакого значения здесь и, возможно, должен быть оставлен до Duck Typing, чтобы проверить, и, возможно, попытаться сложить что-то в этом нет. определенный на языке, не лучший подход. Я прочитал в Ady Osmani шаблоны Javascript об интерфейсах, хотя моя реализация была не такой, и хотя использование интерфейса приемлемо, вероятно, это не нужно.

Может быть проще сохранить стратегический подход, но с другой реализацией, описанной в шаблонах Javascript Stoyan Stefanov, где у вас есть одна реализация и на основе какой-то конфигурации вы знаете, какую стратегию использовать.

Псевдо выборка

QueryBuilder = function () {
   this.types = [];
}

QueryBuilder.prototype.create = function (type, query) {
    var strategy = types[type];
    strategy.create(query);
};

QueryBuilder.types.Select = {

    create: function (query) {
        params = query.parameters,
        paramList = query.parameters.length >= 1 ? '' : '*';

        for (var i = 0; i < params.length; i++) {
            var suffix = i === (params.length - 1) ? '' : ', ';
            paramList = paramList + params[i].key + suffix;
        }

        return util.format("SELECT %s FROM %s", paramList, query.entity);
    }
};

Это может быть более чистым способом, но я уверен, что я должен добавить типы к прототипу, но я думаю, что в этом простом случае это не понадобится, но в более сложном сценарии вы может иметь много стратегий, и в одном файле может потребоваться абстрагироваться.

- это более приемлемый подход?

Что я закончил с

Я огляделся и попытался понять больше о Duck typing, "за" и "против" и т.д. и попытался упростить мой дизайн, основываясь на некоторых комментариях и ответах, которые я получил. Я придерживался стратегического подхода, так как каждая стратегия определяла ту же функцию (ы), которая в значительной степени инкапсулировала то, что варьировалось во всех реализациях, и давало им общий контракт из-за отсутствия лучшего термина.

Одной из вещей, которые были изменены, был базовый класс/интерфейс, который, как правильно указал, не делал ничего конструктивного, удалялся, каждая стратегия независима (а значит, не 100% в классическом представлении шаблона) они, как указано, просто определяют та же функция создания.

От места, где вложенные элементы ifs и переключатели некогда принадлежали, был заменен одним коммутатором, который решает, какую стратегию запроса использовать в зависимости от типа запрашиваемого типа запроса. В дальнейшем это может быть переопределено в factory, но пока оно служит своей цели:

Query.prototype.generate = function () {

var self = this,
    strategy = undefined;

switch (this.queryType) {
    case "SELECT":
        strategy = new Select(self);
        break;
    case "INSERT":
        strategy = new Insert(self);
        break;
    case "UPDATE":
        strategy = new Update(self);
        break;
    case "DELETE":
        strategy = new Delete(self);
        break;
}

return strategy.create();
};

Каждая стратегия проходит независимую проверку, и я чувствую, что ее легче поддерживать, как будто что-то не удается, она не срабатывает в одном месте, и расследование будет проще на основе модульных тестов. Очевидно, что есть компромисс, больше файлов и немного более сложная структура... мы увидим, что произойдет.

4b9b3361

Ответ 1

Это приемлемый подход, но он никоим образом не принесет вам никакой пользы и может затруднить его выполнение. Шаблоны стратегии действительно полезны только для языков со статической типизацией. Вы можете заглянуть в TypeScript, как упоминал еще один комментатор.

В JavaScript вы будете больше полагаться на "Duck Typing"... Если это похоже на утку, пахнет уткой, это, вероятно, утка.

http://en.wikipedia.org/wiki/Duck_typing

Ответ 2

Я всегда пропускал интерфейсы в Javascript тоже. Мне нравится проверять типы с помощью instanceof, но, как отметил Райан, "проверка типов" в Javascript основана на идее, что если вы передадите неправильный объект, то он может потерпеть неудачу, вызвав для него несуществующий метод. Это работает в большинстве случаев.

Грустная часть в том, что язык плохо спроектирован (или вообще не спроектирован), поэтому нативные функции, включающие примитивы, молча терпят неудачу, вместо того, чтобы генерировать исключение, как вы ожидаете от верхней логики. Например, console.log(Math.pow(123, "x")) будет регистрировать NaN (не число) вместо выдачи ошибки. Поэтому для чисел лучше выполнять проверку типа с помощью typeof x == "number" иначе вы можете потратить много часов на отладку кода.

Если вам действительно необходимо проверить интерфейсы по какой-то причине, то вы можете более или менее проверить интерфейс объекта, перечислив его методы, и если вы хотите быть тщательным, вам нужно использовать toString для каждого из методов и получить список аргументов тоже, У нас нет ничего лучше даже в современных браузерах или node.js. Я думаю, что-то вроде этого должно быть достаточно:

var Duckable = new Interface({fly: function (whereTo){}});

Duckable.expected({fly: function (whereTo){}});
    // pass
Duckable.expected({fly: function (whereTo){}, blah: function (){}});
    // pass
Duckable.expected({});
    // InterfaceMismatch: The fly method is not defined.
Duckable.expected({fly: function (){}});
    // InterfaceMismatch: The fly method should have 1 argument(s) instead of 0.
Duckable.expected({fly: function (x){}});
    // InterfaceMismatch: The argument 1 of the fly method should be "whereTo" instead of "x".
Duckable.expected({fly: function (whereTo, x){}});
    // InterfaceMismatch: The fly method should have 1 argument(s) instead of 2.

A - не готов к производству - реализация с использованием vanilla js:

function Interface(descriptor){
    this.descriptor = descriptor;
}

Interface.prototype.expected = function (object){
    for (var method in this.descriptor){
        if (!(object[method] instanceof Function))
            throw new InterfaceMismatch('The ${method} method is not defined.');
        var expectedArguments = this.listArguments(this.descriptor[method]);
        var actualArguments = this.listArguments(object[method]);
        if (actualArguments.length !== expectedArguments.length)
            throw new InterfaceMismatch(
                'The ${method} method should have ${expectedArguments.length} argument(s)' +
                ' instead of ${actualArguments.length}.'
            );
        for (var index in expectedArguments)
            if (actualArguments[index] !== expectedArguments[index])
                throw new InterfaceMismatch(
                    'The argument ${Number(index)+1} of the ${method} method' +
                    ' should be "${expectedArguments[index]}" instead of "${actualArguments[index]}".'
                );
    }
};

Interface.prototype.listArguments = function (func){
    var code = String(func);
    var argumentListFinder = /^function[^(]+\(([^)]*)\)/;
    var whiteSpaceFinder = /\s+/g;
    var match = code.match(argumentListFinder);
    if (!match)
        throw new Error("Unknown function syntax.");
    var argumentsDefinition = match[1].replace(whiteSpaceFinder, "");
    if (argumentsDefinition === "")
        return [];
    var argumentDelimiter = ",";
    return argumentsDefinition.split(argumentDelimiter);
};

function InterfaceMismatch(message){
    this.message = message;
}
InterfaceMismatch.prototype = Object.create(Error.prototype);
InterfaceMismatch.prototype.name = "InterfaceMismatch";
InterfaceMismatch.prototype.constructor = InterfaceMismatch;