您将如何组织大型复杂的Web应用程序(请参阅基本示例)?

时间:2010-03-18 08:19:52

标签: javascript design-patterns web-applications code-organization

为了让事情变得有趣并且关闭我最后的开放式问题,以一种合理的架构以良好的组织方式实现以下功能的解决方案获得了很好的赏金。完整的代码在jsfiddle上,随时可以提问:)

您通常如何组织客户端极其丰富的复杂Web应用程序。我已经创建了一个人为的例子来表明如果对于大型应用程序管理不好而容易进入的那种混乱。您可以随意修改/扩展此示例 - http://jsfiddle.net/NHyLC/1/

该示例基本上反映了SO上评论发布的部分内容,并遵循以下规则:

  1. 最少必须有15个字符, 修剪多个空格后 一个人。
  2. 点击Add Comment,但是 去除后尺寸小于15 多个空格,然后显示一个弹出窗口 错误。
  3. 表示剩余字符数量 总结颜色编码。灰色表示a 小评论,褐色表示一个 中评,橙色大 评论,红色评论溢出。
  4. 只能提交一条评论 每15秒一次。如果评论是 提交太快,显示一个弹出窗口 有适当的错误信息。
  5. 我在这个例子中注意到了几个问题。

    • 理想情况下,这应该是一个小部件或某种打包的功能。
    • 每15秒发表一次评论,至少15个字符的评论属于某些应用程序范围的政策,而不是嵌入到每个小部件中。
    • 太多硬编码值。
    • 没有代码组织。模型,视图,控制器都捆绑在一起。并非MVC是组织富客户端Web应用程序的唯一方法,但在此示例中没有。

    你会如何清理它?沿途应用一点MVC / MVP?

    这是一些相关的函数,但如果你在jsfiddle上看到整个代码会更有意义:

    /**
     * Handle comment change.
     * Update character count. 
     * Indicate progress
     */
    function handleCommentUpdate(comment) {
        var status = $('.comment-status');
    
        status.text(getStatusText(comment));
        status.removeClass('mild spicy hot sizzling');
        status.addClass(getStatusClass(comment));
    }
    
    /**
     * Is the comment valid for submission
     * But first, check if it's all good.
     */
    function commentSubmittable(comment) {
        var notTooSoon = !isTooSoon();
        var notEmpty = !isEmpty(comment);
        var hasEnoughCharacters = !isTooShort(comment);
    
        return notTooSoon && notEmpty && hasEnoughCharacters;
    }
    
    /**
     * Submit comment.
     * But first, check if it's all good!
     */
    $('.add-comment').click(function() {
        var comment = $('.comment-box').val();
    
        // submit comment, fake ajax call
        if(commentSubmittable(comment)) {
            .. 
        }
    
        // show a popup if comment is mostly spaces
        if(isTooShort(comment)) {
            if(comment.length < 15) {
                // blink status message
            }
            else {
               popup("Comment must be at least 15 characters in length.");
            }
        }
        // show a popup is comment submitted too soon
        else if(isTooSoon()) {
            popup("Only 1 comment allowed per 15 seconds.");
        }
    
    });
    

    修改1:

    @matpol感谢您对包装器对象和插件的建议。这将是对现有混乱的重大改进。但是,插件并不是独立的,正如我所提到的,它将成为更大的复杂应用程序的一部分。客户端/服务器端的应用程序范围的策略将决定评论的最小/最大长度,用户评论的频率等等。当然,插件可以作为参数提供此信息。

    此外,对于富客户端应用程序,数据必须与其html表示分离,因为可以保存许多服务器往返,因为应用程序是数据感知的,并且可以在本地存储内容,并定期更新在服务器上,或在应用程序本身内的有趣事件上(例如窗口关闭时)。这就是我不喜欢插件方法的原因。它可以像提供打包的表示一样工作,但它仍然以DOM为中心,当你在应用程序中有20个这样的插件时,这将是有问题的,这无论如何都不是荒谬的数字。

4 个答案:

答案 0 :(得分:23)

我这样做的方式是3倍。

  1. 将javascript封装在命名空间内的小型明确定义的类中
  2. Javascript类应该有HTML,它们需要“注入”它们作为依赖允许out-of-browser unit testing
  3. 尽可能多地将客户端功能移至服务器并使用称为AHAH
  4. 的概念

    Javascript名称间距

    这可以轻松实现,并已在其他帖子中介绍过,例如Is there a "concise" way to do namespacing in JavaScript?

    小型封装类

    Javascript代码,就像服务器端代码一样,应该用小的内聚类和方法很好地封装。每个类都位于一个单独的文件中,与其所在的命名空间一起命名,例如:MyCompany.SomePackage.MyClass.js。 在构建时,可以通过combining and minifying这些类文件保存对每个文件的过多HTTP请求。

    Javascript中的依赖性反转

    如此有效,而不是选择在课堂内使用所需的元素,如下所示:

    var MyNamespace.MyClass = function() {
      var elementINeed = $('#IdOfElementINeed');
    }
    

    你会这样注射它:

    var foo = new MyNamspace.MyClass($('#IdOfElementINeed'));
    
    var MyNamespace.MyClass = function(incomingDependency) {
      var elementINeed = incomingDependency;
    }
    

    这种技术非常适合于可测试的javscript,并通过MVC style分层代码来分离关注点。

    AHAH和客户端简化

    AHAH是一项相当古老的技术,已经在网络开发中存在了相当长的一段时间,尽管由于其纯粹的简单性而在网络爱好者中重新崛起。但是,哲学必须超过建筑技术水平,并且必须用作所有客户端javascript的替代品,例如:验证,显示/隐藏动态内容,计算等

    您可能曾经附加过具有客户端复杂性的onClick事件:

    $('#someElement').click(function(){
      // insert complex client-side functionality here, such as validating input
      // eg var isValid = $(this).val() < minimumCommentLength;
      // update page based on result of javascript code
      // eg $('#commentTooLong').show();
    })
    

    现在您只需将ajax请求触发回服务器即可获取新的HTML并简单地替换您感兴趣的全部或部分元素:

    $('#addCommentButton').click(function(){
      $.ajax({ 
        url: "/comment/add", 
        context: document.body, success: 
        function(responseHTML){
            $('body').html(reponseHTML);
          }});
    })
    

    显然这是一个简单的例子,但是当有效使用时,页面上的任何javascript事件,只需触发相同的ajax请求和HTML替换,大大减少了所需的客户端代码量。将其移至可以进行有效测试的服务器。

    AHAH nay-sayers会说,这不是运行网站的高效方式,但是我已经在具有56k调制解调器访问的网站上使用和看到了这种技术,并且还大规模扩展了公共网站。结果当然是较慢,但你仍然可以产生100毫秒以下的往返次数,这对人类来说几乎是即时的。

答案 1 :(得分:4)

Matpol为所提供的具体信息提供了文字解决方案。您的编辑意味着您正在寻找更具假设性问题的答案。换句话说,你正在寻找“方法”。

以这种方式回答问题;你给出的一个例子是一点红鲱鱼。它只是整个应用程序中的一个项目,并阻止我们“从树上看到森林”。那么森林是什么?这样问的问题没有定义。但是我认为所有程序员都会同意,当我说这是一个没有定义的项目的噩梦。所以,我会重新解释你的问题和答案,“定义一个项目的过程是什么?”

事实上,我在提出一系列问题:

  • 此应用程序的核心目的是什么?
  • 您希望何时推出?如果我们必须在1/4的时间内启动它,你会保留哪些功能?
  • 您确定之后需要哪些功能?

很多时候,为了解决这些问题的底部,我需要提出其他商业问题:

  • 您的受众群体是谁?
  • 他们为什么关心这个网站?什么会阻止他们回来?
  • 你将如何产生收入?
  • 你的行动呼吁是什么?如果您可以将所有用户汇集到一条路径上,那会是什么?

希望这些问题能够产生一系列基础代码,您可以将其视为核心代码。您怀疑,您的核心可能不适合模块化方法。正如您所确定的那样,您将希望将该核心分解为模型/视图/控制器布局。

但是你不可避免地需要添加设计绒毛。这让我们回到你的代码示例 - 它的毛茸茸。 Fluff属于一个插件,与核心分开。这并不是说你的所有插件都应该在单独的js文件中交付给用户......但是出于开发目的,它们应该被视为模块化的,并且独立于核心代码库。

答案 2 :(得分:3)

我要把它变成jQuery插件或静态对象。

静态对象只是一种类型或包装器。我也会把它分解成更小的函数,例如

init()
checkLength()
checkTime()

所以你最终会得到类似的东西:

Widget = {

init:function(){//setup events etc},
checkLength:function(){},
checkTime:function(){},
doMessage:function(){}


}

答案 3 :(得分:1)

当前的代码是一个良好的开端,可以“扩展”为大型应用程序,避免硬编码,并通过一些更改实现明确的MVC分离。

  
      
  • 理想情况下,这应该是一个小部件或某种打包的功能
  •   

小部件将使重用评论功能更容易,并提供在不同页面/应用程序中的重用。将问题的封装和分离不仅扩展到演示文稿,还扩展到小部件模型。当您考虑注释字段时,可以直观地将组件的状态视为注释文本,但影响其行为的所有参数都可以是其模型的一部分,包括验证参数。所以,除了评论文本,我还会将模型包括在内:

  • 字符数到大小类别的映射(太小,小,中,大,溢出)。
  • 最大评论提交频率(15秒)

随着文本的更改,窗口小部件模型会更新大小类别。视图侦听对size-category的更改,以及用于更新文本类以生成不同注释长度的CSS样式的size-category值。单击“添加注释”时也会检查大小类别。如果它“太小”或“溢出”,则可以显示弹出窗口。请注意,添加注释处理程序不检查字符数 - 在模型中隔离 - 它检查大小类别。如有必要,“添加注释”控制器可以使用相同的字符数映射到大小类别,模型使用该映射为用户生成有用的消息。例如。 “注释必须至少为15个字符”,其中15是从大小类别地图中提取的。

该模型还提供“剩余字符数”属性,其更改事件用于更新窗口小部件的UI。最大字符数从字符提取到大小类别映射。

  
      
  • 每15秒发表一次评论,至少15个字符的评论属于某些应用程序范围的政策,而不是嵌入到每个小部件中。
  •   
  • 太多硬编码值。
  •   
  • 没有代码组织。模型,视图,控制器都捆绑在一起。并非MVC是组织富客户端Web应用程序的唯一方法,但在此示例中没有。
  •   

可能有许多类型的评论(例如,评论的不同项目)和不同类型的评论小部件。如果它们都应具有相同的最小/最大范围,则应使用控制注释验证的相同模型值对它们进行参数化。在为注释模型构建数据时,最好在服务器端完成此操作。将为该特定注释提取注释文本,并从页面或应用程序配置默认值中提取注释验证值,例如大小类别映射。通过具有用于产生组件验证模型的中心逻辑,添加新规则变得更加简单 - 例如,例如“版主可以发表评论高达1K”,成为一段代码的变化。使组件模型计算服务器端的另一点是模型也应该在服务器端验证 - 客户端验证更多的是用户方便(有些人可能认为不方便!) - 而不是强制执行。可以禁用JavaScript,并且独立于验证客户端构建HTTP请求。

总而言之,其中大部分可以看作是组织小部件模型服务器端的生产。通过执行此服务器端,服务器可以强制执行验证规则,并使窗口小部件免受规则和应用程序范围配置的复杂性的影响。

我没有提到jQuery或任何UI技术,因为这种模式对应用程序有效,而与UI技术无关。如何应用模式将在某种程度上特定于UI,例如如何向窗口小部件提供验证模型,或者如何将侦听器连接到模型,但模式的组织级别与UI正交。主要关注的是模型 - 将其扩展到包括验证方面和计算服务器端。一旦到位,组织问题就会得到解决。