精华内容
下载资源
问答
  • 经过一个月时间,在我遇到了很多“这个我不会啊?”,“这个到底怎么问题后,它终于成功上线了!下面总结一下整整一个月时间我是...项目最大的困难就是动效开发复杂和开发排期紧凑问题。JDR DESIGN...

    经过一个月的时间,在我遇到了很多“这个我不会做啊?”,“这个到底怎么做“的问题后,它终于成功上线了!下面总结一下整整一个月的时间我是如何开发JDRD,遇到的各种问题以及解决方案。

    JDR DESIGN 是京东零售设计中台的门户站点,展示京东零售设计服务平台的产品以及应用场景,特点是动效丰富、图片细节多、要求整站文案和外链可配置。项目最大的困难就是动效开发复杂和开发排期紧凑的问题。

    JDR DESIGN 地址:https://jdrd.jd.com/

    这是我入职以来负责的第一个项目,需要花大量时间来熟悉新的开发流程,项目排期非常紧凑,并且在排期完后又新增了窄版、骨架屏、首页图标动效、入场动画、产品页头部动效等新的需求,每天高强度的加班,回想起来虽然很难,但是非常有挑战性,非常有收获。

    16d8d163a0243ecf3b40744ce17c5d67.gif
    ca45e55c6c2f1f6939b6ae05d1f05e18.gif

    项目架构

    技术选型

    作为一个 9012 年的 PC 端项目,我们自然也需要非常先进的技术选型来帮助我们提升研发生产力,所以一个优秀的前端框架和一个高效的前端工程化工具,自然是必不可缺的选择。选择团队自研的 Nerv 进行开发,Nerv 是一个基于 virtual dom 的类 Reac t组件框架,比 React 更小的体积更高的性能,还保持了对 IE 浏览器的兼容,满足了 JDRD 需要兼容IE10的要求。

    自动化前端构建工具选择了团队自研的前端工程化工具 Athena,简化 webpack 配置工作,帮助我们在项目中实现自动化编译、代码处理、依赖分析、文件压缩、文件 MD5 戳等需求。

    项目整体架构

    在前端架构方面,根据上述的技术选型以及常见的前端体系,基于本项目的需求进行了些调整,整体架构设计如下:

    530f413ceeb7a6d8c3eae229d9bafad5.png

    Athena 和 Nerv 上文已经介绍过,下面介绍一下另外几个:

    • 通用工具库:基于以往的项目建立的公共函数库,包括 Slider、Lazyimg、Lazyload、Nerv-loadable 等。
    • NEOS 管理平台:将整站的文案和外链数据放在 NEOS 平台进行管理,简化文案修改等工作。
    • 兜底展示:在请求到错误链接时重定向到 error.html 页面图片加载失败时展示兜底图
    • MTA 数据分析:将网站的所有点击事件添加埋点进行数据上报,根据实时数据统计分析服务,监控版本质量、渠道状况、用户画像属性及用户细分行为。

    开发过程

    整个开发流程总结如下图所示:

    6b090d8069b3a533edd963a68d2a2b3a.png

    既然是总结,开发过程中的流程当然没有这么完整,漏掉了一部分(已红色标注),导致开发到后面因为前面漏掉的环节,浪费了很多的时间。

    • 整站设计规范:由于开发和设计是同时进行的,开始开发时只有首页定稿,没有其他页面的设计稿,以及窄版的规范,我们应该在开发前和设计师明确整站的设计规范,根据设计规范建立通用样式表,整站引入。
    • 页面&楼层结构:楼层的结构设计对于后续做楼层懒加载非常重要,根据设计稿的功能结构区分楼层。
    • 兼容 IE:JDRD 是需要兼容 IE 的,一些不兼容 IE 的 API 和样式属性应该尽量避免使用。
    • 骨架屏:骨架屏的高度和楼层间隔和内容是一致的,应该建立公共类,定义骨架屏和内容一致的样式,在后续调整内容时,不需要再去调整一遍骨架屏的样式。

    数据埋点:交互稿其实有详细介绍哪些地方需要点击跳转的事件,开发时应该在定义点击跳转时就给数据埋点转参。

    除了在开发流程中的问题外,最头疼的就是动效的开发了,下文会详细介绍。

    项目优化

    性能优化

    性能优化的初衷就是加快网站的加载速度,让用户能够更快的看到内容,上面介绍到前端工程化工具 Athena 已经做到合并、压缩了静态资源文件,那还有什么方法能够缩小请求的静态资源体积,加快首屏的加载速度呢,我们尝试了以下性能优化手段。

    楼层懒加载

    楼层懒加载就是按楼层划分组件,并进行代码切割,在页面滚动时按需加载组件。

    Nerv-loadable 是一个专门用于动态 import 的 React 高阶组件,你可以把任何组件改写为支持动态 import 的形式,利用 import() 来进行动态加载。

    const NewsBannerLoadable = Loadable({  loader: () =>    import(/* webpackChunkName: "news_banner" */    './news_banner'),  loading: loadingPlaceholder.bind(null, loadingBlock),  delay: 0});

    上面的代码在首次加载时,会先展示一个 loadingBlock,然后动态加载 news_banner 的代码,组件代码加载完毕之后,便会替换掉 loadingBlock。Lazyload 通过监听 window 对象或者父级对象的 scroll 事件,触发 load,实现懒加载,让组件进入页面可视区时才加载该组件。需要注意的是 lazyload 需要设置高度,才会撑起懒加载的区域。

    以首页为例,有四处组件是不需要首次加载的,而是使用动态加载:多端适配、物料、应用场景、设计思考。首次加载实际上只需要加载首屏的头部、视频、banner 即可。切分之后,首屏 js 体积缩减了 50KB。

    0b7aa48ecaf7f7fbffc853e3acd17202.gif

    图片懒加载

    整站图片非常多,为了保持清晰度而且全部采用二倍图引入,消耗资源比较大,为了加快加载速度,我们选择让滚动条滚动到图片的可视区后才加载该图片。

    使用 Lazyload 实现,和上述组件懒加载介绍的一样,包裹着需要懒加载的图片,就可以实现图片懒加载。

    图片懒加载之外还有个优化,就是图片加载中、加载失败、加载成功的状态的判断,根据不同状态展示图片的内容。

    使用 Lazyimg 实现这个功能:

    • 使用 new Image() 创建一个新的 HTMLImageElement 实例
    • img.onload(),img.onerror() 捕获到图片加载成功或者失败的状态加载中:显示兜底图加载成功:显示加载到的图片加载失败:显示兜底图
    81148d0e0b576675d8c181b44247de90.gif

    其他优化手段

    除了上面两点外,还可以从 webpack 打包进行性能优化,webpack 打包后会生成一个或多个包含源代码最终版本的“打包好的文件”,它们由 chunks 组成,SplitChunks 插件可以将公共依赖项提取到现有的 entry chunk 或全新的代码块中,进行代码切割,减小 chunks 包的大小。

    体验优化

    骨架屏

    以往的传统网站一般会在加载中展示一个 loading 态,也可以达到占位的效果,但是 loading 动画和真实模块耦合度低,界面效果不够优美,JDRD 则是选用骨架屏进行占位,以灰色豆腐块的形式尽量缩小真实模块结构与加载占位之间的视觉差异。

    骨架屏的两个用途:

    • 组件加载完之前的占位

    使用 Lazyload 懒加载楼层组件,加载中使用 Loadable 提前占位,占位符设置为骨架屏。

    • 数据加载完之前的占位

    设置组件的 state.loaded 初始值为 false ,数据加载成功时 state.loaded = true ,render 函数里如果 loaded === false ,则显示骨架屏。

    骨架屏的实现方式有两种,一是下载并引入骨架屏插件(如 antd ),根据不同模块引入对应的骨架屏组件,这种方式和 loading 动画一样,耦合度低,但是全局通用,节省代码量。二是根据视觉稿写骨架屏的样式。JDRD 选择的是第二种,骨架屏和真实模块实现高度耦合。每个页面结构不一样,对应的骨架屏也是完全不同,骨架屏暂时不能抽成公共组件全局通用。

    d49b4e08820c7ca00bce9ae3c99d6ce5.gif

    宽窄版

    首页定稿设定的宽度为 1240px ,对小屏不够友好,我们增加了一版窄版样式兼容小屏。

    宽版和窄版开发的重点是定好通用的变量,包括字号粗细、宽窄版宽度、窄版尺寸比。这些通用样式规范需要和设计师统一规范,兼容窄版的开发就会变得非常简单。

    只需要在两个地方判断宽窄版,给最顶层的标签加上 wide/narraw 类,在 narrow 下添加窄版的自定义样式。

    1. 在页面刚加载到时判断宽窄版,在加载到样式表之前给 html 标签添加 wide/narraw
    !function(e) {  window.pageConfig = {};  pageConfig.isWide = function() {  var n = e,  i = document,  o = i.documentElement,  t = i.getElementsByTagName("body")[0],  a = n.innerWidth || o.clientWidth || t.clientWidth;  return a >= 1300  } ();  var n = [];  pageConfig.isWide ? (n.push("wide")) : n.push("narrow");  var i = document.getElementsByTagName("html")[0];  i.className = n.join(" ")} (window, void 0);
    1. 文档视图调整大小时判断宽窄版,修改 html 标签的 className

    先引入 Events.js ,然后在 componentDidMount 里生命周期函数里绑定 ‘ isWideChange ‘ 事件,在文档视图宽度达到宽窄版临界点时调用。

    componentDidMount() {  window._.eventCenter.on('isWideChange', evt => {    this.setState({//更新state,更新视图      isWide: evt.detail.isWide    });  });}

    动效开发

    首页图标动效

    为了突出设计理念,首页图标动效包含大量位移、旋转、缩放、形变、路径动画等细节,由始末动画+循环动画合成,传统做法是 css3 实现,这需要逐帧写动画细节,工作量非常大,我们尝试使用 Lottie 直接解析从 AE 导出的 json 格式的动画(方案由燕婷提出),发现能够完全还原AE动画。

    Lottie 是 Airbnb 开源的一套跨平台的完整的动画效果解决方案,可实时渲染 After Effects 动画,从而使应用程序可以像使用静态图像一样轻松地使用动画。这样实现起来就非常简单了。分以下两步:

    1. 在 AE 软件中用 bodymovin 插件将动画导出为 json 文件
    2. 在项目中使用 lottie-web 将 json 格式的动画解析为 SVG(使用文档)

    lottie-web 文档中的方法非常全面,JDRD 图标动效使用加载动画、播放指定帧区间、反向播放动画方法,就实现了起始动画 20 帧+循环动画 60 帧+起始动画反向播放 20 帧的动画合成操作。

    项目示例代码如下:

    npm install lottie-web //安装lottie-webimport lottie from 'lottie-web' //引入lottie-web到项目中//lottie-web常用方法this.anim = lottie.loadAnimation({ //加载动画  container: element,   renderer: 'svg',  loop: true,  autoplay: true,  path: 'data.json'});this.anim.playSegments([[0,60]], true); //播放指定帧区间this.anim.setDirection(-1);//动画反向播放this.anim.play();//播放动画this.anim.pause()//暂停动画

    经过以上两步,Lottie 已经将一个 AE 格式的动画渲染在 web 页面上。

    e6acc448713468b62124398fd26eb63b.png
    c83f89fd76cf7ae2bd925623c42b035a.gif
    457a3719bedf69a5985d6cc8696c0e89.gif

    这里有 2 个需要注意的点:

    1. json 文件的引入要使用 CDN,引入本地 json 文件会解析失败。
    2. 如果动画源文件中有引入图片文件,bodymovin 导出的动画为 json+img。图片动画的兼容性有待确认。

    以上就是用 Lottie 实现的动画,看到这里,是不是觉得 so easy,但是 Lottie 并不是万能的,不能解析所有的动画特性,开发前需要先看下支持列表。并和设计师确认是否都支持。

    入场动画

    JDRD 整站采用了骨架屏占位,那么入场动画最大的问题就是如何让它不和骨架屏冲突,解决方法就是楼层懒加载里面,再加一层入场动画的组件懒加载,两层懒加载的设置 offset 差,就可以做到在可视区外加载楼层组件,在可视区内播放入场动画。具体实现如下:

    1. 楼层懒加载,在页面滚动时按需加载楼层组件
    // 在距离底部200px时,加载楼层组件getMaterialLoadable(){  return this.getFloor(      );}
    1. 楼层中的入场动画组件懒加载
    // 在距离底部-200px时,加载入场动画组件,这时因为楼层组件已经加载过了,页面显示是真实组件而不是骨架屏
    {this.renderMaterial()}

    另外一个难点是序列动画的效果,序列动画就是将列表元素的动画执行时机错开,具体实现参考css3 animation 属性众妙。实现代码如下:

    @for $i from 1 to 6 {  .list__item:nth-child(#{$i}) {    animation-delay: (-1+$i)*0.1s; /*计算每个元素的 animation-delay */  }}
    16d8d163a0243ecf3b40744ce17c5d67.gif

    产品页头部动效

    产品页的头部动效分两部分,氛围动效 + 波浪动效。

    • 氛围动效

    氛围动效的实现比较简单,也是使用 Lottie 实现,这里遇到了 Lottie 不支持的特性,就是渐变,对于不支持的特性,我们可以拿到需要自定义样式的标签的 id,自定义样式,如图所示:

    fb3a4f6ba2e210f20443761bed3e40fc.png
    #__lottie_element_369 {  stop[offset="0%"] {    stop-color:  #FDFDFF;  }  stop[offset="100%"] {    stop-color: #F7F7FB;  }}

    自定义样式的时候,这里有个坑一定要注意,SVG 的 ID 是会变的,例如开发时,这个 ID 是 100,测试时这个 ID 有可能变成 101,这个是偶现的,目前还没有找到 ID 值 + 1 的原因,但是为了让自定义的样式生效,需要给 ID 为 100 和 ID 为 101 的标签都加上样式。

    8de2da00ff47d57cd42d109993c192df.gif
    • 波浪动效

    通过引入正弦波浪动效库 sine_wave 实现。sine_wave 使用 canvas 元素生成多个可配置的正弦波,这样我们就可以通过配置参数得到想要的正弦波浪,具体实现如下:

    new SineWaves({  el: document.getElementById( `waves`),//dom  speed: 0.75,//速度  width: function() {//canvas宽度    return document.body.clientWidth;  },  height: 68,//canvas高度  ease: 'Linear',//动画曲线  waves: [//需要配置的正弦波浪    {      "timeModifier":1,//速度      "lineWidth":1,//线条宽度      "amplitude":30 * window.devicePixelRatio,//波浪高度      "wavelength":125 * window.devicePixelRatio,//波长      "strokeStyle":"rgba(221,221,233,1)",//颜色      "type": function(x, waves) {//自定义波浪类型        return waves.sine(x); // Combine two together      }    }  ],   rotate: 0,//旋转角度  wavesWidth: '400%',//波浪宽度});

    根据文档介绍的参数来看,有两个参数是实现 3D 旋转波浪效果的关键:

    1. type,自定义波浪的类型,可以修改 x 轴的位移,让 3 根波浪错位,实现旋转的效果。
    type: function(x, waves) {  return waves.sine(x+8); // Combine two together}
    1. wavelength,波浪长度,3 根弧度一模一样的波浪明显是不符合要求的,只需要将红色的波浪的弧长加长,就可以实现一根波浪环绕另外两根波浪的效果。

    开发过程中发现 sine_waves 的一个显示问题,它以默认二倍屏的方式定义的 canvas,这样在一倍屏下波浪是有问题的,解决方法是在参数波浪高度 amplitude 和波长 wavelength 根据 window.devicePixelRatio 来定义。

    {  "amplitude":30 * window.devicePixelRatio,//波浪高度   "wavelength":125 * window.devicePixelRatio,//波长}

    全局细节动效

    最后介绍的就是全局细节动效的优化,为了让整站的动效流畅,平滑的过渡,做了以下工作:

    • 用 SASS 变量 $common_animation 管理通用动画,让整站动画一致化
    • 图标动效和边框动效采用 SVG 实现,实现动画组件化的同时,矢量元素不失真
    • 需要过渡的元素,用 visibility 代替 display 控制元素的显示隐藏
    fcf09f137b8955ccbbe2171afeb39434.gif
    059b1fa74836fb603688d8451f1979d1.gif
    1ff382144a05145eb20eea81674f6db7.gif

    整体总结

    本文从项目架构、开发流程、项目优化 3 个方面阐述 JDRD 官网的开发过程,中间遇到了太多问题,在问题的解决过程中记录和总结,是收获满满的喜悦,也发现了一些可以优化的模块,让下次能够做得更好,在开发过程中的多些思考和探究,最优化的设计项目。

    感谢阅读!

    凹凸实验室原链接:https://aotu.io/notes/2020/02/21/jdrd-summary/

    展开全文
  • 遇到需要若干个团队通力配合大型项目,我们应该做的是有意识地将大模型分解成数个较小部分。只要遵守相绑定契约,整合得好小模型会越来越有独立性。每个模型都应该有一个清晰边界,模型之间关系也应该...

    保持模型一致性

    当遇到需要若干个团队通力配合的大型项目,我们应该做的是有意识地将大模型分解成数个较小的部分。只要遵守相绑定的契约,整合得好的小模型会越来越有独立性。每个模型都应该有一个清晰的边界,模型之间的关系也应该被精确地定义。有一整套技术可以保证模型的完整性:
    保证模型完整性技术

    界定上下文(Defined Context)

    定义模型的范围,画出它的上下文的边界,然后尽最大可能保持模型的一致性。要在模型涵盖整个企业项目时保持它的纯洁是很困难的,但是在它被限定到一个特定区域时就相对容易很多。要在应用到模型的地方明确定义上下文。在团队组织里明确定义边界,在应用的具体部分明确定义用法,以及像代码库和数据库 Schema 的物理显示。保持模型在这些边界里的严格一致,不要因外界因素的干扰而有异动。被界定的上下文不是模型,界定的上下文提供有模型参与的逻辑框架。模块被用来组织模型的要素,因此界定的上下文包含模块

    当不同的团队不得不共同工作于一个模型时,我们必须要各司其职。要时刻意识到任何针对模型的变化都有可能破坏现有的功能。当使用多个模型时,每个人可以自由使用自己的那一部分。我们都知道自己模型的局限,都恪守在这些边界里。我们需要确保模型的纯洁、一致和完整。每个模型应能使重构尽可能容易,而不会影响到其他的模型。而且为了达到纯洁的最大化,设计还要可以被精简和提炼。

    持续集成(Continuous Integration)

    一个团队工作于一个界定的上下文,也有犯错误的空间。在团队内部我们需要充分的沟通,以确保每个人都能理解模型中每个部分所扮演的角色。如果一个人不理解对象间的关系,他就可能会以和原意完全相反的方式修改代码。如果我们不能百分之百地专注于模型的纯洁性,就会很容易犯这种错误。团队的某个成员可能会在不知道已经有自己所需代码的情况下增加重复代码,或者担心破坏现有的功能而不改变已有的代码选择重复增加。

    模型不是一开始就被完全定义。模型先被创建,然后基于对领域新的发现和来自开发过程的反馈等再继续完善,这意味着新的概念会进入模型,新的部分也会被增加到代码中。所有的这些需求都会被集成进一个统一的模型,进而用代码实现之。这也就是为什么持续集成在界定的上下文中如此必要的原因。我们需要这样一个集成的过程,以确保所有新增的部分和模型原有的部分配合得很好,在代码中也被正确地实现。我们需要有个过程来合并代码,合并得越早越好。

    持续集成是基于模型中概念的集成,然后再通过测试实现。任何不完整的模型在实现过程中都会被检测出来。持续集成应用于界定的上下文,不会被用来处理相邻上下文之间的关系。

    上下文映射(Context Map)

    一个企业应用有多个模型,每个模型有自己界定的上下文。建议用上下文作为团队组织的基础。在同一个团队里的人们能更容易地沟通,也能很好地将模型集成和实现。但是每个团队都工作于自己的模型,所以最好让每个人都能了解所有的模型。上下文映射是指抽象出不同界定上下文和它们之间关系的文档,它可以是像下面所说的一个示意图(Diagram),也可以是其他任何写就的文档。详细的层次各有不同。它的重要之处是让每个在项目中工作的人都能够得到并理解它。
    示意图
    只有独立的统一模型还不够。它们还要被集成,因为每个模型的功能都只是整个系统的一部分。在最后,单个的部分要被组织在一起,整个的系统必须能正确工作。如果上下文定义的不清晰,很有可能彼此之间互相覆盖。如果上下文之间的关系没有被抽象出来,在系统被集成的时候它们就有可能不能工作。

    共享内核(Shared Kernel)

    当缺少功能集成时,持续集成可能就遥不可及了。尤其是在团队不具备相关的技术或者行政组织来维护持续集成,或者是某个团队又大又笨拙的时候。协同工作于有紧密关系的应用程序上的不协调团队有时会进展很快,但他们所做的有可能很难整合。他们在转换层和技巧花样上花费了过多的时间,而没有在最重要的持续集成上下功夫,做了许多重复劳动也没有体味到通用语言带来的好处。因此,需要指派两个团队统一共享的领域模型子集,当然除了模型的子集部分,还要包括代码自己或者和模型相关联的数据库设计子集。这个明确被共享的东西有特别的状态,没有团队之间的沟通不能做修改。要经常整合功能系统,但是可以不用像在团队里进行持续集成那么频繁。在集成的时候,在两个团队里都要运行测试。

    共享内核的目的是减少重复,但是仍保持两个独立的上下文。对于共享内核的开发需要多加小心。两个开发团队都有可能修改内核代码,还要必须整合所做的修改。如果团队用的是内核代码的副本,那么要尽可能早地融合(Merge)代码,至少每周一次。还应该使用测试工具,这样每一个针对内核的修改都能快速地被测试。内核的任何改变都应该通知另一个团队,团队之间密切沟通,使大家都能了解最新的功能。

    客户-供应商(Customer-Supplier)

    我们经常会遇到两个子系统之间关系特殊的时候:一个严重依赖另一个。两个子系统所在的上下文是不同的,而且一个系统的处理结果被输入到另外一个。它们没有共享的内核,因为从概念上理解也许不可以有这样一个内核,或者对两个子系统而言要共享代码在技术上也不可能实现。

    我们曾讨论了一个关于在线商店应用的模型,包括报表和通讯两部分内容。我们已经解释说最好要为所有的那些上下文创建各自分开的模型,因为只有一个模型时会在开发过程中遇到瓶颈和资源的争夺。假设我们同意有分开的模型,那么在Web 商店字系统和报表系统间的关系是什么样子的呢?共享内核看上去不是好的选择。子系统很可能会用不同的技术被实现。一个是纯浏览器体验,而另一个可能是丰富的 GUI 应用。尽管如果报表应用是用 Web 接口实现,各自模型的注意概念也是不同的。也许会有越界的情况,但还不足以应用共享内核。所以我们选择走不同的道路。另外,E 商店子系统并不全依赖报表系统。E 商店应用的用户是 Web 客户,是那些浏览商品并下单的人。所有的客户、商品和订单数据被放在一个数据库里。就是这样。E 商店应用不会真的关心各自的数据发生了什么。而同时,报表应用非常关心和需要由 E 商店应用保存的数据。它还需要一些额外的信息以执行它提供的报表服务。客户可能在购物篮里放了一些商品,但在结账的时候又去掉了。某个客户访问的链接可能多于其他人等。这样的信息对 E 商店应用没有什么意义,但是对报表应用却意义非凡。由此,供应商子系统不得不实现一些客户子系统会用到的规范。这是联系两个子系统的纽带。

    当我们面对这样一个场景时,应该就开始“演出”了。报表团队应该扮演客户角色,而 E 商店团队应该扮演供应商角色。两个团队应该定期碰面或者提邀请,像一个客户对待他的供应商那样交谈。客户团队应该代表系统的需求,而供应商团队据此设置计划。当客户团队所有的需求都被激发出来后,供应商团队就可以决定实现它们的时间表。如果认为一些需求非常重要,那么应该先实现它们,延迟其他的需求。客户团队还需要输入和能被供应商团队分享的知识。

    在两个团队之间确定一个明显的客户/供应商关系。在计划场景里,让客户团队扮演和供应商团队打交道的客户角色。为客户需求做充分的解释和任务规划,让每个人理解相关的约定和日程表。

    顺从者(Compliant)

    在两个团队都有兴趣合作时,客户-供应商关系是可行的。客户非常依赖于供应商,但供应商不是。如果有管理保证合作的执行,供应商会给于客户需要的关注,并聆听客户的要求。如果管理没有清晰地界定在两个团队之间需要完成什么,或者管理很差,或者就没有管理,供应商慢慢地会越来越关心它的模型和设计,而也越来越疏于帮助客户。毕竟他们有自己的工作完成底线。即使他们是好人,愿意帮助其他团队,时间的压力却不允许他们这么做,客户团队深受其害。在团队属于不同公司的情况下,这样的事情也会发生。交流是困难的,供应商的公司也许没兴趣在关系沟通上投资太多。他们要么提供少许帮助,或者直接拒绝合作。结果是客户团队孤立无援,只能尽自己的努力摸索模型和设计。

    如果客户不得不使用供应商团队的模型,而且这个模型做得很好,那么就需要顺从了。客户团队遵从供应商团队的模型,完全顺从它。这和共享内核很类似,但有一个重要的不同之处——客户团队不能对内核做更改,他们只能用它做自己模型的一部分,可以在所提供的现有代码上完成构建。在很多情况下,这种方案是可行的。当有人提供一个丰富的组件,并提供了相应的接口时,我们就可以将这个组件看作我们自己的东西构建我们的模型。如果组件有一个小的接口,那么最好只为它简单地创建一个适配器,在我们的模型和组件模型之间做转换。这会隔离出我们的模型,可以有很高的自由度去开发它。

    防崩溃层(Anticorruption Layer)

    我们会经常遇到所创建的新应用需要和遗留软件或者其他独立应用相交互的情况。对领域建模器而言,这又是一个挑战。很多遗留应用从前没有用领域建模技术构建,而且它们的模型模糊不清,难于理解,也很难使用。即使做得很好,遗留应用的模型对我们也不是那么有用,因为我们的模型很可能与它完全不同。因此,在我们的模型和遗留模型之间就须要有一个集成层,这也是使用旧应用的需求之一。

    让我们的客户端系统和外面的系统交互有很多种方法。一种是通过网络连接,两个应用需要使用同一种网络通信协议,客户端需要遵从使用外部系统使用的接口。另外一个交互的方法是数据库。外部系统使用存储在数据库里的数据。客户端系统被假定访问同样的数据库。在这两个案例中,我们所处理的两个系统之间传输的都是原始数据。但是这看上去有些简单,事实是原始数据不包括任何和模型相关的信息。我们不能将数据从数据库中取出来,全部作为原始数据处理。在这些数据后面隐含着很多语义。一个关系型数据库含有和创建关系网的其他原始数据相关的原始数据。数据语义非常重要,并且需要被充分考虑。客户端应用不能访问数据库,也不能不理解被使用数据的含义就进行写入操作。我们看到外部模型的部分数据被反映在数据库里,然后进入我们的模型。

    如果我们允许这样的事情发生,那么就会存在外部模型修改客户端模型的风险。我们不能忽视和外部模型的交互,但是我们也应该小心地将我们的模型和它隔离开来。我们应该在我们的客户端模型和外部模型之间建立一个防崩溃层。从我们模型的观点来看,防崩溃层是模型很自然的一部分,并不像一个外部的什么东西。它对概念和行为的操作和我们的模型类似,但是防崩溃层用外部语言和外部模型交流,而不是客户端语言。这个层在两个域和语言之间扮演双向转换器,它最大的好处在于可以使客户端模型保持纯洁和持久,不会受到外部模型的干扰。

    实现防奔溃层一个非常好的方案是将这个层看作从客户端模型来的一个服务。使用服务是非常简单的,因为它抽象了其他系统并让我们在自己的范围内定位它。服务会处理所需要的转换,所以我们的模型保持独立。考虑到实际的实现,可以将服务看作比作一个Facade。除了这一点,防崩溃层最可能需要一个适配器(Adapter)。适配器可以使你将一个类的接口转换成客户端能理解的语言。在我们的这个例子中,适配器不需要一定包装类,因为它的工作是在两个系统之间做转换。
    防奔溃层
    防崩溃层也许包含多个服务。每一个服务都有一个相应的 Facade,对每一个 Facade 我们为之增加一个适配器。我们不应该为所有的服务使用一个适配器,因为这样会使我们无法清晰地处理繁多的功能。

    我们还必须再增加一些组件。适配器将外部系统的行为包装起来。我们还需要对象和数据转换,这会使用一个转换器来解决。它可以是一个非常简单的对象,有很少的功能,满足数据转换的基本需要。如果外部系统有一个复杂的接口,最好在适配器和接口之间再增加一个额外的 Facade。这会简化适配器的协议,将它和其他系统分离开来。

    独立方法(Independent Method)

    独立方法模式适合一个企业应用可由几个较小的应用组成,而且从建模的角度来看彼此之间有很少或者没有相同之处的情况。它有一套自己的需求,从用户角度看这是一个应用,但是从建模和设计的观点来看,它可以由有独立实现的独立模型来完成。我们应该先看看需求,然后了解一下它们是否可以被分割成两个或者多个不太相同的部分。如果可以这样做,那么我们就创建独立的界定上下文(Bounded Context),并独立建模。这样做的好处是有选择实现技术的自由。我们正创建的应用可能会共享一个通用的瘦 GUI,作为链接和按钮的一个门户来访问每一个程序。相对于集成后端的模型,组织应用是一个较小的集成。在继续谈论独立方法之前,我们需要明确的是我们不会回到集成系统。独立开发的模型是很难集成的,它们的相通之处很少,不值得这样做。

    开放主机服务

    当我们试图集成两个子系统时,通常要在它们之间创建一个转换层。这个层在客户端子系统和我们想要集成的外部子系统之间扮演缓冲的角色。这个层可以是个永久层,这要看关系的复杂度和外部子系统是如何设计的。如果外部子系统不是被一个客户端子系统使用,而是被多个服务端子系统使用的话,我们就需要为所有的服务端子系统创建转换层。所有的这些层会重复相同的转换任务,也会包含类似的代码。当一个子系统要和其他许多子系统集成时,为每一个子系统定制一个转换器会使整个团队陷入困境。会有越来越多的代码需要维护,当需要做出改变时,也会越来越担心。

    解决这一个问题的方法是,将外部子系统看作服务提供者。如果我们能为这个系统创建许多服务,那么所有的其他子系统就会访问这些服务,我们也就不需要任何转换层。问题是每一个子系统也许需要以某种特殊的方式和外部子系统交互,那么要创建这些相关的服务可能会比较麻烦。

    定义一个能以服务的形式访问你子系统的协议。开放它,使得所有需要和你集成的人都能获取到。然后优化和扩展这个协议,使其可以处理新的集成需求,但某团队有特殊需求时除外。对于特殊的需求,使用一个一次性的转换器增加协议,从而使得共享的协议保持简洁和精干。

    参考

    《领域驱动设计精简版》

    展开全文
  • “你可以说说你毕业设计遇到的最大困难是什么吗?”“公寓没电来!!!”“啊......”“因为没有电来,项目时间严重不足,严重拖慢;因为没有电来,每天都浪费掉精神最好的时光,工作都在产能最低的时候;因为没有...

    “你可以说说你做毕业设计遇到的最大困难是什么吗?”
    “公寓没电来!!!”
    “啊......”
    “因为没有电来,项目时间严重不足,严重拖慢;因为没有电来,每天都浪费掉精神最好的时光,工作都在产能最低的时候;因为没有电来......”
    “那是因为......”
    “因为啥,真不知道那个狗屁想的点子,说是促进学习噢,没电的时候人都跑哪去了???手上生了个胞,你就把手割掉么???你以为谁都是杨过阿,没有一支手都牛B阿......”
    ......

    原梦一场~~最近压力太大了吧,梦里出出气减减压也好~Bingo!


    1.
    昨天我再次试用了一下久违的Picasa,发现他已经出现了中文版,第一眼还不错,毕竟GOOGLE产品"毕是精品"骂,不过GOOGLE之心与APPLE推出I系列都是一丘之骆,众人皆知,那就不用说了~
    操作还算方便,但技术和DesktopSearch一个狗娘生的,靠的就是索引再索引~
    老大,我有N个G的图片阿,让他那让搞,硬盘又给吃空了~
    马上找设置改路径去!!
    一轮搜索,他妈的影子都没有一个,真是他奶奶的XX
    他的兄弟DesktopSearch尚且会自动找个地方大的"宾馆"下脚阿,而他死要住进我最简陋的C号公寓,想给他搬家都不知道往哪里拉他的脚~Regedit上满是他的法术,就是不见他那张丑恶的嘴脸,网上连老外门都在GOOGLE上骂,也不见他啃一声!
    上他主页一看Q&A
    "他能自动搜索你电脑上的图片,而且不会占用你的地方"我靠!!!!
    用户体现:
    "太好了"
    "太喜欢了"
    ......
    真的无语,又被人强奸了一次,郁闷!!

    2.
    LightScripe原来也支持DVD的,看来上次我那种想当然的见识真的阻碍我的正确判断阿,要引以为戒才行!!紧记!紧记!
    展开全文
  • OSG程序设计教程pdf版

    热门讨论 2011-07-16 10:38:46
    但是可惜是前一段时间 FlySky 突然说 Array 写书遇到困难,恐怕要耽搁一段时间。我突然 发现我离工作时间越来越近了,还有个把月总得点什么。于是我又重新计划写本适应 OSG2.20 版本 基础教程念头。以...
  • 目前,企业面临的最大问题就是管理。随着中国加入WTO和全球经济一体化进程的加快,产业结构的调整会在整个世界范围内进行。企业能否在市场上赢得竞争,能否具备持续发展的能力,关键在于管理。 在全球竞争激烈的大...
  • 遇到的第一个问题就是没有同盟者,虽然团队成员有6人,但是大家对Code Review并没有什么概念,试图影响其他人并重视Code Review是十分困难的,在代码注释、文档、测试用例十分匮乏的业务...
  •  异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获...
  • 疯狂程序员

    热门讨论 2012-07-18 18:05:32
    比如卖车找个好造车不容易,炒蛋炒饭找只好母鸡不容易,拉广告要找个好的做广告更不容易。大广告公司,别人瞧不起你;小广告公司,绝影瞧不起别人。 一直过了半个月,这事情才总算落实,还像模像样...
  • 暑假跟着学长做的一个项目,其中涉及到多播流编写,设计思路是 为了便于管理每一个广播,把每一个广播创建为一个单独子窗口,这样方便管理而且不会影响到其他广播流。但是多个MDI子窗口被创建后,为了视觉...

    暑假跟着学长做的一个项目,其中涉及到多播流的编写,设计思路是 为了便于管理每一个广播,把每一个广播创建为一个单独的子窗口,这样方便管理而且不会影响到其他的广播流。但是多个MDI子窗口被创建后,为了视觉效果和便于管理,我们需要把很多的子窗口最小化到主界面的某个角落,便于需要观看的时候。只要点击最大化就行。现在说一下遇到的困难,第一种情况:一开始如果主界面没有任何的控件,这样就可以任意创建多个窗口而且可以最小化到主界面的角落里管理,下面会给出代码。第二种情况:主界面上有很多的控件这样如果按照上面那样创建MDI子窗口的话,子窗口会被控件挡住。第三种情况:为了防止第二的情况(子窗口被遮住)出现,此时会使用API函数SetParent(参数1,参数2)。等一下介绍该函数,该函数可以解决第二种情况出现的情况,但是当子窗口最小化的时候,子窗口会消失掉,但是没有关掉,只是不能看见了(待会说原因)。所以为 了解决第三种情况,第四情况出现了:就是在创建子窗口之后,不为子窗口指定父窗口。执行SetParent函数时为它创建一个父窗口,而且这个父窗口就是主界面。这样就可以所有问题。不会被遮住,不会最小化消失。以下是代码。

     

     

    第一种情况:父窗口没有任何控件,除了一个按钮。

    父窗口:frmMain   子窗口:frmchild

     

    frmMain->IsMdiContainer = true;//说明父窗口是个mdi 容器

    frmchild->TopMost = true;//永远显示在最上面

    单击按钮事件执行以下函数:

     

     

    frmchild^ Child = gcnew frmchild();

    Child->Show();

    Child->MdiParent = this;//指定子窗口的父窗口是frmMain

     

     

    第二种情况:主界面有控件(panel),代码如情况1

     

     

    第三种情况:父窗口有一个panel时,他会挡住子窗口(如果按照上面的代码)

     

    [DllImport("user32")]

     static int SetParent(int hWnChild, int hWndNewParent);

     

     

    此时调用API 函数 SetParent(参数1,参数2);

    代码:

    父窗口:frmMain   子窗口:frmchild

     

    frmMain->IsMdiContainer = true;//说明父窗口是个mdi 容器

    frmchild->TopMost = true;//永远显示在最上面

    单击按钮事件执行以下函数:

     

     

    frmchild^ Child = gcnew frmchild();

    Child->Show();

    Child->MdiParent = this;//指定子窗口的父窗口是frmMain

    SetParent((int) Child.Handle,(int)this.Handle)//位子窗口换一个父窗口,

     

    第四种情况:解决第三种情况出现的问题(最小化消失)

     

    [DllImport("user32")]

     static int SetParent(int hWnChild, int hWndNewParent);

     

     

    此时调用API 函数 SetParent(参数1,参数2);

    代码:

    父窗口:frmMain   子窗口:frmchild

     

    frmMain->IsMdiContainer = true;//说明父窗口是个mdi 容器

    frmchild->TopMost = true;//永远显示在最上面

    单击按钮事件执行以下函数:

     

     

    frmchild^ Child = gcnew frmchild();

    Child->Show();

    //Child->MdiParent = this;//指定子窗口的父窗口是frmMain

    SetParent((int) Child.Handle,(int)this.Handle)//位子窗口换一个父窗口,

     

    我们的目的是使子窗口的父窗口是主界面,因为在情况三中,父窗口被换了,不再是主界面,这时如果最小化,则子窗口肯定不是在主界面上,而是在新建的的一个父窗口上,所以在主界面看不到最小化的子窗口。所以我们在创建子窗口后 不马上立即给它指定父窗口,而是通过API把主界面作为他们创建的父窗口,指定给子窗口,所以注释该代码

    //Child->MdiParent = this;//指定子窗口的父窗口是frmMain ,可以达到在主界面既可以看见子窗口不会被遮住,又可以看见最小化不会消失掉。

    展开全文
  • 软件工程教程

    热门讨论 2012-07-06 23:10:29
    在软件开发和维护过程中所遇到的一系列严重问题 软件危机的表现 对软件开发成本和进度的估算很不准确 用户很不满意 质量很不可靠 没有适当的文档 软件成本比重上升 供不应求:软件开发生产率跟不上计算机应用...
  • 图书管理系统数据库实验报告

    热门讨论 2009-10-24 13:25:30
    各地教育主管部门也都逐步提出学校要采用图书馆管理系统,从而实现人工管理不到的一些功能并发挥图书馆的最大效益。越来越多的学校采用了现代化的管理软件进行管理,进一步提升了学校管理的现代化水平。 第二章 ...
  • c语言编写单片机技巧

    2009-04-19 12:15:17
    有可能话,还可以参加一些电子设计大赛,借此机会2--3个人合作一个完整系统,会更有帮助。到了大四毕业设计阶段,也可以选择相关课题作些实际案例增长经验。什么事情都有个经验积累过程,循序渐进。 8....
  • 在本书第1版出版时隔4年后,Thomas Kyte及时了解了大家这一迫切需求,根据他实战经验以及人们最关心问题对这本书了全面补充和调整,以涵盖11g最受关注多项特性。例如11g引入dbms_parallel_execute包来帮助...
  • 操作系统(内存管理)

    热门讨论 2009-09-20 12:55:25
    在对内存块进行了 free 调用之后,我们需要做的是诸如将它们标记为未被使用等事情,并且,在调用 malloc 时,我们要能够定位未被使用内存块。因此, malloc 返回每块内存起始处首先要有这个结构: 清单 3...
  • 调用之后,我们需要做的是诸如将它们标记为未被使用等事情,并且,在调用 malloc 时,我们要能够定位未被使用内存块。因此,malloc 返回每块内存起始处首先要有这个结构: 清单 3. 内存控制块结构定义 ...
  • c#学习笔记.txt

    热门讨论 2008-12-15 14:01:21
    但是当他们看完C#文档后又开始高兴起来,因为C#是如此简单:事实上,简单正是C#最大的特点。除此之外,它还具有现代、面向对象、类型安全、版本控制、兼容、灵活等特点。详细介绍请参阅rainbow(一个长着胡子...
  • 我们需要让开发出组件原生支持 typescript 的项目,得到更好开发体验,同时对 js 项目也能优雅降级。 由于现在 typescript 已原生支持 npm 生态,如果组件本身使用 typescript 开发,...
  • java 面试题 总结

    2009-09-16 08:45:34
    异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的...
  • --我们了优化,现在要使一个Asp.net按钮能够AJAX提交,你不需要任何设置(PageManager属性EnableAjax为true即可,这是默认属性)。 +PageManager实例方法AddAjaxUpdateControl改名为...
  • ExtAspNet_v2.3.2_dll

    2010-09-29 14:37:08
    -修正EnableMaximize属性不能使Window最大BUG,修正了双击标题栏不能最大BUG。 -删除Button控件SystemIcon属性,比如以前这样定义SystemIcon="Close",现在需要这样定义Icon="SystemClose"。 -Window...
  • JAVA面试题最全集

    2010-03-13 13:09:10
    26.javaawt和swing组件GUI设计的关键 27.对于java流认识 28.简单描述一下awt与swing区别。 29.简述java编程中事件处理模式。 30.你编写过applet吗?applet安全权限如何?试列举java application或者...
  • 汽车驾驶教程图解

    2012-05-26 08:56:12
    不好姿势会使操作转向盘和踩踏板困难,影响安全。 座位和后视镜调整 1.座位调整 用一只手握住转向盘,另一只手控制座位调节柄,调整到使脚能将离合器踏板和制动踏板轻松踩到底。 将座位调整到最适合自己...
  • 1.1当析构函数遇到多线程. . . . . . . . . . . . . . . . .. . . . . . . . . . . 3 1.1.1线程安全定义. . . . . . . . . . . . . . . . .. . . . . . . . . . . 4 1.1.2MutexLock 与MutexLockGuard. . . . . . ....

空空如也

空空如也

1 2
收藏数 23
精华内容 9
关键字:

做设计项目遇到的最大困难