Day10-微信小程序實戰-交友小程序-創建friendList字段實現好友關係(添加好友功能)–內附代碼

回顧:之前我們進行了刪除的功能,以及對message消息的增刪,下面實現添加好友的功能

我們先在數據庫中,在message這個字段的list裏面,添加上測試號的id,就是模擬這個兩個測試號要加我主號的效果“

 

 

 

就可以達到這個效果了

 

下面我們正式開始實現

1、在removeList的wxml的昵稱結構處添加一個點擊事件handleAddFriend

2、在removeList.js中來實現這個點擊事件即可的,並且它也是要提示(讓用戶選擇確認的這種,所以就可以直接copy前面的刪除按鈕的代碼

 直接把hanleDelMessage函數裏面的:

   wx.showModal({
        title: '提示信息',
        content: '刪除消息',
        confirmText: "刪除",
        success: (res) => {
          if (res.confirm) {
            
          } else if (res.cancel) {
            console.log('用戶點擊取消')
          }
        }
      })
    }

也就是只用設計在點擊了確定之後的一些列操作即可了

3、因為要構建好友之間的關係,所以要在user裏面加一個friendlist字段才行了,並且這個字段的數據類型是數組的,因為好友肯定不只是一個

4、在數據庫中給每個人都添加一個friendList字段

 

 但是不要忘記了在原來的程序中 初始化 用戶的時候也要加上讀i這個字段的初始化才行的

打開user.js:

 

5、後面的事情大概的邏輯就是:同意了好友申請的話,這個用戶的id就會出現在這個用戶的friendList數組裡面了

所以就可以回到removeList.js文件中去了

6、通過在開頭的時候 設置 const _ = db.command 就可以讓這個文件有了運算的能力了

handleAddFriend(){
      wx.showModal({
        title: '提示信息',
        content: '申請好友',
        confirmText: "同意",
        success: (res) => {
          if (res.confirm) {
              db.collection('users').doc(app.userInfo._id).update({
                data:{
                    friendList : _.unshift(this.data.messageId)
                }
              }).then((res)=>{});
          } else if (res.cancel) {
            console.log('用戶點擊取消')
          }
        }
      })
    }
  }

 

 

在點擊消息之後,就可以選擇“確定”,點擊了之後,就可以在數據庫上面看到添加的用戶好友了

(以上就是第一步,把要申請的用戶的id給添加過來了)—-但是問題來了,我的主號裏面的friendList裏面有了這個申請人的id,但是這個申請人在我

同意了之後,它的friendList字段裏面也應該有我的主號的id才對的—也就是同時添加他們兩個的好友關係即可

 

 但是能不能通過上面的這種方式,把兩個變量之間的值作為交換就可以的,普通的數據庫裏面是可以這樣的。但是在小程序中式不可以這樣進行操作的

(因為在小程序裏面對數據庫的訪問式有權限的,在客戶端是組偶到這樣的操作

(也就是對其他的賬號進行更新操作的話在客戶端裏面是不允許的—同理也是要在服務端裏面來實現的))

–也就是要用雲函數來實現了

7、如果要像的客戶端中調用unshift一樣的話在服務端裏面進行調用的話,之前也搞過就是用一個模板字符串的寫法 用Esc下面的那個 “類單引號”的符號

來進行包裹就好了

 wx.cloud.callFunction({
            name : 'update',
            data : {
              collection : 'users',
              doc : this.data.messageId,
              data : {
                friendList: ` _.unshift('${app.userInfo._id}')`
              }
            }
          });

 

 出現的錯誤就是,我們把unshift也一起搞過來了,也就是我們傳過去的字符串沒有解析成功

 主要就是下面這裏寫錯了

 data : `{ friendList:  _.unshift('${app.userInfo._id}')}`

是把後面整個的串都用一個`   ….   `來圍住的(也就是後面整個的json對象都直接被這個符號扣住的)

 

修改了之後就成功了

 

8、新建一個removeMessage 函數,可以直接copy上面的handledelMessage方法裏面的:這個函數主要是為了點擊這個消息加好友的時候,可以選擇是

添加它為好友,就是在點擊了添加好友之後,就要把這個消息刪掉了,所以這兩個地方都用到了這個功能的話,就可以把這個功能封裝在removeMesage函數裏面,如何直接用this.removeMessage來調用即可了

效果就是:點擊了添加它為好友之後,這個申請為好友的消息就會被刪掉了

 

 

db.collection('message').where({
            userId : app.userInfo._id
          }).get().then((res)=>{
              // console.log(res);
              let list = res.data[0].list;
            console.log(list);
              list = list.filter((val , i)=>{
                  return val != this.data.messageId
              });
              // console.log(list);
              wx.cloud.callFunction({
                name : 'update',
                data : {
                  collection : 'message',
                  where : {
                    userId : app.userInfo._id
                  },
                  data : {
                    list : list
                  }
                }
              }).then((res)=>{
                this.triggerEvent('myevent',list) 
              });
          });

 

 整體邏輯:

1、在removeList.wxml 中的昵稱中添加一個點擊事件 

<movable-view bindtap="handleAddFriend" direction="horizontal" class="view">{{ userMessage.nickName }}</movable-view>

2、在removeList.js中隊這個點擊事件進行渲染

 handleAddFriend(){
      wx.showModal({
        title: '提示信息',
        content: '申請好友',
        confirmText: "同意",
        success: (res) => {
          if (res.confirm) {
              db.collection('users').doc(app.userInfo._id).update({
                data:{
                    friendList : _.unshift(this.data.messageId)
                }
              }).then((res)=>{});

          //其他用戶和我構成好友的關係,用到客戶端來實現(也就是用雲函數來實現)
          wx.cloud.callFunction({
            name : 'update',
            data : {
              collection : 'users',
              doc : this.data.messageId,
              data : `{ friendList:  _.unshift('${app.userInfo._id}')}`
            }
          }).then((res)=>{});
          this.removeMessage();
          } else if (res.cancel) {
            console.log('用戶點擊取消')

          }
        }
      })
    }

3、點擊了接受邀請之後,把這個申請好友的消息刪除(和之前實現刪除功能一樣,就可以直接把這個功能封裝到removeMessage這個函數裏面

removeMessage(){
      //也就是點擊了確定的話,就不提示這個了,而是刪除信息
      //  目前沒有直接更新的,智能是這個過程就變成了先查詢然後再更新
      db.collection('message').where({
        userId: app.userInfo._id
      }).get().then((res) => {
        // console.log(res);
        let list = res.data[0].list;
        console.log(list);
        list = list.filter((val, i) => {
          return val != this.data.messageId
        });
        // console.log(list);
        wx.cloud.callFunction({
          name: 'update',
          data: {
            collection: 'message',
            where: {
              userId: app.userInfo._id
            },
            data: {
              list: list
            }
          }
        }).then((res) => {
          this.triggerEvent('myevent', list)
        });
      });

注意:因為我們開通了一個friendList 給每一個用戶數據庫字段,所以在user.js初始化用戶數據庫的時候也要加上初始化這個friendList數組才行

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

小班同學學習經歷分享(一)遊戲程序員成長札記

作者信息

昵稱:目及遠方

課程設計 HumanFramework:

https://github.com/cyclons/HumanFramewo

正文

大四畢業,心血來潮,閑余之際,撰文留念。

萌芽

遊戲程序員,把這個分成兩塊的話就是,遊戲,程序。

這兩个中,只有一個,遊戲,在我很小的時候就開始接觸,在那個視电子遊戲如电子海洛因,父母抵制到要送孩子去網癮治療所的年代,三年級的我就已經在玩ps2了,周圍的同學還在玩紅警qq大亂斗冒險島飛車的時候,我已經玩高達戰神龍珠古墓麗影,最終導致和周圍同學沒有共同話題。

隨後,按順序入手了nds,psp,xbox360,ps4,可以說從小到大,遊戲沒有停過。

如果說這條職業路上我有什麼超前之處,就是玩遊戲玩得多。

起步

那什麼時候開始想做遊戲了呢?要等到大學。

整個大一的我沉浸在社團幹事與學習中,完全沒有接觸過遊戲編程,學了個譚浩強的c語言,寒假里把下學期的高數學了一遍,然後發現課堂上除了能裝個逼好像也沒什麼特別大用。

直到下半學期的一天夜裡,我在床上思考,以後究竟干什麼比較好,突然一個念頭冒出來,要不去做遊戲吧!這個想法一冒出來,我猛地從床上坐了起來,彷彿一股能量貫穿了全身一般,於是我下定決心買一台電腦,開始我的程序之路。

暑假的時候,搜了好久,終於找到一個感覺靠譜的教程,那就是SIKI,現在已經是遊戲網課的巨頭了,然而當時僅僅只是一個維持了一個小小的公眾號而已,唯一收費的只有一個A計劃,終身費只要400塊。當時選擇的原因有很多,其中最主要的就是大量的免費課程,一個個案例都是自己想要實現的遊戲,總感覺每實現一個,就離成為遊戲製作人更近一步(當時沒有遊戲職業概念)。

成長

剛開始學的階段,可以說我就是個沙雕,半吊子中的半吊子,打字速度20字母/s,不懂語法,大小寫不分,對着視頻敲代碼。(彎路)

後來發現儘管照着視頻做出來了,但仍然不熟練,於是逼迫自己,看視頻不寫代碼,寫代碼不看視頻,偶爾實在不知道怎麼做了,再回去看視頻里怎麼做,一個視頻要看兩三遍。(稍微正了一些)

為了提高自己的打字速度,把手機的鍵盤從9鍵變成26鍵。(有一定作用但不是正道,推薦https://www.keybr.com/)

那時候我還心血來潮,趕VR潮流,在學校里搞起了VR工作室還有VR社團,但剛開始不做技術,是賣硬件的,賣手機紙盒子VR給學生。後來發現潮流過了,硬件沒前途,隨後就想把工作室往技術上轉,當時心裏的想法是邊能學Unity又能搞起一番事業(too young,too simple)。

大二的寒假里,我馬不停蹄,不斷學習SIKI公眾號上的項目案例,每做完一個就信心爆棚,彷彿自己已經是個優秀的遊戲程序員了。之後還在社團里教大家如何做一個AR應用,Unity小遊戲等等。

在大二的暑假里,我還認識了一個朋友,一起做汽車VR噴漆,想通過這個賺錢,但最終失敗了。同時,我發現專業偏硬件,於是我轉專業到計算機學院,開始了第二個大二生活。

旅途

後來的日子里,我依然是一有時間就跟着教程學,但彷彿到了一定的瓶頸,感覺做遊戲不就是調調api,用用插件,什麼遊戲都能做出來啊,恰巧當時看到心動在搞獨立營活動,我就立即報名了,這也成為了我第一次的gamejam。

gamejam的感覺呢,怎麼說呢,就好像回家了一樣,裏面各個都是人才,說話又好聽,超喜歡這裏面的~。在活動里,能夠充分體會到周圍人對遊戲的熱愛,精妙的遊戲設計,驚艷的美術,牛逼的程序老哥,主辦方給我們學生還特別優待,給我們免費訂了两天的五星級酒店,還包早餐,可惜都沒怎麼好好享受到,两天可能就住了8個小時不到吧,但整個活動充滿樂趣,給我的第一次gamejam留下了非常棒的印象。

之後便開始積极參加各種jam活動,線上線下到現在快應該有10場,每一次都很有收穫,無論是認識了新的朋友,還是看到了非常驚艷的遊戲,每次都是一場充電之旅。

里程碑

改變我職業生涯的是一次比賽。

還記得隔壁工作室的老哥問我一句有個AR的比賽來不來參加,我說來。那次比賽是一次hackthon,恰巧有一個單項獎由網易贊助,而且專門設置的AR題材。對我來說,我不了解hackthon,所以就把它當作是一場gamejam,看着周圍一圈985的同學們,壓力山大。

那次我們做了一個AR版本的胡鬧廚房,現在回想起來那代碼寫得就是一坨屎,但遊戲運行非常順暢,沒有bug,從可玩性來看還是挺不錯的,但和以前看的優秀作品比差距還是太遠。聽到主辦方在選出十個演講隊伍中沒有我們的名字時,我們已經收拾好行李,開始回校了。然鵝,這時候主辦方說不要急着走,網易的獎還沒開,我們一路就火急火燎的趕了回去。

由於來得太晚,演講已經開始了一大半,我們幾個人就站在入口的地方聽演講,看着別人的項目,什麼機器學習,區塊鏈,智能小車,各個高大上的不行,彷彿改變未來的技術一樣,而且沒有一個是做遊戲的,我這時候意識到,是不是走錯場了?

等其他獎開完了,才等到網易的負責人上台,大概是這麼說的,“我們在兩支隊伍里徘徊,所以一直沒能下一個定論,但最終我們在完整度上考慮,最終決定把一等獎給9號隊。”

當時整個人都已經懵逼了,周圍隊友興奮的握着我的手,這時候感到一切的努力都是值得的。

獲獎是次要的,最主要的是一等獎附贈一個網易終面機會,作為項目主程,我成功通過了,拿到了實習offer。

這次事件是里程碑,告訴我在這條路上繼續走下去是值得的。

泥潭

網易實習生活非常豐富,由於是實習生還是在一個偏創新的部門,我和周圍的小夥伴們一起做了非常多好玩有趣的AR遊戲,回來的我也是信心爆棚。

我繼續不斷學習,做項目。但做着做着發現,項目都能跑,但是最終的成品要想改功能,牽一發而動全身,最後改着改着就變成了一坨屎,而那些神乎其神的插件,自己始終停留在會用不會做的階段。

那時候的我非常的慌張,加群,逛知乎,看教程。最後我找到了一本遊戲設計模式,看完之後才知道,原來代碼能這麼寫,好方便啊,這之後代碼又上升了一個階段。

轉眼又一年經過,大三末的我又開始找實習。我本以為我那項目滿滿,經歷豐富的簡歷,一投一個准,做個offer收割機不是問題,然而事實就是,我就是a piece of shit。

算法,數據結構,計算機組成原理,是面試的重中之重,而這裏面每一個都是我的弱點,筆試都通不過。做了幾套面試題之後,我意識到,自己的基礎太弱了。

我開始瘋狂看面經,牛客網,leetcode,uwa也看。最終的出來一個結論,原來我就是個小白。

人貴有自知,知道自己多弱是件好事,至少知道自己要補哪些。這時候就非常感謝恭弘=叶 恭弘大的遊戲程序員學習路線,在書籍的指導下我決定從0開始,從primer cpp開始,從頭重新練,隨着一個個的知識點梳理過去,自己的知識漏洞逐漸補全。

一邊惡補一邊找工作,此時的我就是任人宰割的羔羊,哪家公司要我就去哪裡,大不了過半年,我又是一條好漢。

沒想到,本以為已經涼涼了的騰訊來了電話,那就索性面下去吧,沒想到一路面到了底,拿到了實習offer。。

升華

這次的實習和之前就完全不是一個感覺,正規的大項目,專業的導師,完善的框架,專業的團隊。據說實習留用率低,感覺壓力山大,一邊做着業務,一邊把手邊該看的基礎書在看。

這次依然運氣可以,上岸了。

回校之後,我開始繼續看基礎部分,但發現學習的面越來越廣,尤其是遊戲這塊更是複雜,因此,我逐漸放緩,雖然我的目標是做遊戲,但具體最終是做哪個職位的研究依然不夠清晰,甚至中途還打起了轉行做策劃的念頭。

我設立了第一個目標,搞一個框架。為什麼是這個目標?原因大致如下:

  • 目前我做了很多遊戲,都是小項目,做大了,代碼就變成一坨屎,攪都攪不動。
  • 框架可以讓項目變得有結構,是職業必經之路。
  • 想要做大項目,一定要有框架

我搜索了很多現有的框架,首先就是學着用,其中就包括strangeIoC,還有MVC等。不得不說,StrangeIoC是新手勸退框架,那一堆東西理念對初級程序員來說就是一頭霧水,明明三行就能實現的東西,為什麼要8個類幾百行實現。

偶然發現了一個QFramework,github千星項目,還有文檔,於是我就開始搞QFramework。

又是一個機會,發現QFramework的作者涼大準備搞事,做一個小班,專心帶學生,12月分期,學生還帶優惠,我轉念一想,當年SIKI還是個小公眾號,現在A計劃永久能賣大幾千,這個車一定要上。

交錢上車后,跟着涼大學,一天兩篇,框架搭建和shader都有涉及。有一說一,雖然是日更的,但是我一般三四天一看,甚至一周一看,剛開始比較勤勞,看得多,有一段時間看着比較累,就斷了一大片。

這裏非常感謝涼大時不時會來私聊,問問學習情況,有沒有遇到什麼困難。我當然也心知肚明,聊完就去補文章了。

正是在這樣的一步步過坎之後,自己的框架意識也逐步建立,共有問題也逐步顯現,C#上欠缺的部分通過中毒篇專欄有了很大的彌補,更重要的是,在未來的路途上有了專業的指導,少走了非常多的彎路,這點真的非常重要。

不知不覺間,一年就過去了,我也幸運的交上了一份畢業設計,學習過程中幾次差點放棄,但看看文章之後覺得這個知識有必要掌握,就一直續到了現在。

本來這篇文章是涼大讓我談談這個框架學習之路,扯了太多自己的東西,這裏就詳細聊聊框架的學習心得:

  • 課程內容里,最核心的還是框架學習,是主菜,至於shader部分,其實只是一個補充,比如業務里要用到相關知識,和專業技美或圖形程序不同,屬於副菜。
  • 文章講框架部分寫得非常好,由淺入深,講解細緻,且代碼詳盡,每一個學習單元都可以做一個小實現。
  • 記得涼大在學習開始的時候提到,小班文章的目的是讓大家看文章就行,不用做筆記,不用真的動手,看就完事了,但這一套在我這邊的嘗試下不太行的通,簡單的或者熟悉的內容可以一遍過,但一些需要反覆理解的如IoC部分,只看文章會覺得迷迷糊糊的。古人有句話,“紙上得來終覺淺,絕知此事要躬行”,對於難理解的內容,一定要下手嘗試,即使簡單的內容,親自做過之後都會有不一樣的感覺,有時間一定要多練習,偷懶只會虧待自己。
  • 明白最終自己想要的是什麼。由於課程涉及方方面面,學員的學習程度也層次不齊,有時候碰到的內容不感興趣,不是自己主要目標的組成部分,那便可以選擇性的不太需要花精力去學習,但這類情況較少,如果平時較為空閑,建議都看,有益無害。
  • 課程不太適合小白,適合有一定項目經驗的同學。
  • 剛開始會充滿激情,但中間會由於各種事情被打斷,最好養成一個看文章的固定時間段,比如996晚上摸魚的時候看,或者在周末挑一個時間。
  • 學習框架的過程為:使用框架to看懂框架to能寫框架,各自為階段,越過會導致許多障礙,比如沒有用過消息中心很難寫出一個消息中心
  • 不用害怕學不會,只要方向正確,沒有完成不了的

涼大在小班上非常負責,可以說關心到了每一個成員,內容質量也非常有保證,每每我有“棄坑”的想法時,涼大都會來“善意的提醒”。而我遇到什麼問題時,都能夠得到專業的回答。

大學的前幾年實際上走了不少彎路,如果能夠早期遇到專業的老師來指點的話能少走很多。如果入職一段時間了,職業提升遇到瓶頸,尤其還是從事Unity行業的話,那非常推薦來小班,這裏交流活躍,同行眾多,總有老哥給你指條明路。

在一年結束后,我也最終實現了我的目標,實現了自己的框架——HumanFramework,在大佬眼中應該就是個小不點的存在,但即使這個框架不會成為流行,這個過程也使我對軟件設計的理解更上了一層樓。

退潮

最近各種事情算是告一段落,畢業也好,小班也好,工作也好,自己的學生時代也結束了,即將開啟工作時代,由於之前的幾年坎坷奮鬥,加上自己的身體不算強健,現在留下了點胃病,這幾個月里都在養生,時不時看看業內新聞之類的,最近越來越對shader相關的內容感興趣了,之後的主要平台也會變為Unreal,想想也挺有趣的。

在這一年中,學到了很多,尤其是技術分享的重要性,自己也會寫一些文章分享出來,包括HumanFramework的製作過程分享,歡迎來知乎關注我

最終祝願所有讀者在學習的同時身體健康,身體是革命的本錢,有好的身體才有力氣追求更美好的生活。

轉載請註明地址:liangxiegame.com

更多內容

QFramework 地址:https://github.com/liangxiegame/QFramework
QQ 交流群:623597263
涼鞋的主頁:https://liangxiegame.com/zhuanlan
關注公眾號:liangxiegame 獲取第一時間更新通知及更多的免費內容。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

小師妹學JVM之:java的字節碼byte code簡介

目錄

  • 簡介
  • Byte Code的作用
  • 查看Byte Code字節碼
  • java Byte Code是怎麼工作的
  • 總結

簡介

Byte Code也叫做字節碼,是連接java源代碼和JVM的橋樑,源代碼編譯成為字節碼,而字節碼又被加載進JVM中運行。字節碼怎麼生成,怎麼查看字節碼,隱藏在Byte Code背後的秘密是什麼呢?快跟小師妹一起來看看吧。

Byte Code的作用

小師妹:F師兄,為什麼Java需要字節碼呢?直接編譯成為機器碼不是更快嗎?

小師妹,Java的設計初衷是一次編寫,到處運行。為了兼容各個平台的運行環境,java特別為各種平台設計了JVM。

我們可以把JVM看做是一種抽象,對外提供了統一的接口。這樣我們只需要編寫符合JVM規範的代碼,即可在JVM中運行。

回想下之前我們提到過的java的執行過程:

  1. 編寫java代碼文件比如Example.java
  2. 使用java編譯器javac將源文件編譯成為Example.class文件
  3. JVM加載生成的字節碼文件,將其轉換成為機器可以識別的native machine code執行

小師妹:F師兄,我有一個大膽的想法,JVM的作用是將字節碼解釋或者編譯成為機器碼。然後在相應的運行環境中執行。那麼有沒有可能,不需要JVM,不需要機器碼,而是直接在對應的平台上執行字節碼呢?

愛因斯坦說過沒有想像力的靈魂,就像沒有望遠鏡的天文台。小師妹你這個想法很好,這種實現有個專業的說法叫做:Java processor。

Java processor就是用硬件來實現的JVM。因此字節碼可以直接在Java processor中運行。

其中比較出名的是Jazelle DBX,這是一個主要支持J2ME環境的硬件架構。為了提升java在手機端的執行速度。

但是這樣做其實也是有缺點的,後面我們會講到,java字節碼中的指令非常非常多。所以如果用硬件來實現的話,就會非常非常複雜。

一般來說Java processor不會實現全部的字節碼中的功能,只會提供部分的實現。

查看Byte Code字節碼

小師妹:F師兄,那使用javac編譯過後的class文件跟字節碼有什麼關係呢?

class文件中大部分都是byte code,其他的部分是一些meta data元數據信息。這些組合在一起就是class文件了。

小師妹:F師兄,你說class文件是byte code,為什麼我在IDE中打開的時候,直接显示的是反編譯出來的源文件呢?

小師妹,這是IDE的一個便利功能。因為大多數情況下,沒有人想去看class文件的Byte code的,大家都是想去看看這個class文件的源文件是什麼樣的。

我們舉個最簡單的例子:

這個類中,我們定義了一個很簡單的testByteCode方法,裏面定義了兩個變量,然後返回他們兩個的和。

現在有兩種方法來查看這個類的Byte Code:

第一種方法是用javap命令:

javap -c ByteCodeUsage.class

生成的結果如上所示。

第二種方法就是在IDEA中,選中class文件,然後在view中選中show Bytecode:

我們看下輸出結果:

兩個的結果在显示上面可能有細微的差異,但是並不影響我們後面對其的解析。

java Byte Code是怎麼工作的

小師妹:F師兄,能講解一下這些byte code到底是怎麼工作的嗎?

首先我們要介紹一下JVM的實現是基於棧的結構的。為什麼要基於棧的結構呢?那是因為棧是最適合用來實現function互相調用的。

我們再回顧一下上面的testByteCode的字節碼。裏面有很多iconst,istore的東西,這些東西被稱作Opcode,也就是一些基於棧的操作指令。

上面講了java bytecode的操作指令其實有很多個。下面我們列出這些指令的部分介紹:

實在是太多了,這裏就不把所有的列出來了。

我們看到的指令名字其實是一個助記詞,真實的Opcode是一個佔用兩個字節的数字。

下面我們來詳細解釋一下testByteCode方法:

public int testByteCode();
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: ireturn

第一步,iconst_1將int 1加載到stack中。

第二步,istore_1將入棧的int 1出棧,並存儲到變量1中。

第三步,iconst_2將int 2入棧。

第四步,istore_2將入棧的int 2出棧,並存儲到變量2中。

第五步,iload_1將變量1中的值入棧。

第六步,iload_2將變量2中的值入棧。

第七步,iadd將棧中的兩個變量出棧,並相加。然後將結果入棧。

第八步,ireturn將棧中的結果出棧。

這幾步實際上完美的還原了我們在testByteCode方法中定義的功能。

當然我們只介紹了最賤的byte code命令,通過這些簡單的命令可以組合成為更加複雜的java命令。

總結

本文介紹了java byte code的作用和具體的指令,並分析了一個簡單的例子來做說明。希望大家能夠掌握。

本文的例子https://github.com/ddean2009/learn-java-base-9-to-20

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/jvm-byte-code/

本文來源:flydean的博客

歡迎關注我的公眾號:程序那些事,更多精彩等着您!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

Spring7——開發基於註解形式的spring

開發基於註解形式的spring
SpringIOC容器的2種形式:
(1)xml配置文件:applicationContext.xml; 存bean:<bean> 取bean:

ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");

(2)註解:帶有@Configuration註解的類(配置類)

存bean:@Bean+方法的返回值

//配置類,相當於applicationContext.xml
@Configuration
public class MyConfig {


    @Bean //id=方法名(myStudent)
    public Student myStudent(){
        Student student=new Student(2,"fg",34);
        return student;
    }
}

取bean: 

ApplicationContext context=new AnnotationConfigApplicationContext(MyConfig.class);
Student myStudent = (Student) context.getBean("myStudent");

注意:兩種形式獲取的IOC是獨立的  
註解形式向IOC容器存放bean詳解:
1.必須有@Configuration
2.形式:
2.1 三層組件(Controller、Service、Dao):             (1)將三層組件分別加註解@Controller、@Service、@Repository等價於@Commponent              (2)納入掃描器 a.xml配置

<context:component-scan base-package="org.ghl.controller"></context:component-scan>

b.註解形式      component-scan只對三層組件負責。    
給掃描器指定規則:                 過濾類型:FilterType(ANNOTATION, ASSIGNABLE_TYPE, CUSTOM)                 ANNOTATION:三層註解類型@Controller、@Service、@Repository等價於@Commponent

排除:
@ComponentScan(value = "org.ghl",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION ,value = {Service.class,Repository.class})})
包含:(有默認行為,可以通過useDefaultFilters禁止)
@ComponentScan(value = "org.ghl",includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION ,value = {Service.class,Repository.class})},useDefaultFilters = false)

  ASSIGNABLE_TYPE:指具體的類。

@ComponentScan(value = "org.ghl",includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE ,value = {StudentController.class})},useDefaultFilters = false)

區分:ANNOTATION:Service.class指標有@Service的所有類;           ASSIGNABLE_TYPE:指具體的類。                 CUSTOM:自定義:自己定義規則

@ComponentScan(value = "org.ghl",includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM ,value = {MyFilter.class})},useDefaultFilters = false)

 

public class MyFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //拿到掃描器value = "org.ghl"包中的所有標有三層組件註解類的名字
        String className = classMetadata.getClassName();
        //只過濾出和student相關的三層組件
        if (className.contains("Student")){
            return true;  //表示包含
        }
        return false; //表示排除
    }
}

  

2.2 非三層組件(Student.clss/轉換器等): (1)@Bean+方法的返回值,id的默認值為方法名,也可以通過@Bean(“stu”)修改。 (2)import/FactoryBean  
bean的作用域

(@Scope(“singleton”))scope=”singleton”:單例 scope=”prototype”:原型、多實例。
執行的時機(產生bean的時機):         singleton:容器在初始化時,就創建對象,且只創建一次; 也支持延遲加載:在第一次使用時,創建對象。在config中加入@Lazy。         prototype:容器在初始化時,不創建對象,在每次使用時(每次從容器獲取對象時),再創建對象。         
條件註解 可以讓某一個Bean在某些條件下加入IOC容器。 (1)準備bean; (2)增加條件bean:給每個bean設置條件,必須實現Condition接口。 (3)根據條件加入IOC容器
  回顧給IOC加入Bean的方法:        註解:全部在@Configuration配置中設置:                 三層組件:掃描器+三層註解                 非三層組件:(1)@Bean+返回值                                      (2)@import                                      (3)FactoryBean(工廠Bean)
 
@import使用:         (1)直接編寫到@Import中; @Import({Apple.class,Banana.class})         (2)自定義ImportSelector接口的實現類,通過selectimports方法實現(方法的返回值就是要納入IOC容器的Bean)。並告知程序自己編寫的實現類。

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"org.ghl.entity.Apple","org.ghl.entity.Banana"}; //方法的返回值就是要納入IOC容器的Bean
    }
}
@Import({MyImportSelector.class})

    (3)編寫ImporBeanDefinitionRegistrar接口的實現類並重寫方法。 

public class MyImporBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        //BeanDefinition beanDefinition=new RootBeanDefinition(Orange.class);
        BeanDefinition beanDefinition=new RootBeanDefinition("org.ghl.entity.Orange");
        beanDefinitionRegistry.registerBeanDefinition("myorange",beanDefinition);


    }
}  
@Import({MyImporBeanDefinitionRegistrar.class})

  

FactoryBean(工廠Bean)         1.寫實現類和重寫方法;  

public class MyFactoryBean implements FactoryBean{
    @Override
    public Object getObject() throws Exception {
        return new Apple();
    }

    @Override
    public Class<?> getObjectType() {
        return Apple.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

  2.註冊到@Bean中  

@Bean
public FactoryBean<Apple>  myFactoryBean(){
    return new MyFactoryBean();
}

注意:需要通過&區分獲取的對象是哪一個。不加&,獲取的是最內部真實的apple,如果加&,獲取的是FactoryBean。  

  

  

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?

Spring Boot2+Resilience4j實現容錯之Bulkhead

Resilience4j是一個輕量級、易於使用的容錯庫,其靈感來自Netflix Hystrix,但專為Java 8和函數式編程設計。輕量級,因為庫只使用Vavr,它沒有任何其他外部庫依賴項。相比之下,Netflix Hystrix對Archaius有一個編譯依賴關係,Archaius有更多的外部庫依賴關係,如Guava和Apache Commons。

Resilience4j提供高階函數(decorators)來增強任何功能接口、lambda表達式或方法引用,包括斷路器、速率限制器、重試或艙壁。可以在任何函數接口、lambda表達式或方法引用上使用多個裝飾器。優點是您可以選擇所需的裝飾器,而無需其他任何東西。

有了Resilience4j,你不必全力以赴,你可以選擇你需要的。

https://resilience4j.readme.io/docs/getting-started

概覽

Resilience4j提供了兩種艙壁模式(Bulkhead),可用於限制併發執行的次數:

  • SemaphoreBulkhead(信號量艙壁,默認),基於Java併發庫中的Semaphore實現。
  • FixedThreadPoolBulkhead(固定線程池艙壁),它使用一個有界隊列和一個固定線程池。

本文將演示在Spring Boot2中集成Resilience4j庫,以及在多併發情況下實現如上兩種艙壁模式。

引入依賴

在Spring Boot2項目中引入Resilience4j相關依賴

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot2</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-bulkhead</artifactId>
    <version>1.4.0</version>
</dependency>

由於Resilience4j的Bulkhead依賴於Spring AOP,所以我們需要引入Spring Boot AOP相關依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

我們可能還希望了解Resilience4j在程序中的運行時狀態,所以需要通過Spring Boot Actuator將其暴露出來

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

實現SemaphoreBulkhead(信號量艙壁)

resilience4j-spring-boot2實現了對resilience4j的自動配置,因此我們僅需在項目中的yml/properties文件中編寫配置即可。

SemaphoreBulkhead的配置項如下:

屬性配置 默認值 含義
maxConcurrentCalls 25 艙壁允許的最大并行執行量
maxWaitDuration 0 嘗試進入飽和艙壁時,應阻塞線程的最長時間。

添加配置

示例(使用yml):

resilience4j.bulkhead:
  configs:
    default:
      maxConcurrentCalls: 5
      maxWaitDuration: 20ms
  instances:
    backendA:
      baseConfig: default
    backendB:
      maxWaitDuration: 10ms
      maxConcurrentCalls: 20

如上,我們配置了SemaphoreBulkhead的默認配置為maxConcurrentCalls: 5,maxWaitDuration: 20ms。並在backendA實例上應用了默認配置,而在backendB實例上使用自定義的配置。這裏的實例可以理解為一個方法/lambda表達式等等的可執行單元。

編寫Bulkhead邏輯

定義一個受SemaphoreBulkhead管理的Service類:

@Service
public class BulkheadService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private BulkheadRegistry bulkheadRegistry;

    @Bulkhead(name = "backendA")
    public JsonNode getJsonObject() throws InterruptedException {
        io.github.resilience4j.bulkhead.Bulkhead.Metrics metrics = bulkheadRegistry.bulkhead("backendA").getMetrics();
        logger.info("now i enter the method!!!,{}<<<<<<{}", metrics.getAvailableConcurrentCalls(), metrics.getMaxAllowedConcurrentCalls());
        Thread.sleep(1000L);
        logger.info("now i exist the method!!!");
        return new ObjectMapper().createObjectNode().put("file", System.currentTimeMillis());
    }
}

如上,我們將@Bulkhead註解放到需要管理的方法上面。並且通過name屬性指定該方法對應的Bulkhead實例名字(這裏我們指定的實例名字為backendA,所以該方法將會利用默認的配置)。

定義接口類:

@RestController
public class BulkheadResource {
    @Autowired
    private BulkheadService bulkheadService;

    @GetMapping("/json-object")
    public ResponseEntity<JsonNode> getJsonObject() throws InterruptedException {
        return ResponseEntity.ok(bulkheadService.getJsonObject());
    }
}

編寫測試:

首先添加測試相關依賴

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <version>3.0.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility</artifactId>
    <version>4.0.2</version>
    <scope>test</scope>
</dependency>

這裏我們使用rest-assured和awaitility編寫多併發情況下的API測試

public class SemaphoreBulkheadTests extends Resilience4jDemoApplicationTests {
    @LocalServerPort
    private int port;
    @BeforeEach
    public void init() {
        RestAssured.baseURI = "http://localhost";
        RestAssured.port = port;
    }

    @Test
    public void 多併發訪問情況下的SemaphoreBulkhead測試() {
        CopyOnWriteArrayList<Integer> statusList = new CopyOnWriteArrayList<>();
        IntStream.range(0, 8).forEach(i -> CompletableFuture.runAsync(() -> {
                statusList.add(given().get("/json-object").statusCode());
            }
        ));
        await().atMost(1, TimeUnit.MINUTES).until(() -> statusList.size() == 8);
        System.out.println(statusList);
        assertThat(statusList.stream().filter(i -> i == 200).count()).isEqualTo(5);
        assertThat(statusList.stream().filter(i -> i == 500).count()).isEqualTo(3);
    }
}

可以看到所有請求中只有前五個順利通過了,其餘三個都因為超時而導致接口報500異常。我們可能並不希望這種不友好的提示,因此Resilience4j提供了自定義的失敗回退方法。當請求併發量過大時,無法正常執行的請求將進入回退方法。

首先我們定義一個回退方法

private JsonNode fallback(BulkheadFullException exception) {
        return new ObjectMapper().createObjectNode().put("errorFile", System.currentTimeMillis());
    }

注意:回退方法應該和調用方法放置在同一類中,並且必須具有相同的方法簽名,並且僅帶有一個額外的目標異常參數。

然後在@Bulkhead註解中指定回退方法:@Bulkhead(name = "backendA", fallbackMethod = "fallback")

最後修改API測試代碼:

@Test
public void 多併發訪問情況下的SemaphoreBulkhead測試使用回退方法() {
    CopyOnWriteArrayList<Integer> statusList = new CopyOnWriteArrayList<>();
    IntStream.range(0, 8).forEach(i -> CompletableFuture.runAsync(() -> {
            statusList.add(given().get("/json-object").statusCode());
        }
    ));
    await().atMost(1, TimeUnit.MINUTES).until(() -> statusList.size() == 8);
    System.out.println(statusList);
    assertThat(statusList.stream().filter(i -> i == 200).count()).isEqualTo(8);
}

運行單元測試,成功!可以看到,我們定義的回退方法,在請求過量時起作用了。

實現FixedThreadPoolBulkhead(固定線程池艙壁)

FixedThreadPoolBulkhead的配置項如下:

配置名稱 默認值 含義
maxThreadPoolSize Runtime.getRuntime().availableProcessors() 配置最大線程池大小
coreThreadPoolSize Runtime.getRuntime().availableProcessors() - 1 配置核心線程池大小
queueCapacity 100 配置隊列的容量
keepAliveDuration 20ms 當線程數大於核心時,這是多餘空閑線程在終止前等待新任務的最長時間

添加配置

示例(使用yml):

resilience4j.thread-pool-bulkhead:
  configs:
    default:
      maxThreadPoolSize: 4
      coreThreadPoolSize: 2
      queueCapacity: 2
  instances:
    backendA:
      baseConfig: default
    backendB:
      maxThreadPoolSize: 1
      coreThreadPoolSize: 1
      queueCapacity: 1

如上,我們定義了一段簡單的FixedThreadPoolBulkhead配置,我們指定的默認配置為:maxThreadPoolSize: 4,coreThreadPoolSize: 2,queueCapacity: 2,並且指定了兩個實例,其中backendA使用了默認配置而backendB使用了自定義的配置。

編寫Bulkhead邏輯

定義一個受FixedThreadPoolBulkhead管理的方法:

@Bulkhead(name = "backendA", type = Bulkhead.Type.THREADPOOL)
public CompletableFuture<JsonNode> getJsonObjectByThreadPool() throws InterruptedException {
    io.github.resilience4j.bulkhead.ThreadPoolBulkhead.Metrics metrics = threadPoolBulkheadRegistry.bulkhead("backendA").getMetrics();
    logger.info("now i enter the method!!!,{}", metrics);
    Thread.sleep(1000L);
    logger.info("now i exist the method!!!");
    return CompletableFuture.supplyAsync(() -> new ObjectMapper().createObjectNode().put("file", System.currentTimeMillis()));
}

如上定義和SemaphoreBulkhead的方法大同小異,其中@Bulkhead显示指定了type的屬性為Bulkhead.Type.THREADPOOL,表明其方法受FixedThreadPoolBulkhead管理。由於@Bulkhead默認的BulkheadSemaphoreBulkhead,所以在未指定type的情況下為SemaphoreBulkhead。另外,FixedThreadPoolBulkhead只對CompletableFuture方法有效,所以我們必創建返回CompletableFuture類型的方法。

定義接口類方法

@GetMapping("/json-object-with-threadpool")
public ResponseEntity<JsonNode> getJsonObjectWithThreadPool() throws InterruptedException, ExecutionException {
    return ResponseEntity.ok(bulkheadService.getJsonObjectByThreadPool().get());
}

編寫測試代碼

@Test
public void 多併發訪問情況下的ThreadPoolBulkhead測試() {
    CopyOnWriteArrayList<Integer> statusList = new CopyOnWriteArrayList<>();
    IntStream.range(0, 8).forEach(i -> CompletableFuture.runAsync(() -> {
            statusList.add(given().get("/json-object-with-threadpool").statusCode());
        }
    ));
    await().atMost(1, TimeUnit.MINUTES).until(() -> statusList.size() == 8);
    System.out.println(statusList);
    assertThat(statusList.stream().filter(i -> i == 200).count()).isEqualTo(6);
    assertThat(statusList.stream().filter(i -> i == 500).count()).isEqualTo(2);
}

測試中我們并行請求了8次,其中6次請求成功,2次失敗。根據FixedThreadPoolBulkhead的默認配置,最多能容納maxThreadPoolSize+queueCapacity次請求(根據我們上面的配置為6次)。

同樣,我們可能並不希望這種不友好的提示,那麼我們可以指定回退方法,在請求無法正常執行時使用回退方法。

private CompletableFuture<JsonNode> fallbackByThreadPool(BulkheadFullException exception) {
    return CompletableFuture.supplyAsync(() -> new ObjectMapper().createObjectNode().put("errorFile", System.currentTimeMillis()));
}
@Bulkhead(name = "backendA", type = Bulkhead.Type.THREADPOOL, fallbackMethod = "fallbackByThreadPool")
public CompletableFuture<JsonNode> getJsonObjectByThreadPoolWithFallback() throws InterruptedException {
    io.github.resilience4j.bulkhead.ThreadPoolBulkhead.Metrics metrics = threadPoolBulkheadRegistry.bulkhead("backendA").getMetrics();
    logger.info("now i enter the method!!!,{}", metrics);
    Thread.sleep(1000L);
    logger.info("now i exist the method!!!");
    return CompletableFuture.supplyAsync(() -> new ObjectMapper().createObjectNode().put("file", System.currentTimeMillis()));
}

編寫測試代碼

@Test
public void 多併發訪問情況下的ThreadPoolBulkhead測試使用回退方法() {
    CopyOnWriteArrayList<Integer> statusList = new CopyOnWriteArrayList<>();
    IntStream.range(0, 8).forEach(i -> CompletableFuture.runAsync(() -> {
            statusList.add(given().get("/json-object-by-threadpool-with-fallback").statusCode());
        }
    ));
    await().atMost(1, TimeUnit.MINUTES).until(() -> statusList.size() == 8);
    System.out.println(statusList);
    assertThat(statusList.stream().filter(i -> i == 200).count()).isEqualTo(8);
}

由於指定了回退方法,所有請求的響應狀態都為正常了。

總結

本文首先簡單介紹了Resilience4j的功能及使用場景,然後具體介紹了Resilience4j中的Bulkhead。演示了如何在Spring Boot2項目中引入Resilience4j庫,使用代碼示例演示了如何在Spring Boot2項目中實現Resilience4j中的兩種Bulkhead(SemaphoreBulkhead和FixedThreadPoolBulkhead),並編寫API測試驗證我們的示例。

本文示例代碼地址:https://github.com/cg837718548/resilience4j-demo

歡迎訪問筆者博客:blog.dongxishaonian.tech

關注筆者公眾號,推送各類原創/優質技術文章 ⬇️

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

如果人生也能存檔——C#中的備忘錄模式

大家好,老胡又和大家見面了。首先承認今天的博客有點標題黨了,人生是沒有存檔,也沒有後悔葯的。有存檔和後悔葯的,那是遊戲,不知道這是不是遊戲讓人格外放鬆的原因之一。

今天恰逢端午放假,就讓我們來試着做一個小遊戲吧,順帶看看備忘錄模式是如何在這種情況下面工作的。

遊戲背景

這是一個簡單的打怪遊戲,有玩家,有怪獸,玩家作為主角光環,有如下三個特殊能力

  • 攻擊怪獸有暴擊幾率
  • 有幾率迴避怪獸攻擊
  • 可以自己治療一定生命值

遊戲實現

角色類
角色基類

首先是角色類,角色類提供玩家和怪獸最基本的抽象,比如血量、攻擊力、攻擊和治療。(對於怪獸來說,治療是沒有提供實現的,壞人肯定不能再治療了)

class Character
{
    public int HealthPoint { get; set; }
    public int AttackPoint { get; set; }        

    public virtual void AttackChracter(Character opponent)
    {
        opponent.HealthPoint -= this.AttackPoint;
        if (opponent.HealthPoint < 0)
        {
            opponent.HealthPoint = 0;
        }
    }

    public virtual void Cure()
    {
		//故意留空給子類實現
    }
}
玩家類

玩家實現了治療功能並且有暴擊幾率。

class Player : Character
{
    private float playerCriticalPossible;
    public Player(float critical)
    {
        playerCriticalPossible = critical;
    }

    public override void AttackChracter(Character opponent)
    {
        base.AttackChracter(opponent);
        Console.WriteLine("Player Attacked Monster");

        Random r = new Random();
        bool critical = r.Next(0, 100) < playerCriticalPossible * 100;
        if (critical)
        {
            base.AttackChracter(opponent);
            Console.WriteLine("Player Attacked Monster again");
        }
    }

    public override void Cure()
    {
        Random r = new Random();
        HealthPoint += r.Next(5, 10);
        Console.WriteLine("Player cured himself");
    }
}
怪獸類

怪獸沒有治療能力但是有一定的幾率丟失攻擊目標。

class Monster : Character
{
    private float monsterMissingPossible;
    public Monster(float missing)
    {
        monsterMissingPossible = missing;
    }

    public override void AttackChracter(Character opponent)
    {
        Random r = new Random();
        bool missing = r.Next(0, 100) < monsterMissingPossible * 100;
        if (missing)
        {
            Console.WriteLine("Monster missed it");
        }
        else
        {
            base.AttackChracter(opponent);
            Console.WriteLine("Monster Attacked player");
        }
    }
}
遊戲類

遊戲類負責實例化玩家和怪獸、記錄回合數、判斷遊戲是否結束,暴露可調用的公共方法給遊戲操作類。

class Game
{
    private Character m_player;
    private Character m_monster;
    private int m_round;
    private float playerCriticalPossible = 0.6f;
    private float monsterMissingPossible = 0.2f;
    
    public Game()
    {
        m_player = new Player(playerCriticalPossible)
        {
            HealthPoint = 15,
            AttackPoint = 2
        };
        m_monster = new Monster(monsterMissingPossible)
        {
            HealthPoint = 20,
            AttackPoint = 6
        };
    }

    public bool IsGameOver => m_monster.HealthPoint == 0 || m_player.HealthPoint == 0;

    public void AttackMonster()
    {            
        m_player.AttackChracter(m_monster);
    }

    public void AttackPlayer()
    {
        m_monster.AttackChracter(m_player);
    }

    public void CurePlayer()
    {
        m_player.Cure();
    }

    public void BeginNewRound()
    {
        m_round++;
    }

    public void ShowGameState()
    {
        Console.WriteLine("".PadLeft(20, '-'));
        Console.WriteLine("Round:{0}", m_round);
        Console.WriteLine("player health:{0}", "".PadLeft(m_player.HealthPoint, '*'));
        Console.WriteLine("monster health:{0}", "".PadLeft(m_monster.HealthPoint, '*'));
    }
}
遊戲操作類

在我們這個簡易遊戲中,沒有UI代碼,遊戲操作類負責在用戶輸入和遊戲中搭建一個橋樑,解釋用戶的輸入。

class GameRunner
{
    private Game m_game;
    public GameRunner(Game game)
    {
        m_game = game;
    }

    public void Run()
    {
        while (!m_game.IsGameOver)
        {
            m_game.BeginNewRound();
            bool validSelection = false;
            while (!validSelection)
            {
            	m_game.ShowGameState();
                Console.WriteLine("Make your choice: 1. attack 2. Cure");
                var str = Console.ReadLine();
                if (str.Length != 1)
                {
                    continue;
                }
                switch (str[0])
                {
                    case '1':
                        {
                            validSelection = true;
                            m_game.AttackMonster();
                            break;
                        }
                    case '2':
                        {
                            validSelection = true;
                            m_game.CurePlayer();
                            break;
                        }
                    default:
                        break;
                }
            }
            if(!m_game.IsGameOver)
            {
                m_game.AttackPlayer();
            }
        }            
    }
}
客戶端

客戶端的代碼就非常簡單了,只需要實例化一個遊戲操作類,然後讓其運行就可以了。

class Program
{
    static void Main(string[] args)
    {
        Game game = new Game();
        GameRunner runner = new GameRunner(game);
        runner.Run();
    }
}

試着運行一下,

看起來一切都好。

 

加上存檔

雖然遊戲可以正常運行,但是總感覺還是少了點什麼。嗯,存檔功能,一個遊戲沒有存檔是不健全的,畢竟,人生雖然沒有存檔,但是遊戲可是有的!讓我們加上存檔功能吧,首先想想怎麼設計。
 

需要存檔的數據

首先我們要明確,有哪些數據是需要存檔的,在這個遊戲中,玩家的生命值、攻擊力、暴擊率;怪獸的生命值、攻擊力和丟失率,遊戲的回合數,都是需要存儲的對象。
 

存檔定義

這是一個需要仔細思考的地方,一般來說,需要考慮以下幾個地方:

  • 存檔需要訪問一些遊戲中的私有字段,比如暴擊率,需要在不破壞遊戲封裝的情況下實現這個功能
  • 存檔自身需要實現信息隱藏,即除了遊戲,其他類不應該訪問存檔的詳細信息
  • 存檔不應該和遊戲存放在一起,以防不經意間遊戲破壞了存檔數據,應該有專門的類存放存檔

 

備忘錄模式出場

這個時候應該是主角出場的時候了。看看備忘錄模式的定義

在不破壞封閉的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。這樣以後就可將該對象恢復到原先保存的狀態

再看看UML,

看起來完全符合我們的需求啊,Originator就是遊戲類,知道如何創造存檔和從存檔中恢復狀態,Memento類就是存檔類,Caretaker是一個新類,負責保存存檔。

經過思考,我們決定採取備忘錄模式,同時加入以下措施:

  • 將存檔定義為遊戲中的私有嵌套類,這樣存檔可以毫無壓力的訪問遊戲中的私有字段,同時外界永遠沒有辦法去實例化或者嘗試通過轉型來獲得這個類,完美的保護了存檔類
  • 存檔類是一個簡單的數據集合,不包含任何其他邏輯
  • 添加一個存檔管理器,可以放在遊戲操作類中,可以通過它看到我們當前有沒有存檔
  • 存檔放在存檔管理器中
  • 存檔實現一個空接口,在存檔管理器中以空接口形式出現,這樣外部類在訪問存檔的時候,僅能看到這個空接口。而在遊戲類內部,我們在使用存檔之前先通過向下轉型實現類型轉換(是的,向下轉型不怎麼好,但是偶爾可以用一下)
代碼實現
空接口
interface IGameSave
{

}
私有嵌套存檔類

該類存放在game裏面,無壓力地在不破壞封裝的情況下訪問game私有字段

private class GameSave : IGameSave
{
    public int PlayerHealth { get; set; }
    public int PlayerAttack { get; set; }
    public float PlayerCritialAttackPossible { get; set; }
    public int MonsterHealth { get; set; }
    public int MonsterAttack { get; set; }
    public float MonsterMissingPossible { get; set; }
    public int GameRound { get; set; }
}
創建存檔和從存檔恢復

game中添加創建存檔和從存檔恢復的代碼,在從存檔恢復的時候,使用了向下轉型,因為從存檔管理器讀出來的只是空接口而已

public IGameSave CreateSave()
{
    var save = new GameSave()
    {
        PlayerHealth = m_player.HealthPoint,
        PlayerAttack = m_player.AttackPoint,
        PlayerCritialAttackPossible = playerCriticalPossible,
        MonsterAttack = m_monster.AttackPoint,
        MonsterHealth = m_monster.HealthPoint,
        MonsterMissingPossible = monsterMissingPossible,
        GameRound = m_round
    };
    Console.WriteLine("game saved");
    return save;
}

public void RestoreFromGameSave(IGameSave gamesave)
{
    GameSave save = gamesave as GameSave;
    if(save != null)
    {
        m_player = new Player(save.PlayerCritialAttackPossible) { HealthPoint = save.PlayerHealth, AttackPoint = save.PlayerAttack };
        m_monster = new Player(save.MonsterMissingPossible) { HealthPoint = save.MonsterHealth, AttackPoint = save.MonsterAttack };
        m_round = save.GameRound;
    }
    Console.WriteLine("game restored");
}	
存檔管理器類

添加一個類專門管理存檔,此類非常簡單,只有一個存檔,要支持多存檔可以考慮使用List

    class GameSaveStore
    {
        public IGameSave GameSave { get; set; }
    }
在遊戲操作類添加玩家選項

首先在遊戲操作類中添加一個存檔管理器

private GameSaveStore m_gameSaveStore = new GameSaveStore();

接着修改Run方法添加用戶操作

public void Run()
{
    while (!m_game.IsGameOver)
    {
        m_game.BeginNewRound();
        bool validSelection = false;
        while (!validSelection)
        {
            m_game.ShowGameState();
            Console.WriteLine("Make your choice: 1. attack 2. Cure 3. Save 4. Load");
            var str = Console.ReadLine();
            if (str.Length != 1)
            {
                continue;
            }
            switch (str[0])
            {
                case '1':
                    {
                        validSelection = true;
                        m_game.AttackMonster();
                        break;
                    }
                case '2':
                    {
                        validSelection = true;
                        m_game.CurePlayer();
                        break;
                    }
                case '3':
                    {
                        validSelection = false;
                        m_gameSaveStore.GameSave = m_game.CreateSave();
                        break;
                    }
                case '4':
                    {
                        validSelection = false;
                        if(m_gameSaveStore.GameSave == null)
                        {
                            Console.WriteLine("no save to load");
                        }
                        else
                        {
                            m_game.RestoreFromGameSave(m_gameSaveStore.GameSave);
                        }
                        break;
                    }
                default:
                    break;
            }
        }
        if(!m_game.IsGameOver)
        {
            m_game.AttackPlayer();
        }
    }            
}

注意,上面的3和4是新添加的存檔相關的操作。試着運行一下。

看起來一切正常,這樣我們就使用備忘錄模式,完成了存檔讀檔的功能。

 

結語

這就是備忘錄模式的使用,如果大家以後遇到這種場景

  • 想要保存狀態,又不想破壞封裝
  • 需要把狀態保存到其他地方

那麼就可以考慮使用這個模式。

遊戲有存檔,人生沒存檔,願我們把握當下,天天努力。
祝大家端午安康,下次見。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

「敢檢討核電海嘯對策就開除你喔!」 解析處處矛盾的福島核災判決

文:宋瑞文(媽媽監督核電廠聯盟特約撰述)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

象牙海岸法令鬆綁 開放可可業剷平雨林

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?

瑞典智庫:氣候變遷加劇衝突 阻礙和平建設

摘錄自2019年10月23日中央社報導

瑞典智庫斯德哥爾摩國際和平研究所(SIPRI)23日公布報告指出,氣候變遷對當前及未來的和平建設構成嚴峻挑戰,並且可能加劇衝突。該研究所氣候變遷計畫高級研究員科蘭普(Florian Krampe)指出,報告顯示安全形勢正隨著氣候變遷改變,這次許多發現也適用於其他衝突。

索馬利亞被形容為「世界氣候變遷脆弱度最高的國家之一」。報告顯示,索國數十年來的衝突,因為一系列嚴重乾旱而加劇,加深國家建設進展的壓力,在多個層面對聯合國駐索馬利亞援助團的工作構成更多挑戰。科蘭普並未斷言氣候變遷本身可能造成衝突,但他認為證據明確顯示「氣候變化增加衝突及暴力的可能性」。

根據聯合國難民事務高級專員公署(UNHCR),由於武裝衝突及重複不斷的乾旱,索馬利亞境內現今約有260萬人流離失所,逾80萬人仍離鄉背井滯留鄰國。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

林火肆虐、霾害有感 蘇門答臘村落反思傳統 自主禁燒

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?