2016-11-03 1 views
4

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

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

Возьмите этот гипотетический пример, где мы имеем страницу типа электронной коммерции с множественным Product зрения, каждый представляющий коллекцию basketable опций для конкретного продукта:

var ProductView = (function(Backbone, JST) { 
    'use strict'; 

    return Backbone.View.extend({ 
    className: 'product', 
    template: JST['application/templates/product'] 

    initialize: function(options) { 
     this.options = options || {}; 
     this.collection.fetch(); 
     this.listenTo(this.collection, 'loaded', this.render); 
    }, 

    render: function() { 
     this.$el.html(
     this.template(this.collection) 
    ); 

     return this; 
    }, 
    }, { 
    create: function(el) { 
     var endpoint = '/api/options/' + el.getAttribute('data-basket-id') + '/' + el.getAttribute('data-product-id'); 

     new ProductView({ 
     el: el, 
     collection: new ProductCollection(null, { url: endpoint }) 
     }); 
    } 
    }); 
})(Backbone, JST); 

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

var InsuranceProductView = (function (_, ProductView) { 
    'use strict'; 

    return ProductView.extend({ 
    consentTemplate: JST['application/templates/product/insurance_consent'], 

    initialize: function (options) { 
     this.listenTo(this.model, 'change:selected', function (model) { 
     if (!model.get('selected')) { 
      this.removeMessage() 
     } 
     }); 

     ProductView.prototype.initialize.apply(this, arguments); 
    }, 

    events: function() { 
     return _.extend({}, ProductView.prototype.events, { 
     'change input[type=radio]': function() { 
      this.el.parentElement.appendChild(this.consentTemplate()); 
     }, 
     'change .insurance__accept': function() { 
      ProductView.prototype.onChange.apply(this); 
     }, 
     }); 
    }, 

    removeMessage: function() { 
     var message = this.el.parentElement.querySelector('.insurance__consent'); 
     message.parentNode.removeChild(message); 
    }, 
    }); 
})(_, ProductView); 

Есть ли более сложный способ написать это? Или это ситуация, когда нужно покончить с наследством?

ответ

0

Для этого конкретного случая наследование работает хорошо. Аргумент о способности к наследованию бесполезен, используйте то, что лучше всего подходит для ситуации.

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

Один из способов достижения этого - положить инициализацию родителя в конструктор, оставив функцию initialize всем ребенку. И то же самое с хешем events.

var ProductView = Backbone.View.extend({ 
    className: 'product', 
    template: JST['application/templates/product'], 
    events: {}, 

    constructor: function(options) { 
     // make parent event the default, but leave the event hash property 
     // for the child view 
     _.extend({ 
      "click .example-parent-event": "onParentEvent" 
     }, this.events); 

     this.options = options || {}; 
     this.collection.fetch(); 
     this.listenTo(this.collection, 'loaded', this.render); 

     ProductView.__super__.constructor.apply(this, arguments); 
    }, 

    /* ...snip... */ 
}); 

И вид становится ребенок:

var InsuranceProductView = ProductView.extend({ 
    consentTemplate: JST['application/templates/product/insurance_consent'], 

    events:{ 
     'change input[type=radio]': 'showConsent', 
     'change .insurance__accept': 'onInsuranceAccept' 
    } 

    initialize: function(options) { 
     this.listenTo(this.model, 'change:selected', function(model) { 
      if (!model.get('selected')) { 
       this.removeMessage() 
      } 
     }); 
    }, 

    showConsent: function() { 
     // I personally don't like when component go out of their root element. 
     this.el.parentElement.appendChild(this.consentTemplate()); 
    }, 

    onInsuranceAccept: function() { 
     InsuranceProductView.__super__.onChange.apply(this); 
    }, 

    removeMessage: function() { 
     var message = this.el.parentElement.querySelector('.insurance__consent'); 
     message.parentNode.removeChild(message); 
    }, 
}); 

Кроме того, Backbone extend добавляет __super__ свойство с прототипом родителя. Мне нравится использовать это, потому что я могу изменить родительский класс, не беспокоясь об использовании его прототипа где-то в функции.


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

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

var FoodMenu = Backbone.View.extend({ 
    template: '<div class="food-search"></div><div class="food-search-list"></div>', 

    // abstracting selectors out of the view logic 
    regions: { 
     search: ".food-search", 
     foodlist: ".food-search-list", 
    }, 

    initialize: function() { 

     // build your view with other components 
     this.view = { 
      search: new TextBox({ 
       label: 'Search foods', 
       labelposition: 'top', 
      }), 
      foodlist: new FoodList({ 
       title: "Search results", 
      }) 
     }; 
    }, 

    render: function() { 
     this.$el.empty().append(this.template); 

     // Caching scoped jquery element from 'regions' into `this.zone`. 
     this.generateZones(); 
     var view = this.view, 
      zone = this.zone; 
     this.assign(view.search, zone.$search) 
      .assign(view.foodlist, zone.$foodlist); 

     return this; 
    }, 

}); 
Смежные вопросы