Angular 1 学习笔记五

随着JavaScript的日渐成熟,在web应用开发中越来越多的功能通过JavaScript类库来实现加速了
从基于服务端的应用向基于客户端的应用迁移。

将功能从服务器移到客户端的趋势取决于很多因素,最终要的是因为mobile先进的技术提供了和台式机
同样的运算速度和处理能力。这种趋势让越来越多的开发者从服务端转向了客户端,JavaScript则处在这场运动的核心位置。
开发者们创建了跨编译器和模拟器运行游戏引擎和虚拟机运行在浏览器,提供跟原生平台一样甚至更好的性能的应用。

同样的变化出现在非宿主Web应用程序上,应用程序不再依靠服务器处理逻辑,而是利用基于云服务来执行校验和数据访问,实现业务逻辑。
这些非宿主web应用可以运行在任何平台,允许你选择数据的存储位置来保持数据的私有性病远离应用的提供者。
它们同时部署非常简单,容易做镜像,提供了几乎无限的可伸缩性。

实现基于复杂规则的计算和基于一个有限状态机的用户工作流处理。

封装业务逻辑到models:

我们研究过使用Angular Module的value方法和一个标准的JavaScript构造函数定义,来创建model服务。
如此我们就可以将model注入到我们的controller,services,directives等使用它们。

我们还研究了使用原型继承来定义逻辑代码到model的原型属性。
我们又有一个通用的转换服务来将来自外部数据服务的普通JSON对象转换成我们的model对象。

var Fermentable = function() {
    var self = this;
    self.Name = '';
    self.Type = ''; // can be "Grain", "Sugar", "Extract",
    // "Dry Extract" or "Adjunct"
    self.Amount = 0.0;
    self.Yeild = 0.0; // percent
    self.Color = 0.0; // Lovibond units (SRM)
    self.AddAfterBoil = false;
    self.Origin = '';
    self.Supplier = '';
    self.Notes = '';
    self.CoarseFineDiff = 0.0; // percent
    self.Moisture = 0.0; // percent
    self.DiastaticPower = 0.0; // in lintner units
    self.Protein = 0.0; // percent
    self.MaxInBatch = 0.0; // percent
    self.RecommendMash = false;
    self.IBUGalPerPound = 0.0;
    self.DisplayAmount = '';
    self.Potential = 0.0;
    self.DisplayColor = 0.0;
    self.ExtractSubstitute = '';
};
Fermentable.prototype = {
    /**
    * calculates the gravity per pound for the fermentable
    * @param brewHouseEfficiency - the estimated brew house
    * efficiency
    * @returns {number} - potential extract per pound for the
    * fermentable
    */
    gravityPerPound: function(batchType, brewHouseEfficiency){
        var result = ((this.Potential - 1) * 1000.0);
        switch(batchType)
        {
            case "All Grain":
                result = result * brewHouseEfficiency;
                break;
            case "Partial Mash":
                result = result * 0.33;
                break;
            case "Extract":
                break;
        }
        return result;
    },
    
    /**
    * calculates the gravity for the ingredient
    * @param brewHouseEfficiency - the estimated brew house
    * efficiency
    * @returns {number} - returns the total potential extract for
    * the fermentable
    */
    ingredientGravity: function(batchType, brewHouseEfficiency){
        return this.Amount * (this.gravityPerPound(batchType,brewHouseEfficiency) / 1000);
    },
    
    /**
    * calculates the gravity points for a given recipe
    * @param batchVolume - total pre boil volume
    * @param brewHouseEfficiency - the estimated brew house
    * efficiency
    * @returns {number} - returns the total gravity points for the
    * fermentable
    */
    gravityPoints: function(batchType, batchVolume,brewHouseEfficiency){
        return (this.ingredientGravity(batchType, brewHouseEfficiency)/ batchVolume);
    },
    
    /**
    * calculates the color the fermentable will contribute to a
    * batch of beer based on the amount of the feremntable
    * fermentable used in the recipe
    * @returns {number} - the srm color value for the fermentable
    */
    srm: function(){
        return parseFloat(this.Amount) * parseFloat(this.Color);
    },
    
    /**
    * calculates the color the fermentable will contribute to a
    * batch of beer based on the pre-boil volume of the recipe
    * @param batchVolume - total pre-boil volume
    * @returns {number} - the srm color value for the fermentable
    */
    colorPoints: function(batchVolume){
        return this.srm() / batchVolume;
    }
};

我们的调用方式可以如下进行:
var grain = new Fermentable();
grain.Amount = 5;
grain.Color = 3;
grain.Potential = 1.039;

var gravityPoints = grain.gravityPoints('All Grain', 10, 0.65);


封装业务逻辑到Services:
另外一种方式就是将业务逻辑包含到一个服务中,公开各种函数。

angular.module('brew-everywhere')
    .factory('fermentableHelper',function() {
        /**
        * calculates the gravity per pound for the fermentable
        * @param fermentable – the fermentable to calculate the
        * value for
        * @param brewHouseEfficiency - the estimated brew house
        * efficiency
        * @returns {number} - potential extract per pound for the
        * fermentable
        */
        var gravityPerPound = function(fermentable, batchType,brewHouseEfficiency){
            var result = ((fermentable.Potential - 1) * 1000.0);
            switch(batchType)
            {
                case "All Grain":
                    result = result * brewHouseEfficiency;
                    break;
                case "Partial Mash":
                    result = result * 0.33;
                    break;
                case "Extract":
                    break;
            }
            return result;
        },
        
        /**
        * calculates the gravity for the ingredient
        * @param fermentable – the fermentable to calculate the
        * value for
        * @param brewHouseEfficiency - the estimated brew house
        * efficiency
        * @returns {number} - returns the total potential extract for
        * the fermentable
        */
        var ingredientGravity = function(fermentable, batchType,brewHouseEfficiency){
            return this.Amount * (gravityPerPound(fermentable, batchType,brewHouseEfficiency) / 1000);
        },
        
        /**
        * calculates the gravity points for a given recipe
        * @param fermentable – the fermentable to calculate the
        * value for
        * @param batchVolume - total pre boil volume
        * @param brewHouseEfficiency - the estimated brew house
        * efficiency
        * @returns {number} - returns the total gravity points for the
        * fermentable
        */
        var gravityPoints = function(fermentable, batchType,batchVolume, brewHouseEfficiency){
            return (ingredientGravity(fermentable, batchType,brewHouseEfficiency) / batchVolume);
        },
        
        /**
        * calculates the color the fermentable will contribute to a
        * batch of beer based on the amount of the fermentable used in
        * the recipe
        * @param fermentable – the fermentable to calculate the
        * value for
        * @returns {number} - the srm color value for the fermentable
        */
        var srm = function(fermentable){
            return parseFloat(fermentable.Amount) * parseFloat(fermentable.Color);
        },
        
        /**
        * calculates the color the fermentable will contribute to a
        * batch of beer based on the pre-boil volume of the recipe
        * @param fermentable – the fermentable to calculate the
        * value for
        * @param batchVolume - total pre-boil volume
        * @returns {number} - the srm color value for the fermentable
        */
        var colorPoints = function(fermentable, batchVolume){
            return this.srm(fermentable) / batchVolume;
        }
        
        
        var helper = {
            gravityPerPound: gravityPerPound,
            ingredientGravity: ingredientGravity,
            gravityPoints: gravityPoints,
            srm: srm,
            colorPoints: colorPoints
        };
        
        return helper;
});

如此将model和业务逻辑分开定义成两个服务。调用方式:
var grain = new Fermentable();
grain.Amount = 5;
grain.Color = 3;
grain.Potential = 1.039;

var gravityPoints = fermentableHelper.gravityPoints(grain,'All Grain', 10, 0.65);

这样做的好处是,所有的业务都在一个地方定义,不会传遍整个应用程序。
并且便于测试,因为不需要为每个model提供单元测试。缺点是需要额外的代码区便利model数组。


到底是Models还是Services ?
对于大型应用程序,混合使用这两者是不错的选择。

我们经常将一些小运算放到model对象定义里面,从而减少我们需要定义并进行注入的Service数量。
我们会考虑使用Service来封装一些比较大型的复杂的业务逻辑,这样可以让我们能集中管理这些主要的业务逻辑,
保持代码的高可维护性和便于测试。
在其他应用程序中重用时只需要拷贝它们到相应的项目即可。


使用状态机控制视图流:
State Machines表现为一个具有计算行为的数学模型。
该模型在任意给定时间在任意有限状态值上。
通过一个触发事件或者条件,模型可以从一个状态到另一个状态。

状态机非常善于在整个应用程序中处理复杂的用户界面流。
每个状态都可以表现为一个视图显示给用户。
用户的交互作为触发事件,能够用于让视图从一个状态转为另一个状态。

我们可以使用一个状态机来控制整个应用程序的用户流,而不是将其编码在我们的controllers里。
我们还可以在会话之间使用状态机来管理长时间运行的过程。

我们可以用它来管理路由,长时间运行的进程等。
它由状态机引擎和视图控制器组成,用于在不同的view之间进行路由。
视图控制器能还能在一个流程控制对象上基于状态转换和无论何时出现的事件触发一个转换
来调用函数。

比如酿造啤酒过程需要分好几个阶段:
该过程开始于预热水,然后混合谷物,浸泡谷物将淀粉starch转变为糖。然后将糖水混合物烧开,我们称之为麦芽汁wort,
麦芽汁里的糖达到一定比例,发酵来制造酒精alcohol

我们将设计一个状态机来管理这个酿造过程,在不同的阶段显示不同的视图给酿造者。
在设定的时间到或者指定的阶段完成时转换到下一个视图。

我们的状态机是一个表现各种状态的对象,每个状态之间的转换,和一个命令或者事件触发列表来用于状态从一个转变为另一个。

brewingProcessStateMachine.js:

{
    id: 'BrewProcess',
    processController: 'BrewProcessController',
    title: 'Brew Process',
    description: 'UI Process flow for Brewing a Batch of Beer',
    transitions: [
        {id: 'Begin', targetState: 'PreHeatMash'},
        {id: 'PreHeatMashHeating', targetState: 'PreHeatMash'},
        {id: 'MashTempReached', targetState: 'Mash'},
        {id: 'MashTimeWaiting', targetState: 'Mash'},
        {id: 'MashTimeReached', targetState: 'MashOut'},
        {id: 'MashOutTimeWaiting', targetState: 'MashOut'},
        {id: 'MashOutTimeReached', targetState: 'PreHeatBoil'},
        {id: 'PreHeatBoilHeating', targetState: 'PreHeatBoil'},
        {id: 'BoilReached', targetState: 'Boil'},
        {id: 'BoilTimeWaiting', targetState: 'Boil'},
        {id: 'BoilTimeReached', targetState: 'Exit'},
        {id: 'exit', targetState: 'Exit'}
    ],
    
    states: 
    [
        { 
            id: 'Exit',
            activityId:'BrewProcess',
            url: '/',
            title: 'Exit',
            description: 'Return to Home Screen',
            commands: []
        },
        
        { 
            id: 'Error',
            activityId:'BrewProcess',
            url: '/Error',
            title: 'Error',
            description: 'An error has occurred while performing the last action.',
            commands: [
                { id: 'Previous', transition: 'exit'},
                { id: 'Next', transition: 'exit'},
                { id: 'Cancel', transition: 'exit'}
            ]
        },
        
        { 
            id: 'PreHeatMash',
            activityId:'BrewProcess',
            url: '/PreHeatMash',
            title: 'Pre-Heat Mash',
            description: 'Pre-heat the mash water to reach the desired mash temperature',
            commands: [
                { id: 'Waiting', transition: 'PreHeatMashHeating'},
                { id: 'TemperatureReached', transition:'MashTempReached'},
                { id: 'Exit', transition: 'exit'},
                { id: 'DisplayErrorMessage', transition:'displayErrorMessage'}
            ]
        },
        …
        source removed for brevity
        …
        { 
            id: 'Boil',
            activityId:'BrewProcess',
            url: '/Boil',
            title: 'Boil',
            description: 'Boil the wort for the desired time',
            commands: [
                { id: 'Waiting', transition: 'BoilTimeWaiting'},
                { id: 'TimeReached', transition: 'BoilTimeReached'},
                { id: 'Exit', transition: 'exit'},
                { id: 'DisplayErrorMessage', transition:'displayErrorMessage'}
            ]
        }
    ]
}

状态机的定义包含一个转换数组,指定哪个状态应该被指定为当前状态和一个状态数组,给数组的
每一项都有一个命令数组用于状态发生转换时被触发。

状态机还定义一个流程控制器用于视图控制,在视图转向新的状态时执行代码。

http://odetocode.com/blogs/all
http://www.getbreezenow.com/
http://docs.datastax.com/en/cassandra/2.0/cassandra/architecture/architectureIntro_c.html
http://tutorials.jenkov.com/

状态机包含一个转换动作数组指定哪个状态应该被设置为当前状态。
一个状态数组,每一个状态定义项都包含一个命令数组,这些命令向用于触发状态与状态之间的转换。
状态定义项中还定义了一个流程控制器可以用于视图控制,在其转换为新的状态时执行代码。
主要是在从当前状态发生转换时,以及该状态的任何命令被触发时。
流程控制需要遵循一个标准的命名规范,如果我们想提供一个函数,当我们转换到一个新的状态时执行,该函数必须
命名为pre<state.id>
如果想定义一个函数想在转换到某个新状态后执行,则需命名为post<state.id>

指令的命名也是类似,<state.id><command.id>,比如如果我们想提供一个函数在Boid状态下的Waiting指令使用,
则需要在我们的处理controller里添加方法:BoilWaiting

状态机可以单独使用也可以作为其它服务的一部分,比如视图控制器等。
状态机的接口非常简单,允许消费者去添加状态定义,为指定的活动获取状态,转换,或者指令。
为了简化其定义,我们使用underscore类库_.findWhere方法基于各种查询方法获取一个状态。
/**
* @description
*
* Returns the target state for the transition associated with
* the command of the given state object
*
* @param state {object} the state object instance to use
* @param commandId {string} the command to follow
* @returns {*} {object} the target state for the transition
* associated with the given command or null if the state
* is not found
*/
getTargetStateForCommand: function (state, commandId) {
    var outState = null;
    var command = _.findWhere(state.commands, { id:commandId});
    if (command) {
        outState = stateMachine.getTargetStateForActivity(
            state.activityId, command.transition);
    }
    return outState;
}
上面的方法首先从状态对象的指令数组里查找指令对象。
然后调用其它方法来返回一个状态,跟指令关联的转换的状态。

状态机设计出来是用于连接其它服务比如视图控制服务。
它主要是实现了维护当前状态并查找状态管理的转换或者指令。
在我们应用程序中要定位到不同的页面,我们需要切换到每个状态定义的URL上。
这就意味着一旦我们转换到新的状态视图控制器需要去做导航URL工作。
因为视图控制在AngularJS里是有ngRoute类库负责,我们还需要确保我们要定位的
每个视图在我们应用程序中都已经通过routeProvider组件创建了。
下面的代码是视图控制服务基于指令来导航到其它视图:
/**
* @description
*
* Invokes the navigation process based on the given command
* identifier, which will result in a redirection to
* a new view. If the resulting state is not set, that means
* we redirect to the home view.
*
* @param commandId {string} identifier of the command of
* the current state to use invoke the state navigation
*/
navigateToStateUsingCommand: function (commandId) {
    // if we have a current state execute the post processing
    // for the state
    if (controller.currentState) {
        controller.executePostProcessMethodForActivity(
            controller.currentState.id,
            controller.currentState.activityId);
    }
    // We are navigating within an activity, which means we
    // have a current state, and a command to execute within
    // that state anything else is incorrect usage
    var command = _.findWhere(controller.currentState.commands, { id: commandId });
    if (command) {
        // execute the process method for the command
        controller.executeCommandProcessMethod(commandId,
            controller.currentState.id,controller.currentState.activityId);
        
        // navigate to the new state based on the command
        controller.currentState = stateMachine.getTargetStateForCommand(controller.currentState,commandId);
    }
    
    // check to make sure we have a currentState
    // and return success if we do
    if (controller.currentState) {
        // execute and pre processing for the new state
        controller.executePreProcessMethodForActivity(
            controller.currentState.id,controller.currentState.activityId);
        // change the view based on the new state
        $location.path(controller.currentState.Url);
    }
},

首先检查看当前状态是否加载到controller里,如果已加载,就执行该状态的post-transition函数。
一旦该函数返回,它会从当前状态中获取指令然后调用跟指令关联的函数。
然后,它调用getTargetStateForCommand函数来获取当前新的状态。
如果当前状态返回一个合法对象,那么其相应的pre-transition函数将被执行。
最后,函数使用$location 服务来改变URL,使其对应到新的当前状态。


使用规则引擎来处理复杂校验:
规则由条件和结果组成,结果会在条件为true时执行。
规则还包含一个名字和描述文字以及优先级和指示该规则是否被执行的指示器。
我们可以根据条件来打开和关闭规则以及该变规则的优先级,让规则动态的执行。

转载于:https://my.oschina.net/u/924064/blog/901384

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值