组合优于继承,什么是在不求助于继承的情况下向视图添加附加功能的更好方法
Composition over inheritance, what is a nicer way to add additional functionality to a view without resorting to inheritance
我过去读过很多关于可组合性优于继承的内容,我完全相信这个概念,并在我的代码中大量使用了这个原则。
但是,我 运行 在我的日常工作中遇到继承往往会渗透到视图中的问题,我努力想看看我如何才能实现更具可组合性的东西(没有帮助的事实是我在日常工作中使用 Backbone)。这些往往是当我想使用现有 Backbone 视图的所有功能,同时在顶部添加一些附加功能时。
以这个假设为例,我们有一个电子商务类型的页面,其中包含多个 Product
视图,每个视图代表特定产品的一组可购物选项:
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);
然后假设我们要显示一些需要使用确认框提示访问者的产品(比方说出于保险原因,该特定产品必须在销售时购买保险,因此我们需要在用户访问时提示他们将其添加到他们的购物篮):
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);
有没有更可组合的写法?还是在这种情况下通过继承中断是正确的?
对于这种特定情况,继承效果很好。关于继承的可组合性的争论是徒劳的,使用最适合手头情况的东西。
但是,仍然可以进行改进以简化继承。当我制作要继承的 Backbone class 时,我尝试将其设置为 child class.
实现这一点的一种方法是将 parent 的初始化放入构造函数中,将 initialize
函数全部留给 child。 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... */
});
child 视图变为:
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
以 parent 的原型添加了一个 __super__
属性。我喜欢使用它,因为我可以更改 parent class 而不必担心在函数中某处使用其原型。
我发现在构建具有较小组件的视图时,合成效果非常好。
下面的视图几乎什么都没有,除了较小组件的配置,每个组件都处理大部分复杂性:
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;
},
});
我过去读过很多关于可组合性优于继承的内容,我完全相信这个概念,并在我的代码中大量使用了这个原则。
但是,我 运行 在我的日常工作中遇到继承往往会渗透到视图中的问题,我努力想看看我如何才能实现更具可组合性的东西(没有帮助的事实是我在日常工作中使用 Backbone)。这些往往是当我想使用现有 Backbone 视图的所有功能,同时在顶部添加一些附加功能时。
以这个假设为例,我们有一个电子商务类型的页面,其中包含多个 Product
视图,每个视图代表特定产品的一组可购物选项:
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);
然后假设我们要显示一些需要使用确认框提示访问者的产品(比方说出于保险原因,该特定产品必须在销售时购买保险,因此我们需要在用户访问时提示他们将其添加到他们的购物篮):
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);
有没有更可组合的写法?还是在这种情况下通过继承中断是正确的?
对于这种特定情况,继承效果很好。关于继承的可组合性的争论是徒劳的,使用最适合手头情况的东西。
但是,仍然可以进行改进以简化继承。当我制作要继承的 Backbone class 时,我尝试将其设置为 child class.
实现这一点的一种方法是将 parent 的初始化放入构造函数中,将 initialize
函数全部留给 child。 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... */
});
child 视图变为:
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
以 parent 的原型添加了一个 __super__
属性。我喜欢使用它,因为我可以更改 parent class 而不必担心在函数中某处使用其原型。
我发现在构建具有较小组件的视图时,合成效果非常好。
下面的视图几乎什么都没有,除了较小组件的配置,每个组件都处理大部分复杂性:
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;
},
});