银行转账业务场景的几种实现思路对比

前言

ps: 由于本篇文章是我早期所写,文中的思想已经和我现在的想法完全不同了。目前我所理解的领域模型,是被调用的,应用层使用领域模型,调用领域模型中的各种构造块完成用例场景。另外,关于银行转账,我们还可以使用另一种更好的实现方案,即最终一致性的方案,通过事件驱动的流程的方式来实现转账。具体实现见ENode框架中的BankTransferSample中的代码实现:https://github.com/tangxuehua/enode

这篇文章希望通过一个银行转账的例子来和大家分享一些我最近想到的关于如何组织业务逻辑的心得和体会。当然,本人的能力和领悟有限,如有不正确之处,还希望各位看官能帮我指出来。我始终坚持一个信念,没有讨论就没有进步,任何一个非盈利为目的的人或组织始终应该把自己所学的知识共享出来与人讨论,这样不管对自己或对他人或对整个社会都大有好处。因为一个人的知识毕竟是有限的,但可以(并且也只能)和别人相互沟通交流学习来弥补这个缺陷。

转账过程简单描述

银行转账的核心业务逻辑大家应该都很熟悉了,主要有这么几步:

  1. 源账户扣除转账金额,当然首先需要先判断源账户余额是否足够,如果不够,则无法转账;
  2. 目标账户增加转账金额;
  3. 为源账户生成一笔转账记录;
  4. 为目标账户生成一笔转账记录;

下面让我们来看看各种实现该业务场景的方法,并且来做一个对比。

事务脚本(Transaction Script、贫血模型) 

这种方法的优缺点网上找一下一大堆,我这里也啰嗦列举一些:

  1. 容易理解,符合我们大脑过程化思考的习惯;
  2. 完全没有面向对象的思想,纯粹是面向过程式的一种组织业务逻辑的方式,所有的业务逻辑全部在一个方法中完成;
  3. 对象只包含数据而没有行为,对象只是用来被操作的“数据”,一般我们会设计很多的Item,以及ItemManager;
  4. 结构层次比较清晰,业务逻辑层和其他各层之间单项依赖;业务逻辑层中Item只代表数据,ItemManager则负责所有的业务逻辑实现,ItemManager只依赖于IDAL接口来完成持久化Item或重建Item; 
  5. 由于所有的业务逻辑全部写在一个方法内,如果有另外一个需求也需要类似的业务逻辑,通常我们是写一个新的方法来实现,这样就很容易导致相同的业务逻辑出现在两个方法中,导致可维护性降低;虽然可以用一些重构的技巧或设计模式来解决重用的问题,但这往往需要开发人员具有很高的编码水平,并且往往很多时候因为时间紧迫导致不允许我们花很多时间去重构;
  6. 如果业务逻辑一旦改变,我们必须去修改实现该业务逻辑的方法,并且如果该业务逻辑在多个方法中出现,我们必须同时修改多个方法;

演示代码:

 1     public class BankAccountManager
 2     {
 3         private IBankAccountDAL bankAccountDAL;
 4 
 5         public BankAccountManager(IBankAccountDAL bankAccountDAL)
 6         {
 7             this.bankAccountDAL = bankAccountDAL;
 8         }
 9 
10         /// <summary>
11         /// 该方法完成转账业务逻辑
12         /// </summary>
13         public void TransferMoney(Guid fromBankAccountId, Guid toBankAccountId, double moneyAmount)
14         {
15             var fromBankAccount = bankAccountDAL.GetById(fromBankAccountId);
16             var toBankAccount = bankAccountDAL.GetById(toBankAccountId);
17             if (fromBankAccount.MoneyAmount < moneyAmount)
18             {
19                 throw new NotSupportedException("账户余额不足。");
20             }
21             fromBankAccount.MoneyAmount -= moneyAmount;
22             toBankAccount.MoneyAmount += moneyAmount;
23 
24             DateTime transferDate = DateTime.Now;
25             fromBankAccount.TransferHistories.Add(new TransferHistory
26             {
27                 FromAccountId = fromBankAccountId,
28                 ToAccountId = toBankAccountId,
29                 MoneyAmount = moneyAmount,
30                 TransferDate = transferDate
31             });
32             toBankAccount.TransferHistories.Add(new TransferHistory
33             {
34                 FromAccountId = fromBankAccountId,
35                 ToAccountId = toBankAccountId,
36                 MoneyAmount = moneyAmount,
37                 TransferDate = transferDate
38             });
39         }
40     }
41     /// <summary>
42     /// 银行帐号
43     /// </summary>
44     public class BankAccount
45     {
46         public BankAccount() { this.TransferHistories = new List<TransferHistory>(); }
47         public Guid Id { get; set; }
48         public double MoneyAmount { get; set; }
49         public IList<TransferHistory> TransferHistories { get; set; }
50     }
51     /// <summary>
52     /// 转账记录
53     /// </summary>
54     public class TransferHistory
55     {
56         public Guid FromAccountId { get; set; }
57         public Guid ToAccountId { get; set; }
58         public double MoneyAmount { get; set; }
59         public DateTime TransferDate { get; set; }
60     }
61     public interface IBankAccountDAL
62     {
63         BankAccount GetById(Guid bankAccountId);
64     }

Evans DDD(充血模型)

这种方法的特点在网上也可以找到很多,但我也有一些其他自己的看法,见红色字体的部分:

  1. 基本是一种基于OO思想的开发方法,对象既有属性也有行为,对象之间通过相互引用和方法调用来完成对象之间的交互;
  2. 由于这是一种OO思想的设计方法,所以各种设计原则和模式都可以被充分利用;
  3. Evans对这种开发方法又作了进一步的完善,提出了:聚合、实体、值对象、服务、工厂、仓储、上下文,等这些概念;这确保我们在基于OO的思想组织业务逻辑时有了很好的指导思想;
  4. 需要特别指出的一点是,真正的Evans的DDD领域模型中的聚合根所内聚的所有值对象应该都是只读的,这一点特别重要。
  5. 基于Evans DDD的CQRS架构。这种架构的主要思想是将命令和查询分离,另一个重要的特点就是事件溯源,意思是领域对象不需要有公共的属性,只需要有行为即可,并且在任何一个行为发生后,都会触发一个事件。然后我们持久化的不是对象的状态,而是引起该对象状态改变的所有的事件。当我们需要重建一个领域对象时,只要先创建一个干净的只有唯一标识的对象,然后把和该对象相关的所有领域事件全部重新执行一遍,这样我们就得到了该对象的最终的状态了。说的简单点,就是我们不保存对象本身,而是只保存该对象的操作历史(或者叫操作日志),当我们需要重建该对象时只要”重演历史“即可。当然,为了避免性能的问题,比如因为一个对象可能会有很多的操作历史,如果每次重建该对象都是从头开始应用每个事件,那效率无疑是非常低的。因此我们使用了快照,快照保存了对象某个时刻的二进制形式(即被序列化过了)的状态。所以通常情况下,当我们要重建一个对象时都是从某个最近的快照开始回溯发生在快照之后的事件。
  6. 不管是Evans的DDD也好,CQRS架构也好,虽然都做到了让领域对象不仅有状态,而且有行为,但我觉得这还不够彻底。因为对象的行为总是“被调用”的,当现在有一个业务逻辑需要调用多个对象的一些行为来完成时,我们往往会一个一个地将对象从仓储中取出来,然后调用它们的方法。虽然Evans提出了领域服务(Service)的概念,并将一个领域对象不能完成的事情交给了领域服务去完成。但领域服务内部还是在一个个的取出对象然后调用它们的方法。这个做法在我看来和凭血模型没有本质区别,还是没有真正做到OO。因为贫血模型的情况下,对象是提供了数据让别人去操作或者说被别人使用;而充血模型的情况下,对象则是提供了数据和行为,但还是让别人去操作或者说被别人使用(数据被别人使用或方法被别人调用都是“被别人操作”的一种被动的方式)。所以从这个意义上来看对象时,我觉得贫血模型和充血模型没有本质区别。

 下面也给出一个实现了银行转账业务逻辑的充血模型实现:

 1 /// <summary>
 2     /// 银行帐号, 它是一个Evans DDD中的实体, 并且是聚合根
 3     /// </summary>
 4     public class BankAccount
 5     {
 6         private IList<TransferHistory> transferHistories;
 7 
 8         public BankAccount() : this(Guid.NewGuid(), 0D, new List<TransferHistory>()) { }
 9         public BankAccount(Guid id, double moneyAmount, IList<TransferHistory> transferHistories)
10         {
11             this.Id = id;
12             this.MoneyAmount = moneyAmount;
13             this.transferHistories = transferHistories;
14         }
15         public Guid Id { get; private set; }
16         public double MoneyAmount { get; private set; }
17         public IList<TransferHistory> TransferHistories
18         {
19             get
20             {
21                 return transferHistories.ToList().AsReadOnly();
22             }
23         }
24 
25         public void TransferTo(Guid toBankAccountId, double moneyAmount, DateTime transferDate)
26         {
27             if (this.MoneyAmount < moneyAmount)
28             {
29                 throw new NotSupportedException("账户余额不足。");
30             }
31             this.MoneyAmount -= moneyAmount;
32             this.TransferHistories.Add(
33                 new TransferHistory(this.Id, toBankAccountId, moneyAmount, transferDate));
34         }
35         public void TransferFrom(Guid fromBankAccountId, double moneyAmount, DateTime transferDate)
36         {
37             this.MoneyAmount += moneyAmount;
38             this.TransferHistories.Add(
39                 new TransferHistory(fromBankAccountId, this.Id, moneyAmount, transferDate));
40         }
41     }
42     /// <summary>
43     /// 转账记录, 它是一个Evans DDD中的值对象
44     /// </summary>
45     public class TransferHistory
46     {
47         public TransferHistory(Guid fromAccountId,
48                                Guid toAccountId,
49                                double moneyAmount,
50                                DateTime transferDate)
51         {
52             this.FromAccountId = fromAccountId;
53             this.ToAccountId = toAccountId;
54             this.MoneyAmount = moneyAmount;
55             this.TransferDate = transferDate;
56         }
57 
58         public Guid FromAccountId { get; private set; }
59         public Guid ToAccountId { get; private set; }
60         public double MoneyAmount { get; private set; }
61         public DateTime TransferDate { get; private set; }
62     }
63     /// <summary>
64     /// BankAccount聚合根对应的仓储
65     /// </summary>
66     public interface IBankAccountRepository
67     {
68         BankAccount GetBankAccount(Guid bankAccountId);
69     }
70     /// <summary>
71     /// 转账服务, 它是一个Evans DDD中的领域服务
72     /// </summary>
73     public class BankAccountService
74     {
75         private IBankAccountRepository bankAccountRepository;
76 
77         public BankAccountService(IBankAccountRepository bankAccountRepository)
78         {
79             this.bankAccountRepository = bankAccountRepository;
80         }
81 
82         /// <summary>
83         /// 该方法完成转账业务逻辑
84         /// </summary>
85         public void TransferMoney(Guid fromBankAccountId, Guid toBankAccountId, double moneyAmount)
86         {
87             var fromBankAccount = bankAccountRepository.GetBankAccount(fromBankAccountId);
88             var toBankAccount = bankAccountRepository.GetBankAccount(toBankAccountId);
89 
90             DateTime transferDate = DateTime.Now;
91             fromBankAccount.TransferTo(toBankAccountId, moneyAmount, transferDate);
92             toBankAccount.TransferFrom(fromBankAccountId, moneyAmount, transferDate);
93         }
94     }

基于事件驱动(EDA)的设计

这是一种根据我自己的想法而设计出来的一种设计与实现,但是离我理想中的设计还有一些距离。在我看来,真正理想的组织业务逻辑的方法或者说模型应该是这样的:

  1. 当外界需要领域逻辑的“实现模型”(简称领域模型)做某件事情时,会发出一个命令,这个命令可以理解为一个消息或者是一个事件。消息一旦创建出来后就是只读的,因为消息从某种程度上来说就是历史;
  2. 领域模型中的相关领域对象会主动响应该消息;
  3. 需要特别指出的是:我们不可以自己去获取一些相关的领域对象,然后进一步调用它们的方法而实现响应;而是应该所有可能被用到的领域对象必须好像永远已经存在于内存一样的永远在不停的在等待消息并作出响应。以银行转账作为例子,外界发出一个转账的消息,该消息会包含源帐号唯一标识、目标帐号唯一标识、转账金额这些信息。该消息的目的是希望两个两个银行帐号之间能进行转账。好了,外界要做的仅仅是发出这条消息即可。那么领域模型内部该如何去响应该消息呢?一种方法是将两个银行帐号先取出来,然后调用它们的转账方法(如TransferTo方法和TransferFrom方法)以实现转账的目的,前面的Evans的DDD的例子就是这样实现的。但这样做已经违反了我前面所说的理想的情况了。我的理想要求是,这两个银行帐号对象会像已经存在于内存一样可以直接主动去响应转账的消息,而不是转账的那两个方法(TransferTo方法和TransferFrom方法)被我们自己定义的领域服务所调用。
  4. 更加需要着重强调的是,我始终认为,真正的面向对象编程中的对象应该是一个”活“的具有主观能动性的存在于内存中的客观存在,它们不仅有状态而且还有自主行为。这里需要从两方面来解释:1)对象的状态可以表现出来被别人看到,但是必须是只读的,没有人可以直接去修改一个对象的状态,因为对象是一个在内存中的有主观意识的客观存在,它的状态必须是由它自己的行为导致自己的状态的改变。就好像现实生活中的动物或人一样,我不能强制你做什么事情,一定是我通知你(即发送消息给你),你才会做出响应并改变你自己的状态。2)对象的行为就是对象所具有的某种功能。对象的行为本质上应该是对某个消息的主动响应,这里强调的是主动,就是说对象的行为不可以被别人使用,而只能自己主动的去表现出该行为。另外,行为可以表现出来给别人看到,也可以不表现出来给别人看到。实际上,我们永远都不需要将对象的行为表现出来给别人看到,原因是别人不会去使用该行为的,行为永远只能是对象自己去表现出来。
  5. 领域模型这个生态系统中的各个领域对象在运行过程中如果需要和领域模型之外的东西(如数据持久层)交互,也应该通过消息来进行,因为只有这样才能确保领域对象是一个”活“的具有主观能动性的存在于内存中的客观存在。

以上就是我心目中理想的如何设计对象来实现业务逻辑的方式。我想了很久,要完全实现上面的目标实在是太困难了。但也不是不可能,我按照我的能力,经过不断的设计、编码、测试、重构的反复循环过程。基本上设计出了一个令自己基本满意的基础框架出来,基于该框架,以银行转账为例子,我们可以以如下的方式来实现:

  1     public class TransferEvent : DomainEvent
  2     {
  3         public TransferEvent(Guid fromBankAccountId, Guid toBankAccountId, double moneyAmount, DateTime transferDate)
  4         {
  5             this.FromBankAccountId = fromBankAccountId;
  6             this.ToBankAccountId = toBankAccountId;
  7             this.MoneyAmount = moneyAmount;
  8             this.TransferDate = transferDate;
  9         }
 10         public Guid FromBankAccountId { get; private set; }
 11         public Guid ToBankAccountId { get; private set; }
 12         public double MoneyAmount { get; private set; }
 13         public DateTime TransferDate { get; private set; }
 14     }
 15     public class BankAccount : DomainObject<Guid>
 16     {
 17         #region Private Variables
 18 
 19         private List<TransferHistory> transferHistories;
 20 
 21         #endregion
 22 
 23         #region Constructors
 24 
 25         public BankAccount(Guid customerId)
 26             : this(customerId, 0D, new List<TransferHistory>())
 27         {
 28         }
 29         public BankAccount(Guid customerId, double moneyAmount, IEnumerable<TransferHistory> transferHistories)
 30             : base(Guid.NewGuid())
 31         {
 32             this.CustomerId = customerId;
 33             this.MoneyAmount = moneyAmount;
 34             this.transferHistories = new List<TransferHistory>(transferHistories);
 35         }
 36 
 37         #endregion
 38 
 39         #region Public Properties
 40 
 41         public Guid CustomerId { get; private set; }
 42         [TrackingProperty]
 43         public IEnumerable<TransferHistory> TransferHistories
 44         {
 45             get
 46             {
 47                 return transferHistories.AsReadOnly();
 48             }
 49         }
 50         [TrackingProperty]
 51         public double MoneyAmount { get; private set; }
 52 
 53         #endregion
 54 
 55         #region Event Handlers
 56 
 57         private void TransferTo(TransferEvent evnt)
 58         {
 59             if (this.Id == evnt.FromBankAccountId)
 60             {
 61                 DecreaseMoney(evnt.MoneyAmount);
 62                 transferHistories.Add(
 63                     new TransferHistory(
 64                         evnt.FromBankAccountId,
 65                         evnt.ToBankAccountId,
 66                         evnt.MoneyAmount,
 67                         evnt.TransferDate));
 68             }
 69         }
 70         private void TransferFrom(TransferEvent evnt)
 71         {
 72             if (this.Id == evnt.ToBankAccountId)
 73             {
 74                 IncreaseMoney(evnt.MoneyAmount);
 75                 transferHistories.Add(
 76                     new TransferHistory(
 77                         evnt.FromBankAccountId,
 78                         evnt.ToBankAccountId,
 79                         evnt.MoneyAmount,
 80                         evnt.TransferDate));
 81             }
 82         }
 83 
 84         #endregion
 85 
 86         #region Private Methods
 87 
 88         private void DecreaseMoney(double moneyAmount)
 89         {
 90             if (this.MoneyAmount < moneyAmount)
 91             {
 92                 throw new NotSupportedException("账户余额不足。");
 93             }
 94             this.MoneyAmount -= moneyAmount;
 95         }
 96         private void IncreaseMoney(double moneyAmount)
 97         {
 98             this.MoneyAmount += moneyAmount;
 99         }
100 
101         #endregion
102     }
103     public class TransferHistory : ValueObject
104     {
105         #region Constructors
106 
107         public TransferHistory(Guid fromAccountId,
108                                Guid toAccountId,
109                                double moneyAmount,
110                                DateTime transferDate)
111         {
112             this.FromAccountId = fromAccountId;
113             this.ToAccountId = toAccountId;
114             this.MoneyAmount = moneyAmount;
115             this.TransferDate = transferDate;
116         }
117 
118         #endregion
119 
120         #region Public Properties
121 
122         public Guid FromAccountId { get; private set; }
123         public Guid ToAccountId { get; private set; }
124         public double MoneyAmount { get; private set; }
125         public DateTime TransferDate { get; private set; }
126 
127         #endregion
128 
129         #region Infrastructure
130 
131         protected override IEnumerable<object> GetAtomicValues()
132         {
133             yield return FromAccountId;
134             yield return ToAccountId;
135             yield return MoneyAmount;
136             yield return TransferDate;
137         }
138 
139         #endregion
140     }

以上代码是转账事件、银行帐号(实体),以及转账记录(值对象)的实现代码,然后我们可以通过如下的方式来触发TransferEvent事件来让银行帐号”自动“响应。

1 EventProcesser.ProcessEvent(new TransferEvent(bankAccount1.Id, bankAccount2.Id, 1000, DateTime.Now));

如果不需要增加其他的任何代码就OK了的话,那可就真美了,应该差不多可以实现我上面的目标了。但理想终归是理想,而现实的情况是:

1)领域对象的行为不可能做到别人不去调用它就能自己主动表现出来的地步,毕竟它不是一个真正的”活“的有主观能动性的人或动物;

2)领域对象并没有存在于内存中,而是在数据持久化介质中,如数据库,因此我们必须去把领域对象从数据库取出来;

那么难道我们只能放弃了吗?只能自己去做这两件事情了吗?不是,我们可以告诉基础框架如下一些信息,有了这些信息,基础框架就可以帮助我们完成上面的这两件事情了。

 1     RegisterObjectEventMappingItem<TransferEvent, BankAccount>(
 2         new GetDomainObjectIdEventHandlerInfo<TransferEvent>
 3         {
 4             GetDomainObjectId = evnt => evnt.FromBankAccountId,
 5             EventHandlerName = "TransferTo"
 6         },
 7         new GetDomainObjectIdEventHandlerInfo<TransferEvent>
 8         {
 9             GetDomainObjectId = evnt => evnt.ToBankAccountId,
10             EventHandlerName = "TransferFrom"
11         }
12     );

上面的代码的意思是告诉框架1)BankAccount会去响应TransferEvent事件;2)BankAccount对象的唯一标识是从TransferEvent事件中的哪个属性中来的;3)因为这里BankAccount会有两个方法可能会响应TransferEvent事件,所以还指定了响应方法的名字从而可以区分。当然一般情况下,我们是不需要指定方法的名字的,因为大部分情况下一个对象对同一个事件只会有一个响应方法。比如下面的代码列出了很多中常见的事件与响应对象的映射信息:

 1     public class DomainLayerObjectEventMapping : ObjectEventMapping
 2     {
 3         protected override void InitializeObjectEventMappingItems()
 4         {
 5             //BankAccount Event Mappings.
 6             RegisterObjectEventMappingItem<DepositAccountMoneyEvent, BankAccount>(evnt => evnt.BankAccountId);
 7             RegisterObjectEventMappingItem<WithdrawAccountMoneyEvent, BankAccount>(evnt => evnt.BankAccountId);
 8             RegisterObjectEventMappingItem<TransferEvent, BankAccount>(
 9                 new GetDomainObjectIdEventHandlerInfo<TransferEvent>
10                 {
11                     GetDomainObjectId = evnt => evnt.FromBankAccountId,
12                     EventHandlerName = "TransferTo"
13                 },
14                 new GetDomainObjectIdEventHandlerInfo<TransferEvent>
15                 {
16                     GetDomainObjectId = evnt => evnt.ToBankAccountId,
17                     EventHandlerName = "TransferFrom"
18                 }
19             );
20 
21             //Topic Event Mappings.
22             RegisterObjectEventMappingItem<DomainObjectAddedEvent<Reply>, Topic>(evnt => evnt.DomainObject.TopicId);
23             RegisterObjectEventMappingItem<DomainObjectRemovedEvent<Reply>, Topic>(evnt => evnt.DomainObject.TopicId);
24 
25             //ForumUser Event Mappings.
26             RegisterObjectEventMappingItem<PreAddDomainObjectEvent<Topic>, ForumUser>(evnt => evnt.DomainObject.CreatedBy);
27             RegisterObjectEventMappingItem<DomainObjectAddedEvent<Topic>, ForumUser>(evnt => evnt.DomainObject.CreatedBy);
28 
29             //Reply Event Mappings.
30             RegisterObjectEventMappingItem<DomainObjectRemovedEvent<Topic>, Reply>(evnt => Repository.Find<Reply>(new FindTopicRepliesEvent(evnt.DomainObject.Id)));
31         }
32     }

关于这种组织业务逻辑的方法,大家如果有仔细研究的兴趣,可以下载我的框架源代码和聚合演示例子源代码。

http://files.cnblogs.com/netfocus/EventBasedDDDExample.rar

好了,大家觉得这三种组织业务逻辑的方法如何呢?很想听听大家的声音。我是一个喜欢思考问题、寻找真理的人,期望能和大家多多交流。 

时间: 2024-09-16 00:08:43

银行转账业务场景的几种实现思路对比的相关文章

Photoshop设计商务名片的5种常见思路分享

给各位Photoshop软件的使用者们来详细的分享一下设计商务名片的5种常见思路. 分享一览: 如果你要制作一张商务名片,但是又没有设计师帮忙,如何简单设计出一张像模像样的名片呢?这里优之名片通过一些经典的商务名片设计模板,说一说我的一些经验和心得,与大家交流. 在这之前,一定要强调的一点基本原则:如果你没有莫奈一样的色彩把握能力,千万不要把名片设计的花花绿绿,满满当当.同时也不要过于个性. 一来这样名片显得非常廉价(设计理论:色彩越多越显得便宜,这就是为何家乐福之类的大超市海报都花花绿绿,满满

《Netty 权威指南》—— 4种IO的对比

声明:本文是<Netty 权威指南>的样章,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文. 2.5.1.概念澄清 为了防止由于对一些技术概念和术语的理解或者叫法不一致引起歧义,本小节特意对本书中的专业术语或者技术用语做下声明,如果它们与其它的一些技术书籍术语不一致,请以本小节的解释为准. 2.5.1.1. 异步非阻塞IO 很多人喜欢将JDK1.4提供的NIO框架称为异步非阻塞IO,但是,如果严格按照Unix网络编程模型和JDK的实现进行区分,实际上它只能被称为非阻塞IO,不能叫

Javascript中的几种继承方式对比分析_基础知识

开篇从'严格'意义上说,javascript并不是一门真正的面向对象语言.这种说法原因一般都是觉得javascript作为一门弱类型语言与类似java或c#之类的强型语言的继承方式有很大的区别,因而默认它就是非主流的面向对象方式,甚至竟有很多书将其描述为'非完全面向对象'语言.其实个人觉得,什么方式并不重要,重要的是是否具有面向对象的思想,说javascript不是面向对象语言的,往往都可能没有深入研究过javascript的继承方式,故特撰此文以供交流. 为何需要利用javascript实现继

银行线上、线下贷款业务对比

&http://www.aliyun.com/zixun/aggregation/37954.html">nbsp;       始于传统业务,但若要全面开展网络贷款业务会造成银行人员结构.机制管理.系统运营等全方位的革新,从目前市场情况来看,银行网络贷款业务发展仍旧处于"理念阶段".我将根据业务内容.业务模式.业务覆盖程度.业务优劣势等几方面对比,对银行线上.线下贷款业务的区别作出简单阐述. 一.业务模式对比 1.传统银行贷款业务 根据易贷中国向沪上多家合作银

8种Nosql数据库系统对比(转)

8种Nosql数据库系统对比 原文:Kristóf Kovács 编辑:敏捷翻译:唐尤华 分享到: 更多 导读:Kristóf Kovács 是一位软件架构师和咨询顾问,他最近发布了一片对比各种类型nosql数据库的文章.文章由敏捷翻译 – 唐尤华编译.如需转载,请参见文后声明. 虽然SQL数据库是非常有用的工具,但经历了15年的一支独秀之后垄断即将被打破.这只是时间问题:被迫使用关系数据库,但最终发现不能适应需求的情况不胜枚举. 但是NoSQL数据库之间的不同,远超过两 SQL数据库之间的差别

java创建线程的三种方式及其对比

Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行体. (2)创建Thread子类的实例,即创建了线程对象. (3)调用线程对象的start()方法来启动该线程. package com.thread; public class FirstThreadTest extends Thread{ int i = 0; //重写run方法,run方法的方

老生常谈:微博,QQ,淘宝三种开放平台对比

背景: 最近做一个导购网站(aizher.com)尝试,为了方便用户登陆,分别引入新浪微博,QQ,淘宝三种平台上的开放接口接入系统中. 分别使用了下新浪微博,QQ,淘宝平台三种方式,虽然都是开发平台,但是在使用过程中,每种平台都有自己的特色,也有难用的地方. 第一个新浪微博开发平台: 申请链接:http://open.weibo.com/ 申请方式:微博有三种接入方式,分别为网站接入,站内应用,移动应用三种.如果我们单纯只是想网站支持,微博登陆.选择网站接入即可,如果还想在微博应用广场上曝光自己

Java读取Map的两种方法与对比_java

前言 在java中遍历Map有不少的方法.这篇文章我们就来看一下Java读取Map的两种方法以及这两种方法的对比. 一. 遍历Map方法A Map map = new HashMap(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Object key = entry.getKey(); Object val = en

Yann LeCun新作:473种模型大对比,中日韩文本分类到底要用哪种编码?

雷锋网 AI科技评论按:就在前几天,Yann LeCun(中文名:杨立昆,被称为卷积网络之父)与其学生 张翔在arXiv上发表了一篇新作<Which Encoding is the Best for Text Classification in Chinese, English, Japanese and Korean?>.这篇文章做了一个包含473种模型的大型对比实验,实验的目的是对文本分类任务中不同语言(英语.汉语.韩语和日语)不同的level(utf-8 .字符等)和不同的encodin