繁体   English   中英

继承的组合,在不诉诸继承的情况下向视图添加附加功能的更好方法

[英]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类时,我会尝试将它设置为子类的不可见。

实现这一目标的一种方法是将父进程的initialize放入构造函数中,将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;
    },

});

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM