精华内容
下载资源
问答
  • ElasticSearch企业级开发

    千人学习 2019-12-13 14:39:42
    Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。 本课程深入浅出剖析了Elasticsearch的...
  • 对新手开发者十分友好,无需复杂的操作步骤,仅需 2 秒就可以启动这个完整的商城项目 最终的实战项目是一个企业级别的 Spring Boot 大型项目,对于各个阶段的 Java 开发者都是极佳的选择 实践项目页面美观且实用,...
  • 同时对前面几章的内容进行一个总结性的实战练习,让学生更理解所学内容; 本课程是《Java零基础至高级应用》系统课程的第四章,全套课程精细讲解,高级课程超过其他机构30%的课程量,经过我们全套课程系统学习的同学...
  •  原文链接: http://www.himigame.com/iphone-cocos2d/535.html 这里Himi给出对于开发iOS的朋友们整理一个指南集合,其中主要包括申请IDP需要注意的地方、有了开发者证书如何真机调试、在自己的游戏应用中如何接入...

     李华明Himi 原创,转载务必在明显处注明:
    转载自 【黑米GameDev街区】 原文链接:  http://www.himigame.com/iphone-cocos2d/535.html


            这里Himi给出对于开发iOS的朋友们整理一个指南集合,其中主要包括申请IDP需要注意的地方、有了开发者证书如何真机调试、在自己的游戏应用中如何接入GameCenter以及如何在游戏接入OpenFeint;


            -----------申请企业级IDP,或者个人IDP

           通过Himi的申请经验,直接打苹果在中国的客服,按照步骤一步一步详细的让客服进行指导,可能很多童鞋说我这句跟没说一样,呵呵,如果真的你是第一次申请IDP那么如果你不打客服,N多细节都会造成你1~15天耐心等待,Himi申请过程中由于一个名称和一个勾选错误整整耽误一个月的时间;最后仍是不停的跟客服交涉终于Ok顺利申请到;

         这里Himi给出苹果在中国的客服电话:4006701855 (建议拨打客服之前大致的先百度google下申请IDP的流程,网上一大堆,这里Himi不赘述了)


          ---------------申请到IDP后如何真机调试

         1.制作证书的过程Himi这里不多赘述,百度、google下N多文章呢;制作证书连接(前提是申请IDP成功):http://developer.apple.com/membercenter/index.action

         2.正确制作证书后,有个这样的文件:

                      

         双击此文件,弹出Organizer-Devices界面,连接你的真机iphone、ipad或者touch,然后左侧可以看到如下图:

                 

         右侧的绿色小灯表示可以正常使用,这个小灯如果是黄色,那就说明你的证书有问题,可能是此手机的UDID没有在证书内等原因;

         3.确保真机正常后,点击你的项目,右侧点击PROJECT点击Build Settings页面,然后Code Signing下设置Code Signing Identity为你的证书,如下图:

         


        4.点击你的项目,右侧点击TARGETS,点击Info页面下的设置Bundle identifier,这个Bundle identifier在你制作证书的过程中就会了解到,如下图:

         

      OK,可以编译运行你的项目到真机中了;



         ---------------游戏接入GameCenter 指南


    1.    iTunes Connect 设置

        首先,申请一个应用程序,不必提交.目地是为了得到Bundle ID.
        然后设置一下工程中Info.plist的Bundle identifier使之与iTunes Connect中的Bundle ID相同,否则当你尝试登录GameCenter的时候,会提示一个不支持GameCenter的错误.
        申请完毕,打开你刚申请的application,点击Manage Game Center选项.


        进入后点击Enable Game Center使你的Game Center生效.
        接下来就可以设置自己的Leaderboard和Achievements.

     

    2.    Leaderboard设置
        Leaderboard纵观图如下所示.

     

        1.sort Order: Leaderboard中的内容是以升序还是降序排列.
        2.Score Format Type:分数的类型.
        3.*Categories:Leaderboard的一个分数榜,这个可以创建多个,比如游戏可以分为Easy,Normal,Hard三个难度,每个难度一个榜.


        *设置完成后保存,完成了一个 Leaderboard的设置.我们可以根据需要添加多个 leaderboard.
        4.**Score Format Location: leaderboard支持的语言.


        **可以支持多种语言,每支持一种语言,需要完成一个上述操作.
    这个时候右下角会出现save change按钮,点击完成leaderboard的设置.
    你可以根据需要随时更改你的leaderboard,操作与上述内容类似.

    3.    Achievements设置
        Achievements界面内容比较少,点击左上角的Add New Achievement,打开如下图所示的Achievements创建界面.


        Hidden:表示该成就为解锁前玩家是否可见.
        Achievement ID:程序通过这个属性来识别成就.
        *Achievement Localization:该成就支持的语言.
        *Achievement Localization设置如下图所示.


        *其中,成就的Image必须是512X512,72DPI的.
    一切设置完成后,点击save change按钮即完成一个成就的设置.

    4.总体功能
    在使用各个功能前,你需要了解一下块函数。传送门: https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Blocks/Articles/00_Introduction.html

    4.1 对Game Center支持判断
      

      - (BOOL) isGameCenterAvailable
        {
            Class gcClass = (NSClassFromString(@"GKLocalPlayer"));
             NSString *reqSysVer = @"4.1";
             NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
             BOOL osVersionSupported = ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending);
            
            return (gcClass && osVersionSupported);
         }



    4.2用户登录
       
      - (void) authenticateLocalPlayer
         {
             [[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:^(NSError *error){
                if (error == nil) {
                    //成功处理
                    NSLog(@"成功");
                    NSLog(@"1--alias--.%@",[GKLocalPlayer localPlayer].alias);
                     NSLog(@"2--authenticated--.%d",[GKLocalPlayer localPlayer].authenticated);
                    NSLog(@"3--isFriend--.%d",[GKLocalPlayer localPlayer].isFriend);
                    NSLog(@"4--playerID--.%@",[GKLocalPlayer localPlayer].playerID);
                   NSLog(@"5--underage--.%d",[GKLocalPlayer localPlayer].underage);
               }else {
                    //错误处理
                   NSLog(@"失败  %@",error);
                }
           }];
        }


    对于开发者来说,Game Center必须经过测试才能上线,没有上线的程序在测试环境中登录时会出现sandbox提示.如图.
              

    4.3用户变更检测
    由于4.0以后系统支持多任务,玩家的机器有可能被不同的玩家接触,导致Game Center上的用户发生变化,当发生变化的时候,程序必须在游戏中通知玩家.
       
     - (void) registerForAuthenticationNotification
        {
            NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
            [nc addObserver:self
                   selector:@selector(authenticationChanged)
                        name:GKPlayerAuthenticationDidChangeNotificationName
                     object:nil];
        }
        
       - (void) authenticationChanged
       {
            if ([GKLocalPlayer localPlayer].isAuthenticated)
            {
                ;// Insert code here to handle a successful authentication.
            }
           else
           {
               ;// Insert code here to clean up any outstanding Game Center-related classes.
          }
       }

    5.对Leaderboard进行操作
    5.1上传一个分数
       

      - (void) reportScore: (int64_t) score forCategory: (NSString*) category
        {
           GKScore *scoreReporter = [[[GKScore alloc] initWithCategory:category] autorelease];
            scoreReporter.value = score;
            
             [scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {
                if (error != nil)
                {
                   // handle the reporting error
                    NSLog(@"上传分数出错.");
                   //If your application receives a network error, you should not discard the score.
                   //Instead, store the score object and attempt to report the player’s process at
                    //a later time.
                }else {
                   NSLog(@"上传分数成功");
               }
       
           }];
        }


    当上传分数出错的时候,要将上传的分数存储起来,比如将SKScore存入一个NSArray中.等可以上传的时候再次尝试.

    5.2下载一个分数
      
       //GKScore objects provide the data your application needs to create a custom view.
        //Your application can use the score object’s playerID to load the player’s alias.
         //The value property holds the actual value you reported to Game Center. the formattedValue
         //property provides a string with the score value formatted according to the parameters
         //you provided in iTunes Connect.
         - (void) retrieveTopTenScores
         {
            GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] init];
           if (leaderboardRequest != nil)
            {
               leaderboardRequest.playerScope = GKLeaderboardPlayerScopeGlobal;
               leaderboardRequest.timeScope = GKLeaderboardTimeScopeAllTime;
               leaderboardRequest.range = NSMakeRange(1,10);
                leaderboardRequest.category = @"TS_LB";
                [leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
                    if (error != nil){
                        // handle the error.
                        NSLog(@"下载失败");
                    }
                   if (scores != nil){
                       // process the score information.
                       NSLog(@"下载成功....");
                      NSArray *tempScore = [NSArray arrayWithArray:leaderboardRequest.scores];
                        for (GKScore *obj in tempScore) {
                          NSLog(@"    playerID            : %@",obj.playerID);
                           NSLog(@"    category            : %@",obj.category);
                           NSLog(@"    date                : %@",obj.date);
                           NSLog(@"    formattedValue    : %@",obj.formattedValue);
                            NSLog(@"    value                : %d",obj.value);
                           NSLog(@"    rank                : %d",obj.rank);
                          NSLog(@"**************************************");
                        }
                    }
               }];
           }
       }


    说明:
    1)    playerScope:表示检索玩家分数范围.
    2)    timeScope:表示某一段时间内的分数
    3)    range:表示分数排名的范围
    4)    category:表示你的Leaderboard的ID.

    5.3玩家信息交互
    Game Center最重要的一个功能就是玩家交互.所以,必须检索已经登录玩家的好友信息.根据自己的需要做出设置,比如,可以与好友比较分数,或者好友排行榜等.
    //检索已登录用户好友列表
       
        - (void) retrieveFriends
        {
            GKLocalPlayer *lp = [GKLocalPlayer localPlayer];
            if (lp.authenticated)
            {
                [lp loadFriendsWithCompletionHandler:^(NSArray *friends, NSError *error) {
                    if (error == nil)
                   {
                        [self loadPlayerData:friends];
                   }
                   else
                   {
                       ;// report an error to the user.
                   }
               }];
                
           }
       }


    上面的friends得到的只是一个身份列表,里面存储的是NSString,想要转换成好友ID,必须调用- (void) loadPlayerData: (NSArray *) identifiers方法,该方法得到的array里面存储的才是GKPlayer对象.如下
       
      /*
         Whether you received player identifiers by loading the identifiers for the local player’s
        friends, or from another Game Center class, you must retrieve the details about that player
        from Game Center.
         */
        - (void) loadPlayerData: (NSArray *) identifiers
         {
           [GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
               if (error != nil)
               {
                    // Handle the error.
              }
               if (players != nil)
               {
                  NSLog(@"得到好友的alias成功");
                   GKPlayer *friend1 = [players objectAtIndex:0];
                  NSLog(@"friedns---alias---%@",friend1.alias);
                  NSLog(@"friedns---isFriend---%d",friend1.isFriend);
                  NSLog(@"friedns---playerID---%@",friend1.playerID);
               }
           }];
       }


    至此,leaderboard功能介绍完毕

    6.对Achievement进行操作
      这一部分内容比较多,而且有的地方有点重复的感觉.
    6.1汇报一个成就的进度
      对于一个玩家可见的成就,你需要尽可能的报告给玩家解锁的进度;对于一个一部完成的成就,则不需要,当玩家的进度达到100%的时候,会自动解锁该成就.
       
     - (void) reportAchievementIdentifier: (NSString*) identifier percentComplete: (float) percent
        {
             GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier: identifier] autorelease];
            if (achievement)
           {
                achievement.percentComplete = percent;
                 [achievement reportAchievementWithCompletionHandler:^(NSError *error)
                  {
                      if (error != nil)
                    {
                       //The proper way for your application to handle network errors is retain
                        //the achievement object (possibly adding it to an array). Then, periodically
                       //attempt to report the progress until it is successfully reported.
                       //The GKAchievement class supports the NSCoding protocol to allow your
                        //application to archive an achie
                        NSLog(@"报告成就进度失败 ,错误信息为: \n %@",error);
                  }else {
                         //对用户提示,已经完成XX%进度
                        NSLog(@"报告成就进度---->成功!");
                        NSLog(@"    completed:%d",achievement.completed);
                       NSLog(@"    hidden:%d",achievement.hidden);
                        NSLog(@"    lastReportedDate:%@",achievement.lastReportedDate);
                       NSLog(@"    percentComplete:%f",achievement.percentComplete);
                        NSLog(@"    identifier:%@",achievement.identifier);
                   }
                 }];
           }
       }


    其中该函数的参数中identifier是你成就的ID, percent是该成就完成的百分比

    6.2读取一个成就
    方法一:得到所有的成就
       
     - (void) loadAchievements
         {
           NSMutableDictionary *achievementDictionary = [[NSMutableDictionary alloc] init];
             [GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements,NSError *error)
             {
                 if (error == nil) {
                    NSArray *tempArray = [NSArray arrayWithArray:achievements];
                     for (GKAchievement *tempAchievement in tempArray) {
                         [achievementDictionary setObject:tempAchievement forKey:tempAchievement.identifier];
                        NSLog(@"    completed:%d",tempAchievement.completed);
                        NSLog(@"    hidden:%d",tempAchievement.hidden);
                       NSLog(@"    lastReportedDate:%@",tempAchievement.lastReportedDate);
                        NSLog(@"    percentComplete:%f",tempAchievement.percentComplete);
                        NSLog(@"    identifier:%@",tempAchievement.identifier);
                    }
                }
             }];
       }


    函数中NSArray返回的是你的所有成就ID.

    方法二:根据ID获取成就
       
     - (GKAchievement*) getAchievementForIdentifier: (NSString*) identifier
         {
            NSMutableDictionary *achievementDictionary = [[NSMutableDictionary alloc] init];
           GKAchievement *achievement = [achievementDictionary objectForKey:identifier];
            if (achievement == nil)
            {
                 achievement = [[[GKAchievement alloc] initWithIdentifier:identifier] autorelease];
                [achievementDictionary setObject:achievement forKey:achievement.identifier];
            }
          return [[achievement retain] autorelease];
       }



    6.3获取成就描述和图片
    在自定义界面中,玩家需要一个成就描述,以及该成就的图片,Game Center提供了该功能.当然,你也可以自己在程序中完成,毕竟玩家不可能时刻处于在线状态.
      
      - (NSArray*)retrieveAchievmentMetadata
        {
            //读取成就的描述
            [GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:
              ^(NSArray *descriptions, NSError *error) {
                if (error != nil)
                 {
                      // process the errors
                      NSLog(@"读取成就说明出错");
                }
               if (descriptions != nil)
                 {
                    // use the achievement descriptions.
                    for (GKAchievementDescription *achDescription in descriptions) {
                         NSLog(@"1..identifier..%@",achDescription.identifier);
                         NSLog(@"2..achievedDescription..%@",achDescription.achievedDescription);
                        NSLog(@"3..title..%@",achDescription.title);
                         NSLog(@"4..unachievedDescription..%@",achDescription.unachievedDescription);
                       NSLog(@"5............%@",achDescription.image);
                       
                         //获取成就图片,如果成就未解锁,返回一个大文号
                        /*
                        [achDescription loadImageWithCompletionHandler:^(UIImage *image, NSError *error) {
                            if (error == nil)
                            {
                                // use the loaded image. The image property is also populated with the same image.
                                NSLog(@"成功取得成就的图片");
                                 UIImage *aImage = image;
                               UIImageView *aView = [[UIImageView alloc] initWithImage:aImage];
                                aView.frame = CGRectMake(50, 50, 200, 200);
                                aView.backgroundColor = [UIColor clearColor];
                                [[[CCDirector sharedDirector] openGLView] addSubview:aView];
                             }else {
                               NSLog(@"获得成就图片失败");
                            }
                       }];
                          */
                    }
                }
            }];
          return nil;
       }


    如果你不主动使用注释中的方法,那么你得到的description中不会有图片,这样可以减少网络的使用,尽量少下载东西.当使用注释中的代码时,如果成就已经解锁,则返回该成就的图标,如果没有解锁,则返回一个大问号,至于未解锁图标是否可以自定义,我找寻的结果好像是不可以.

    GameCenter  成就提示更新:

     GameCenter中成就提示需要开发者自定义,即使官方Demo的例子程序中也没有给与提示框(横幅样式)通知用户的官方代码,所以这里Himi介绍如何模仿官方的成就提示:

       1. iOS5以及更高SDK中,apple已经提供官方的成就提示:方法很简单,代码如下:

    - (void)sendAchievement:(GKAchievement *)achievement {
    
    achievement.percentComplete = 100.0;   //Indicates the achievement is done
    achievement.showsCompletionBanner = YES;    //Indicate that a banner should be shown
    [achievement reportAchievementWithCompletionHandler:
    ^(NSError *error) {
    dispatch_async(dispatch_get_main_queue(), ^(void)
    {
    if (error == NULL) {
    NSLog(@"Successfully sent archievement!");
    } else {
    NSLog(@"Achievement failed to send... will try again \
    later.  Reason: %@", error.localizedDescription);
    }
    });
    }];
    }

    将“showsCompletionBanner”属性设置成YES,提交给苹果。新iOS属性“showsCompletionBanner”,其默认设置是NO,但若将其调整成YES,屏幕就呈现包含成就标题和描述的漂亮横幅;

    2.如果低于5.0的SDK设备中是没有 “showsCompletionBanner”属性的,所以需要我们自定义提示样式,当然这里Himi也分享一下如何模仿官方横幅提示样式的方法和代码:

          首先,我借鉴Type One Error所展示的优秀代码,从https://github.com/typeoneerror/GKAchievementNotification中抓取一些代码植入自己的项目。我们将采用GKAchievementNotification和GKAchievementHandler类,同时进行相应更新和修改。首先,若你在游戏中运用ARC,快速扫描代码,移除那些发行、保留和自动发行代码属性。若你不想进行扫描,试着将文件放入项目及修复不符编译程序的内容,然后再创建内容。

    Type One Error类将展示类似于iOS 5所呈现的通知内容,但代码需获悉成就标题和描述是什么。为实现这点,你需要嵌入“showsCompletionBanner”目标。

    GKAchievementDescription目标的优点是它们已根据用户语言设定进行本土化,因此采用此方式不存在任何本土化问题。

    其弊端在于你无法只加载一个成就描述,你需要加载所有内容。我认为进行此操作的最佳时间是用户已在应用上认证Game Center,此时你需要通过异步调用获得这些消息。值得欣慰的是,苹果在此设有API调用,我将此放置在用户认证访问的CompletionHandler中。

    若你采用Ray Wenderlich网站的代码,那么你就既能够运用此方法,又拥有新方法。将NSMutableDictionary * self.achievementsDescDictionary添加至所有处理游戏Game Center代码的类(游戏邦注:它会在随后的体验中存储成就数据)。

        

    - (void)authenticateLocalUser {
    
    if (!gameCenterAvailable) return;
    
    NSLog(@”Authenticating local user…”);
    if ([GKLocalPlayer localPlayer].authenticated == NO) {
    [[GKLocalPlayer localPlayer]
    authenticateWithCompletionHandler:^(NSError *error) {
    if([GKLocalPlayer localPlayer].isAuthenticated){
    [self retrieveAchievmentMetadata];         //Here is the new code
    }
    }];
    }
    }
    
    //Here is the new method.
    - (void) retrieveAchievmentMetadata
    {
    self.achievementsDescDictionary = [[NSMutableDictionary alloc] initWithCapacity:2];
    
    [GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:
    ^(NSArray *descriptions, NSError *error) {
    if (error != nil) {
    NSLog(@"Error %@", error);
    
    } else {
    if (descriptions != nil){
    for (GKAchievementDescription* a in descriptions) {
    [achievementsDescDictionary setObject: a forKey: a.identifier];
    }
    }
    }
    }];
    }
    
    “retrieveAchievmentMetadata”方法会初始化所有信息库,然后调用游戏所有成就描述,进行循环,将它们添加至信息库。这属于异步调用,所以不应减缓游戏或项目的启动。
    
    现在我们握有各成就的标题和描述,因此能够修改原始代码创造iOS 4/5的善意通知,其将通过Type One Error代码连续展示所有成就。
    
    - (void)reportAchievement:(NSString *)identifier
    percentComplete:(double)percentComplete {
    
    GKAchievement* achievement = [[GKAchievement alloc]
    initWithIdentifier:identifier];
    achievement.percentComplete = percentComplete;
    
    if (percentComplete == 100.0) {
    //Show banners manually
    GKAchievementDescription *desc = [achievementsDescDictionary objectForKey:identifier]; //Update pull achievement description for dictionary
    
    [[GKAchievementHandler defaultHandler] notifyAchievement:desc];  //Display to user
    
    }
    [achievementsToReport addObject:achievement];    //Queue up the achievement to be sent
    [self save];
    
    if (!gameCenterAvailable || !userAuthenticated) return;
    [self sendAchievement:achievement];   //Try to send achievement
    }
    
    - (void)sendAchievement:(GKAchievement *)achievement {
    [achievement reportAchievementWithCompletionHandler:
    ^(NSError *error) {
    dispatch_async(dispatch_get_main_queue(), ^(void)
    {
    if (error == NULL) {
    NSLog(@"Successfully sent archievement!");
    [achievementsToReport removeObject:achievement];   //Remove Achievement from queue.
    } else {
    NSLog(@”Achievement failed to send… will try again \
    later.  Reason: %@”, error.localizedDescription);
    }
    });
    }];
    }

    如果你想让成就中显示为你在itunes connect中设置成就的自定义图片,首先将通知部分代码修改成如下代码:

    if (percentComplete == 100.0) {
    //Show banners manually
    GKAchievementDescription *desc = [achievementsDescDictionary objectForKey:identifier];
    
    [desc loadImageWithCompletionHandler:^(UIImage *image, NSError *error) {
    if (error == nil)
    {
    [[GKAchievementHandler defaultHandler] setImage:desc.image];   //If image found, updates the image to the achievement image.
    }
    [[GKAchievementHandler defaultHandler] notifyAchievement:desc];
    }];
    
    }

    使用以上方式默认为横屏显示成就通知,如果想换成竖屏提示,那么这里Himi给出参考代码:

    在“GKAchievementHandler”类中找到“notifyAchievement”,更新为:

    - (void)notifyAchievement:(GKAchievementDescription *)achievement
    {
    
    GKAchievementNotification *notification = [[GKAchievementNotification alloc] initWithAchievementDescription:achievement];
    notification.frame = kGKAchievementFrameStart;
    notification.handlerDelegate = self;
    
    //Adjusting rotation.
    
    if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft) {
    notification.transform = CGAffineTransformRotate(notification.transform, degreesToRadian(-90));
    } else {
    notification.transform = CGAffineTransformRotate(notification.transform, degreesToRadian(90));
    }
    
    [_queue addObject:notification];
    if ([_queue count] == 1)
    {
    [self displayNotification:notification];
    }
    }


          --------------- 游戏接入OpenFeint指南

    OpenFeint 是很多iPhone游戏开发者都要用到的社区功能;

    一、openfeint中的LeaderBoards 和Achievement的一点体会
        1.数据提交的格式
        最近想向自己 的游戏中添加点openfeint功能,使用的时候发现,openfeint的功能虽然比较多,也比较强大,但是,有些地方还是不太如人意。我游戏中的分 数有一项是float型的数据,可是当我提交的时候,发现openfeint的在线排名只支持整数形式的数据,改变了官方的api提交之后,服务器那边仍 自动转换成了整型的数据。我在论坛上求证了一下,虽然没有结论,但我认为openfeint高分排行榜仅支持整型的数据。

        2.数据提交的方法
        [OFHighScoreService setHighScore:你提交的分数 forLeaderboard:@"分数项的ID" onSuccess:OFDelegate() onFailure:OFDelegate()]; //提交高分,如果函数无效,请引入#import "OFHighScoreService.h"
        通过上面那个函数,就可以向服务器提 交数据,其中你要提交的分数,无论是什么类型,最后都会转换成整型的数据,可以参见上一条信息。而分数项ID,则是你在申请LaderBoards的时候 openfeint分配给你的一个数字。后面两个参数,应该不需要改变,我没有尝试过做其他的动作,有兴趣和想法的朋友,可以尝试象@selector那 样使用它。

        [OFAchievementService unlockAchievement: @"成就ID" onSuccess: OFDelegate() onFailure: OFDelegate()];//解锁成就,如果函数无效,请引入#import "OFAchievementService.h"
       这个函数的功能是解锁成就,当你在游戏过程中达到某一个要求时,就可以解锁你在openfeint上预设的成就。
       例如:
       生存游戏中:if (生存时间 > 100s )  { 调用上面的函数解锁你自己预设的成就; }
       jump游戏中:if ( 高度 > 10000m )  { 调用上面的函数解锁你自己预设的成就; }

        3.网络对分数提交的影响
        网络畅通的情况下,调用上述函数提交分数(最高分数被刷新时) 可以成功,并且解锁成就并不会反复出现解锁提示。好吧,既然这个可以完成我们的要求,那么这里就不是重点。

        网络不通的情况下,就会出现一点问题:
        在阐明问 题之前,我想先说一下我对openfeint的数据存储的理解或者说感觉。使用penfeint的时候,在documents目录下会生成两个文件,一个 FakeKeyChain.plist,据我观察,这里面存放的就是我们在openfeint里为这个游戏申请的Product Key和Product Secret,而且Secret经过了加密处理。另一个文件则是feint-offline, 这个文件是无法打开的,在windows用记事本打开也是一堆乱码,也许有其他的办法,不过我没有找到。我对于这个文件的用途猜测是,这个文件用来存档玩 家的一些信息,比如玩家名和分数等,这个文件我暂时叫它为“本地隐 藏信息表”吧。
        
        问题来鸟,在没有网络的情况下,取得了一个分数,然后第一次调用分数提交函数,会提示你得 到了一个高分,存储在本地(我感觉就在本地隐藏信息表中), 问题出现了!当你这时连接网络,分数并不会自动提交,而你自己手动提交(比如点击一个按钮,按钮的功能是提交最高分数)也没有任何的效果。
        而 在官方文档中有这样一Q&A:
        Q:what happens to a high score when a player is offline?
        A:as os openfeint 2.1 high scoreare queued for submission when the player is offline and submitted when next he's online again.

        Q:if a user says no to using openfeint the first time,is there a way that user can change his or her mind to allow openfeint in the future?
        A:when you deny openfeint it will prompt you to approve/deny again when you open the dashboard([OpenFeint launchDashboard]).it will not prompt you on the next app bootup,or submitting any requests.only when you open the dashboard.

        也就是说,提交失败,于是我做了个试验,在有网络的情况下,提交一个分数100,只显示 一次,第二次提交100时,没有提示。然后提交101,有提示,第二次提交101,没有提示。说明了本地隐藏信息表中还存储了一个最高分数的提交次数和提交许可,使用一次提交分数的函数,这些内 容就会改变,只有新提交的分数比原来存储的分数大时,本地隐藏信息 表才会允许你向openfeint正式提交,否则,无效,感觉上和retain与release有点像。也就时说,最高的分数在提交的时 候,没有网络,就等于失败,这里应该算是openfeint的一个小bug吧。也是我遇到问题的所在,没找到什么解决办法,大伙有经验的可以提出来。

        用 个简单的图来形容下吧。
        无网络->得到新高分->存储在本地->联网后->不自动上传最高分。
        
        顺 便说下成就的提交,没有网络,不可解锁成就,也没有存在本地的提示,联网后,同样也不自动解锁,只有再一次达到条件(方才例子中的if成立)时,才会再次 解锁。

        以上,是我的部分openfeint基础使用的经验,也许是我的方法不正确,也许有别的解决办法,我能提供给大家的帮助, 先这么多了。


    二、 openfeint的设置(2.4.8版)
        以下步骤是假设你从没安装过openfeint,如果有,请将以 前老版本的openfeint从机器中删除,并从project中删除所有与openfeint有关的东西,然后,你可以按下面的步骤来做了。
        1. 从官网下载一个最新版本的没有解压的openfeint SDK。
        2.将openfeint文件夹拖入你的project中。
        3. 设置info    
             a.打开project的info,选中build栏,将configuration设置成All configuration
             b.将Other Linker Flags一项的值设置成 -Objc  区分大小写
             c.将Call C++ Default Ctors/Dtors in Objective-c项的选成yes(这一步我没有设置,不知道是什么意思,英文原文如下:Ensure 'Call C++ Default Ctors/Dtors in Objective-c' is checked under the 'GCC 4.2 - Code Generation' section)
             d.设置一个默认的值GCC_OBJC_CALL_CXX_CDTORS 为 YES(这一步我也没有设置)
        4.引入frameworks
             需要引入的frameworks 有,Foundation,UIKit,CoreGraphics,QuartCore,Security,SystemConfiguration,libsql3.0 dylie,libz.1.2.3.dylib这些是官方给出需要引入的frameworks,根据帮我搭建工程的前辈说,必须要引入 CoreLocation.framework    CFNetwork.framework   MapKit.framework  
         5.必须在你的   .pch   文件中引入#import “OpenFeintPrefix.pch”
         6.将你所有使用openfeint功能的函数改为  .mm  文件

         我能想到的就这些了,还有什么问题,大家可以互相讨论。

    三、openfeint的注册
        openfeint的注册并不难,能看懂 文档的水平基本就可以了,也可以配合翻译软件来弄。
        1.打开官网 http://www.openfeint.com 
        2.选右上角的 Developers一项,跳转到的新界面。
        3.点击本页面的右上角的login会进入登陆界面,选择右上角的 sign up进行一个简单注册,本页右下角有一个教学的视频,告诉你如何使用openfeint的基础功能。
        4.简单注册界面,填写完成后跳转到一个新的界面,点击Dive in 进入你自己的openfeint里。
        5.进入自己的openfeint了,需要进行一个prepare for submission的申请,这个可以让你的openfeint有效,否则,你只能使用test user 进行测试。在App Home中,可以看到自己的client Id 这个是用来提问用的,以及最重要的Product Key和Product Secret,这两项是用来识别你的程序独有的openfeint的。
        6.还需要一个你注册时使用的邮箱认证。进入自己的邮箱就能看到 了。
        7.想通过openfeint的审核,还需要完善一个ipurchase的填写,在basic features/iPurchase里面填写,*项必须要有内容。
        8.完成上面这些,你就可以设置自己的LaderBoards和 Achievement了,还有更多的challenge等。


         


                                     ------以上为Himi的经验总结以及参考资料,希望对大家有帮助;








    展开全文
  • 一篇关于Flex技术在企业应用的开发,发表于InfoQ

    Flex技术在企业级开发中的应用

    作者:池建强

    从我个人的从业经历来看,在长达十几年的软件研发过程中,无论是研发的产品或实施的项目,大部分是在为企业客户提供服务。当然,期间我还从事过两年左右的互联网应用的开发。早期的互联网应用开发和企业级应用开发的区别还是很明显的,无论是技术、架构、业务和用户体验,都有很大的不同。举个简单的例子,比如开发语言,最早在2000年左右,大家都用Perl和Asp做网站,后续陆续开始使用PHP、Ruby和Python这样的动态语言来构建丰富多彩的互联网应用,当然这其中也少不了Flex技术。而为企业客户构建的应用,则更多地倾向于静态语言,比如Java和C#等。当然随着技术的发展,这两者之间的交集越来越多,大家会越来越多的发现,很多大规模的互联网站点是基于Java或C#构建的,也有一些企业应用开始使用动态语言。这一点也很明显地展示了企业级应用与互联网的融合。     
      
    什么是企业级应用?   
    说了这么多,需要为企业级应用系统做一个定位。事实上这个概念在业界并不是十分清晰,没有一个明确的定义,什么是企业级,为什么叫企业级呢?有的观点是从系统规模上划分,有的是从团队规模上划分,有的是从开发周期上划分。我个人对企业级应用系统的定义比较简单,主要是用来区分互联网应用和个人软件。什么是互联网应用呢,四大门户(如新浪、网易等)、百度和淘宝、各种SNS网站、博客系统和微博系统等等;个人软件呢,就是指安装在个人PC上的客户端软件,例如编辑器、绘图软件、开发工具等。这两种类型的应用和软件受众都是普通大众,而企业级应用系统的受众是企业客户,是为企业服务的,企业级应用系统的使用者是企业内外部客户以及与企业业务关联的人员。

    2009年,在技术层面可以说是一个风起云涌的年份,互联网像一条巨大而充满吸力的纽带,把各种IT服务相关的技术、应用和实现都吸引过来,形成了一个完整而庞大的互联网生态圈。那身处其中的我们认识到了什么呢?随着我们持续的通过技术、平台、产品和项目为企业客户提供服务,我们发现企业应用不再局限在Intranet内部,企业应用系统的互联网化趋势越来越明显,主要体现在以下三个方面:
    1. Intranet到Internet的转变:企业应用系统由局域网转到互联网,企业应用开始要求多浏览器支持,国际化的支持,全球业务的互联互通。同时企业应用不再满足简单的表单和表格界面,富互联网应用(RIA)的需求应运而生,企业客户越来越倡导用户体验,RIA也是我们后续要重点讨论的话题。
    2. 企业应用的内容转变:除了企业的核心业务系统,这样一些需求渐渐浮出水面:交互性门户系统、电子商务平台、企业级2.0(博客、Wiki、RSS和微博等)、企业级SNS(社区平台)和无线企业应用等。
    3. 需求的转变:除了功能需求,客户对于安全、性能、大容量和大并发等特性愈发关注,在可预见的未来,企业应用一定是构建在互联网而非局域网,可能是在云端,也可能在其他的新技术上实现
    作为现阶段的IT服务提供商,必须从技术层面和业务层面去适应和支撑这样的趋势变化,否则我们会变得步履艰难。

    好了,谈了这么多,主要讲了一个趋势的变化。下面我们来看一下在互联网和企业应用中都能发挥巨大作用的RIA技术。
    RIA简介和选择Flex的原因
    RIA技术的全称是富互联网应用(Rich Internet Application),RIA首先应该是一个网络应用程序,其次它还要具有桌面应用程序的特征和功能。可以这样理解,如果你的桌面程序能在网络上(目前主要是基于浏览器)运行,并且能保持其原来的功能和特征,那么我们就可以称它们为RIA应用(富互联网客户端应用)。

    目前RIA的主流技术主要包括Adobe公司的Flex,微软公司的Silverlight和Java阵营的JavaFX。Flash由于Flex SDK的支撑,很早就从单纯的动画展示转入RIA领域,而且由于Flash的普及,Flex目前应该是三大技术体系中市场份额最大、应用最广泛的技术;Silverlight是微软推出的跨浏览器和跨平台的插件,能在微软的.NET上交付炫目的多媒体体验和有丰富交互功能的Web应用,已经对Flex有了很大的冲击;JavaFX是未被收购前的Sun公司在2007年推出的用来对抗Flex和Silverlight的桌面应用,但由于起步较晚,目前应用并不广泛,但其Java的原生性和开源性质对Java社区的开发人员还是有很大的吸引力。

    基于以上三种技术,我们最终选了Flex做企业级的富客户端应用开发,虽然苹果公司的CEO乔布斯老师已经开始公开表示不在苹果的移动设备上支持Flash,尽管HTML5和CSS3来势汹汹,但是在企业应用开发这样一个不是非常激进的领域,考虑到Flash广泛的群众基础,最终我们还是选择了Flex。

    事实上在互联网应用中,RIA技术早已散发出夺目的光辉和迷人的魅力,无论是电子商务中的产品展示,还是SNS网站上的交友游戏,亦或是游戏和教育领域里的交互性设计,已经为广大互联网用户带来了无以伦比的客户体验。那么在企业应用系统中,企业客户还在满足于呆板的树形结构、简单的表格和文字性质的描述吗?就我们的经验来说,2008年开始,企业客户就开始向我们提出这样的需求了,例如操作复杂的表单、图形化内容展示、动态报表绘图、图形化流程配置、流媒体视频播放和文档播放等,这一切都是在浏览器上进行的。对于大部分这样的需求,我们都是笨手笨脚的使用了Javascript、Extjs、Jquery和Activex等前端技术勉强实现了,对于不能实现的需求,我们只能腼腆的告诉客户,这些功能我们还实现不了,或者说浏览器不应该有这样的操作等等,当然这种话事实上也很难说服我们自己。

    直到我们决定采用Flex技术来实现富客户端操作之后,我们才发现很多问题在Flex面前迎刃而解了。在Full-Stack系统中,如果Ajax技术和Flex技术配合形成前端组件体系,将大大提高开发效率、系统性能和改进客户体验。

    Flex是Adobe公司开发的可以输出成基于Flash Player来运行的互联网应用程序。Flex 基于标准的语言,与各种可扩展用户界面及数据访问组件结合起来,使开发人员能够快速构建具有丰富数据演示、强大客户端逻辑和集成多媒体的应用程序。Flex目前最新版本是4,一个Flex应用程序应该有两种语言代码完成,那就是ActionScript和MXML。ActionScript是一种面向对象的脚本语言,MXML则是一种标记语言,非常类似于大家所熟悉的超文本标记语言(HTML),扩展标记语言(XML)。简单来说MXML用来描述界面,ActionScript用来处理业务逻辑。

    以下是Flex的一些基本特点,也是我们采用Flex的重要原因之一:
    1. 可视化开发,通过拖拽方式开发界面
    2. 对于有XML和脚本开发经验的人员,很容易上手
    3. 可实现表现层与后台的真正分离
    4. 丰富的媒体支持和动画效果,良好的用户体验
    5. 支持多种通讯方式和数据格式
    6. 同时支持客户端和浏览器模式
    7. 跨平台,支持各种操作系统和浏览器
     
    基于Flex构建企业级应用开发平台    
    与普通开发者使用Flex技术不同的是,我们采用了一种组件化的方式引入Flex,这是因为我们对这部分技术的引入并不是从零开始,为了应对企业级应用开发的需求,我们很早就构建了一个企业级应用开发平台——GAP(Global Application Platform)平台,这是一个Full-Stack的应用开发平台,除了底层框架、组织权限、工作流引擎、数据字典等等,还包括界面框架、通用Web控件,Ajax控件等,Flex的引入是对现在平台的补充和完善。

    基于以上考虑,我们对Flex的应用分为三个阶段。
    首先,进行Flex与GAP平台的整合,包括组件化集成、前后台通信机制的设计,在这一阶段我们主要采用了ant和xdoclet技术进行组件的打包、资源文件的合并,采用Spring BlazeDS Integration技术与GAP平台框架进行交互访问,通信方式采用了Felx提供的RemoteObject。(Spring BlazeDS Integration是Adobe与Spring共同联合开发一个开源项目,其目标是开发者可以利用Spring开发模型通过Flex、BlazeDS、Spring以及Java技术创建RIA )。

    其次,构建Flex图形组件框架,对Flex提供的控件进行封装、扩展,形成针对企业应用的个性化RIA控件库。在这一阶段我们主要是基于Adobe的开源项目Cairngorm进行构建的。Cairngorm是一个基于Flex技术的微内核的MVC框架,设计简洁而易于扩展,非常适合构建自己的RIA控件库。

    第三,使用Flex技术解决企业应用中的实际问题。
         
    下面我们主要从实际应用的角度来看一下Flex在企业级系统开发中能够做什么。

    一、企业组织结构的图形化展示    
    凡是为企业开发过系统的人都知道,企业的组织结构管理和权限管理几乎是每个项目或产品不可或缺的基础组件之一。从功能角度分析,GAP平台的组织权限系统已经非常完善了,无论是多关系的组织结构、细粒度的权限控制,都可以非常好地满足客户对于组织管理和安全的需求。直到有一天一个客户提出,能不能把那棵呆板的组织机构树变成组织结构图,如果能支持图形化操作就更好了。听到项目组给我们反馈的这个需求,第一个反应就是拒绝,因为实现起来太麻烦了。

    原有的组织结构树如下图所示:

    客户需要的展示方式可能是:

    也可能是:


    同时企业客户还希望能够进行图形化操作管理,这种功能如果通过Javascript来实现无疑是困难的,而Flex技术在处理类似的功能时则具备先天的优势。首先Flex是一种可以运行在网络上的客户端技术,它提供了一套成熟的图形化控件和类库,可以很容易的实现图形和布局控制。同时,Flex可以通过多种通讯方式(HttpService、RemoteObject、WebService)与Server端的服务进行数据交互,使得图形化操作变得非常简单,例如把人员拖到另一部门,双击显示该机构的详细信息等。最终我们也是通过Flex技术实现了客户的需求。
    二、表单操作
    Flex同样可以构造出复杂的表单功能,操作便捷,响应迅速,适应企业不同场景的需求。例如这样一个基于Flex技术的表格,看似简单,实际上是包含了排序、过滤、表头拖拽、表头固定、合并等功能,类似的功能如果用Ajax的方式来实现代码量会很大,但是在Flex中,这些特性基本上是原生的,或经过简单开发即可实现,代码量非常小,而且性能远远超过普通列表控件和Ajax列表控件。经测试,在同一场景下,通过Flex列表控件加载1000条数据,平均响应时间是0.1秒,Ajax控件0.5秒,普通刷新页面的方式最慢,从发出请求到返回并显示数据,大概需要1秒钟。


    三、流程设计器
    2004年我们开始研发工作流平台,其核心功能是工作流引擎和流程设计器。为了开发出Web-Based(基于Web)的流程设计器,我们投入了极大的人力物力,最终采用ActiveX控件实现了复杂的流程设计、流程监控等功能。到目前为止基于浏览器的流程设计和监控仍然是我们的功能特色之一。但是随着技术的发展,基于Activex控件的流程设计器越来越显示出局限性,例如不支持多浏览器,不支持国际化,在各种Windows和IE版本中的自动安装经常会出现问题,最重要的是扩展起来比较复杂。
    下图就是基于ActiveX技术的流程设计器。看上去很美,但的确存在着问题。事实上我们正在积极准备基于Flex技术对流程设计器进行改造,改造完成后,上面提到的问题即可迎刃而解。技术的进步带来应用的改进,所以我一直强调,技术创新才是软件企业的原动力。



    四、动态图表
    通过图表描述业务数据,加强数据的展现能力。每个图表都可以支持参数的动态变化,响应点击事件,实现动态效果,而这些几乎不需要额外编写代码。传统的报表工具或制图工具,例如BIRT或JFreeChart等,处理报表图片时都是通过流的方式输入静态图片,一旦生成,就是静态页面,用户无法与之交互。而使用Flex进行图表的开发,则可以轻易突破 这一障碍,Flex提供了大量内置的图表控件,用来进行图表展示,在浏览器上的表现方式为Flash,可以在生成图表之后,继续实现前后台数据的交互和展示。例如使用CandlestickChart控件来实现蜡烛图的动态展示。从一下两张图可以很清楚的看出,通过选择不同的选项,可以显示不同的趋势变化,点击图元还可以显示该图元的相关数据。这些复杂的数据操作基本上是由CandlestickChart控件完成的,开发者只需要组装数据即可。事实上要实现这个功能样例,只需要100行左右的代码。


    五、知识管理
    企业信息化10年,积累了大量的数据、文件、视频需要进行展示,通过Flex可以构建通用的播放器,利用流媒体技术、全文检索技术实现企业内部的知识管理。类似的技术和功能在互联网上已经不是什么新鲜事了,例如slideshare、youtube,国内的豆丁、优酷等,这些网站要么是基于文档的管理,要么是基于视频的处理,但它们的共同点是核心技术都采用了Flex。对于互联网领域,领域的细分无疑是非常明智的,但是在企业级应用领域,企业客户更关注的是应用的整合,我们拟基于Flex技术开发通用的前台播放器,可以同事播放视频和文档,后台采用Flash Midea Server和文件服务器,结合内容管理、全文检索和标签云技术,实现企业信息数据的全流程管理。

    六、动态商品展示
    基于Flex技术的动态商品展示已经在很多中大型电子商务公司应用,通过Flex可以实现很多眩目的效果来进行商品展示,同时可以动态设置商品的属性,让客户更好的了解商品细节,增加客户粘度。这样的技术在企业级应用中同样适用,主要业务领域应该是企业的电子商务平台。

    七、全键盘操作
    某些特殊领域的客户,比如要快速录入大量数据,就会对全键盘操作有需求,要求在浏览器做的应用要能像Excel一样全键盘操作,除了支持Tab加Enter键之外,还需要能够支持四个方向键的操作支持,就像是Excel一样,当单元格中的文字处于全选状态的时候可以通过方向键进行导航。这样的功能用普通的Javascript实现一个是复杂,而是会降低网页性能。但用Flex来实现这种特殊场景就完全没由这些问题,本质上来说Flex还是客户端技术,可以很好的提供键盘支持。

         ......
      
    结束语   
    企业客户越来越认识到RIA技术对于企业应用的重要性,而Flex就是实现RIA重要的选择之一。但是,无论技术也好,创新也好,有用的才是好的,不能因为技术而使用技术,因为创新而创新。就Flex而言,从根本上说它还是一个客户端程序,所以一定会比普通的Web页面更多的占用更多的客户端资源,所以我不建议大家在构建企业应用时大量采用Flex技术,一定要用其所长。Flex在互联网领域早已大放异彩,那么它是否能成功应用于企业应用的开发呢?不必拭目以待,我想这个答案无疑是肯定的。










         



    展开全文
  • 开发健壮的企业级应用的研究

    千次阅读 2006-06-23 10:48:00
    开发健壮的企业级应用的研究 Research on Develop Hale Enterprise Applications 1.03版 作者: shendl_s@hotmail.com [注: 这篇文章,是我的原创。我同时也在其他网站发布了这篇文章。不要误会我掠人之美

     开发健壮的企业级应用的研究
         Research on Develop Hale Enterprise Applications
                              1.03版
                              作者:     shendl_s@hotmail.com

     

    [注:   这篇文章,是我的原创。我同时也在其他网站发布了这篇文章。不要误会我掠人之美哦^-^      ]
        

     

     

     

     

     

     

     


                          写在前面的话
     这是2006年,我参加华中科技大学硕士论文答辩写的一篇论文。但是,这篇论文写的太过Blog化,被导师否决了。我打算重写一篇学位论文,所以,这里把这篇过气的论文公布出来。
     我是华中科技大学电子信息工程系电子信息工程硕士研究生。 大家可以给我写E-Mail来联系我。
        我主要使用的语言是Java。Java社区非常活跃,至少现在是如此,可以说是现在最先进的一门编程语言。当然,未来,一切还未注定!
        动态面向对象语言,比如Smalltalk,Ruby,Python等这些语言,也非常有魅力。我相信,动态面向对象的机制,在未来应该得到发展和应用。Java1.3引入的动态代理,已经为我们展现了强大的威力。
     Smalltalk仅仅只有几个关键词,就构成了一门强大的面向对象语言。动态面向对象语言,不需要类型声明,如果实际类型一致,直接调用好了!当然,也许这并不是静态面向对象编程语言的缺点,毕竟,编译器差错能够帮助我们减少错误。不过,如果能够在编写代码时,模拟运行,也许也能够在编写代码时为我们找出这些错误。
     6月6日,我参加了Martin Fowler在上海交大的敏捷开发座谈会。会上,Martin Fowler做了关于Ruby的演讲。其中,将Ruby和DSL(特定领域语言)联系到了一起。他认为,Ruby简洁的语法,是DSL语言的理想表达工具。
     前些天,我看到Intellj的作者的一篇文章,也提出了发展DSL的设想,他正在致力于将Intellj制作成开发DSL的IDE工具。
     Martin Fowler认为目前xml形式的DSL表达方式过于复杂,应该使用普通英语直接表达。目前,我还是认为XML格式的DSL比较好。我们可以自己用Java等语言编写解释程序,解释自定义的xml形式的文本----DSL。XML格式的DSL的优点是便于验证。
     总之,DSL目前尚未发展成熟,但是未来的前景还是非常乐观的。不过,现在跟进,可能还太早。
        将来,AOP也会有很大的发展。AOP的混入机制,是AOP最强大的机制,未来如果有合适的场合,应该会成为程序员手中又一个杀手锏。
        我想,未来可能会出现这样一种语言:她集中了静态面向对象编程语言和动态面向对象编程语言的机制于一身,还直接支持AOP这样的编程范式。
        尽管ASpectJ已经扩展了Java。但是,由于并不是标准的Java,所以,大家使用起来仍然有困难。当然,现在Java不把AspcetJ引入Java标准,可能也因为现在AOP还没有成熟,众多的产品和理念还没有决出胜负。
        未来这样的一种语言,也许是未来的Java,也可能是一种全新创造的语言,或者是一门动态面向对象编程语言的扩展。不过,没关系,还是Java的理念。到时候,Java社区的程序员也是可以毫不费力的转到新的社区的!
        在本文的最后,有一节是讲“源代码就是设计”。这里讲到了,写源代码和写文章很类似。不过,本人虽然博学,但是对怎样写文章,这样一门学问,确实没什么研究。我希望,未来有这方面背景的程序员,能够将文章学引入到程序员的世界来,这也是一件造福人类的事情!
        文学系的朋友们,你们论文的题目有了啊^-^

             好了,废话不说了,请看在下得拙著吧!
                                    2006-06-23 于上海家中


                    目 录
    摘要…………………………………………………………………………………….I
    ABSTRACT…………………………………………………………………………..II
    1 什么是企业级应用 4
    2 为什么我们需要开发健壮的企业级应用 6
    3 什么是健壮的企业级应用 7
    3.1 什么是健壮的企业级应用 7
    3.2 企业级应用的一般结构 7
    3.3 健壮的企业级应用的一般结构 8
    4 怎样开发健壮的企业级应用 11
    5 面向对象编程技术 12
    5.1 依赖于抽象,而不要依赖于具体实现 12
    5.2 使用委派而不是继承 13
    5.3 “客户—服务器”关系中,应该是“瘦”客户类,“胖”服务器类 15
    5.4 类存在的意义是提供的服务,而非保存的数据 15
    5.5 单一功能的方法 17
    5.6 单一职责的接口 18
    5.7 用接口来隔离实现类 18
    5.8 直接使用编程语言的概念进行设计 19
    5.9 尽量使用模式来解决问题 19
    6 面向方面编程技术 20
    6.1 AOP的重要概念 22
    6.2 实现AOP的主要技术策略 25
    6.3 Spring AOP框架 27
    6.4 如何更好的使用AOP 28
    7 面向关注软件开发 29
    8 敏捷开发方法的代表作—XP 30
    8.1 XP的前提 30
    8.2 为什么需要XP 31
    9 融合XP的软件开发过程 32
    9.1 获取需求 32
    9.2 测试驱动开发和验收测试级重构 33
    9.3 单元测试驱动开发和单元测试级重构 35
    9.4 小结 36
    10 使用Java开发企业级应用的参考架构 36
    10.1 JavaEE 36
    10.2 “经典”的JavaEE架构 36
    10.3  Java开源软件 38
    10.4 不用EJB的简单Java EE架构 38
    10.5 使用“轻量级容器”的Java EE架构 42
    11 总结 45
    11.1 “源代码就是设计” 45
    11.2 总结 45
                    致  谢 46
                   参 考 文 献 46

    什么是企业级应用
     企业级应用(Enterprise Applications),顾名思义,就是企业经营中需要使用的应用程序。又叫作企业信息系统(Enterprise Information System)或者管理信息系统(Management Information Systems)。其主要任务是最大限度的利用现代计算机及网络通讯技术加强企业的信息管理,通过对企业拥有的人力、物力、财力、设备、技术等资源的调查了解,建立正确的数据,加工处理并编制成各种信息资料及时提供给管理人员,以便进行正确的决策,不断提高企业的管理水平和经济效益。可以说,它的涵盖面是非常广的,很难给它下一个确切的定义,但是我可以罗列一些个人的理解。[1]
     先举几个例子。企业级应用包括工资单、患者记录、发货跟踪、成本分析、信誉评估、保险、供应链、记账、客户服务以及外币交易等。企业级应用不包括车辆加油、文字处理、电梯控制、化工厂控制器、电话交换机、操作系统、编译器以及电子游戏等。
     企业级应用一般都涉及到持久化数据。数据必须持久化是因为程序的多次运行都需要用到它们—实际上,有些数据必须持久化若干年。在此期间,操作这些数据的程序往往会有很多变化。这些数据的生命周期往往比最初生成它们的那些硬件、操作系统和编译器还要长。在此期间,数据本身的结构一般也会被扩展,使得它在不影响已有信息的基础上,还能表示更多的新信息。即使是有根本性的变化发生,或公司安装了一套全新的软件,这些数据也必须被“迁移”到这些全新的应用程序上。
     企业级应用一般都涉及到大量数据—一个中等规模的系统往往都包含1GB以上的数据,这些数据是以数百万条记录的方式存在的。巨大的数据量导致数据的管理成为系统的主要工作和挑战!早期的系统使用的是索引文件系统,如IBM的VSAM和ISAM。现代的系统往往采用数据库。数据库有层次数据库、网状数据库、关系数据库和对象数据库,还有对象—关系数据库。现在最成熟,应用也最多的是关系数据库。数据库的设计和演化已使其本身成为新的技术领域。
     数据库在企业级应用中处于重要的地位。选择性能优良的数据库和有效的使用数据库,是开发企业级应用的一项核心工作!
     最近兴起的使用XML文件存储少量数据这一技术。实际上,XML文件格式,就是早期的层次数据库。它具有丰富的表达能力和简单的优点,但是数据库发展的历史已经表明,XML文件不可能取代现在的关系数据库和对象—关系数据库。
     企业级应用一般还同时涉及到很多人同时访问数据。对于很多系统来说,人数可能在100人以下,但是对于一些基于Web的系统,人数则会呈指数级增长。要确保这些人都能够正确地访问数据,就一定会存在这样或那样的问题。即使人数没有这么多,要确保两个人在同时操作同一数据项时不出现错误,也是存在问题的。事务管理工具可以处理这个问题,但是最为程序员,我们仍然要正确地使用它们,这同样不容易做到。
     企业级应用还涉及到大量操作数据的用户界面。有几百个用户界面是不足为奇的。用户使用频率的差异很大,他们也经常没什么技术背景。因此,为了不同的使用目的,数据需要很多种表现形式。
     企业级应用也很少单独存在,通常需要与企业的合作伙伴的企业级应用集成。这些各式各样的系统是在不同时期,采用不同的技术开发的。甚至连通讯机制都不相同:基于Socket的系统、CORBA系统、COM系统、Java的RMI、EJB和消息系统,以及最新的Web Service等。企业经常希望能够用一种同一的通信技术来集成所有的系统。然而,每次这样的集成工作都很难真正实现,留下来的就是一个个风格各异的集成环境。
     即使是某个企业统一了集成技术,他们还是会遇到业务过程中的差异,以及数据库中数据概念的不一致性。不同部门、不同系统,甚至不同时期,都会对业务数据有不同的定义和理解。
     随着企业级应用的发展和集成,整个企业的企业级应用程序就成了一个不同技术、不同数据混杂在一起组成的复杂系统。不断的修改和添加新功能,也使系统的Bug越来越多,修改和添加新功能变得越来越困难。
     对于一些人来说,“企业级应用”这个词指的是大型系统。但是要注意,并不是所有的企业级应用都是大型的,尽管它们可能都在为企业提供巨大的价值。

    为什么我们需要开发健壮的企业级应用
     开发企业级应用程序并不是一件简单的事情,开发出一个能令客户满意的企业级应用程序更非易事。这需要考虑太多的事情、克服太多的难关—技术上、商业上、人际关系上的。
     事实上,企业级应用的主要成本和困难并不在开发软件这个时期,而是在软件的维护阶段。企业级应用在其交付使用后,其维护阶段在软件生命周期或生存期中占较大比重,有的可达软件生命周期总成本(TCO)的50-70%。因为,企业的业务环境一直处于不断的变化之中,这就要求企业级应用也要能够适应企业的变化。而这些可能的变化,对于开发者来说,是很难预见的。如果开发的企业级应用,其架构没有很强的适应能力、不够健壮的话,那么在维护阶段修改软件,或者增加新功能将是极其困难的,甚至是不能做到,必须推倒重来!
     业务需求的变化,才是企业级应用最大的风险和难点!而且,这种变化,基本上每个项目都会出现,这是企业级应用开发的“常态”。甚至,一般在软件开发的过程中,就会出现业务需求的变化。
     现在,市面上有很多套装的企业级应用,如ERP,CRM,财务软件等。很多企业花了大价钱买了回来,但是应用下来,失败的案例很多。不仅造成了软件购置费用的浪费,更严重的是扰乱了企业正常的业务活动,造成了严重的损失。也使不少企业对信息化望而却步。
     企业,作为市场经济的主体,其面临的内外部环境是在不断变化的,企业本身也会针对这种变化,经常性的调整其组织结构和业务流程。并不存在适用于所有企业的一套一成不变的组织结构和处理流程。
     使用套装软件,就好比是“削足适履”,为了适应软件的需要,改变企业原来运转正常的组织结构和业务流程,实在是不智之举。企业级应用程序,是为企业服务的,应当服从企业的需要,而不是相反,企业成了软件的奴隶!
     IBM的广告词“随需应变的软件”,就是企业级应用软件业者理想中的软件。可是,依靠IBM,就能够开发出“随需应变的软件”吗?当然不可能,一切只能够靠我们程序员自己才能做到!

    什么是健壮的企业级应用
    什么是健壮的企业级应用
     “随需应变的软件”,就好像是塑胶泥,我们可以任意拿捏,变化出不同的形状。“随需应变的软件”,必然是健壮的软件,不论怎样折腾,都能够应对自如。
     什么是“健壮的企业级应用”,对此我无法给出一个精确的定义,我只能够罗列一些我的理解。
     “健壮的企业级应用”,其各个部分应该是低耦合、高内聚的。其内部的各个模块之间的关系最低,且可以互相替换,从而可以方便地拆卸、替换和修改各个模块。
     其核心思想,就是“接口”。各个部分之间通过接口,并且只通过接口互相衔接,一起合作。只要接口相同,那么这些模块就可以互相替换。对于其他模块来说,其合作部分的具体实现是不重要的。
     这样,我们在需要“随需应变”的改变软件时,只要简单的提供在原有系统上接插上不同的实现模块即可。
     这实际上,就是面向对象(OO)思想的一种体现。更深入的说,就是“抽象”的思想。把具体的源代码通过接口屏蔽、抽象起来。这样,只要接口不变,那么无论源代码怎样变化,都不会影响整个软件的正常运行。
     当然,不仅企业级应用需要“健壮”,任何软件都应该是健壮的。但是,在企业级应用程序的开发和维护过程中,由于其需求的多变性,就更需要是“健壮”的。
    企业级应用的一般结构
     现在,让我们再来看一看企业级应用的结构。企业级应用在结构上,一般可以分为三大模块:表现模块,业务模块,领域模块。这里,我将它们称作“模块”,而不是“层”。很多人喜欢划分“层次”,但我觉得划分“模块”更合适。因为,“层”有上下之分,只能是上层调用下层;而“模块”就没有上下之分,可以根据实际情况任意调用。这里,我不想分清什么是上层,什么是下层,用模块来表示应该更加合适。
     一、表现模块
     表现模块,又叫作客户端。用于向客户提供使用软件系统的途径。一般有图形用户界面GUI,命令行界面,文本文件等等。这个模块,仅仅是用来接收用户请求,再将这个请求委派给业务模块提供的方法(这就是业务模块提供的服务),从而实现软件的功能。一个软件的好坏与否,与之无直接的关系。
     二、业务模块
     业务模块,封装了为特定的业务需求提供服务的方法。表现模块就是通过它提供的方法来实现业务需求的。所以,业务模块是直接对应于系统的业务需求的,是系统的关键和最重要的部分。
     三、领域模块
     也许你会问:既然业务模块已经提供了客户所需的功能,那么还要这个领域模块干什么呢?其实,这个领域模块就是为业务模块服务的更底层的功能。
     在整个软件系统中,存在一些实体。这些实体包含了一些数据和责任,它们的交互协作,就可以实现软件系统的业务功能。
     实现一个个业务需求的业务模块,可能需要这些实体中的一个或者多个的功能和数据。
     这些实体的集合,就是领域模块。由此可见,领域模块实际上才是整个系统的核心和灵魂。业务模块也只有委托它们才能提供系统所需的业务功能。
    健壮的企业级应用的一般结构
     健壮的企业级应用,在结构上又应该是怎样的呢?
     一、表现模块
     表现模块仅仅是一个界面,用于向用户提供使用系统的途径而已。所以,尽可能“薄”的表现模块,就是理想的表现模块。“薄”的表现模块,就可以在用户想要改变用户界面时,轻松的加以改变。改变用户界面,是系统变化最多的需求。
     使用MVC模式设计的表现模块,可以分为3个组成部分:M(Model)模型,V(View)视图,C(Control)控制器。
     其中,模型,是一些类,它们封装了视图所要呈现给用户的数据,也用来将用户操作信息传递给后台的控制器模块。
     视图,就是用户界面,是用户看到的那部分。它能够接受用户的请求,并将请求信息发送给控制器,再在控制器完成操作之后,从模型类中获得数据,展现给用户。
     控制器,它接收到用户请求之后,就委托业务模块的业务服务对象提供的服务,完成业务功能。如果有数据需要返回给用户,就将数据存放到Model模型类中,以备视图取用。控制器,虽然是表现层的一部分。但是,它实际上是“业务代表模式”的一种应用。所谓业务代表,是指在客户端和业务服务层之间,增设一个“代表层”,所有客户端到服务器的调用,都“委托”该层完成。[9]业务代表虽然身处表现模块内,但实际上执行的是调用业务服务模块功能的任务,完成的是业务功能。因此,不少人都将业务代表划分在业务模块内。我也认同这种划分方法。至少,我们可以认为,它是横跨表现模块和业务模块的一个部分。
     表现模块中,模型和视图两部分通常都很小,而且它们是表现模块固有的,不能够省略,就算够“厚”(比如,富客户端技术),也没办法变小。
     实际上,我们说企业级应用的表现模块太“厚”,都是指太“厚”的控制器。理想的控制器,只应该根据接收到的界面上不同的请求,调用业务模块的不同业务服务,完成这些请求,然后将得到的数据塞进Model模型类即可。
     我们常常犯的错误,就是在控制器中塞进了太多的应该放在业务模块的业务服务类中的代码。实际上,判断控制器是不是太厚,有一个非常简单的方法:假设我们使用另一种表现模块技术,那么,这个新的表现模块中的控制器类中有多少代码和现有的控制器是重复的。如果存在重复代码,就使用“重构”技术,先将它们提炼成方法,然后再移到业务模块的业务服务类中。这样,我们就能够得到一个理想的“瘦”表现模块!
     二、业务模块
     业务模块,包括2个部分:一个是表现模块的控制器,它是“业务代理”,提供的也是与业务相关的服务。我认为,把它划分在业务模块也许比表现模块更加合适。另一个是业务服务模块,我用Service表示它,它封装了为特定的业务需求提供服务的方法。它与控制器配合,共同完成用户需要的业务功能。
     既然理想中的控制器是“瘦”的,而且所有的重复代码都移到了业务服务模块中。那么,理想的业务服务模块必然是“胖”的。
     实际上,控制器和业务服务模块,是典型的“客户—服务器模式”。控制器作为客户,需要调用作为服务器的业务服务模块提供的服务,来完成用户需要的功能。所以,服务器越胖,提供的服务越多,那么系统的重复代码就越少,功能也越强大!
     三、领域模块
     业务服务模块和领域模块,实际上也是典型的“客户—服务器模式”。业务服务模块虽然提供的服务功能强大,很“胖”。但是,它的“胖”也是有限度的!它的“胖”来自于控制器中理应属于它管辖的重复代码。实际上,在“控制器模块—业务服务模块”这对“客户—服务器”关系中,是控制器模块“瘦”,而业务服务模块“胖”。
     而在“业务服务模块—领域模块”这对“客户—服务器”关系中,则是作为“客户”的业务服务模块“瘦”,而作为“服务器”的领域模块“胖”。
     领域模块,主要就是领域模型(也叫作业务对象Bussiness Object)。领域模型,封装了业务实体的数据,还提供一些处理这些数据的服务(“服务”在编程语言中就是由方法提供的)。一般,在企业级应用中,有一些领域模型需要持久化存储,就是保存到数据库(关系型数据库或对象数据库)、文本文件、XML文件、序列化文件等持久地存储起来,已备下次再用。这时,需要持久化的业务对象就需要对应的提供数据访问服务的类(也叫作DAO,Data Access Object数据访问对象)。
     这样,一般的企业级应用的领域模块,主要有两个模块:领域模型(Domain Model)和数据访问服务模块(DAO)。
     在“业务服务模块—领域模块”这对“客户—服务器”关系中,
    应该把业务服务模块中所有可以移到领域模块的领域模型类和数据访问服务类中的代码都移到领域模型类和数据访问服务类中去。
     因为,业务模块的控制器和业务服务模块,它们与领域模块的领域模型和数据访问服务模块之间的关系是“多对多”的关系。一个业务模块可以使用零个或者多个领域模块;一个领域模块也可以被零个或者多个业务模块所调用!所以,领域模块越“胖”,提供的服务越多,业务模块就越少重复代码,系统的功能就越强大!
     现在,很多程序员都接受了UML的用例驱动开发的思想。诚然,用例驱动开发的思想确实很好,但是很多程序员都由此犯了一个毛病:他们常常按照用例为系统的源代码分包(就是Java中的Package,.Net中的namespace),错误的将领域模块的领域模型、DAO数据访问服务类和业务模块的控制器、业务服务类划分在一个包里。实际上,领域模块和业务模块完全不同,它们并不是从属于某一个用例的,而是属于整个系统的,可以被多个业务模块共同使用的。
     如果把领域模块放在首次用到它们的业务模块之中。那么我们就很难在其他业务模块调用它们时很好的使用它们。因为,其他业务模块的使用,可能会要求领域模型类和DAO类增加新的字段和方法。而将它们放在另一个业务模块所在的包里,我们就很难将这些新增的功能放到领域模型类和DAO类中,使它们更“胖”。而是会倾向于在控制器类和业务服务类中增加方法。这样,就会导致各个业务模块中出现重复代码,引发逻辑重复。
     
    怎样开发健壮的企业级应用
     企业级应用程序,按照是否能够“随需应变”来划分,可以分为两类:健壮的和脆弱的。
     画一根数轴,我把它叫作“软件健壮度”图。“健壮”在正方向,“脆弱”在负方向。有很多技术原理和开发方法,可以让我们的应用程序更加健壮,而违反这些原理和方法,我们的应用程序就会变得更加脆弱,修改和扩充新功能也更加困难。
     要想开发出健壮的企业级应用,首先需要的就是开发人员扎实的编程技能和对编程原理的清楚认识和应用。没有高水平的开发人员,而奢谈“健壮的企业级应用”,是毫无意义的。
     
    面向对象编程技术
     面向对象编程技术(OOP,object-oriented programming)是近几十年来编程领域最伟大的成就。健壮的企业级应用程序,甚至任何健壮的软件,必须首先是一个很好的实践了OO思想的软件。脱离了OO,就不用奢谈什么合格的软件了!
     面向对象编程技术,早已经从昔日的神坛上走了下来。今天,任何一门主流的编程语言都是支持面向对象编程的。现在,几乎所有的程序员都自称已经掌握了面向对象编程的技术。但是,真正掌握OO的程序员却远远没有这么多。而且,面向对象编程技术还在飞速发展的过程中,我们还远没有发掘出它的全部内涵。
     并不是说,你使用了面向对象的编程语言开发软件,你就能够开发出实践了OO思想的软件。
     要想开发出健壮的企业级应用,我们需要的是全面皈依OO!
     面向对象编程的几个原理[2]:
    依赖于抽象,而不要依赖于具体实现
     具体来说,就是依赖于接口(Interface),而不要依赖于接口的具体实现类。或者是,依赖于父类(最好是抽象的),而不是具体实现的子类。又或者是,依赖于父接口,而不是子接口。总之,只使用提供了所需方法的最基本的类型。这样,当程序需要改变时,我们就可以仅仅提供另一个“服务器”实现类或者实现子类,然后在“客户类”新建实例的地方更换成这个新实现类即可,无须更换“客户类”的调用代码。在使用IOC(反转控制)容器,如Spring框架时,我们甚至可以不用改动“客户类”的任何代码,而只需更改元数据(在Spring 框架中,是简单的Xml文件),将旧的实现类的类名换成新的即可。
     使用越是抽象的接口或者类,我们可以选用的实现类也就越多!
     我们知道,在Java语言中有这样几种作用域:Private私有,Protected保护,Package包,public公共。按照“依赖于接口Interface”这一原则,我们又有了一个新的作用域:Publish已发布。接口Interface中声明的方法就是已发布的。既然我们现在是通过接口来使用实现类的方法,那么就是说,即使实现类还有其他的Public可见方法,我们也不会调用它们。
     比如说,A类实现了B和C接口。调用A类的客户代码中,B b=new A();这样调用,那么我们通过对象b就只能够调用B接口发布(Publish)的代码,这也就保证了不会因为程序员一时的疏忽而造成了代码不必要的耦合。如果需要改变B接口的实现,假设D类实现了B接口,只需要在客户代码中改成这样:B b=new D();就可以了。
     当然,凡事总有例外的情况。对于特别简单,而且不大会改变的类,我们也可以直接使用实现类,而不是接口。如:表现层MVC模式中的Model模型类,还有领域模型类,它们主要是提供数据,只有很少的方法(set/get方法不算),而且不经常变化,所以一般我们直接使用它们,而不使用接口。
    使用委派而不是继承
     要让一个类实现一些功能,有三种方法:
     1,在类中直接写上实现功能的代码。
     2,使用继承。让类继承另一个类,从而可以使用另一个类的所有公共的和受保护的方法。
     3,使用委派。在一个类中,通过定义对象类型的实例变量,可以调用那些对象的方法,来提供功能。
     另外还有一种特殊的使用委派的方式—“回调模式”。就是在类的方法中声明对象类型的参数,然后调用这个参数的方法来提供功能。在使用时,客户类需要提供对象的实例作为参数传给这个类。
     这三种方法中,对于没有现存代码的特殊功能,我们可以使用在类中直接写上实现功能的代码这一方法来实现。而在已经存在拥有可以使用的方法的类时,我们可以使用继承或者委派使用它们的方法。
     如果我们使用继承这种方式来获得功能,那么我们就会获得可能我们并不需要的大量的父类字段和功能。这样的冗余,就会造成逻辑上的混乱。而且,Java只能够进行单继承,即,一个类只能够继承一个父类。这样,一旦继承了一个类,就剥夺了它继承的能力。
     也许你会问,为什么Java要取消多继承呢?因为,多继承提供的好处远比它造成的问题更多。如果2个父类的字段、方法同名怎么办?而且,在逻辑上也会造成极大的混乱。
     继承能够做到的事情,委派一样都能够做到。而且做得更好!
     我们可以把所需的任务委派给任意多个类(别忘了,应该尽量使用接口Interface,最好用上IOC容器),然后在提供服务的方法中,使用委派对象的方法来实现。这样,在逻辑上,我们能够借助于这些类实现所需要的功能,而没有实现增加不需要的字段和方法。
     所以,我们应该牢记“使用委派而不是继承”这一条原则。当然,也还是有场合可以使用“继承”的。
     1,一个类和另一个类是纯粹的扩展关系,逻辑上没有半点不符合的地方。如,几何类和方形类。当然,这种情况下,使用委派仍然是可以的,而且更加保险。因为,常常有很多看上去非常像父子关系的类,实际上并不是真正的父子关系。父类可能有几个子类不可能有的方法,这会成为一颗定时炸弹,在我们需要给子类增加相关方法时引发问题。
     2,模板方法模式(Template Method)[3]适用的情况下。当知道如何实现一个工作流程,但不知道所有单个步骤被如何实现时,使用模板方法模式比较简单。[4]在父类(通常是抽象类)中提供一个工作流方法,然后再提供几个工作流方法需要用到的方法的抽象方法原型。子类只需要覆盖这几个抽象方法,就能够提供不同的实现效果。这种用法也叫作“多态”。
     但是,即使是这种情况,委派仍然能够胜任,而且比继承提供的解决方案更加灵活,只是稍微要复杂一些。这就是Strategy策略模式[3]。策略模式将不变的行为集中到一个接口中。用接口的实现类来实现具体的功能。
     “依赖于抽象,而不要依赖于具体实现”和“使用委派而不是继承”这两条原则,其思想都是相同的:不要为类提供不需要的能力。只要正巧够用就行。与其他类通讯时,也只使用正巧够用的服务。只有这样,才能够保证所有符合接口的类能够被自由的替换成其他实现类。
    “客户—服务器”关系中,应该是“瘦”客户类,“胖”服务器类
     在使用委派的类中。委派的类就是“客户”类,被委派的类,就是“服务器”类(也有人把它叫作“助手类”)。在这样一对关系中,应该尽量给客户类“减肥”,而给服务器类“增肥”。因为,“客户—服务器”关系中,客户和服务器类一般是“多对多”的关系。变胖的服务器类可以在未来给更多的客户类提供更好的服务,而原本会在客户类这边重复的代码就都消失了。
     这个原理,就是处理“委派关系”中两个类之间关系的一个原则。
    类存在的意义是提供的服务,而非保存的数据
     对于类,最重要的是什么,一向都有争议:有的认为,类存在的意义就是封装的数据,有的认为,类存在的意义就是提供的服务(也就是方法)。我赞同后者的观点。类存在的意义就是通过方法提供服务。而类保存的数据也需要通过set/get方法暴露出来。
     我们都知道这样一个经典的公式:数据+算法=程序。而类就是同时封装了数据及其相关算法的模块。或者说是算法及其使用的数据的模块。
     方法,是很早就出现的一个概念,在面向过程编程时代就是一个核心的概念,是那时最重要的抽象机制,它的出现,使我们第一次拥有了将具体实现代码屏蔽了起来的能力。
     方法,提供了一个程序执行的点。在面向方面编程(AOP)中,叫作“连接点(join point)”。我们可以在方法调用的前后进行拦截,增加其他代码。这是AOP面向方面的编程思想,在下面会详细讲解。另外,客户代码调用方法时,只需要给出方法名和参数,并不需要了解方法的实现,这也就给了“客户—服务器”之间解除耦合的一次机会。方法的内部实现可以任意改变,只要不改变方法签名即可。
     在OOP中,我们还可以使用这个接口的另一个实现类提供的另一个方法实现版本,来提供不同的服务。
     我们知道类是封装了数据和方法的集合。其实,从用例驱动开发的角度来看,是类封装了服务,然后服务需要使用一些数据,就把这些数据也封装在了类中。
     我编写类的方式,是“客户—服务器”的方式。使用委派,也就是使用了“客户—服务器”的方式来编程。首先,是用户提供的用例(XP中叫作“用户故事”)。但是用例太宽泛了,不足以支持编程工作的展开。于是,用户再提供每个用例的具体的事件流。在XP中,是提供验收测试,验收测试中也包含了事件流。
     事件流,就是驱动我们开发的第一个“客户”。理解了事件流,我们就可以画出UML的序列图。序列图描述了系统的业务模块提供了哪些服务,从而完成事件流。我们可以直接将序列图的逻辑编写成控制器类。控制器就是我们源代码中的第一个“客户”。它和业务服务Service类构成了“客户—服务器”关系,可能也会和领域模块的领域模型类构成“客户—服务器”关系。
     在控制器类这个“客户”中,我们已经实现了整个事件流的功能。只不过,有不少要调用的服务(也就是方法,不管是哪一个类的方法,控制器自己的方法,属于Service业务服务类的方法或是领域模型类的方法等)还没有实现。
     将这些服务按照逻辑和是否会在客户端造成重复为标准,分配给各个模块的各个类。按照“针对接口”编程的原则,将这些服务分发到各个接口中去,而不是实现类。
     现在,虽然程序还没有开发完成,但我们已经知道程序在逻辑上已经完成了,或者说,已经设计完成了。我们只剩下两项简单的实现层面的工作要做:1,编写服务的实现代码;2,利用重构,将这些方法移到最适合的接口和实现类中去。
     “服务”是我们所需要的。我们用方法来实现服务。而方法又可能需要一些变量来保存状态,其中有些状态需要使用实例变量来保存。仅此而已!
     重构,其实总的思路也是尽量消除变量,特别是实例变量;尽量提炼出方法,而不使用变量。因为,变量是“实现”级别的,是直接的源代码,是死的,不允许变化。而方法是“设计”级别的,是活的,只要方法签名不变,其内部的实现代码可以任意变化。而且,变量不是一个“程序执行点”,不可以拦截,而方法就可以拦截,如AOP面向方面编程,或者代理模式,装饰者模式等的拦截。
     按照方法和数据的比例,类可以分为三种类型:只有数据和get/set方法的哑容器类,既有数据又有实际方法的一般类,只有方法没有实例变量的类。
     1,只有数据和get/set方法的哑容器类
     它们仅仅是数据的容器。Martin Fowler将它们称为“婴儿类” [5]。作为一个起点,是可以的,但是它并不成熟。甚至,我们很难认为它们是真正面向对象的。但是,现实中,还是有不少这样的类存在。比如MVC模式的表现模块中的Model模型。它的任务就是封装将要呈现给客户的数据。还有,领域模块的领域模型类,它封装了业务实体的数据。但是,我们都可以在它们内部封装一些使用这些数据的方法。
     总之,碰到这种只有数据和get/set方法的哑容器类,请特别留意,看是否能够重构,让它成为正常的类。
     2,只有方法没有实例变量的类
     由于方法是编程世界的一等公民,所以这种类型的类是正常的,健康的,在现实世界还是非常普遍的。这些类,是提供了服务,但是服务的实现代码不需要保存实例变量的类。像表现模块(也许说它属于业务模块更贴切)的控制器类,业务模块的业务服务Service类,领域模块的DAO数据访问服务类都是这一类型的类。
     这些类有一个优点,那就是它们不怕多线程的应用。因为它们不需要保存特定于线程的数据。所以,我们可以用单例模式来使用它们。即,整个应用程序中,只生成这些类的一个实例,用于为所有用户的请求提供服务。JavaEE的Web容器中,Servlet就是单例的。Spring框架管理的类,也可以使用单例。
    单一功能的方法
     在面向对象的开发方法中,接口、类、方法都需要对应于单一的逻辑概念。贯彻这一原则,就可以使接口、类和方法的数量变多、块头变小、关系变简单、逻辑变清晰。重构的一大目标,就是将一个大方法编成多个小的单一责任的方法。单一责任的方法,很多都是重构的结果。
     OO的委派,实际上就是委派给其他类的方法来提供服务。优秀的OO软件,就是一层一层的方法委派其他类(作为“服务器”的类)的方法来提供功能。其代码的特点就是一个方法内部调用了几个方法来实现功能,这些方法的名字就解释了它们的功能。然后,这些被调用的方法内部又像这样调用了一些方法。循环不已,直到最底层的充当“服务器”的类的方法中是调用API类库的几个方法而告终。
    单一职责的接口
     一个设计上的逻辑概念,应该有且仅有一个提供对应逻辑的接口。这条原则就是类的内聚性原则:一个模块的组成元素之间的功能相关性。Robert C. Martin把内聚性和引起一个模块或者类改变的作用力联系起来。[2]
     “就一个类而言,应该仅有一个引起它变化的原因”。接口也是类。所以,我们可以说“就一个接口而言,应该仅有一个引起它变化的原因”。实际上,一个实现类,是很难做到“仅有一个引起它变化的原因”的。而接口这个纯设计层面的概念就不同了,它是可以真正做到“仅有一个引起它变化的原因”这一要求的。即使一个实现类能够做到“仅有一个引起它变化的原因”,那也可能会造成实现类太小、太多的问题。而接口只包含方法签名,并没有实现代码,所以,即使存在大量的接口也没有问题。
     职责,就是“变化的原因”。如果你能够想到多于一个动机去改变一个类或接口,那么这个类或接口就具有多于一个的职责。
     那么,为什么要把两个职责分类到两个单独的接口中呢?因为每一个职责都是一个变化的轴线。当业务需求发生变化时,该变化就会反映为接口的职责的变化。如果一个接口承担了多余一个的职责,那么引起它变化的原因就会有多个。
     如果一个接口承担的职责过多,就等于把这些职责都耦合到了一起。一个职责的变化,可能会引起接口完成其他职责的能力被削弱。
     比如说,一个业务模块的业务服务模块有两类业务服务,一类是提供增删改查领域模型数据的服务,另一类是判断数据完整性和正确性的服务。那么,这些服务就需要分别存放在两个不同的业务服务Service接口中。这样,在需求变化,从而导致服务改变时,我们可以只改变一个接口,而另一个接口及其所有客户类都不会受到影响。
    用接口来隔离实现类
     一个设计上的逻辑概念,应该有且仅有一个提供对应逻辑的接口。现实中,我们的实现类可能并不是这么纯正的,可能,我们的实现类同时实现了很多个接口,这样的实现类,叫作“杂凑类”。但是这并没有多大的关系,因为这仅仅是一个“实现”级别的问题。我们仍然拥有一个纯正的接口,我们在使用这个实现类的时候,是通过接口来使用的。这样,杂凑类实现的其他接口的方法,我们并不会使用,也不能够使用。接口的任何实现类都和“客户”调用代码无关!
    直接使用编程语言的概念进行设计
     软件开发的真正进步依赖于编程技术的进步,而这又意味着编程语言的进步。C++就是这样的一个进步。它已经取得了爆炸式的流行,因为它是一门直接支持更好的软件设计的主流编程语言。
     C++在正确的方向上迈出了一步,但是还需要更大的进步。[6]
     Java就是这样一门比C++更加先进,更加面向对象的语言。Java可以更加有效的直接支持软件设计。
     我们在进行软件设计时,应该直接使用Java的概念来描述软件系统,进行设计。这样,我们就可以直接将设计转化成实现。既然我们主张“针对接口编程”,那么,我们就应该主要使用“接口(Interface)”这个概念来描述系统。另外,既然我们更重视“服务”,也就是方法,那么我们就使用方法,而不是数据来描述接口。
     XP提出的CRC图(Class,Responsibilities,Collaboration类、责任、类间关系)很符合我们的要求[7]。CRC图,描述了一个类,我们这里通常是描述一个接口Interface。其中的责任,就是服务,也就是方法。它是接口的内在要求,是接口之所以存在的原因。类间关系,就是接口和其它接口之间的关系。接口之间互相协作,才能够完成业务功能。
     我们在进行软件设计时,不要考虑设计的具体实现的细节问题。我们只需要考虑接口应该提供那些服务,以及和哪些接口协作,怎样协作即可。
    尽量使用模式来解决问题
     内行的设计者们都知道:不是解决任何问题都要从头做起。它们更愿意复用以前的解决方案。当找到一个好的解决方案,他们会一遍又一遍的使用它们。这些经验是他们成为内行的部分原因。因此,你会在很多面向对象系统中看到类和相互通信的对象的重复模式。这些模式解决特定的设计问题,使面向对象设计更灵活、优雅,最终复用性更好。它们帮助设计者将新的设计建立在以往工作的基础上。服用以往成功的设计方案。一个熟悉这些模式的设计者不需要再去发现它们,而能够立即将它们应用于设计问题中。[3]
     给“模式”所下的定义是这样的:
     自从1994年,GoF的划时代名著《设计模式—可复用面向对象软件的基础》问世之后,在程序员世界引起了轩然大波。众多的模式著作纷纷推出,涉及各个领域。其中,Martin Fowler的《分析模式:可复用的对象模型》和《企业应用架构模式》,以及Sun Java中心编写的《J2EE核心模式》都是其中的上乘之作。另外还有很多特定领域的模式著作。它们搜集了特定领域的一些模式。
     今天,简单的浏览这些模式书籍,我们就可以得到一大堆的专家级解决方案和经验。
     实际上,使用模式来解决问题,并不需要你精研很多种模式,只需要你大致了解它们使用的场合,能够在遇到这类问题时想起应该到哪里寻找对应的模式即可。模式,仅仅应该作为一个字典。
     但是,《设计模式—可复用面向对象软件的基础》这本书还是应该精心研读。因为这本书中的23种模式,是最重要,应用最广泛的模式。
     当然,凡事都不可太过,过犹不及!现在,有一些程序员凡事都以模式马首是瞻,不分场合都要套用模式。我们应该注意到,用模式来解决问题,一般情况下都会增加软件的复杂性。本来,一个类解决的问题,现在需要几个类协作才能够解决。所以,对于明显简单的功能,不宜使用复杂的模式。另外,一个软件中不宜使用太多的模式。否则,会产生大量的类和类间的关系,使系统过于复杂。

    面向方面编程技术
     面向方面编程AOP(Aspect-Oriented Programming)是1996年Gregor Kiczales在PARC提出的一种新的编程范式。AOP是一种与OOP截然不同的看待应用程序结构的方式,按照AOP的观念,系统被分解为方面(aspect)而不是对象。
     OOP是一种成功的、极具表现力的编程范式,很多领域概念都可以自然的表达为对象,从而将其中通用的代码模块化。然而,还是有OOP照顾不到的角落。
     衡量OOP成功与否的标准就是它在多大程度上避免了代码重复。
     代码重复是最糟糕的代码臭味。只要出现重复的代码,必定有什么地方存在严重的问题—要么是设计有问题,要么是实现有问题。
     一般情况下,OOP能够很好地避免代码重复。具体继承可以帮助我们在不同类型之间共享相同的行为;多态让我们可以用同样的方式处理不同类型的对象,将注意力集中到它们的共同之处。
     而委派,使类之间构成了“客户—服务器”关系。客户类代码简单的调用服务器类的方法即可完成众多的功能“委派”。
     但是,有些时候,我们无法用OOP避免代码重复,或者无法用OOP得到一个优雅的解决方案。
     就拿日志记录来说吧。假设我们要对每一个控制器类的每一个方法调用都进行日志记录。那么,使用OOP委派的编程风格,也就是使用“客户—服务器”调用模式。我们需要在软件的每一个控制器类的每一个方法中都增加一行调用Log日志记录的代码。尽管OOP委派已经很好的将记录日志的服务封装在作为“服务器类”的Log类中,但是,作为“客户—服务器”调用模式,总是不能够去掉客户类中调用服务器的服务的代码。如果很多个“客户类”都需要调用“服务器类”提供的“服务”,那么“客户类”中的调用代码,就会不可避免的存在大量的重复。这是“客户—服务器”这种客户主调模式不可避免的弊端。
     当然,老实说,这类“客户类”中的重复出现的调用代码,并不是什么大不了的问题。存在这些重复代码的软件,照样可以是一个比较健壮的软件。这也正是我把OOP面向对象编程技术作为开发有效软件的第一技术的原因。
     但是,今天,AOP面向方面编程提供了另一种调用模式,使客户类能够不需要调用代码,就能够获得所需的功能!这样,就完全消除了重复代码。
     AOP的调用模式,就是好莱坞原则:“不要试图联系我们,我们到时候自会通知你。”这完全不同于OOP委派的“客户—服务器”调用模式。OOP中,如果一个类(客户类)需要另一个类(服务器类)提供的服务,就需要在客户类的源代码中显式地加上调用代码。而在AOP的“好莱坞原则”中,如果一个类(客户类)需要另一个类(服务器类)提供的服务,那么就不需要在客户类中作任何需要服务的声明。而是在服务器类中指定需要将服务提供给哪些客户类。
     用我们现实生活中的事情做个类比:
     我需要坐出租车,那么我就需要亲自打电话去给出租车公司,调用它们的“服务”。这就是OOP委派的“客户—服务器”调用模式的工作原理。出租车公司提供专业化的出租车服务,客户只需要简单的调用它们提供的服务。
     或者,我走在路上,随地扔了一张废纸。然后,清洁工会将这张废纸捡走。这里,我并没有主动要求清洁工提供服务,而是清洁工自己为我提供的服务。
     又或者,我每天走在公路上。但是,这条公路并不是我建造的,它的维修也和我无关。
     清扫垃圾,维修道路、桥梁,实际上是政府为公民提供的一项服务,或者叫作“基础设施”,不管是有形的基础设施,还是无形的基础设施。
     政府提供“基础设施”(如:清扫垃圾,维修道路等),和AOP的“好莱坞原则”原理是一致的。我把“好莱坞原则”称作是“客户—基础设施”调用模式。“客户”,就是实际上需要服务的类。“基础设施”,就是为客户封装和提供服务的类(AspectJ这样直接支持AOP的语言中,叫作“方面”,而在像SpringAOP这样的OOP语言的AOP框架中,还是用一般的类来表示“基础设施”)。“客户—基础设施”调用模式中,是“基础设施”自己作用于“客户”。“客户”根本不知道有“基础设施”的存在!
     基础设施,又分为两部分:封装服务的类和指定该类为哪些客户类的哪些方法服务的部分。
    AOP的重要概念
     首先,让我们来澄清AOP中的各个重要概念的定义。由于AOP兴起时间不久,而且流派众多,再加上国内翻译又各异,所以在这些概念的定义上有很多不同的版本。[8]
     一、关注(concern)
     一个关注可以使一个特定的问题、概念、或是应用程序的兴趣区间。或者说,是“涉众”对应用程序的期望。涉众,就是与程序有关的人或者物,如客户,程序员,关联系统,客户端等应用程序的环境中的某些部分。总而言之,是应用程序必须达到的一个目标。
     日志、安全性、事务管理、性能要求、用户管理、角色管理、角色授权管理等等,都是系统中常见的“关注”。在一个OO的应用程序中,关注可能已经被代码模块化了,也可能还散落在整个对象模型之中。
     实际上,“关注”不是AOP独有的概念,应该是应用程序都有的一个概念。它有些类似于面向对象的UML图中“用例”,或者是XP的“故事”。
     二、横切关注(crosscuting concern)
     如果一个关注的实现代码散落在很多个类或方法之中(如:日志、安全性检查),我们就称之为“横切关注”。
     如果用OOP实现横切关注,那么必然会造成调用代码重复。如果我们使用AOP实现横切关注,就可以让客户类中不必进行任何代码调用。
     横切关注,正是AOP的用武之地。
     三、方面(aspect)
     一个方面是对一个横切关注的模块化,它将那些本来散落在各处的、用于实现这个关注的代码规整到一处。它一般包括两个模块:封装服务代码的模块和指定该服务为哪些客户类的哪些方法服务的 模块。
     四、连接点(join point)
     程序执行过程中的一点。如:
     1,方法调用(method invocation):对方法(可能包括构造器)的调用,不过并非所有AOP框架都支持在对象构造时的增强(advise)。
     这是最重要,最常用的连接点。我们在面向对象编程技术中曾经说过,重构的一大手段和目标就是构造大量的方法。因为,方法是一个连接点,是一个抽象,我们可以利用方法这一层抽象,任意的修改方法内的实现代码。所以,我们的代码中应该是大量存在方法这个兰接点的。即使没有,我们也可以在应用AOP编程时重构,在需要拦截的地方重构出一个方法,来作为连接点!
     有些AOP实现,如JBoss AOP,就只提供了方法调用这一种连接点。
     2,字段访问(field access)
     读或者写实例变量。同样,并非所有的AOP框架都支持对字段访问的增强。那些支持这类增强的AOP框架都可以区分读操作和写操作。
     Spring AOP,JBoss AOP都不支持字段拦截。字段拦截是一种潜在的危险,它违反了OO的封装原则。
     一般来说,我认为最好还是不要使用字段增强。OO程序中对字段的访问,可以用set/get属性这样的方法调用来代替。AOP对字段的拦截,通常也可以通过方法层面的增强来代替,从而保持对象的封装。
     3,异常抛出(throws)
     特定的异常被抛出。JBoss AOP框架只支持方法调用。但是,仍然可以通过编程获得异常抛出。实际上,异常抛出这个连接点,是方法调用这个连接点的衍生品。能够拦截方法,那么一定能够拦截抛出的异常。
     五、增强(advice)
     这个术语有很多种译法,罗时飞在《精通Spring》一书中译作:“装备”;Spring中文论坛在《Spring Framework开发参考手册》中译作“通知”,石一楹在《expert one-to-one J2EE Development without EJB中文版》一书中译作“增强”。这里,我就把它称作是“增强”吧!
     增强(advice),是在特定的连接点执行的动作。很多AOP框架都以拦截器(interceptor)的形式来表现增强—所谓拦截器,就是这样一个对象:当连接点被调用时,它会收到一个回调消息。增强的例子包括:
     1,在允许执行连接点之前,检查安全凭证。如Spring框架的一个附属开源项目Acegi,就是这样一个使用Spring AOP拦截方法访问的项目。
     2,在执行某个方法连接点之前开启事务,在连接点执行完毕后提交或者回滚事务。Spring AOP框架提供了这个功能。
     六、切入点(pointcut)
     一组连接点的总称,用于指定某个增强应该在何时被调用。切入点常用正则表达式或别的通配符语法来描述。有些AOP实现技术还支持切入点的组合。
     切入点加上增强,就是一个完整的方面,或者叫作“基础设施”。可以实现横切关注。
     七、引入(introduction)
     又译作“混入”。指,为一个现有的类或接口添加方法或字段。这种技术用于实现Java众多多重继承,或者给现有的对象模型附加新的API。譬如说,可以通过引入让一个现有的对象实现一个接口。
     八、混入继承(mixin inheritance)
     一个“混入类”封装了一组功能,这组功能可以被“混入”到现有的类当中,并且无需求助于传统的继承手段。在AOP这里,混入是通过引入来实现的。在Java语言中,可以通过混入来实现多重继承。
     九、织入(weaving)
     将方面整合到完整的执行流程(或完整的类,此时被织入的就是引入)中。这是AOP的实现机制,AspectJ是使用预编译器,在编译之前通过生成代码实现织入的,这叫作“静态织入”。
     Spring AOP等AOP框架是在运行时通过动态代理生成匿名类的匿名对象的方式织入的。这叫作“动态织入”。
     十、拦截器(interceptor)
     很多AOP框架(例如Spring和JBoss 4,但不包含AspectJ)用它来实现字段和方法的拦截(interceptor)。随之而来的就是在连接点(譬如方法拦截)处挂接一条拦截器链(interceptor chain),链条上的每个拦截器通常会调用下一个拦截器。实际上,拦截是一种AOP的实现策略,而不是AOP的核心概念。
     十一、AOP代理(AOP proxy)
     AOP框架创建的对象,这个匿名类的匿名对象,它既委派目标对象完成目标对象的工作,也织入了拦截连接点的通知。在Spring AOP中,AOP代理可以是JDK动态代理或者CGLIB代理。
     AOP代理也并不是所有AOP实现都有的一个概念,它是Spring AOP框架和JBoss AOP框架等动态AOP框架爱实现AOP的根本方法。
    实现AOP的主要技术策略
     AOP面向方面编程思想目前已经有很多种实现和实现方法。下面是用于实现AOP的主要技术策略:
     一、J2SE动态代理
     在Java中,实现AOP最显而易见的策略莫过于使用Java1.3引入的动态代理。动态代理是一种强大的语言结构,它使我们可以为一个或多个接口“凭空”地创建实现对象,而不需要预先有一个实现类。
     如果需要用动态代理实现环绕增强,可以在其中调用必要的拦截器链。拦截器链上的最后一个拦截器将借助反射调用目标对象—如果有目标对象的话。
     动态代理最大的好处在于:这是一种标准的Java语言特性。除了AOP框架之外不需要第三方库,也不会受到应用服务器的任何影响。
     动态代理的最大局限性在于:它只能针对接口进行代理,不能针对类。即,需要“客户—基础设施”中的客户类实现所需的接口,然后在程序中使用接口来使用新的对象的方法。不过,既然我们主张“针对接口编程”,那么这项限制并不是坏事,反而能够使程序员养成良好的“针对接口编程”的习惯。
     另外,动态代理只能对方法调用进行增强,而不能像AspectJ那样对字段进行增强。不过,既然我们使用方法来提供所有的服务,那么“对字段进行增强”这项功能也就是完全无用的,反而会引起程序员使用不良的编程方法。
     Spring框架,默认时使用J2SE动态代理提供AOP实现。开发者也可以指定使用“动态字节码生成”技术来实现AOP。Nanning框架也使用J2SE动态代理提供AOP实现。
     二、动态字节码生成
     为了针对Java类提供代理,我们需要动态代理之外的工具,那就是动态字节码生成(dynamic byte code generation)。在这方面,最流行的工具是CGLIB(Code Generation Library)。在Spring中,如果需要针对类(而不是接口)提供代理,就会用到CGLIB。它可以针对指定的类动态生成一个子类,并覆盖其中的方法,从而实现方法拦截。
     不过CGLIB有一个小问题:因为它是通过继承来实现代理的,所以无法为final方法提供代理。
     三、Java代码生成
     最笨的方法,就是让容器生成新的源码。这种方法,最早大概是MTS微软事务服务器采用的,后来的Java的EJB也采用了这种方法来提供“基础设施”。虽然这种方法很笨。但是,它们确实是早期的AOP尝试。现在,随着动态代理和动态字节码生成技术的出现,这种做法正在逐渐退出流行。
     另外,不得不补充一句:代码生成,基本上都是最糟糕的编程技术。通常,我们都可以使用OOP的委派或者AOP来达到相同的目的。自动生成代码,将会在需要修改代码时引起众多可怕的问题!微软是用了不少自动代码生成技术,另外MDA模型驱动开发方法和基于元数据的产生式编程都是常见的源代码生成技术。对于这些技术,我一直都抱着怀疑的态度!只要还有其他的方法可以实现目标,我是绝对不会使用源代码生成这种技术的!
     四、使用定制的类加载器
     使用定制的类加载器,我们可以在一个类被加载时自动对其进行增强。即便用户使用new操作符构造实例,增强仍会生效。JBoss AOP和AspectWerk都采用这种做法对类进行增强,具体的增强信息则是在运行时从XML配置文件中读取。
     这种做法的风险在于:它偏离了Java的标准。在某些应用服务器中,这种做法可能会导致问题,因为J2EE服务器需要控制整个类加载的层级体系。
     五、语言扩展
     如果我们希望把方面当作一等公民来对待,就需要一种同时支持AOP和OOP的语言。为了达到这个目的,可以对现有的OO语言进行扩展,就像C++扩展C语言、引入OO的概念那样。最早出现的AOP实现AspectJ就对Java进行了这样的扩展。
     AspectJ是功能最强大的AOP实现。但是,它是一种新的语言,语法比Java更复杂。而且,还需要使用AspectJ的预编译器首先编译AspectJ源码,将它们变成增强后的Java代码,然后再进行Java的编译,太过繁琐。
    Spring AOP框架
     在所有的AOP实现中,我认为Spring AOP框架是最好的选择。尽管它使用的动态代理和动态字节码生成技术实现的AOP功能并不是最强大的,但是对于大多数情况已经够用,而且够简单,没有任何特殊的要求。
     并且,Spring框架很好的整合了AspectJ,在必要的时候,我们可以使用AspectJ的强大能力来实现AOP。
     Spring AOP是用纯Java实现的,它不像AspectJ那样需要特殊的编译过程,也不需要像JBoss AOP那样需要控制类装载器层次,因此适用于J2EE容器或应用服务器,也适用于任何使用Java的程序。不过,你的程序也必须要使用Spring框架来管理。Spring AOP只能够为Spring框架管理的Java类提供AOP服务。
     Spring目前只支持拦截方法调用和异常抛出,不支持拦截字段访问。
     Spring提供代表切入点或各种通知类型的类。Spring使用术语advisor顾问来表示代表方面(aspect)的对象。它包含一个增强(advice)和一个指定特定连接点(join point)的切入点(pointcut)。
     Spring AOP框架的目标并不是提供极其完善的AOP实现(虽然Spring AOP非常强大),而是提供一个和Spring IOC容器紧密结合的AOP实现,帮助解决企业级应用中的常见问题。
     因此,Spring AOP的功能通常是和Spring IOC容器联合使用的。AOP通知是用普通的bean定义语法定义的。增强和切入点本身由Spring IOC管理—这是一个重要的和其他AOP实现的区别。
     有些事使用Spring AOP是无法容易或高效的实现的,如非常细粒度的对象,此时可以使用AspectJ。
     总的来说,Spring针对J2EE企业级应用中大部分能用AOP解决的问题提供了一个优秀的解决方案。
    如何更好的使用AOP
     面向方面编程(AOP)为软件开发提供了一种全新的视角—横切的视角。让我们看待软件的视角从纵向的一维世界,变为平面的世界。也大大提高了我们开发软件的能力。最近,AOP将会取代OOP的论调层出不穷。
     的确,AOP是一种不错的编程范式和思考方法,但是,OOP才是编程的根本。AOP只是OOP的有益补充,它们之间不是对立关系,而是互补的关系。AOP绝对不会,也不可能替代OOP。两者各有各的领地,而OOP的应用范围更大!
     如果一个软件,连最起码的OO原则都没有遵循,又怎么可能奢望依靠AOP来达到健壮的目的呢!
     现在,在一些程序员中有一种倾向,就是在编程中一门心思的应用AOP来编程。只要发现任何调用代码重复,都要使用方面来解决。
     这就造成了程序中使用了太多的方面。整个程序的逻辑变得难以理解和修改。
     我认为,只应该在出现大量调用代码重复的情况下,才应该使用AOP的“客户—基础设施”来解决,否则应该使用OOP的“客户—服务器”模式。对于难以决定应该使用OOP还是AOP的场合,应该使用OOP。

    面向关注软件开发
     面向对象编程技术,是一种解决业务关注的编程技术。比如说,一个企业级应用,用户需要用户管理的功能,同时这个软件的所有模块都需要事务处理功能,日志功能和安全性检查的功能。
     使用OO技术时,当我们要编写实现用户管理功能的代码模块时,我们得到的系统关注(UML称作“用例”,XP称作“用户故事”),是:用户管理模块,需要有增加、删除、修改和查询用户的功能。同时,所有的数据库操作要有日志,要使用事务,要检查安全性,只有符合条件的用户才能够调用这些管理功能。
     然后,我们根据这个业务关注,编写出满足这些要求的软件模块。其中,日志、事务、安全性检查代码都与其它业务关注的实现模块出现了代码重复。
     可以说,OOP是用纵向的一维的视角来看待软件系统的。
     AOP则是一种解决横切公共关注[9]的编程技术。对于上面例子中的日志、事务、安全性检查等公共的关注,我们不是把它们分散到各个业务关注中,而是集中在一起,构成“横切公共关注”,使用AOP的技术,在一个模块,也就是方面内实现,各个业务关注根本不用知道还存在这些关注,当然,在实现业务关注的OO代码中也不会体现这些横切关注。
     我们可以看到,不论是OOP还是AOP,都只是一种编程的范式,是面向关注,解决关注的一种手段。
     但是软件开发的根本是什么呢?就是“涉众”的“关注”。软件就是要满足涉众的关注,满足了涉众的关注,那么这个软件就成功了!
     没有“关注”,OOP和AOP就都成了无本之木,无源之水,没有目标的箭。而企业级应用,最大的变数和风险,就是客户的需求的变化,也就是系统“关注”的变化。面向关注的软件开发(COSD,concern-oriented Software Development),让我们始终面向涉众的“关注”这个根本的目标来开发软件。也让我们在“关注”的旗帜下,综合运用OOP和AOP编程技术,分别解决纵向的业务关注和横切的公共关注,相辅相成。
     面向关注软件开发(COSD)的思想,是来自于AOP提出的概念“关注(concern)”,是重新审视OOP和AOP相互之间关系的思考结果。
     
    敏捷开发方法的代表作—XP
     极限编程(Extreme Programming,简称XP)是目前讨论最多、实践最多、争议也是最多的一种敏捷开发方法。XP是一套能够快速开发高质量软件所需的价值观、原则和活动的集合,使软件能以尽可能快的速度开发出来并向客户提供最高效益。
    XP的前提
     极限编程的假设:平滑成本曲线—变化的成本不会随时间的推移而急剧上升。而非传统中普遍认为的“变化的成本随时间的推移而以指数方式上升”[7]。传统的计算机科学家认为,对软件的改变,会随着项目时间的推移而变得越来越困难,其成本将是在项目开始初期就将这些需求考虑进来的几十倍。这在过去,确实是软件开发的事实。
     但是,随着面向对象编程技术的发展,这一个假设已经变得不符合实际情况了!具有良好面向对象编程技术功底的程序员,完全可以开发出高内聚、低耦合,结构简单,易于修改的健壮的软件,其“成本曲线”是“平滑的”,而非“陡峭的”。即使在软件完成之后,对软件进行大规模的修改和添加新功能,也仅仅比在项目开始的时候明确定义这些需求增加极少的成本。
     所以,如果要使用XP取得成功,首先就需要能够掌握本文前面章节介绍的那些方法,让软件的健壮度更高。可以说,XP对开发人员的要求是比较高的,需要有扎实的编程水平和面向对象的思想。
    为什么需要XP
     那么我们为什么不在项目开始之前就确定所有的需求,而要在项目进行,甚至已经完成的时候修改软件呢?如果能够做到这一点,当然好,但是,现实中,我们常常无法做到这一点。
     1,程序员对于开发软件是专家,但却不是软件应用领域的专家。而用户虽然是软件应用领域的专家,但是他们往往不清楚软件能够做到什么,不能够做到什么,或者做一项工作的成本如何。由于开发人员和用户对相互领域的不了解,所以在软件实际开发出来之前,开发人员和用户都无法清楚的定义软件需要的功能。
     往往直到软件被实际开发出来的,用户试用之后,才能够明白自己到底需要怎样的软件。特别是在企业级应用中,软件开发完成之后,大规模的修改软件是常事。
     2,随着时间的推移,软件应用的环境本身就在发生变化。特别是在企业级应用中,企业的业务环境一直处于不断的变化之中,这就要求企业级应用也要能够适应企业的变化。而这些可能的变化,对于开发者来说,是很难预见的。
     3,一般的软件开发过程中,我们往往选择分多个版本、阶段开发软件。就是首先开发一个初级的版本,发行。之后再在其基础上添加其他功能。这是一般的软件开发的策略。这也要求,我们不可能在软件开发的初期就能够考虑到未来若干年内我们软件的全部需求。比如,Windows操作系统,这个软件已经历时十多年,发布了无数个版本。显然,最初开发Windows的时候是不可能预见到今天的Windows的。
     总之,软件开发过程中,需求的变化是必然的,绝对的。不变的需求是暂时的,相对的。
     面对不确定的未来,以前,我们一般基于“变化的成本随时间的推移而以指数方式上升”这一假设,在项目开发的初期,想方设法的预测未来的种种变化,试图抓住“不确定的未来”。但是,这常常是徒劳的。而且,这么做的结果,往往是把本来简单的需求复杂化了。其中的很多需求,根本就是客户所不需要的。
     譬如说,现在很流行远程方法调用。很多程序员想当然的认为软件将来需要提供远程调用这样的功能。他们花了大力气来使用复杂的CORBA,Web Services,RMI,EJB等技术提供远程调用功能,大大增加了软件的开发成本,而实际上,大部分的软件最后都没有这样的需求。而且,即使将来真的需要提供远程调用的功能,对于设计良好的软件,到时候再增加这种功能也非难事。
     简单的软件被搞复杂了,而客户却需要为并不需要的功能埋单!
     
    融合XP的软件开发过程
     如果我们在软件开发中贯彻了那些开发健壮软件的编程技术,使我们的软件比较健壮,也就是说符合“平滑成本曲线—变化的成本不会随时间的推移而急剧上升”这一实践XP的前提,那么我们就可以使用极限编程方法来开发软件,提高软件开发的生产效率和商业价值。
    获取需求
     传统的软件开发方法把这个阶段称作“需求调研”。因为,按照传统的方法,是开发人员调查研究软件的需求。而XP不同,XP是让客户在软件开发人员的引导下为开发人员提供软件的需求。因为,程序员对于开发软件是专家,但却不是软件应用领域的专家。显然,对于软件应该是什么样的,客户是更有发言权的。软件的需求,理所当然应该由客户来提供。
     一、初步提出软件需求
     客户提出软件应该实现的功能,而开发人员则告诉客户这些功能是否能够实现,以及需要花费多少成本。由客户最后决定需要实现哪些功能。
     这个阶段,客户提出的需求,不必深入到细节,只需要一个个粗略的需求。如果深入到细节,那么就会陷入传统开发方法过份需求调研和分析的陷阱中。
     XP实践之:小版本—将一个简单系统迅速投入生产,然后以很短的周期发布新版本。
     XP将整个软件分成多个小版本,多阶段发行。客户根据功能的优先级将功能划分到多个版本中分批予以实现。但是,第一个版本,如果还没有一个软件的架构,那么这个版本需要实现哪些功能由开发人员决定。开发人员选择那些便于搭建架构的功能,比如用户管理,权限管理等功能,首先实现。
     二、版本开发中的软件需求
        初步提出的软件需求是大致的,概括的,还没有深入软件需求的细节。进入版本开发之后,我们需要客户提供足够的需求细节,以支持“需求驱动开发”。
     比如说,初步需求阶段用户管理这样的需求,在版本开发阶段,需要明确怎样的用户管理功能,比如,需要有增加、删除、查看、修改用户信息的功能。
     用UML表示,就是“用户管理”用例。增加、删除、查看、修改用户这四个详细的子用例,或者说是四个事件流。一旦程序员得到了确定的事件流,就可以以此为依据,画出UML的“序列图”,驱动软件开发的展开。
     三、横切公共关注
     开发人员从客户提供的系统需求中,提取出公共的系统需求,譬如说,日志,访问控制等。对于这些横切公共关注,我们可以使用OOP的方式实现。编写一个类来提供这些服务,然后在各个业务模块中通过“客户—服务器”模式,调用这个类的方法来使用这些公共关注的服务。
     或者,更进一步,使用AOP的方式实现。编写方面来提供这些服务。通过“客户—基础设施”这样的调用模式,在业务模块并不知道的情况下,为它们提供这些服务。
    测试驱动开发和验收测试级重构
     极限编程的哲学思想是实证主义的哲学思想:“任何不能度量的事物都是不存在的”[4]。极限编程反对传统开发方法重视过程和文档的做法,主张快速的开发、构建、测试和部署软件。认为,一切决策都应该基于软件的实际运行结果,而不是无端的猜测。经常“问问电脑”[8],而不是基于一种毫无证据的信念,这是XP人的基本编程原则。
     XP人首先就需要是一个皈依实证主义哲学的信徒。
     因此,XP的开发方法和过程,可以用这样的公式来表示:
     软件=测试驱动开发+重构。
     一、验收测试驱动开发
     怎样才能够证明开发人员已经实现了一个客户需要的功能呢?这就需要测试—验收测试。验收测试,是在用例实现之前由客户在开发人员的帮助下编写的。验收测试,就是一段文字,用来验证系统是否已经满足客户需求。
     开发人员可以根据这段文字,用规范的脚本语言来形式化的定义。通常,我们为每个应用程序编写一套脚本语言。一般,我们使用XML文件的形式,或者是文本文件的形式。开发人员自己编写一个简单的程序,读取这个验收测试的文本文件,根据文本文件中的内容,调用软件中相应的方法,予以执行。脚本语言和具体的方法之间的对应关系,由开发人员自己定义。
     实际上,验收测试[2],就是程序的一个用户界面,是基于文本文件或者XML文件的一个用户界面。是调用软件核心业务逻辑的一个简单界面。通过它,我们能够知道软件的核心业务逻辑是否运行正常。
     验收测试是程序,因此是可以运行的。我们在软件开发的过程中可以经常运行这些验收测试,以保证软件一直处于正确的状态。
     而且,验收测试没有用户界面,可以和单元测试一样批量快速运行,把结果保存到文件里。
     验收测试是关于一项软件功能的最终可执行文档。程序员可以阅读这些验收测试来真正的理解这些功能,从而明白应该怎样来实现这些功能。验收测试,本身就是“事件流”,程序员可以据此画出“序列图”,实现验收测试驱动的软件开发。
     通过验收测试,驱动软件开发,是XP推荐的软件开发方法。
     二、“客户—服务器”模式层层委派开发软件
        我们使用“客户—服务器”模式,通过层层委派的方式来编写软件。
     序列图描述了系统的业务模块提供了哪些服务,完成了事件流。我们可以直接将序列图的逻辑编写成控制器类(或者叫作“业务委派”, 所谓业务委派,是指在客户端和业务服务层之间,增设一个“代表层”,所有客户端到服务器端的调用,都“委托”该层完成。[10])。
     控制器就是我们核心业务逻辑的第一个客户。它和业务服务类构成了“客户—服务器”关系,可能也会和领域模块的领域模型类构成“客户—服务器”关系。
     为了实现用户请求的功能,我们在控制器中调用几个方法来实现这个用户需求。但是,现在我们还没有任何可以使用的方法。我们现在照样在控制器中写上要调用的方法,尽管它们还没有被编写和实现。没关系,以后会实现的!
     将这些方法按照逻辑和是否会在客户端造成重复为标准,分配给各个模块的各个类。按照“针对接口”编程的原则,将这些服务分发到各个接口中去,而不是实现类。
     我们可以使用CRC图来帮助完成这个分配方法的工作。
     现在,虽然程序还没有开发完成,但我们已经知道程序在逻辑上已经完成了,或者说,已经设计完成了。我们只剩下两项简单的实现层面的工作要做:1,编写接口的实现类,实现所需的方法;2,利用重构,将这些方法移到最适合的接口和实现类中去。
     三、验收测试级重构
     重构,就是“在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内在结构”[5]。一旦编写好代码,对代码的任何修改,都可以算是重构。
     重构总是和测试联系在一起的。没有自动化测试的支持,是不可能进行有效的重构的。按照所需要的测试,重构可以分为两类:
     一是“单元测试级的重构”,也是大家使用最为广泛的重构。但是,这类重构改善的只是软件局部的质量,一般就是一两个类的改善。
     另一种是“验收测试级的重构”,这是改善整个模块的质量的重构。它是对整个模块实现机制的改善,常常涉及到多个接口和类的修改。
     在需求变化的过程中,依靠自动化的验收测试,我们可以很容易的进行验收级重构,快速地响应软件需求的变化。也可以使软件变得越来越健壮。
    单元测试驱动开发和单元测试级重构
     在编写具体的类时,我们也使使用“单元测试驱动开发”的。首先编写测试类,然后再编写实现类,逐个方法的完成单元测试,最后完成类的编写。在需要改善类的实现代码时,使用单元测试级的重构,改善类的内部结构。
     XP开发软件,就是自顶向下,一层层的由测试驱动开发,在需要改善使,使用这些测试安全的进行重构。
    小结
     最后,整个软件的开发工作和软件质量,都可以通过验收测试和单元测试得到保障和度量。客户可以随时监控软件开发的进展情况。客户花的钱可以直接看到成果。而且,通过小版本快速迭代的发布,客户可以及早的享用软件开发的成果,也可以即时地终止不合算的软件项目,而不会造成投入全部付之东流。
       
    使用Java开发企业级应用的参考架构
     现在,让我们使用Java实际的开发两个企业级应用的参考架构。这个架构并不实现特定的业务需求,仅仅是两个开发企业级应用的架构。这个架构是健壮的,易于修改的,也是符合“平滑成本曲线—变化的成本不会随时间的推移而急剧上升”这一XP要求的。
    JavaEE
     Java EE,Java平台企业版(Java Platform Enterprise Edition), 是Sun公司为企业级 应用推出的标准平台。Java平台共分为三个主要版本Java EE、Java SE和Java ME。原来,它们叫作J2EE、J2SE、J2ME。
     Java EE是使用Java进行企业开发的一套扩展标准。Java EE平台提供了多层、分布式的应用模型,重新利用组件的能力,统一安全的模式以及灵活的处理控制能力。Java EE包括EJB, JTA, JDBC, JCA, JMX, JNDI, JMS, JavaMail, Servlet, JSP等规范[10]。
    “经典”的JavaEE架构
     通常,人们认为Java EE是一种分布式平台,EJB在该平台中占有中心地位。“经典”的Java EE架构是这样的[7]:
     1,Java类分散在不同的JVM中,虽然要想把这些类并置在一起、消除远程调用的程中负载也是可以办到的。
     2,Web层一般由一个MVC架构实现(其他架构方案也都是如此)。如果应用系统设计的比较精心,那么就会有一个专门的客户端代理层,通过它调用远程EJB,这样就能够干净利落的解除web层和EJB之间的耦合。
     3,所有的业务对象都是带远程接口的无状态session bean,运行在EJB容器中。EJB容器提供远程调用机制、事物管理、线程管理,可能还有基于角色的安全服务。
     4,所有的数据访问都要通过entity bean。Entity bean提供了O-R映射。
     5,EIS层由一个或多个数据库或者遗留系统组成。如果存在多个带事务的资源,通过EJB容器访问的JTA服务会负责协调分布式服务。
     对于不准备提供EJB集群服务或者远程调用EJB的企业级应用,也可以使用本地EJB。
     Java EE的这种架构提供了强大的远程调用能力、JTA声明式事务和声明式安全等服务,以及集群部署EJB的能力。从而使Java在短短几年时间内占领企业级应用市场。
     但是,这种架构在多年的实践中,被发现了不少弱点:
     1,这种架构开发起来十分困难和低效。这主要是EJB规范太过复杂。按照EJB2.1规范的定义,EJB组件必须事先很多的接口, 比如Home接口、Remote接口、local 接口,等等.还要针对各种应用类型定义许多的xml描述文件。当我们需要访问某个组件或者服务的时候,必须通过JDNI查找,通过名字绑定服务,才能找到我们需要的对象。
     2,运行起来也是十分低效的。Entity Bean的实现机制有问题,导致了Entity Bean访问数据库十分低效。甚至,目前很少有人采用Entity Bean来访问数据库。但是,如果不使用Entity Bean,Session Bean
    使用其他数据库访问方式,则会存在一些问题。因为有些数据库访问机制,如Hibernate的Session Factory必须是单个实例的,而Session Bean中是无法实现单例的。
     另外,EJB的执行效率也比POJO慢。
        过去,人们认为EJB是Java EE的核心。但是,实际上,所有的Java EE服务,包括JTA,RMI,JNDI,JMS等,都可以脱离EJB容器来使用,它们实际上都是Web容器提供的功能,EJB容器只是提供了一种访问这些服务的方法而已,我们完全可以不通过EJB而直接使用这些Java EE服务。
     Java开源软件
     Java是一种开放的技术,它的发展,既由官方的JCP(Java Community Process)组织通过制定规范推动,也由Java开源社区推动。战斗在一线的程序员,开发出了很多开源的Java软件,这些软件得到了很大的应用,极大地推动了Java的发展。我将使用这些开源软件,结合Java EE提供的服务,不使用EJB,来开发企业级软件。
     Struts是一种Web层开发框架,它是使用MVC模式实现的。目前,它是JavaEE领域中主流的Web层开发技术,不论使用EJB还是其他技术,一般都使用它来编写Web层。
     Ant是java世界事实上的标准构建工具。它可以执行一系列的自动化任务,包括构建项目,运行软件和自动化运行单元测试等。
     JUnit是XP创始人Kent Beck开发的一个单元测试工具。可以使用Ant自动化运行Junit测试。
     Hibernate则是事实上的O-R映射的标准框架。它通过xml文件配置,来映射数据库表和Java程序中的POJO对象,通过同步缓存和数据库,自动更新数据库。它使我们可以像使用对象型数据库那样使用关系型数据库。
     Spring框架,是一个轻量级的应用框架。Spring的目标就是提供一中贯穿始终的解决方案,将各种专用框架整合成一个连贯的整体。它可以整合Struts、Hibernate等框架,甚至简化EJB的开发和访问。
     Spring的威力来源于两个方面:IOC容器和AOP框架。使用Spring框架,完全可以取代EJB的功能。
     这些开源框架,可以帮助我们实现非EJB的企业级应用。
    不用EJB的简单Java EE架构
     对于简单的企业级应用,我们可以使用简单的技术实现。这种架构,虽然简单,但仍然很健壮。
     使用Struts作为Web层开发技术,使用POJO作为业务对象,使用DAO模式封装的JDBC作为数据访问层技术。通过DAO模式,我们也实现了简单的O-R映射。
     一个典型的客户用例的实现模块中,我们的类分为以下几个部分:
     1,Struts的控制器模块Action,实际上,也就是“业务委派”模式的业务委派类。它们从视图中接收用户请求,然后调用业务服务类的方法实现用户的请求。
        Action和Servlet一样,都是单例。也就是说,一台机器上只有一个Action类的实例,为所有的客户服务。也就是说,它不能够保存客户的状态,因为状态可以保存在HttpRequest、HttpSession、ServletContext里。
     2,Struts的MVC模式中的Model—ActionForm,它用于在Action和视图之间传递数据。ActionForm中的数据,如果作为表单提交,需要是String型的。
     3,领域模块的领域模型类。它是O-R映射中代表数据库表的对象。也作为参数和返回值在控制器Action类和业务服务Service类之间,以及Service类和DAO类之间传递数据。
     在Action中,Action从视图中拿到的数据容器类ActionForm并不能够直接调用业务服务Service类的方法,必须要转换为领域模型对象,或者简单对象才能够作为参数调用业务服务Service类的方法。
     Service类的方法返回的参数,也必须要封装到ActionForm中才能够让视图拿到显示给用户。
     4,业务模块的业务服务Service接口及其实现类
     Service接口中封装了Action要调用的所有业务操作。其实现类实现了这个接口。Service实现类是POJO,而不是session bean。
     Service类中要与数据库交互的操作,都委派给DAO接口的实现类。
     Service实现类也应该是单例的,因为它只是提供服务,并不需要保存状态。所有的数据都可以通过方法的参数传递进来。
     要实现单例,我们可以使用一个工厂类,也可以直接在Service实现类里提供单例的实现方法。这里,为了简单,我在Service实现类里直接实现了单例。
    public class JDBCMessageServiceImpl implements IMessageService{
     private static JDBCMessageServiceImpl instance=null;
     /**
    * @return JDBCMessageServiceImpl 返回这个Service类的一个实例
        */
       public static JDBCMessageServiceImpl getInstance(){
        if(null==JDBCMessageServiceImpl.instance){
         JDBCMessageServiceImpl.instance=new JDBCMessageServiceImpl();
          }
            return instance;
       }
    ……
     5,领域模块的数据访问对象DAO接口及其实现类
        DAO接口中封装了持久化领域模型类的所有方法。通过DAO,我们实现了内存中的领域模型类和硬盘上的数据库表之间的映射。这里,DAO的实现类是使用JDBC方式访问数据库的类。
     DAO实现类一样是提供数据访问服务的服务类,也应该是单例的。我们使用和Service类一样的方式实现单例。
     在DAO实现类中,我们尽量使用领域模型类作为参数和返回值。这样,就实现了领域模型类和数据库表之间的O-R映射。在DAO实现类之外,我们可以使用领域模型类来操作数据库表中的数据。
     这个架构中,我们在Service模块和DAO模块中都使用了接口—实现类这种方式。对于同一个接口,可以有无数个实现类。
     这里,我仅仅使用JDBC这一种方式实现DAO接口。实际上,我们可以很方便的编写一个使用iBatis,Hibernate或者JDO等其他数据访问技术的DAO实现类。
     另外,我们可以使用独立于Web容器的连接池。
     我们可以使用Web容器的JNDI将实现连接池的数据源发布到JNDI上,客户代码从JNDI上得到数据源的实例,调用连接池中的连接。但是,不同的Web容器发布JNDI是很不一致的,而且获得JNDI也是非常费事的。
     我们可以用Apache的DBCP框架,直接编写连接池。这不需要任何Web容器的支持,可以在一般的Java程序中使用。
     如果需要远程调用,我们可以在应用中集成Axis,提供Web Servics。
     如果需要集群部署,我们也可以在多台服务器上部署多个Web应用程序。
     一、这个架构的优点
     1,不需要EJB容器,只需要Web容器即可,降低了成本。
     2,不需要EJB那样累赘的部署描述符。
     3,容易在不同的Web容器和应用服务器之间切换。
     4,实现更加简单,业务服务Service和数据访问对象DAO都是简单的POJO类。
     二、这个架构的缺点
     1,这里使用了直接的单例模式来生成单例,而不是使用容器管理来实现单例,代码之间因而有一定的耦合。
     2,同样,由于没有容器管理对象的实例。所以,我们必须在客户代码中手工的调用接口实现类的创建单例的方法。这样,在代码中就出现了一定的耦合。如果需要改变实现类,我们就必须修改调用实现类的客户的源代码。
    如,在Action中:
     ImessageService messageService=JDBCMessageServiceImpl.getInstance();
     另外,在Service实现类中,也需要得到DAO实现类的单例:
     public class JDBCMessageServiceImpl implements IMessageService{
     private IMessageDao messageDao=null;
       public JDBCMessageServiceImpl() {
         /*
          由于没有元数据配置,所以,必须要在构造器中直接传入类的实例。
          */
         setMessageDao(JDBCMessageDaoImpl.getInstance());
       }
     ……
     3,我们必须手工编写事务,不能够使用EJB容器提供的容器管理事务CMT。EJB的CMT允许用户在配置文件中声明使用事务,而不需要手工编写事务。
     4,不能够使用EJB提供的对Session Bean,Entity Bean的声明性访问控制。EJB提供的访问控制,可以控制EJB方法的访问权。
     当然,这个架构一样可以通过Web Services的访问控制机制实现这一点。而且,EJB虽然提供了声明性访问控制的机制,但是由于各个EJB容器都有不同的配置方法,移植起来十分困难,所以也很少使用。
     总之,这个架构是简单、健壮、功能强大的。对于小规模的企业级应用完全可以使用这种简单的架构。
     对于更大规模的企业级软件,也许使用更加强大、复杂的技术框架,效率更高。比如说,使用Hibernate技术提供DAO实现类,或者更进一步,使用Spring框架来管理事务和业务对象的单例。
     由于这个架构在业务服务模块Service和数据访问模块DAO中使用了接口和实现分离的方式,增加Spring、Hibernate等框架都是非常容易的。这也正反映了这个架构的健壮之处。
    使用“轻量级容器”的Java EE架构
     “轻量级容器”,是指提供了管理、定位业务对象的方法。一般就是指IOC反转控制容器。它实际上是提供了一个Dictionary类或相似的类,也就是名—值对的集合。在容器启动时,它生成类的实例,然后放在这个字典内,当应用程序中需要这个类的实例时,如果声明为单例,就从这个字典内按照名字取出对象。如果声明为多例模式,容器就生成一个新的实例返回给客户代码。
     这样就实现了单例。不再需要像上面那样在一般的POJO类中编写实现单例的代码了。
     而且,现在,调用代码中就只需要接口,不需要具体的实现类了。我们可以在配置文件中改变提供的实现类,不需要再修改源代码了。
     Spring就是这样一个轻量级的容器。Java EE的JNDI实际上也可以看作是一个容器。它是一个注册表,也就是“名—值”对的集合。不同的是,它把这些对象放在了网上进行远程发布。访问JNDI对象是十分麻烦的。
     另外,Spring还提供了AOP框架,这可以让它提供声明式的服务,如,EJB容器那样的声明式事务等。而且,比EJB容器更加强大,可以自己编写方面。
     Spring的IOC容器和AOP框架,让它完全能够替代EJB容器。比EJB容器更简单,更强大。
     当然,还存在其他的IOC容器和AOP框架,但是目前只有Spring结合了这两者。所以,这里我使用Spring来开发架构。
     一、使用“轻量级容器”的Java EE架构
     这个架构和前面那个不用EJB的简单Java EE架构十分类似。这里,我们给予前面的架构进行修改,将它变为使用“轻量级容器”的Java EE架构。
     Web层,我们仍然使用Struts框架。业务服务模块Service和数据访问模块DAO仍然使用POJO和接口—实现类分离的风格。在DAO的实现类中,我们为了演示Hibernate,所以增加了Hibernate的Dao实现类。当然,原来的基于JDBC的实现类一样可用。
     首先,使用Spring以后,借助于Spring 的IOC容器,我们没有必要再在Service和DAO实现类中提供创建单例的方法了,也没有必要再在Service的实现类的默认构造其中创建DAO实现类的单例了。只需要空的构造器,然后再在配置文件中配置属性,将DAO实现类的实例通过set方法传进来即可。
     在Struts的Action类中,也不再需要直接用Service实现类的单例方法来获得单例了,而是通过Spring的getBean(“*”)方法从Spring的注册表中得到Service实现类的单例。即使实现类改变,也不需要改变源代码,只需要修改Spring的xml配置文件即可。
     Spring提供了直接集成Hibernate的方法。可以将Hibernate的SessionFactory对象也置于SpringIOC容器的管理下,生成单例。
     Spring直接提供了几个类,可以在xml文件中通过配置,声明式的提供事务。不仅可以为Hibernate提供事务,也可以为我们前面架构中的JDBC实现提供声明式的事务。如果我们使用的应用服务器提供JTA事务支持,Spring也可以通过声明的方式提供JTA事务。
     另外,Spring还有一个子项目,Acegi Security System for Spring。这个框架是使用Spring AOP框架和Web容器的Filter机制,再加上本地线程变量ThreadLocal实现的一种安全机制。它在保护Web资源时,使用Web容器标准的Filter过滤器机制,在保护POJO类时,使用Spring AOP的声明式保护,可以保护任何Spring管理的POJO的方法。这比EJB的声明式安全机制更加强大,EJB只能够保护EJB组件。
     但是,Acegi的安全机制比较复杂。如果是B/S应用,仅仅需要提供对Web资源的保护,建议不要使用它。它更适合对远程调用的组件(如,Web Services,RMI,EJB,Hessian,Burlap和Spring Http Invoker等)的方法级访问控制。
     二、这个架构的优点
     1,架构简单,但是功能强大,甚至比使用重量级的EJB容器的经典架构更加强大。
     2,这种架构和不用EJB的简单架构一样,都可以通过部署多个应用程序,来实现集群部署。
     3,与不用EJB的简单架构相比,本方案使用了一个轻量级的容器,这样做并没有增加多少复杂性,反而是减少了代码,降低了复杂性。与EJB容器相比,当然是更加容易学习和配置。
     4,如果轻量级容器提供了AOP,那么就能够提供比EJB更加强大的基础设施和声明式服务。
     5,这种架构不需要EJB容器。
     6,Spring已经提供了很多类,让我们可以简单的访问很多Java EE服务,如JNDI,属性文件等,都可以简单的通过配置来完成。它还对一些常用的框架提供了支持,如iBatis,Hibernate等。通过Spring提供的Hibernate支持类,我们可以简单的使用Hibernate,代码量要比直接使用Hibernate少很多!
     7,由于我们不使用EJB容器的服务,也很少使用Web容器提供的服务,因此,我们的应用程序可以方便的在不用的应用服务器间移植。
     8,IOC可以让轻量级服务器为你“组装”对象。也就是说应用程序中不在需要编写寻找或者生成合作对象的代码了。比如说,Service实现类中已经不需要再生成DAO实现类的实例的代码了。
     9,没有这些组装对象的代码,我们仍然可以使用JUnit来测试我们的应用程序。因为轻量级框架可以在任何Java程序中运行。我们可以在Junit测试类中创建和使用Spring等轻量级容器。
     
    总结
    “源代码就是设计”
     1992年,Jack W.Reeves写了一篇论文《什么是软件设计》,答案是“源代码就是设计”![2]
     源代码编译的过程,就是软件实现的过程,而源代码编写的过程,就是软件设计的过程。至于我们通常所说的分析软件和设计软件,只是对软件设计过程之中的更高层次的分析和设计过程。它们分析和设计的仍然是源代码这个真正的最终的设计文档。
     源代码,有两类读者,一类是编译器,它将源代码编译成可以运行的软件,它对于源代码的唯一要求,就是符合语言规范,没有语法错误。另一类,就是软件的开发人员,他们需要经常地阅读和修改源代码。他们是普通的人类,只能够理解组织优良,逻辑清晰的文字。显然,源代码最重要的读者显然是这些挑剔的人类。
        源代码就是设计文档,就是一篇关于软件怎样组织的说明文章。你读过几十万行字的书吗?如果你要写一本几十万行字的书,那么怎样才能够让读者能够很容易的阅读、理解、然后修改呢?
     写这样一本书的要求,就是写代码的要求!显然,我们编写的代码,应该组织合理,赋予逻辑,有足够的抽象层次,能够让读者一步步地深入到细节,而不是直接把细节全部抛给读者。
     章,节,段,不是很像源代码的包、类和方法吗?
     健壮的软件,就是开发者容易阅读、理解,然后修改的软件;就是结构简单,逻辑清晰的软件;就是经过了层层抽象的软件,每一层都是结构简单,逻辑清晰的!
     软件技术的发展,就是对源代码抽象能力的发展!
    总结
     本文所论述的这些编程技术和软件开发方法,不仅对于开发健壮的企业级应用有积极的意义,对开发所有类型健壮的软件也一样有意义。学习和实践这些编程技术和开发方法,将会有效的提高我们编写的软件的质量和商业价值。
     
                    致  谢
     
     本文是在刘文予教授的悉心指导下完成的。刘教授在理论、方法和实践中都给予了我莫大的指导和帮助,使我在理论和实践上有了很大的提高,顺利完成了本篇论文。在此,谨向导师致以衷心的感谢。
     我还要感谢公司里的同事。
     还有James Gosling,感谢他为这个世界带来了Java这门我最钟爱的编程语言。Java改变了我的一生!
     同时,还要感谢我在华中科技大学求学期间,对我给予关心,帮助的所有老师和同学。
     最后,再次对所有关心、帮助我的老师、同学和同事表示衷心地感谢!
     
     
                   参 考 文 献
     
     [1]  Martin Fowler. 企业应用架构模式. 王怀民 周斌 译. 北京。机械工业出版社,2005年.
     [2]  Robert C.Martin. 敏捷软件开发:原则、模式与实践. 邓辉 译. 北京。清华大学出版社,  2003年.
     [3]  Erich Gamma, Richard Helm, Ralph Johnson, et al. 设计模式:可复用面向对象软件的基础. 李英军,马晓星,蔡敏等 译. 北京。机械工业出版社, 2005年.
     [4]  Rod Johnson.  J2EE设计开发编程指南. 魏海萍 译. 北京。 电子工业出版社, 2003年.
     [5]  Martin Fowler. 重构—改善既有代码的设计. 侯捷,熊节 译.  北京。 中国电力出版社, 2003年.
     [6]  Kent Beck. 解析极限编程—拥抱变化. 唐东铭 译. 北京。人民邮电出版社, 2002年.
     [7] Rod Johnson, Juergen Hoeller.  Expert One-on-One J2EE Development without EJB中文版.  JavaEye 译. 北京。 电子工业出版社, 2005年.
     [8]  Iver Jacoboson, Pan-Wei Ng.  AOSD中文版—基于用例的面向方面软件开发.  徐锋 译.  北京。 电子工业出版社, 2005年.  .77-85
     [9]  Deepak Alur, John Crupi, Dan Malks.  J2EE核心模式、第二版.
         北京。 机械工业出版社, 2005年.  .218-227
     [10]  Mark Cade, Simon Roberts.  J2EE架构师认证指南. 武欣,罗云峰,刘侃 译.  北京。 机械工业出版社, 2004年.  .15-17

    展开全文
  • 关注ITValue,查看企业级市场最新鲜、最具价值的报道!美团点评联合创始人王慧文曾在2013年的时候做了这样一件事—— 他把美国科技业的公司和中国的公...

            640?wx_fmt=gif 关注ITValue,查看企业级市场最新鲜、最具价值的报道!


    640?wx_fmt=jpeg


    美团点评联合创始人王慧文曾在2013年的时候做了这样一件事——

     

    他把美国科技业的公司和中国的公司拉了一个名单,试图从中寻找出在美国已经很厉害了但在中国还没有被真正做起来的产业。他发现了Salesforce,Workday,Oracle。

     

    “他们基本占据科技业的另外一半……但是我们把这个放到中国来看的话,to B的公司居然找不到,基本上找不到,有活着的,但是活得很惨。”在一次内部讲话中,王慧文抛出了一个问题:为什么中国这些to B的企业活得这么惨呢?

     

    的确,中国互联网的上一个十年是关于用户端(C端)的十年,人口红利、消费红利让各种商业模式创新迅速成长起来,消费级业务无论是盈利效果、创新难度还是企业品牌塑造都明显优于企业级服务。

     

    与此同时,上一个十年,水大鱼大,依靠市场红利,业务的驱动并非步履维艰,企业对于提高效率的新工具、新方法的采用意愿并不高。那些昂贵的、短期效果不显著的企业服务自然也不好卖。百度李彦宏曾经在百度联盟大会上曾指出,过去中国劳动力成本很低,使用企业级软件的效应并没有起来,另外,很多传统企业的老板并不用电脑,不会用PC提升效率。

     

    更何况,中国企业对于数据的安全性顾虑更多的,而美国数据立法相对成熟,对于企业服务类应用的数据安全性信任程度比中国高。

     

    老牌企业服务公司用友、金蝶在过去很长一段时间里都不是聚光灯的所在,曾在2014、15年享受到资本“泡沫”的SaaS风口也没有真正地起飞,在中国诞生一家类似SAP、Oracle一样专注服务企业端(B端)的伟大公司,并不是一朝一夕的事情。


    640?wx_fmt=jpeg

    全球最大的企业级软件公司Oracle位于硅谷的总部

     

    时机似乎到了。C端业务的瓶颈愈发明显、需求侧达到饱和,以企业为核心角色的供给侧改革势在必行。

     

    但这并不是一个创业的机遇期。百度、腾讯、阿里早就在B端有所布局,其业务发展的深度广度远远超出行业的想象。如今他们不约而同地加大投入力度,进一步教育市场。至于京东、今日头条(字节跳动)、美团等公司,虽然在B端也有布局,但周密程度和BAT无法相比,而那些15年SaaS创业浪潮中的幸存者,也都只是专注于某一个非常细分的领域。

     

    人们呼唤着“下一个十年,一切皆重来”,但事实上,下一个十年,to B业务前所未有的好时代,恐怕还是BAT的。

     

    *注:Salesforce专注提供销售团队管理解决方案,Workday提供人力资源解决方案,Oracle(甲骨文)是全球最大的企业级软件公司。


    BAT蓄谋已久的B计划

     

    百度是BAT中最有to B气质的,其大部分营收也来自B端。定位为AI公司的百度,更多地会为企业提供无人驾驶汽车的、云的、整个AI应用生态平台的人工智能解决方案。

     

    具体来看,百度未来的三个主要发展方向都是非常to B的:

     

    第一,Apollo无人驾驶汽车生态。

     

    Apollo计划,是针对汽车行业的自动驾驶开放平台。阿波罗平台包括一套完整的软硬件和服务体系,包括车辆平台、硬件平台、软件平台、云端数据服务等四大部分。这些都是To B的业务。

     

    百度与金龙客车合作的、搭载了百度Apollo自动驾驶解决方案系统的全球首款L4级自动驾驶巴士“阿波龙”已经量产下线。同时,百度在“车”方面的企业级合作伙伴还包括戴姆勒、福特、博世、NVIDIA、Intel、BlackBerry等。

     

    百度是一个供给端的平台,向包括运营商、汽车服务厂商、整车厂商、零部件厂商、芯片厂商等等在内的合作伙伴们提供“车”相关的服务。

    搭载Apollo2.0无人驾驶系统的汽车

     

    640?wx_fmt=jpeg

    搭载Apollo2.0无人驾驶系统的汽车

     

    第二,DuerOS度秘。

     

    一直以来,度秘被理解为人机对话的接口,用来“唤醒万物”,但如果往深了看,DuerOS是目前中国唯一一个从硬件到框架,再到平台、开发生态、生态应用系统、终端硬件全覆盖的AI应用生态平台。这使得DuerOS的生态接入的吸纳性更高,能解决很多场景问题。

     

    比如,在洲际酒店的“小度智慧客房”中。客人可以通过语音控制客房设备、播放音乐、询问天气、检索信息。AI时代,高端酒店正在借助新一代人机交互方式提升用户体验。


    640?wx_fmt=jpeg

    2018百度AI开发者大会发布的DuerOS3.0框架图

     

    第三,ABC云。

     

    2017年,百度云推出了ABC战略,即AI人工智能、BigData大数据、Cloud Computing云计算。在李彦宏看来,百度的“云”和其他传统的“云”服务是不一样的,它在每一个行业的应用都有智能的因素在里面。

     

    在上个月的百度云智峰会上,百度云推出了AI to B平台,兼具深度学习、对话式搜索、自然语言处理等全面的AI能力,以及涵盖新零售、新制造、交通与公共安全等领域的解决方案。

     

    以国内最大钢铁企业宝武集团与百度云在质检领域的合作为例,依托百度云质检云行业解决方案,通过机器视觉模型量化质检流程,为质检流程提供决策依据,在降低误报率的同时,提升作业效率。而百度云与优信二手车的合作,则是通过VR技术升级远程选车,并在线下门店内部署AI摄像头,通过人脸识别匹配购车信息,结合云平台与业务系统沉淀用户数据,为后续经营提供支撑。

     

    现在百度云的行业版图已经覆盖了农业、工业制造业、金融服务业等领域,不光是简单地存储数据,而是通过AI能力为企业提供解决实际问题的解决方案。


    640?wx_fmt=jpeg

    百度副总裁尹世明发布百度云ABC 3.0

     

    百度在B端的野心其实李彦宏在2017年的世界互联网大会上就有透露,他当时分享了百度与连锁超市的合作,用人工智能的技术去提升超市里生鲜货品的效率,使得超市利润提升了20%,报损率降低了30%以上。

     

    “人工智能对于B端的生态改变是非常明显的。无论是金融还是房产,无论是教育还是医疗,无论是能源还是物流等等,每一个方面,人工智能都有非常非常多的应用。”

     

    简言之,百度的“B计划”是以AI切入,服务B端的每一个方面


    640?wx_fmt=jpeg



    做云的远不只有百度,事实上,“云”正在成为BAT在企业服务领域的必争之地。

     

    因为云确实是对企业有非常大的价值:一方面,云计算是公共服务,是可变成本,可按需使用,不再是固定资产投入,创业公司的成本可以降下来;另一方面,移动+云,相当于把IT服务在线化了,让技术门槛大大降低;而云与大数据的结合可以负能相当多的领域。

     

    阿里是BAT中第一个提出做云计算业务的:2008年确定云计算战略,2009年成立阿里云公司。

     

    就在前不久的2018杭州·云栖大会上,阿里云宣布成立全球交付中心,海公布了新一代云计算操作系统飞天2.0。在8月阿里巴巴公布的2019财年第一季度财报中,阿里云营收46.98亿元,为全球第三大IaaS服务提供商。

     

    而云只是to B业务中的一部分。

     

    《彭博商业周刊》对企业服务产品划分为三种主要流行模式:IaaS(Infrastructure as a Service,基础设施即服务)、PaaS(Platform as a Service,平台即服务)和SaaS(Software as a Service,软件即服务)。

     

    云属于IaaS。阿里云的亮点在于,阿里巴巴拥有海量用户数据,这些数据叠加在云服务上会展现出极大的价值。同时,阿里本身的企业基因也是非常“to B”的,B2B业务起家就是面向着中小企业。

     

    将自己现有生态客户转化为企业服务客户是非常顺畅的一条思路。比如,淘宝系厂商将自己的进销存系统放在阿里云上,顺理成章。从这个角度来说,阿里的先天优势很明显。


    640?wx_fmt=jpeg

    2018杭州·云栖大会上,阿里云公布了面向万物智能的新一代云计算操作系统飞天 2.0

     

    而阿里巴巴在SaaS服务领域的“钉钉”则是在SaaS的新战场上。

     

    2014年阿里的“来往”在C端社交市场上全面溃败,团队转向企业服务市场,打造钉钉,争夺企业级市场的“入口”。2015年1月16日,钉钉发布1.0版本,DING功能上线。到今年3月31日,已有超过700万家企业组织在使用钉钉,注册用户过亿。


    640?wx_fmt=jpeg


    如此一来,阿里与腾讯在to B业务上的竞争更加直接了——阿里云vs腾讯云,钉钉vs企业微信。在刚刚过去的9月,钉钉和企业微信分别与OA市场的龙头公司蓝凌、泛微达成深度的合作,行业内深感AT之间的硝烟弥漫。

     

    不过,比起SaaS领域的擦抢走火,腾讯在国庆前的第三次大变阵更是明确了其在to B领域的决心。

     

    新成立的云与智慧产业事业群(CSIG,即 Cloud and Smart Industries Group)被视为腾讯在B端业务的主阵地,具体产品上,将整合包括腾讯云、智慧零售、安全产品、腾讯地图、优图等核心产品线。

     

    尽管腾讯总是被质疑to B业务能力,没有阿里的销售铁军,C端经验不适合B端,部门墙数据墙很厚,数据丢失事件造成巨大负面影响等等,但它始终还是迈出了这一步,toB已经被列为公开的重点,是腾讯转型产业互联网的重地。

     

    与阿里云相比,腾讯云起步较晚,但依托游戏、视频领域的深耕,腾讯云也快速占据一席之地。


    640?wx_fmt=jpeg


     过去未去,未来已来,BAT在B端的布局脉络已非常清晰,无论是谁,单独拿出来看都已自成体系。而格局也是显然的,BAT的提前布局与其在技术、研发、人才、企业端的影响力等方面的优势,已经为他们筑起了一道非常坚固的护城河。在to B时代,BAT扮演着基础服务商、平台运营方的角色,是赋能行业,是基础设施,是企业的第一选择。


    还有机会吗?

     

    toB领域巨头换人的可能性几乎为零。

     

    但另一个问题是BAT永远无法避免的——企业用户对数据存储的安全感很低。直白地说,在国外,Netflix敢将自己的基础架构搭载亚马逊云服务AWS上,在国内,爱奇艺敢将自己的性命交给腾讯云吗?

     

    这给了一些中型公司机会。比如UCloud反复强调的就是“中国领先的中立云计算服务商”,其中“中立”二字格外显眼。

     

    而在BAT之外,很多看似完全to C的互联网公司都在B端有所布局。

     

    比如字节跳动(今日头条,后文简称头条)。企业服务竟然是其头条投资第二多的领域(第一领域为文化娱乐)。人们熟知的一些明星项目,例如坚果云、石墨文档、Tower等,头条都有所投资。


    640?wx_fmt=jpeg


    京东在企业服务领域一共有超过20笔投资,包括甄云信息、EasyStack、通天晓软件、凌雄租赁、加推科技等等,最近京东云还发布了“医疗健康战略”,想要做医疗行业的基础设施,推动医疗信息化。

    640?wx_fmt=jpeg


    不可否认,未来B市场的格局更有可能是BAT继续占领大部分市场,提供基础设施服务,而其他公司则可以选择垂直领域提供更多个性化的服务。毕竟在全球市场上,除了微软、甲骨文等to B巨头,也有很多优秀的“小”公司,比如Slack、Tanium、Sprinklr、AppNexus等等。

     

    至于中国过去的老牌IT公司,或许这一轮to B浪潮会给他们带来新机遇。

     

    一个典型的例子是联想,PC业务之外,联想的数据中心业务正在高速增长。而这一业务增长的关键驱动因素之一便是HyperScale超大规模数据中心业务,简单说,就是联想给做云服务的公司提供设备,这些公司包括美国的云服务公司微软、Google、亚马逊等、也包括中国的云服务公司BAT等等。

     

    华为虽然在去年3月才宣布成立Cloud BU,6月“华为云”品牌才首次亮相,但一年时间,华为云的成绩也非常不错。

     

    而像用友、金蝶等ERP厂商,在过去一段时间里疲态尽显,若是能借此时机重整旗鼓,或着与强者结成联盟,也是不错的选择。

     

    毕竟,To B的金矿就在那里,中国工商登记的企业数量近3000万家,其中中小企业占比90%以上,整体数量超过美国,且仍在持续增加,企业级市场红利非常丰厚。

     

    BAT按照惯例会继续缠斗但又一起分走大部分,而剩下的就要各凭本事了。





    点击www.itvalue.com.cn,进入ITValue社区,与CIO们一起脑力激荡!


    我们只提供有价值的干货!

    640?wx_fmt=jpeg

    长按二维码
    关注ITValue

    展开全文
  • ASP.NET MVC Linq 技术 企业级通用OA系统 全程开发  大型企业级别OA项目实战全新上线啦!本项目由小孔子讲师全程录制。小孔子老师大家都很熟悉了,他所录制的其他课程都受到了学员的一致好评!小...
  • 摘要:企业级开发首选技术是什么?JavaEE和.Net哪个技术更好?在JavaEE开发中主要用哪些框架?另外在移动大热的趋势下如何开发出一个成功的Android产品?带着这些问题,社区之星记者第12期采访了海隆技术经理——...
  • 由于《深入理解Android 卷一》和《深入理解Android卷二》不再出版,而知识的传播不应该因为纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容 第4章 深入理解PackageManagerService本章主要...
  • 自从3月离开360之后,已经很久没有出现在国内游戏圈里了,因为选择了在一家香港上市的游戏公司工作,这段时间一直在海外市场到处看,也花了不少时间思考整个游戏行业的发展历史和未来趋势,对比中国游戏市场和海外...
  • 最早仅仅在电商领域“疯狂肆虐”,如今已经发展为“无处不在+层出不穷”……额,头疼! 起初只是简单易识破的刷单and薅羊毛,而现在却统统升级为...形势如此严峻,身处电商、社交、游戏以及金融等“欺诈重灾区”的成...
  • 我所理解的生活!

    千次阅读 2019-01-01 11:21:18
    ----这就是我所理解的生活! 写于:2013-7 我也许能够一直打着寻找自我的幌子,继续在这个社会招摇撞骗。凭良心说我喜欢千奇百怪的结果,于是这个刚刚走出校园的年头像一个gap year一样,我带着那个偏光镜把...
  • 前言: 高效读书,一张逻辑图带你读懂、读薄书中重点。 注:下面文字只是对逻辑思维图的”翻译“,节省时间,只看图即可。 ...切入“企业安全”的视角 ...企业安全是什么 ...企业安全包括哪些事情 ...生态级企业vs...
  • 仿写“跳一跳”微信小游戏

    千次阅读 2018-04-12 10:41:43
    在 2018 微信公开课 Pro 上,微信游戏产品总监孙春光指出,小游戏的累计用户达到了 3.1 亿(微信日活超过 9 亿)。 孙春光表示,2017 年移动游戏的自然人有 4 亿多,他们用 20 天就达到了这样一个数据。 作为微信小...
  • 理解 Web 3 —— 用户控制的互联网

    千次阅读 2019-05-14 09:47:46
    如今的互联网已经将信息转移效率提高了好几个数量,因而为新的业务和服务创造了极大的发展空间。然而,如果企业找不到一种简单的方法交易价值,就需要另辟蹊径,从自己提供的服务中获利。 这就是为什么多年以来...
  • 理解专业程序员

    千次阅读 2014-11-21 10:36:21
    理解专业程序员 Understanding the Professional Programmer   杰拉尔德 温伯格      如果你是一个程序员,或是程序员的管理者,或者处于任何和程序员紧密...
  • 深入理解ClickHouse

    千次阅读 2020-07-15 13:21:19
    这个问题应该这样理解,那些过于简单直接、没啥维度和灵活度需求的数据,咋能体现ClickHouse的优势呢,你用啥数据库都能做得很6啊) 推荐阅读:《ClickHouse原理解析与应用实践》 推荐理由: ClickHouse被称为最快的...
  • 游戏编程入门(1):游戏专业概论

    千次阅读 2017-06-15 19:25:45
    游戏的类型 游戏开发人员需求及过程 游戏软件技术结构
  •  这一切在今天看来匪夷所思,但在当时完全可以理解。微软是那个 时代科技 ( 000611 , 股吧 )企业中仅有的明星,其市值高达2500亿美元。在强大的Windows面前,网络似乎没有任何商业价值。微软自创立以来一共拆分股份...
  • RPC(Remote Procedure Call)服务,也即远程过程调用,在互联网企业技术架构中占据了举足轻重的地位,尤其在当下微服务化逐步成为大中型分布式系统架构的主...
  • 微服务的理解

    千次阅读 2016-09-15 18:48:41
    它能给企业带来什么价值 背景传统企业的IT软件大多都是各种独立系统的堆砌,这些系统的问题总结来说就是扩展性差,可靠性不高,维护成本高.SOA的软件架构专门针对这些问题给出了一套解决方案. 由于SOA早期均使用了...
  • 下面是本文将涉及到的一些相关技术的列表,(需要说明的是,这些技术单独来看并不复杂,实际动手实现并理解各种取舍以后,在项目当中针对具体的需求去设计和搭配才是关窍之所在) 一、程序技术篇:算法和...
  • 作为游戏邦(GamerBoom)第一份正式社交游戏和手机游戏行业分析报告,我们的第一使命是使这份报告具有阅读价值和参考价值。   本报告的两个参考前提:   由于手机社交游戏的强势崛起...
  • 痛与教训,我所亲历的3个失败游戏创业公司

    千次阅读 多人点赞 2017-05-15 12:21:06
    霖哥的家里人对于我这种长期996加班的日子无法理解,也无法容忍,同时,我也有了一个更好的选择——  我遇到了一个草根的公司创始人,他一个人融资后,刚起步,四处找寻合适的合作伙伴。在跟他交谈以后,觉得他...
  • 游戏邦成立以来部分游戏设计观点回顾(五万字长文) 发布时间:2012-01-10 17:04:40 Tags:微博平台,游戏邦,部分游戏设计观点回顾 2010年底,游戏邦从数据分析的角度解析了当时手机游戏和社交游戏...
  • 简单谈谈对软件工程的理解

    千次阅读 2020-02-24 03:50:48
    对软件工程的理解 在阅读了《软件工程实践这的研究方法》之后,对软件工程有了一定的了解和掌握。...而且,软件工程在企业范围内运行,一定需要企业资源的支持,要与企业的经营、决策、管理体系...
  • 对于大数据的理解

    千次阅读 2016-12-26 18:32:49
    对于大数据的理解 一、 大数据基本概念 大数据Big Data是指大小超出了常用的软件工具在运行时间内可以承受的收集,管理和处理数据能力的数据集;大数据是目前存储模式与能力、计算模式与能力不能满足存储与...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 39,963
精华内容 15,985
关键字:

企业级游戏理解