iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 4
0
Software Development

每天 Racket 3 分鐘系列 第 15

(send day-14 set-title! "Racket 也有物件導向 — Class 與 Object — 2")

  • 分享至 

  • xImage
  •  

1. 細說物件導向

我們先回頭來看昨天的程式:

(define student%
    (class object%
      (init id name gender)

      (define student-id id)
      (define student-name name)
      (define student-gender gender)
      (define courses '())

      (super-new)

      (define/public get-name
        (lambda ()
          student-name))
      (define/public join-course
        (lambda (course)
          (set! courses (cons course courses))))
      (define/public get-current-courses
        (lambda ()
          courses))))

(define racket (new student% [id 1]
                             [name "Racket"]
                             [gender 'M]))

(send racket get-name)
(send racket join-course 'PLT)
(send racket join-course 'OS)
(send racket join-course 'DataStructures)
(send racket get-current-courses)

記得昨天講了物件導向的概念嗎?我們第一段程式,正是定義類別的方式:(define class-name% (class object%)),在 Racket 裡,定義類別習慣使用 % 做結尾,當然你若想用 Java/C# CamelCase 的命名法,也不是不行。先宣告了一個 id 名為 student% 之後,再宣告它一個以 object% 為基礎的類別 classobject% 是 Racket 裡頭的根類別,因此若你的類別沒有繼承別的,預設要以 object% 為根基。

接下來的 init,你若看到第二段程式的時候,會知道原來這是一個 建構子(Constructor),用來作為初始化這個物件的內容。我們在 init 裡頭定義了三個變數:idnamegender,這三個變數接下來就用來傳給另外三個正式的物件屬性:student-idstudent-namestudent-gender。而除了這三個外,student% 另有一個屬性 courses,內容是一個空 list

接下來有一個很奇妙的東西,回想一下我們在 Java/C# 寫物件導向時,預設建構子裡似乎不需要加東西。但若翻 Java 語言規格書 [1],它說到當你的類別的建構子,若是沒有參數、沒有呼叫其他建構子,即使你不寫,它也會隱性地呼叫 super(),也就是繼承上層的建構子。然而,我想為何要隱性呼叫呢?這應該是一種語法糖。換句話說,Racket 在提供物件導向特性時,其實是把那些語法糖都拿掉,好讓程式更清晰。

接下來便是定義物件的行為(method)了,我們定義了三個,分別是 get-namejoin-courseget-current-courses。裡面所使用的機制其實很簡單,相信各位從前文中可以了解它的作用。

最後,定義完了類別,我們便來建立物件。使用 new,後面接著類別名稱 student% 與它的參數,於是我們可以使用這個物件了!

2. 繼承、封裝也難不倒 Racket

那好,你想,物件導向的四大特性:繼承、抽象化、封裝、多型,這些 Racket 能不能勝任。Racket 可是 Northeast PLT 的代表作。我們介紹繼承與封裝,多型與抽象化讓你回去研究看看。

再來看個範例程式:

(require racket/date)

(define student%
  (class object%
    (init id name gender)

    (define student-id id)
    (define student-name name)
    (define student-gender gender)
    (define courses '())

    (super-new)

    (define/public get-name
      (lambda ()
        student-name))
    (define/public join-course
      (lambda (course)
        (set! courses (cons course courses))))
    (define/public get-current-courses
      (lambda ()
        courses))))

(define alumni%
  (class student%
    (init id name gender year)

    (define graduated-year year)

    (super-new [id id] [name name] [gender gender])
        
    (define/public get-graduated-year
      (lambda ()
        graduated-year))
    (define/override get-name
      (lambda ()
        (string-upcase (super get-name))))
    (define/public get-years-after-graduated
      (lambda ()
        (- (current-year) graduated-year)))
    (define/private current-year
      (lambda ()
        (date-year (current-date))))))

(define scheme (new alumni% [id 2] [name "Scheme"] [gender 'F] [year 1995]))
(send scheme get-graduated-year)
(send scheme get-name)
(send scheme get-years-after-graduated)

首先,我們引入一個好用的日期工具:racket/date,並且在 student% 下方,定義了新的類別:alumni%,表示校友。在 class 定義處,不再寫 object% 了,而是寫 student% 表示 alumni% 是從 student% 繼承而來。校友有一個重要的屬性,就是畢業年:graduated-year

我們在 init 看到宣告了四個變數,除了 student% 的三項之外,還有一個 year,並且(這裡是重點!),在 super-new 時,傳了 student% 所需要的三個變數給上層的 student% 類別。

在下方 alumni% 的 method 定義處,我們一樣給了一個 get-graduated-year 的 method,但是多了三個特別的 method:

  1. 我們試著覆寫(override)上層 get-name 的 method,因此用 define/override 宣告,這時我們可以回傳一個轉大寫的 name 值回去
  2. 我們再定義了一個特別的 method,可以計算這位校友畢業了幾年:get-years-after-graduated,但我們怎麼取到現在的年份呢?透過下方再定義了一個 current-year 的 method
  3. 關鍵在 current-year 使用了 define/private,使其存取範圍為物件內部限定,我們用這個方式進行了資訊的封裝,好讓在物件外部只能呼叫 get-years-after-graduated,而不知道裡頭有個計算 current-year 的 method。

以上,我們已經介紹完 Racket 的物件導向很基礎的部份,更深入的部份可以參考 Racket Guide[2] 與 Racket Reference[3]


上一篇
(send day-13 set-title! "Racket 也有物件導向 — Class 與 Object")
下一篇
(display (call/cc (lambda (day-15) (day-15 "天下第一奇招 — Racket 的 Continuation"))))
系列文
每天 Racket 3 分鐘17
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言