Мышление ООП в JavaScript / Node.js

Я хорошо понимаю концепции ООП и прототипного наследования в JavaScript, но иногда мне интересно, как их использовать в реальных приложениях.

Я возьму в качестве примера простое (истинное) веб-приложение для управления контактами, которое я разместил на GitHub пару месяцев назад.

В основном обработчике находятся функции:

var UserModel = require('../models/userModel.js');
var checkObjectId = new RegExp('^[0-9a-fA-F]{24}$');
var root;

exports.getContacts = function(request, response) {
    var id = JSON.parse(request.params.user)[0];

    // validate
    if (!checkObjectId.test(id)) {
        return res.status(400).json({error: 'Not a user id'});
    }

    UserModel.findById(id, function(err, user) {
        if (err) {
            return console.log(err);
        }

        response.send(user.contacts);
    });
};

exports.addContact = function(request, response) {
    var id = JSON.parse(request.params.user)[0];

    // validate
    if (!checkObjectId.test(id)) {
        return res.status(400).json({error: 'Not a user id'});
    }

    UserModel.findById(id, function(err, user) {
        if (err) {
            return console.error(err);
        }

        var contact = {};

        // avoid to save empty info
        if (request.body.first.length > 1) {contact.first = request.body.first;}
        if (request.body.last.length > 1) {contact.last = request.body.last;}
        if (request.body.mobile.length > 1) {contact.mobile = request.body.mobile;}
        if (request.body.home.length > 1) {contact.home = request.body.home;}
        if (request.body.office.length > 1) {contact.office = request.body.office;}
        if (request.body.email.length > 1) {contact.email = request.body.email;}
        if (request.body.company.length > 1) {contact.company = request.body.company;}
        if (request.body.description.length > 1) {contact.description = request.body.description;}
        if (request.body.keywords.length > 1) {contact.keywords = request.body.keywords;}

        user.contacts.push(contact);

        user.save(function(err) {
            if (err) {
                return console.error(err);
            }

            console.log('contact saved');
            response.send(user.contacts);
        });
    });
};

exports.updateContact = function(request, response) {
    var id = JSON.parse(request.params.user)[0];

    // validate
    if (!checkObjectId.test(id)) {
        return res.status(400).json({error: 'Not a user id'});
    }

    var contact = {
        _id: request.body._id,
        first: request.body.first,
        last: request.body.last,
        mobile: request.body.mobile,
        home: request.body.home,
        office: request.body.office,
        email: request.body.email,
        company: request.body.company,
        description: request.body.description,
        keywords: request.body.keywords
    };

    UserModel.update({_id: id, "contacts._id": request.body._id}, {$set: {"contacts.$": contact}}, function(err, user) {
        if (err) {
            return console.error(err);
        }

        response.sendStatus(user);
    });
};

exports.deleteContact = function(request, response) {
    var id = JSON.parse(request.params.user)[0];

    // validate
    if (!checkObjectId.test(id)) {
        return res.status(400).json({error: 'Not a user id'});
    }

    return UserModel.update({_id: id}, {$pull: {contacts: {_id: request.params.id}}}, function(err, user) {
        if (err) {
            return console.error(err);
        }

        console.log('contact removed');
        console.log(user);
        response.sendStatus(user);
    });
};

Он мало что делает: извлекает данные из БД и возвращает их или берет данные у пользователя и сохраняет их в БД.

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

Тем не менее, этот код выглядит скорее процедурным, как и гипотетическая более сложная версия с отдельными функциями. Как бы это было организовано по принципу ООП и что я от этого выиграю?

Например, могу ли я получить пользу от конструктора User?


person Buzut    schedule 27.08.2015    source источник
comment
Я думаю, что ваш код идеально подходит для разделения на объект. Причина в названии ваших функций, которые интонируют кандидата в объект, т.е. addContact, getContacts, updateContact ... все они используют существительное contact. Поэтому создайте отдельный модуль под названием contact с конструктором функции и в верхней части кода используйте var contact = new Contact();. Тогда проще экстраполировать функциональность в этот новый модуль и взаимодействовать с contact.update () и т. Д.   -  person Data    schedule 27.08.2015
comment
Конечно, я могу это сделать, но поскольку у меня есть обработчик для определенных вещей (например, обработчик контактов, обработчик почты…), я не уверен, что выиграю от выполнения var contact = new Contact();, а затем exports.getContacts = contact.getContact();, или я бы стал?   -  person Buzut    schedule 27.08.2015
comment
в вашем основном файле-обработчике вы можете создать массив, а затем поместить в него новый контакт, var contacts = [];, а затем, когда вы вызовете addContact(), вы сделаете contacts.push(new Contact('stuff'));. Все дело в том, что объект будет обрабатывать все, что есть в вашем основном файле, что является основной целью инкапсуляции.   -  person Data    schedule 27.08.2015
comment
Понятно, я попробую!   -  person Buzut    schedule 27.08.2015
comment
@ user1717735: Вы должны создать метод Contact.fromRequest, а затем поместить туда остальные как методы.   -  person Bergi    schedule 27.08.2015


Ответы (2)


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

var connection = (function() {

  var UserModel = require('../models/userModel.js');
  var notAUser = {error: 'Not a user id'};

  function init(request, response) {   
    var status = validate(JSON.parse(request.params.user)[0]);
    if (!status.id) return response.status(400).json(status);
    return new Connect(request, response, status.id);
  }

  function Connect(request, response, id) {
    this.request = request;
    this.response = response;
    this.id = id;
    this.info = { _id: id, "contacts._id": request.body._id };
  }

  function validate(id) {
    if (!/^[0-9a-fA-F]{24}$/.test(id)) return notAUser;
    else return {id: id};
  }

  Connect.prototype.getContact = function() {} 
  //etc...

  return init;

})();

module.exports = connection;

Затем в вашем реальном приложении

var connection = require("./connection.js");
someAsync(data, function(req, res) {
    var query = connection(req, res); //returned instance of constructor
    query.getContact(someData, callback);    
});
person Daniel Lizik    schedule 27.08.2015
comment
Я люблю этот узор. Таким образом, я сначала был озадачен, поскольку конструктор является выражением IEF. Я считаю более понятным объявить правильную функцию, например function connection() {…}, а затем сделать module.exports = connection(); - person Buzut; 30.08.2015
comment
Кстати, зачем называть это подключением? Для меня это скорее контактная фабрика ... - person Buzut; 30.08.2015
comment
@ user1717735 распространенным шаблоном API является создание объекта подключения, к которому прикреплены методы RESTFUL. Таким образом, логика вашего приложения не тесно связана для создания контакта, а для создания [чего-то], и это может быть контакт, пользователь, продукт, что угодно. Ознакомьтесь с mysql api для nodejs, чтобы увидеть пример github.com/felixge/node-mysql - person Daniel Lizik; 30.08.2015
comment
Спасибо за образец. Будет ли у меня объект подключения, который будет иметь как контактные, так и пользовательские методы REST? Или это два разных объекта, которые называются связью, потому что они обрабатывают REST-методы объектов? В любом случае я не управляю пользователями и контактами в одном обработчике… - person Buzut; 30.08.2015

Я бы начал с инкапсуляции request и response, поскольку они нужны каждому методу. Нравится:

var contact = function (request, response) {
    return {
        add: add
    }

    function add() {
        // add() gets access request and response for free
    }
};

ИЛИ, если вам нравится новый оператор:

function Contact(request, response) {
    this.request = request;
    this.response = response;
}

Contact.prototype.add = function () {
    this.request;
}

Затем переместите повторяющийся код и обратные вызовы в частные методы, которые вы можете повторно использовать внутри объекта.

person beautifulcoder    schedule 27.08.2015
comment
Спасибо вам обоим, beautifulcoder и Daniel_L. Мне очень жаль, что я долго не отвечал. Я хотел подумать обо всем этом, чтобы лучше понять это. Я постараюсь избегать new насколько это возможно, поскольку это подвержено ошибкам, и я буду использовать заводскую функцию. - person Buzut; 30.08.2015
comment
Однако основное различие между этими двумя способами заключается в том, что первая функция добавляет add() в сам экземпляр, а вторая добавляет его к своему прототипу. - person Buzut; 30.08.2015