如何开发高度可定制的产品

您是否听说过:“我们非常喜欢您的产品……除了一些小细节”。 然后,CIO推出了一系列其他“必备”要求的清单,其中有数百个要求添加到您的惊人产品中。 您是否听说过,甚至说过:“团队,我们即将签署一份利润丰厚的合同,但是……”? 然后,客户对附加功能的愿望清单使开发人员感到头疼。

木制拼图 那么,如何使产品远离客户的潜在危险想法,同时又使他们满意呢? 对于专门设计为以特定方式运行但现在具有大量附加组件的产品,如何保持最高性能水平呢? 为已开发的解决方案提供不间断且出色的支持的基本需求将带来多少挑战?

在商业世界中,产品定制已成为越来越令人期望的要求,并且响应于这种客户需求,已经发展了许多通用实践。 您可以在下面找到典型方法的概述。 如果您已经熟悉它们,那么欢迎您直接滚动至“ 扩展方法 ”部分,并了解我们如何以我们认为更有效的方式解决这些挑战。

一体

定制的最直接,最明显的解决方案是实施一个核心产品所需的所有内容,然后采用“ 功能切换 ”技术来满足每个特定客户的需求。

多合一方法的主要优点是保留了整体产品,这对于某些类型的产品似乎是一种很好的方式,这些产品通常可以满足业务需求,而无需进行广泛的定制。

这种方法的自然局限性隐藏在“不需要太多定制”的假设中。 通常,产品开发就是从这种信念开始的,但是经过多次交付,您才真正意识到需要多少特定于客户的功能。 陷入困境的情况并不少见。 拒绝定制开发并可能失去客户,或者将源代码转换为具有针对单个客户的功能的垃圾箱 ,这些功能对于大多数最终用户而言可能是无用的。

您会选择哪个选项? 显然,在艰难和艰难的地方之间进行选择并不是成功的方法。

简介:仅当您确定需要罕见且有限的定制时,“多合一”方法才是合适的选择。 否则,您将面临可管理和可支持产品与客户满意度之间的选择。 让我引用杰里·加西亚(Jerry Garcia)的话:“不断选择两种邪恶中的较小者仍然是选择邪恶”。

分枝

如果重大定制是交付的“必不可少”部分,则不能采用“多合一”技术。 还有另一种简单的方法- 分支 。 您只需分支产品代码库,然后单独进行更改即可。

分支“多合一”进行比较,最大的优点是,没有适用于自定义范围的限制。 您使用单独的分支来满足不同客户的特定要求,并避免在同一代码库中混合使用所有功能。

但是,它的另一面可能会在产品发展方面变成死胡同。 显然,产品分支是主要的开发空间:大多数错误修正,改进和新功能都首先被应用到产品中。 因此,需要频繁合并以使所有定制分支与核心产品保持同步。 只要原始产品源代码不受定制分支的影响,合并是一项简单的操作,否则,合并将变得非常耗时,并可能导致不可避免的回归错误。

如果您仅限于很少的自定义分支,则此方法仍然可以使用。 但是,随着交付实例数量的增加,面临“通过合并实施酷刑”的可能性迫在眉睫。

简介: 分支方法无疑是非常灵活和直接的-产品的任何部分都可以修改。 但是,交付后阶段可能非常费力,随着时间的推移变得更加困难,并且不太可能导致交付大量可管理的定制分支。

实体-属性-价值模型

实体-属性-价值模型 (又名对象-属性-价值模型,垂直数据库模型和开放式架构)是众所周知的且被广泛使用的数据模型。 EAV支持动态实体属性,通常与标准关系模型并行使用。

从产品化的角度来看,使用EAV的主要优点是您可以“按原样”交付产品,然后通过在运行时添加所需的属性来调整数据模型,从而保持源代码的整洁。

与以往一样,还有一个缺点:

  • 有限的适用性–仅通过允许向实体添加属性来限制EAV模型,然后根据预编程的逻辑将其自动嵌入到UI中。
  • 额外的数据库服务器负载–垂直数据库设计通常成为企业应用程序的瓶颈,企业应用程序通常使用大量与它们相关的实体和属性进行操作。
  • 最后,如果没有复杂的报告引擎,就无法想象企业系统。 EAV模型具有“垂直”数据库结构,因此有可能带来许多麻烦。

简介: 实体-属性-价值模型在某些情况下具有很大的价值,例如当需要提供通过具有附加信息性数据而实现的灵活性时,该信息性数据未在业务逻辑中明确使用。 换句话说,EAV具有良好的适度性,例如,除了标准的关系模型和插件体系结构之外

插件架构

插件体系结构是最流行且功能最强大的方法之一,其中功能逻辑作为单独的工件(称为插件)保存。 要覆盖现有的开箱即用行为并运行插件,必须在产品源代码中定义“定制点”(即扩展点)。 “定制点”是源代码中的某个位置,应用程序在该位置上浏览附加的插件,以检查插件是否包含要在此处运行的替代实现。 插件体系结构的一种变化是外部脚本。 在实现功能实现并将其作为脚本存储在外部时。 脚本调用也由预定义的“定制点”控制。

使用这种插件方法,可以使产品“清洁”特定的客户要求,“按原样”交付核心产品,并根据插件或脚本的要求自定义行为。 这种方法的另一个优点是管理完善的更新过程。 产品和插件功能的完全分离使彼此之间可以独立更新。

当然存在限制:主要限制是不可能完全知道将来可能会提出哪些自定义要求。 因此,只能猜测应该在何处嵌入“定制点”。 当然,这些可以作为缓解“防万一”计划的零星散布在各处,但这将导致代码可读性差,硬调试以及复杂的支持。

简介:如果易于预测“定制点”,那么插件架构确实可以工作,但是请注意,“定制点”之间的定制是不可能的。

扩展方法

我们在企业软件开发平台CUBA中实施了独特的方法。 正如我们上一篇文章所述 ,CUBA是一种非常实用的活生物体,它是通过开发人员驱动的演变过程创建的。 因此,根据我们在现成产品上的丰富经验,我们提出了两个最终要求:

  • 客户特定的代码应与核心产品代码完全分开
  • 产品代码的每个部分都应该可以修改

我们设法满足了这些要求,并通过我们的“扩展”机制实现了更多目标。

CUBA扩展

扩展是一个单独的CUBA项目,它继承了基础项目(即您的核心产品)的所有功能,并将其用作库。 显然,这使开发人员可以实现全新的功能而不会影响父项目,但是由于使用了“ 开放继承”模式和特殊的CUBA工具,您还可以覆盖父项目的任何部分。 总之,扩展是实现本文开头讨论的数百个“少量次要细节”的地方。

实际上,每个CUBA项目都是CUBA平台本身的扩展-因此它可以覆盖任何平台功能。 我们自己采用了这种方法,以从核心平台中分离出一些现成的功能(全文搜索,报告,图表等)。 因此,如果您在项目中需要它们,则只需将它们添加为父项目-就是这样,是多重继承!

您可以用相同的方式构建分层的定制模型 。 这听上去很复杂,但是很合理。 让我举一个真实的例子: Sherlock –是Haulmont完整的出租车管理解决方案,支持从预定,派遣到应用程序和计费的出租车业务的方方面面。 该解决方案涵盖了客户业务的许多不同方面,其中许多是与位置相关的。 例如,所有英国出租车公司都具有相同的法律法规,但是其中许多法规不适用于美国,反之亦然。 显然,我们不想在核心产品中实施所有这些规定,因为:

  • 这是“特定于操作区域”的功能
  • 当地法规可能会对不同国家的出租车队运营产生完全不同的影响
  • 一些客户根本不需要监管控制

因此,我们组织了多级扩展层次结构:

  1. 核心产品包含出租车业务的通用功能
  2. 定制的第一层实现了区域特性
  3. 第二层定制涵盖了客户的愿望清单(如果有的话!)

古巴平台_产品方案

干净利落。

如您所见,通过使用扩展,您既不需要分支也不需要在核心产品中集成所有需求 ,因此代码保持简洁且易于管理。 听起来真是太好了,所以让我们看看它是如何工作的!

向现有实体添加新属性

假定我们具有用户实体的产品定义,该产品定义由两个字段组成:登录名和密码:

@Entity(name = "product$User")
@Table(name = "PRODUCT_USER")
public class User extends StandardEntity {@Column(name = "LOGIN")protected String login;@Column(name = "PASSWORD")protected String password;//getters and setters
}

现在,我们的一些客户提出了一项附加要求,即向用户添加“家庭住址”字段。 为此,我们在扩展中扩展User实体:

@Entity(name = "ext$User")
@Extends(User.class)
public class ExtUser extends User {@Column(name = "ADDRESS", length = 100)private String address;public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}
}

您可能已经注意到,除@Extends以外的所有注释都是通用的JPA注释。 @Extends属性是CUBA引擎的一部分,它甚至在整个产品功能上都将User实体全局替换为ExtUser

使用@Extends属性,我们强制平台执行以下操作:

  1. 始终创建“最新子级”类型的实体
User user = metadata.create(User.class); //ExtUser entity will be created
  1. 在执行之前转换所有JPQL查询,以便它们始终返回“最新子集”
select u from product$User u where u.name = :name //returns a list of ExtUsers
  1. 始终在关联实体中使用“最新子项”
userSession.getUser(); //returns an instance of ExtUser type

换句话说,如果声明了扩展实体,则基础实体将在整个解决方案(产品和扩展)中被放弃,并且被扩展实体全局覆盖。

屏幕定制

因此,我们通过添加地址属性扩展了User实体,现在希望更改能够反映在用户界面中。 首先,让我们看一下原始(产品)屏幕声明:

<windowdatasource="userDs"caption="msg://caption"class="com.haulmont.cuba.gui.app.security.user.edit.UserEditor"messagesPack="com.haulmont.cuba.gui.app.security.user.edit"><dsContext><datasourceid="userDs"class="com.haulmont.cuba.security.entity.User"view="user.edit"></datasource></dsContext><layout><fieldGroup id="fieldGroup" datasource="userDs"><column><field id="login"/><field id="password"/></column></fieldGroup><iframe id="windowActions" screen="editWindowActions"/></layout></window>

如您所见,CUBA屏幕描述符表示为普通XML。 显然,我们可以简单地在扩展名中重新声明整个屏幕描述符,但这意味着要复制粘贴其中的大部分内容。 因此,如果将来产品屏幕中发生某些更改,我们将必须手动将这些更改复制到扩展屏幕中。 为避免这种情况,CUBA引入了屏幕继承机制,您所需要的只是描述对屏幕的更改:

<window extends="/com/haulmont/cuba/gui/app/security/user/edit/user-edit.xml"><layout><fieldGroup id="fieldGroup"><column><field id="address"/></column></fieldGroup></layout></window>

您可以使用extends属性定义祖先屏幕,并且仅描述要更改的主题。

这个给你! 最后,让我们看一下结果:

cuba-platform_admin

修改业务逻辑

为了实现业务逻辑修改,CUBA平台使用Spring框架,该框架构成了平台基础结构的核心部分。

例如,您有一个中间件组件来执行价格计算过程:

@ManagedBean("product_PriceCalculator")
public class PriceCalculator {public void BigDecimal calculatePrice() { //price calculation}
}

要覆盖价格计算实现,我们只需要执行两个简单的操作。

首先,扩展产品类并覆盖相应的过程:

public class ExtPriceCalculator extends PriceCalcuator {@Overridepublic void BigDecimal calculatePrice() { //modified logic goes here}
}

最后,使用产品Bean标识符在Spring配置中注册新类:

<bean id="product_PriceCalculator" class="com.sample.extension.core.ExtPriceCalculator"/>

现在, PriceCalculator注入将始终返回扩展类实例。 因此,修改后的实现将在整个产品中使用。

扩展基础产品版本

随着核心产品的发展和新版本的发布,您最终将决定将扩展程序升级到最新的产品版本。 过程非常简单:

  1. 在扩展中指定基础产品的新版本。
  2. 重建扩展名:
    1. 如果扩展是基于产品API的稳定部分构建的,则可以运行它。
    2. 如果对产品API进行了一些重大修改,并且这些修改与扩展中实现的自定义重叠,则有必要在扩展中支持新产品API。

大多数情况下,产品API在每次更新之间都不会发生重大变化,尤其是在次要版本中。 但是,即使API发生了“大爆炸”,产品通常也至少保持几个未来版本的向下兼容性,并且旧的实现被标记为“已弃用”,从而允许将所有扩展迁移到最新的API。

结论

作为一个简短的摘要,我想以表格形式说明比较分析的结果:

一体 分枝 电动汽车 外挂程式 CUBA扩展
独立于架构 + +
动态定制 + +/-
业务逻辑定制 + + +/- +
数据模型定制 + + + +/- +
用户界面定制 + + +/- +/- +
代码质量和可读性 +/- +/- +/- +
不影响性能 + + + +
软件回归的风险 介质 介质
长期支持的复杂性 极端的 极端的 介质 介质
可扩展性

如您所见,扩展方法功能强大,但是它缺少的一件事就是能够动态地微调系统(动态定制)。 为了克服这个问题,CUBA还提供了对实体属性值模型和插件/脚本方法的全面支持。

我希望您会发现此概述很有用,当然您的反馈意见也将受到赞赏。

翻译自: https://www.javacodegeeks.com/2015/07/how-to-develop-a-highly-customizable-product.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/357507.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端使用正则表达式从接口地址栏取值并将对应的值展示在页面上

业务场景&#xff0c;APP分享出链接&#xff0c;通过get请求接口方式&#xff0c;展示对应的字段。 需求图&#xff1a; 获取某单号 var name"";//姓名var idNo"";//证件号var applicationNogetParams("applicationNo");//号码window.onload fu…

科学计算机看电量,解密:关于手机电量为1%是如何科学的算出来的?

本文的话题也许是很多人的疑问&#xff0c;对于手机显示电量是怎么推算出来的&#xff0c;到底显示1%的时候还有没有电呢&#xff1f;这是一个直击灵魂的问题——有时候手机最后1%的电能用很久&#xff0c;有时候却只能用一瞬间。给人留下这个印象&#xff0c;有一些心理层面的…

node源码详解(四) —— js代码如何调用C++的函数

本作品采用知识共享署名 4.0 国际许可协议进行许可。转载保留声明头部与原文链接https://luzeshu.com/blog/nodesource4 本博客同步在https://cnodejs.org/topic/56ed249356d74f3d3624b3ff 本博客同步在http://www.cnblogs.com/papertree/p/5285705.html 上面讲到node调用Scrip…

EasyConnect安装使用教程

easyconnect电脑版是一款为企业提供的移动信息化办公软件&#xff0c;这款软件可以让公司经常出差的人员能在公司范围外使用公司的内网系统和相关应用。软件支持移动和pc平台&#xff0c;不管是在电脑上还是手机上使用都非常方便&#xff0c;easyconnect电脑版便捷性和安全性使…

xp如何快速锁定计算机,Window XP中快速锁定计算机两法

在Windows XP时工作时&#xff0c;我们经常要锁定计算机&#xff0c;当计算机被锁定后&#xff0c;只有重新登录才能够使用计算机&#xff0c;从而保证了计算机的安全。但是&#xff0c;一般情况下我们需要锁定计算机操作时&#xff0c;都是按下CTRLALTDEL(或者为Delete)键&…

辅助判卷程序项目的扩展--自动出题

既完成了主模块---计算题目的设计后&#xff0c;我就开始了自动出题程序的设计&#xff0c;这个程序的思路比较简单&#xff0c;并不是很完美 下面是程序截图和生成的算式 题目中最多包含一对括号&#xff0c;此程序唯一的遗憾就是有时候计算结果会很大例如7736/4这样的结果 下…

H5工程师跨页面取值的几种方法

业务场景:作为H5工程师,经常需要到另一个页面去拿变量值,遇到好多次啦,这里总结一下,同等不同的页面一般不可以使用id或者类选择器定位取值,比如A页面有个id="demo",你从B页面取值,取不到,我记得上次在jsp中取到过一次,应该是和属性相关的,在js中完全行不通…

计算机的发展阶段及特点与未来发展,计算机的发展历史及未来

芯片快将近10亿倍。光子计算机光子计算机即全光数字计算机&#xff0c;以光子代替电子, 光互连代替导线互连&#xff0c;光硬件代替计算机中的电子硬件&#xff0c;光运算代替电运算。光的高速&#xff0c;天然地决定了光计算机有超高速运算速度&#xff1b;与只能在低温下工作…

akka2.5_播放2.0:Akka,Rest,Json和依赖项

akka2.5在过去的几个月中&#xff0c;我越来越多地涉足scala。 Scala与“ Play框架”一起为您提供了一个非常有效且快速的开发环境&#xff08;即&#xff0c;您掌握了Scala语言的特质之后&#xff09;。 Play框架背后的家伙一直在努力开发新版本的Play 2.0。 在Play 2.0中&…

第三周学习进度条

第三周 所花时间(包括上课) 26 代码量(行) 253 博客量(篇) 2 了解到的知识点 第一次团队合作&#xff0c;发现要学习的东西还很多&#xff0c;合作伙伴之间还是需要磨合的&#xff0c;两个人之间还是需要沟通的&#xff0c;果然容易打起来。。。 知识上进一步了解了栈…

javaScript实现E-mail 验证

下面的函数检查输入的数据是否符合电子邮件地址的基本语法。 意思就是说&#xff0c;输入的数据必须包含 符号和点号 (.)。同时&#xff0c; 不可以是邮件地址的首字符&#xff0c;并且 之后需有至少一个点号&#xff1a; function validateForm(){var xdocument.forms["…

计算机硬件知识竞赛题库,电脑知识竞赛题库.pdf

1&#xff0e;在下列系统中&#xff0c; ( )是实时系统。A. 计算机激光照排系统 B.航空定票系统 C&#xff0e;办公自动化系统 D.计算机辅助设计系统答案&#xff1a; B2&#xff0e;操作系统是一种 ( ) 。A. 应用软件 B &#xff0e;系统软件 C&#xff0e;通用软件 D &#x…

代码挑战“ Vrolijke Framboos”事后验尸

星期二&#xff0c;我们在JDriven举行了第二次“ Vrolijke Framboos”&#xff08;快乐树莓的荷兰语&#xff09;Java代码挑战赛 &#xff0c;这是爆炸性的&#xff01; 今年的挑战是创建一个REST服务客户端&#xff0c;该客户端将与服务器一起玩猜数字游戏。 设置会话后&#…

电子门锁没电的解决办法

导读:今天对象回家,输入电子门锁密码怎么也打不开,指示灯也不亮,前段时间也时不时的能按,我就预感到电池没电了,那么我是如何进入家门的呢? 一般这种电子门锁可以输入密码,也可以使用机械钥匙。说实话,这钥匙在哪我压根没见过,租的房子,房东都不知道,只能输入密码才…

oracle学习笔记系列------oracle 基本操作之表的增删改查

--创建一个表 CREATE TABLE employee_souvc(id NUMBER(4),name VARCHAR2(20),gender CHAR(1),birth DATE,salary NUMBER(6,2),job VARCHAR2(30),deptno NUMBER(2) ); --DESC table_name:查看表结构,看到表的列的名字&#xff0c;以及对应的类型&#xff0c;长度等 DESC employe…

【前端笔试题】文本居中的几种小技巧

前端面试或者开发总会遇到是文本居中的情况及场景,这里一起总结一下。便于查找和使用。 目录 方法一 方法二 方法三 方法四 方法一 自动外边距 div #container{margin-left:auto;margin-right:auto;width:168px;} 方法二 使用text-align body{text-align:center;}

计算机专业常用图论,同等学力申硕计算机专业--数学公式集合(新增学习笔记)...

组合数学部分&#xff1a;基础公式&#xff1a;定义:从n个不同的元素中, 取r个并按次序排列, 称为从n中取r个的一个排列, 全部这样的排列数记为P(n, r).定义: 从n个不同的元素中, 取r个但是不考虑次序时候, 称为从n中取r个的一个组合, 全部这样的组合总数记为C(n, r).定义: 从n…

使用Ubuntu22+Minikube快速搭建K8S开发环境

安装Vmware 这一步&#xff0c;可以参考我的如下课程。 安装Ubuntu22 下载ISO镜像 这里我推荐从清华镜像源下载&#xff0c;速度会快非常多。 下载地址&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/22.04.3/ 如果你报名了我的这门视频课程&#xf…

linux内核分析——扒开系统调用的三层皮(上)

20135125陈智威 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 系统调用&#xff1a;库函数封装了系统调用&#xff0c;通过库函数和系统调用打交道 用户态&#xff1a;低级别执行状态&#xff0c;代码的掌控范围会受到限…

前端常见浏览器兼容性问题及解决办法

不同浏览器的内核也不尽相同&#xff0c;所以各个浏览器对网页的解析存在一定的差异。 1.不同浏览器的标签默认的外补丁和内补丁不同 *{ margin:0; padding:0; } 2. 块属性标签float后&#xff0c;又有横行的margin情况下&#xff0c;在IE6显示margin比设置的大 style{ disp…