【问题标题】:Composition over inheritance, what is a nicer way to add additional functionality to a view without resorting to inheritance组合优于继承,在不诉诸继承的情况下向视图添加附加功能的更好方法是什么
【发布时间】:2016-11-03 12:45:32
【问题描述】:

过去我读过很多关于可组合性优于继承的文章,我完全认同这个概念,并在我的代码中大量使用了这一原则。

但是,我在日常工作中遇到了一些问题,继承往往会渗透到视图中,并且我很难看到如何实现更可组合的东西(我在日常使用 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);

有没有更可组合的方式来写这个?或者这是一种通过继承中断是正确的情况?

【问题讨论】:

    标签: javascript inheritance backbone.js composition


    【解决方案1】:

    对于这种特定情况,继承效果很好。关于可组合性优于继承的论点是徒劳的,请使用最适合当前情况的方法。

    但是,仍然可以进行改进以简化继承。当我创建一个要继承的 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;
        },
    
    });
    

    【讨论】:

      猜你喜欢
      • 2012-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-28
      • 1970-01-01
      • 1970-01-01
      • 2013-01-03
      • 2011-02-17
      相关资源
      最近更新 更多