封装对象绘制/写入功能的设计模式选型咨询
方案1:拆分现有对象到不同文件
完全可行,这是渐进式重构的最优选择,适合不想大动现有稳定代码的场景。
具体做法:把row里的渲染相关方法(比如生成单选框、图片DOM的逻辑)抽离到单独的row_displayBehaviours.js,collection的表头/表格结构生成逻辑抽离到collection_displayBehaviours.js,通过ES6模块化(import/export)把这些展示行为注入到原对象中——比如原Row类导入渲染方法,在需要生成HTML时调用,而不是把渲染逻辑硬编码在业务类里。
优势:不用重构核心数据逻辑,不会影响现有功能,能边拆边测,逐步降低row文件的代码量。
注意事项:避免把所有展示逻辑堆在一个大文件里,最好按功能细分(比如「表单控件渲染」「图片展示」「文件上传UI」各拆成小模块),防止依赖混乱。
方案2:独立的HTML构造类(htmlTableConstructor.js)
这个方案更彻底,是关注点分离的标准实践,完全切断业务逻辑与渲染逻辑的耦合,非常推荐长期维护。
核心思路:
Collection只负责数据的远程交互(拉取、提交)、批量管理(增删改查)、数据校验;Row只负责单条数据的状态管理(字段值、校验结果、交互状态);HtmlTableConstructor专门接收Collection提供的标准化数据(比如每个字段的类型、值、校验状态),生成<table>、<tr>、<td>等DOM结构,甚至可以把不同控件的渲染拆成更小的子类(比如CheckboxRenderer、ImageRenderer),让构造类依赖这些子渲染器,扩展性更强。
交互逻辑处理:渲染类只负责生成DOM,点击复选框、文件上传等交互事件,通过事件委托或专门的交互模块监听,触发Row/Collection的数据更新,而非让渲染类直接操作业务对象。
优势:职责边界清晰,后续改UI只动渲染类,改数据逻辑只动业务类,极大降低维护成本。
注意事项:要定义好数据契约——Collection给渲染类的输出格式必须统一,避免渲染类直接依赖Row的内部属性,防止隐形耦合。
其他可行方案
1. 模板引擎+组件化拆分
把HTML渲染逻辑从JS代码中抽离成模板文件,比如用Handlebars/EJS这类轻量模板引擎:
<!-- row-template.hbs --> <tr data-row-id="{{rowId}}"> <td>{{id}}</td> <td><input type="checkbox" {{#if isChecked}}checked{{/if}}></td> <td><img src="{{imageUrl}}" alt="item image" class="row-image"></td> </tr>
然后在JS中编译模板,传入Row的数据生成HTML字符串。交互逻辑可以通过事件委托处理,数据校验和管理仍留在Collection/Row中。
这种方式让UI结构更直观,适合复杂的展示布局,后续改UI不用动业务JS代码。
2. 轻量数据驱动(MVVM思路)
借鉴MVVM的核心思想,实现数据与视图的双向绑定:给Row的字段添加getter/setter,当数据变化时自动更新对应的DOM元素;反过来,DOM的交互事件(比如输入框修改、复选框点击)自动同步Row的数据。
可以自己实现简单的绑定逻辑,或者用Knockout.js这类轻量库。这种方式彻底分离数据与视图,后续维护效率极高,但需要改造现有Row的结构,有一定的学习和改造成本。
3. 策略模式拆分多类型渲染
针对Row中不同数据类型的渲染逻辑,用策略模式快速拆分:
const renderStrategies = { checkbox: (value, rowId) => `<td><input type="checkbox" ${value ? 'checked' : ''} data-row-id="${rowId}"></td>`, image: (url) => `<td><img src="${url}" class="row-image"></td>`, text: (value) => `<td>${value}</td>`, // 其他数据类型的渲染策略 };
然后Row只需要调用renderStrategies[field.type](field.value, this.id)来生成对应<td>,把所有渲染逻辑都转移到策略对象中,不用大改现有Row的结构,快速实现代码瘦身。
内容的提问来源于stack exchange,提问作者Derek

