:::warning
**CHANGE LOG**(有任何修改請在此通知)
> [name=chia0][time=Tue, Nov 21, 2023 8:08 PM]註腳標註已修改成跟英文版一樣的了!
>
> [name=Chia0] There are two notes two orphan notes at the end of this chapter. I've checked the code on GitLab and Aesthetic Programming website, but still can't find out where should the notes belong to. Could Winnie help checking this~? Thanks! @siusoon

> [name=andrew] 2023/12/10 11:27pm 已上傳至 [gitlab repo](https://gitlab.com/aesthetic-programming/book/-/tree/master/source.zh_TW/6-ObjectAbstraction). Also, @Chia0 I assigned these two notes in the content.
:::

## setup()
在程式設計中,物件是一個關鍵概念,但這個詞更為普遍的意義,則是一項具備與主體詞語相關屬性的事物。簡言之,在哲學慣例中,主體是觀察者(在此可以說是程式設計師),而物件則是與之不同,被觀察的事物(例如程式本身)。有些人認為,我們必須更加重視非人類的事物,以便更深入地理解物件的存在,以及它們與其他物件和主體之間的互動,遵循上述論點,我們將在本章中學習進一步操作物件,並理解其複雜性。
在之前的章節中,我們使用了不同的物件,例如圓、矩形和多邊形。您可以在這些物件上套用如顏色、大小和位置等不同的屬性,此外,物件也能展現一些行為,例如各種變形和互動功能(參見第三章〈無限迴圈〉和第四章〈資料擷取〉),而這些幾何物件以內建的 p5.js 函式作為一組預先定義的參數和屬性。在本章中,我們將致力於建構自己的物件、屬性和行為,以期能重現真實世界的某些面向。在這裡,物理性的物件需要被轉譯為物件的概念,因此便涉及了抽象的過程,在這個過程中,某些細節和脈絡資訊自然會被省略,這點無可避免,我們稍後將回頭探討此議題。
抽象是「物件導向程式設計」(OOP)的一項關鍵概念,它是一種程式設計範例模式,以資料或物件為基礎組織程式,而非函式和邏輯[^history],主要目標是透過針對某些細節進行抽象,並提供具體模型,從而處理物件的複雜性。針對這一概念更廣泛的意義,以及具體和抽象運算之間的關係,比亞特麗斯・法茲(Beatrice Fazi)和馬修・弗勒(Matthew Fuller)概述如下:「運算這件事不僅只是從世界進行抽象,從而建立模型並予以重現;透過這樣的抽象過程,運算本身也參與其中。」[^Fazi]回顧前面的章節以及許多關於資料擷取和遊戲化的示例,我們就能清楚地看到運算確實可以塑造某些行為和動作。換句話說,在 OOP 中,物件所涉及之事,不僅是與真實世界交涉的一種現實性和重現的形式,也不只是由物件組成的函式和邏輯,還包括了更廣泛的關係,以及「『運算間』和『與運算之間』的互動(interactions between and with the computational)。」[^Fuller]
事實上,抽象存在於許多不同的層次和運算規模之中。最低層次的抽象中,資訊流是以二進制(以 2 為基數的數字系統)數字 0 和 1 的形式儲存、處理和重現。[^binary]換句話說,我們理解文字、圖像、影片或聲音等所有媒體格式的方式,與電腦將它們作為資料(或更準確地說,作為二進制數字)來理解的方式截然不同。[^color]依循此脈絡,我們可以漸次從機器程式碼和電路切換形式的低階抽象,轉向高階抽象,例如圖像化的使用者介面,或這本書中所使用「p5.js」,一種「能產生出可以支援電腦且人類可讀懂的程式碼」[^chun]的高級程式語言。
電腦實際工作方式的具體細節和過程,有很大一部分隱藏在看不見的地方和/或被桌面上的各種「隱喻」所取代(這類隱喻的其中一例,就是透過將檔案扔進「垃圾桶」來刪除檔案),若想理解這點,認識不同的抽象層次非常關鍵。當然,出於易懂程度等多種原因,降低複雜性是一種很實用的做法,但我們仍要考慮到,此處還有更多相關的事物是被省略掉的。學習程式設計時,即使是使用高階語言,我們也參與了抽象現實與具體現實之間移動的政治,而這從來都不是一個中立的過程。[^cecile]更具體而言,在本章中,我們將聚焦物件抽象(OOP 中的一種方法),從概念上思考運算物件如何對世界進行建模,以及這對理解隱藏的作業層次和含義來說,有著什麼樣的意義。
## start()
在運算之中,物件抽象是關乎於表示(representation)的,我們會將某些屬性和關係從現實世界中抽象出來,但此過程同時也會剝離細節和脈絡。這裡先請各位把一個人想像為一個物件(而非一個主體),並將這個人可能具備的屬性和行為納入考量。我們將使用「類別」(Class)這個名稱來概括物件的屬性和行為。
舉例如下:
**屬性**:一位**名**為維妮(Winnie)的人,黑**髮色**、**戴**眼鏡、**身高** 164 公分。**最喜歡的顏色**是黑色,最色愛吃的食物色是豆腐。
**行為**:這個人可以從 A 地(家)**跑步**到 B 地(大學)。
我們可以根據上文的描述,建構一個「偽類別」(pseudo class),並利用它來建立另一個具有以下屬性和行為的物件:
|Person |
| ---------------------- |
| Name(名字)、HairColor(髮色)、withGlasses(戴眼鏡)、Height(身高), FavoriteColor(最喜歡的顏色)、FavoriteFood(最愛的食物)、FromLocation(從哪裡)、ToLocation(去哪裡) |
| run() |
* * *
同理,我們可以「再次使用」相同的屬性和行為,來建立另一個具有相應資料值的「物件實例」(object instance):
| 物件實例(一) | 物件實例(二) |
|-------------------------------|---------------------------|
| Name = Winnie | Name = Geoff |
| HairColor = Black | HairColor = Brown |
| withGlasses = Yes | withGlasses = Yes |
| Height = 164 cm | Height = 183 cm |
| FavoriteColor = Black | FavoriteColor = Green |
| FavoriteFood = Tofu | FavoriteFood = Avocado |
| FromLocation = Home | FromLocation = University |
| ToLocation = University | ToLocation = Home |
| run() | run() |
審視偽物件,即可看出抽象如何在運算中進行,並產生「電腦化材料」(computerized material),而我們只會從中選擇自己認為很重要,因此需在程式中表示的屬性和行為,其餘屬性和行為則予以忽略。這是一種對物理現象進行建模,並模擬真實或虛構實體行為的方式。[^Madsen]不過,克魯岑(Crutzen)和科特坎普(Kotkamp)認為,OOP 的基礎,乃是「對於表示的客觀性和中立性的幻想」,而在這樣的幻想裡,「抽象是一種經過簡化的描述,其中被承認的屬性數量是有限的。這些屬性回應了世界上許多其他方面的壓制。」[^cecile2]這部分可以理解為,現實世界中的物件複雜度高且非線性,而將之抽象化和轉譯過程,則需作出優先考量普遍性,減少關注差異的決策。
上文中提出了一個人物物件的建模示例,隨後,我們將轉頭關注另一個與「豆腐」相關的示例,本章的範例程式碼也對之進行了參考。這個例子的靈感源自《Tofu Go!》(2018),一款由 Francis Lam 開發和設計的遊戲。[^francis]豆腐是一種大豆製成的流行食品,起源可追溯到兩千多年前的中國。大豆浸泡後攪打研磨,然後過濾掉顆粒物質,即得豆漿,一種含有油、水和蛋白質的穩定乳化液體。在豆漿中加入凝固劑,待其凝固後,便可壓製成不同柔軟度,稱為「豆腐」的白色固體塊狀物。豆腐是一項重要的食品,在亞洲尤其如此,這不僅是因為其蛋白質含量很高,更是由於它本身就是一個文化象徵。
在《Tofu Go!》中,豆腐成了運算物件,此時便需要藉由抽象來擷取過程和關係的複雜性,並將被認定為基本或理想的屬性和行為予以表示。遊戲中,豆腐被設計為一個簡單的三維白色立方體,具備一組表達情感,以及移動和跳躍的能力。當然,真正的豆腐無法作出上述行為,但如果您也為自己的遊戲寫出程式,又像 Lam 一樣熱愛豆腐(他表示:「《Tofu Go!》是一款專門用來表達我對豆腐和火鍋的熱愛的遊戲。」[^francis2]),您就能想像物件的行為了。這款遊戲的目的是拯救豆腐,逃離被被筷子夾起的命運。

*圖 6.1:由 Francis Lam 開發設計的《Tofu Go!》遊戲螢幕截圖。圖片由設計者提供*
下面我們將介紹的《吃豆腐》(Eating tofu)的範例程式碼,這是一款簡單的遊戲,靈感源自《Tofu Go!》(可免費下載)、先前於第二章〈變數幾何〉中介紹的作品《Multi》,以及流行的日本飲食遊戲《小精靈》(Pac-Man)。[^pacman]遊戲的其餘部分,展示出理解 OOP 基礎知識所需的運算邏輯和建模過程。
## 課堂練習(解碼)


*圖 6.2:《吃豆腐》遊戲截圖*
RunMe
https://aesthetic-programming.gitlab.io/book/p5_SampleCode/ch6_ObjectAbstraction/
**推測**
根據您在螢幕上的體驗,請試著描述:
* 這款遊戲有**哪些指令/規則**?
* 遊戲中,豆腐被建構為一個類別,而每一塊豆腐都是一個物件實例。能否請您描述一下豆腐的屬性,以及其行為呢?
* 您能否利用「豆腐」、「小精靈」(Pacman)、「鍵盤按下事件」和「移動」這些元素,來描述一下這款遊戲的演算流程和序列?
**更進一步的問題:**
* 遊戲期間一直都會有新的豆腐持續從右邊出現並移動到左邊,請問新豆腐出現的**觸發條件是什麼**?
* 您要**怎麼查看**小精靈是否有吃下豆腐?
* 遊戲會在什麼情況下結束?
**與原始碼對應**
* 請將您在之前的推測中得出的發現/特色與原始碼相對應。哪一個區塊的程式碼與您的發現相關?
* 您能否**辨識出**對應您上面推測的元素的程式碼部分/區塊?
* 請找出你您不熟悉的語法和函式,並查閱 p5.js 參考網頁:<https://p5js.org/reference/>。
## 原始碼
原始碼分為兩部分,其中「sketch.js」這部分包含了所有核心功能,另一部分「Tofu.js」則指定了類別/物件關係。有時候,將程式細分為各種函式和檔案可以協助我們釐清程式。您可以把額外的 JS 檔案,簡單地看成核心草圖的延續,因此不必在新檔案中重複編寫函式 `setup()` 或 `draw()`(當您的程式長度增加、更趨複雜時,您可能會以兩個以上的 JS 檔案將程式組合在一起)。若要在程式中啟用這兩個 JS 檔案,須將以下內容加入 index.html 檔案中:
```javascript=
<script language="javascript" type="text/javascript" src="sketch.js">
</script>
<script language="javascript" type="text/javascript" src="Tofu.js">
</script>
```
sketch.js
```javascript=
letpacmanSize={
w:86,
h:89
};
let pacman;
let pacPosY;
let mini_height;
let min_tofu = 5; //螢幕上出現的最小豆腐數量
let tofu=[];
let score=0,lose=0;
let keyColor=45;
functionpreload(){
pacman =loadImage("data/pacman.gif");
}
function setup(){
createCanvas(windowWidth, windowHeight);
pacPosY = height/2;
mini_height = height/2;
}
functiondraw(){
background(240);
fill(keyColor, 255);
rect(0, height/1.5, width, 1);
displayScore();
checkTofuNum(); //available tofu
showTofu();
image(pacman, 0, pacPosY,pacmanSize.w, pacmanSize.h);
checkEating(); //計分
checkResult();
}
function checkTofuNum(){
if (tofu.length < min_tofu) {
tofu.push(new Tofu());
}
}
function showTofu(){
for (let i = 0; i <tofu.length; i++) {
tofu[i].move();
tofu[i].show();
}
}
function checkEating(){
//計算每塊豆腐的間距
for (let i = 0; i < tofu.length; i++) {
let d=int(
dist(pacmanSize.w/2, pacPosY+pacmanSize.h/2,
tofu[i].pos.x, tofu[i].pos.y)
);
if (d < pacmanSize.w/2.5) { //距離很近,如同可以吃到豆腐一樣
score++;
tofu.splice(i,1);
}else if (tofu[i].pos.x < 3) { //小精靈沒吃到豆腐
lose++;
tofu.splice(i,1);
}
}
}
functiondisplayScore(){
fill(keyColor, 160);
textSize(17);
text('You have eaten '+ score + " tofu(s)", 10, height/1.4);
text('You have wasted ' + lose + " tofu(s)", 10, height/1.4+20);
fill(keyColor,255);
text('PRESS the ARROW UP & DOWN key to eat the ToFu', 10, height/1.4+40);
}
functioncheckResult(){
if(lose>score && lose>2){
fill(keyColor, 255);
textSize(26);
text("Too Much WASTAGE...GAME OVER", width/3, height/1.4);
noLoop();
}
}
functionkeyPressed(){
if (keyCode === UP_ARROW) {
pacPosY-=50;
} else if (keyCode === DOWN_ARROW) {
pacPosY+=50;
}
//小精靈移動至範圍外時重置
if (pacPosY > mini_height) {
pacPosY = mini_height;
} else if (pacPosY < 0 - pacmanSize.w/2) {
pacPosY = 0;
}
// Prevent default browser behaviour
// attached to key events.
return false;
```
**Tofu.js:**
```javascript=
/*建立一個類別:具有屬性和行為的物件模板/藍圖*/
class Tofu {
constructor() { //將物件初始化
this.speed = floor(random(3, 6));
//查看此功能:https://p5js.org/reference/#/p5/createVector
this.pos = new createVector(width+5, random(12, height/1.7));
this.size = floor(random(15, 35));
//以正數表示順時針旋轉
this.tofu_rotate = random(0, PI/20);
this.emoji_size = this.size/1.8;
}
move() { //移動行為
this.pos.x-=this.speed; //i.e, this.pos.x = this.pos.x - this.speed;
}
show() { //將豆腐以方塊的外形呈現
push()
translate(this.pos.x, this.pos.y);
rotate(this.tofu_rotate);
noStroke();
fill(130, 120);//陰影
rect(0, this.size, this.size, 1);
fill(253); //前平面
rect(0, 0, this.size, this.size);
fill(150); //上方
beginShape();
vertex(0, 0);
vertex(0-this.size/4, 0-this.size/4);
vertex(0+this.size/1.5, 0-this.size/4); //無特殊髮型
vertex(0+this.size, 0);
endShape(CLOSE);
fill(220);//側邊
beginShape();
vertex(0, 0);
vertex(0-this.size/4, 0-this.size/4);
vertex(0-this.size/4, 0+this.size/1.5);
vertex(0,0+this.size);
endShape(CLOSE);
fill(80); //面部
textStyle(BOLD);
textSize(this.emoji_size);
text('*', 0+this.size/6, 0+this.size/1.5);
text('-', 0+this.size/1.7, 0+this.size/1.9);
text('。', 0+this.size/3, 0+this.size/1.2);
pop();
}
}
```
## 類別
在 OOP 中建構物件時,很重要的一點是要擁有藍圖。物件屬性的結構,以及物件可能的行為/動作是由類別所指定,因此,我們可以將類別想成萬物的模板和藍圖。
我們可以為豆腐建立類似於之前人物物件的模板,如下所示:
|Tofu |
| ------------------------------------------------ |
| speed, xpos, ypos, size, tofu_rotate, emoji_size |
| move(), show() |
您可以把 `Tofu.js` 稱為「豆腐」這個類別的建構過程。
**(步驟一)命名**:為類別命名
```javascript
class Tofu {
}
```
在上面的範例程式碼中,我們以「Tofu」為類別名稱,「tofu」則為物件實例的名稱(程式設計之中,通常會使用相同名稱來表示類別和物件實例,但類別名稱將以大寫字母開頭)。類別中的內容描述了物件的含義,而這是由屬性、資料值、行為和功能所定義,以實現其形式。在電腦科學中,這被稱為「封裝」。
**(步驟二)屬性**:豆腐的(各種)屬性為何?
```javascript=
/*建立一個類別:具有屬性和行為的物件模板/藍圖*/
class Tofu {
constructor() { //初始化物件
this.speed = floor(random(3, 6));
//查看此功能:https://p5js.org/reference/#/p5/createVector
this.pos = new createVector(width+5, random(12, height/1.7));
this.size = floor(random(15, 35));
//以正數表示順時針旋轉
this.tofu_rotate = random(0, PI/20);
this.emoji_size = this.size/1.8;
}
//在此加入更多內容
}
```
以上程式碼為物件的建構做好了準備,其中有一個名為「constructor」的函式,是用來初始化具有其下所列之屬性的(豆腐)物件,該物件以變數列表的形式表示,包含能指出速度、位置、形狀的大小、旋轉角度和表情符號大小的各種屬性。所有屬性皆以關鍵字 `this` 來定義,用來指稱程式執行期間的目前物件實例,例如 `this.speed = floor(random(3, 6));`,便可大致翻譯為:建立物件實例「tofu」時,那塊特定豆腐(tofu)的速度值將是 3 到 5 之間的隨機整數。
針對另一個變數 `this.pos` ,我們以函式 `new createVector` 建立新的 p5 向量,其中包含 x 和 y 兩個分量。藉由 `createVector()` 函式,我們可以用 `pos.x` 和 `pos.y` 來指定豆腐的 x 和 y 座標位置:
this.pos = new createVector(x, y); => this.pos.x 和 this.pos.y
**(步驟三)行為**:豆腐的行為有哪些?
```javascript=
class Tofu {
constructor() { //初始化物件
// 在此加入其他內容
}
move() { //移動行為
this.pos.x-=this.speed; //i.e, this.pos.x = this.pos.x - this.speed;
}
show() {
//使用 vertex() 將豆腐以方塊形式呈現
//在其中一個表面上呈現表情符號
}
}
```
在程式設計中,我們以術語「方法」(method)來描述物件的行為。`move()` 和 `show()` 這兩個方法是每個物件實例都可以使用的函式,讓各個物件都可以隨著速度、大小等屬性的變化,以不同方式移動。
這個概念對於初學者來說通常相當困難,因為在 OOP 中,物件的顯示也被視為一種方法/行為。物件是在後台建立的,但您必須決定在何處、如何以及以何種形式顯示該物件。
## 物件
我們現在將說明如何建立一個物件實例,這個實例將被編程於 `sketch.js` 檔案中。
**(步驟四)物件的建立與使用**:完成類別結構的基礎設定後,下一步便是要建立一個可顯示在螢幕上的豆腐實例。
```javascript=
let min_tofu = 5; //螢幕上出現的最小豆腐數量
let tofu = [];
function draw() {
//更多內容
checkTofuNum(); //可供取用豆腐數量
showTofu();
}
function checkTofuNum() {
if (tofu.length < min_tofu) {
tofu.push(new Tofu());
}
}
function showTofu() {
//更多內容
for (let i = 0; i <tofu.length; i++) {
tofu[i].move();
tofu[i].show();
}
}
```
從上面的程式碼中可以看出,這個程式有設定螢幕上顯示的最少豆腐數量(以變數 `min_tofu` 表示)。豆腐是以 `checkTofuNum()` 創造的(參見第 6 行和第 10 行),這是一個帶有條件語句,可自訂的函式,用於檢查豆腐物件的數量是否滿足最小值。函式`push()`[^push]透過「豆腐」(Tofu)這個類別建立一個「新的」物件實例(`tofu.push(new Tofu());`),讓所有物件實例都具備相同的屬性和方法,而程式碼也可以再次使用,以建立類似的物件。
就像陣列一樣,物件的索引也是從 0 開始,例如 `tofu[0]`,如果需要更多豆腐,程式將建立並增加索引,即 `tofu[1]`。使用語法 `tofu.length()` 會回傳活躍物件的數量。然後我們就可以使用for迴圈來對所有的豆腐物件進行迴圈,在畫布上移動和顯示它們(使用上面定義的類別方法 `move()` 和 `show()`)。
這一小段程式碼片段顯示出物件可以複製,而且相對容易管理,這也是 OOP 的優點之一。
**(步驟五)觸發點和邏輯**:請將之全盤考量
到目前為止,類別與物件關係的基本結構已經建立完成,但是還需要幾個額外的部分來完成整個遊戲程式,例如遊戲規則的實施:
* * *
1. 小精靈的位置以及它如何與豆腐物件互動。
2. 檢查每塊豆腐是被吃掉還是被浪費了。
3. 遊戲結束的條件和結果為何?
程式是透過 `draw()` 函式中的邏輯連續不斷地運行,因此豆腐會不斷地被創造、移動和顯示。我們必須將較大的任務分解為較小的任務,才能讓此過程順利執行。
**刪除豆腐**
有一項必須留意的重要事項,那便是物件一旦建立,即使我們不再能於螢幕上看到它,該物件仍會保留在程式之中,直到您將其刪除為止。在這個遊戲中,讓豆腐從螢幕上消失的方式有兩種:
1. 豆腐物件沒有被吃掉,並且移動到螢幕範圍之外。
2. 豆腐物件被吃掉。
雖說豆腐可能會從螢幕上消失,但我們還是得使用程式碼來刪除物件,否則它們將繼續往螢幕範圍之外移動,越離越遠(也就是說,除非您有想要重複使用消失的豆腐,才會需要予以保留,不過,針對此範例程式碼,我們演示了如何刪除這些物件)。
既然我們以語法 `tofu.length` 來檢查豆腐物件的最小數量,刪除物件自然變得至關重要,如此螢幕才能檢查並調整豆腐的數量。我們以 `push()` 函式來加入新物件,並用 `splice()` 來進行刪除。[^splice]
```javascript=
function draw() {
checkEating(); //計分
}
function checkEating() {
//計算每塊豆腐的間距
for (let i = 0; i < tofu.length; i++) {
let d = int(
dist(pacmanSize.w/2, pacPosY+pacmanSize.h/2,
tofu[i].pos.x, tofu[i].pos.y)
);
if (d < pacmanSize.w/2.5) { //距離很近,如同可以吃到豆腐一樣
score++;
tofu.splice(i, 1);
}else if (tofu[i].pos.x < 3) { //小精靈沒吃到豆腐
lose++;
tofu.splice(i, 1);
}
}
}
```
`checkEating()` 是一個自訂函式,可於特定情況下刪除豆腐,並依據小精靈吃下或浪費掉的豆腐數量計算分數(見第 5 行)。
我們先從 for 迴圈(參見第 7 行)開始,反覆執行所有「tofu」物件實例。第一步是確認小精靈是否吃掉了其中任何一塊豆腐,這表示我們需要從運算的意義上,思考「被吃掉」代表著什麼。該程式會不斷檢查每塊豆腐和小精靈之間的距離。函式 `dist();`(參見第 9-10 行)採用 (x1, y1, x2, y2) 四個參數來計算兩點之間的距離。x1 和 y1 標記小精靈的所在位置(中心點),x2、y2 則標記每塊豆腐的位置。若計算出的距離小於小精靈圖像寬度的一半,就表示小精靈離豆腐夠近,會給人一種它正在吃豆腐的錯覺,如此一來變數 `score` 就會增加一分,而相關物件(被吃掉的豆腐)則會被刪除(`tofu.splice(i,1);`)。
隨後,我們再來考慮移動至畫布末端的特定豆腐物件,這些物件都不會再次被使用,程式將刪除所有 x 座標位置小於數值 3(`tofu[i].pos.x < 3`)的物件。上述兩個步驟的順序很重要,這是因為我們必須確認刪除的豆腐是小精靈沒吃到的。
到目前為止,函數 `checkTofuNum()` 更適合反映活動物件的數量,即螢幕上可見物件的數量,如果未滿足最小數量,該函式將建立新的物件實例。該函式實作於 `draw()` 函式中,因此,新物件不斷移動,同時持續地創建新的物件實例。
**透過按鍵互動**
要控制小精靈並暢玩遊戲,使用者可利用 UP_ARROW(向上箭頭)和 DOWN_ARROW(向下箭頭)來控制小精靈的位置。我們藉由實現條件結構,設定了小精靈的最大和最小高度邊界,確保它能夠從不同的 y 座標位置吃到豆腐。
```javascript=
function keyPressed() {
if (keyCode === UP_ARROW) {
pacPosY-=30;
} else if (keyCode === DOWN_ARROW) {
pacPosY+=30;
}
//小精靈移動至範圍外時重置
if (pacPosY > mini_height) {
pacPosY = mini_height;
} else if (pacPosY < 0 - pacmanSize.w/2) {
pacPosY = 0;
}
}
```
出於教學目的,我們建立了一個更簡單的版本[^tofu2]來表示豆腐物件(我們沒有使用表情符號和立方體般的矩形,而是採用二維形式的簡單正方形 `rect()`),以期能在即時編程環境中,遵循前四個步驟作為指導,一步步觀察類別-物件的建立。
## 類別-物件的建立
想要在程式中實際放入類別-物件,我們必須先稍做規劃。物件具備屬性/特性,並且會進行行為/行動,而上述屬性和行為是資料的載體,同時也管理著資料,以供人們使用並進行操作。
- **(步驟一)命名**:為類別命名。
- **(步驟二)屬性**:豆腐的(各種)屬性為何?
- **(步驟三)行為**:豆腐的行為有哪些?
- **(步驟四)物件建立和使用**:完成類別結構的基礎設定後,下一步便是要建立一個可顯示在螢幕上的豆腐實例。
- **(步驟五)觸發點和邏輯**:請將之全盤考量。
當然,您可以從一開始就全盤考量一個程式或遊戲,之後再建立各種物件實例,無須完全按照所列步驟順序一一進行。這些僅是建議步驟,主要針對首次處理類別-物件建立的情況的初學者而提供。根據我們的教學經驗,學生通常會認為實際操作自己的物件相當困難,因此我們希望能拋出一些步驟、關鍵字和問題,以便協助他們建立物件。
## 課堂練習
**1. 修改**
- 修改不同的值,以了解《吃豆腐》遊戲的功能/語法。
**2. 分組討論**
* 想出一個您熟悉的遊戲,並使用類別和物件的概念與詞彙,來描述遊戲中的角色/物件。您能否辨識出所選範例中的類別和物件?
* 考慮到物件的建立需要用到抽象的概念,您能否在符合本章的一些引導性想法的前提下,以範例程式碼或您的遊戲為例,從中思考類別/物件抽象的一些政治含義?此外,物件如何與世界互動?世界觀和意識形態又是如何融入物件的屬性和行為?而我們討論的對象是一款遊戲,此一事實是否可以讓我們進一步反思日常活動(例如吃豆腐)會透過何種方式變為物件導向?
## 延伸備註
**`function preload(){}`:這款遊戲中使用了一個動畫 gif,亦即小精靈,玩家可以藉由按下某些鍵碼來控制它。若要將圖像繪製到畫布之上,須在程式運行 `setup()` 和 `draw()` 之前,先行以函式 `loadImage()` 載入圖像檔案。
**`image()`**:想要在 p5.js 畫布上顯示圖像檔案,則須使用函式 `image()`,它接受的參數如下:哪個圖像(檔案及其在電腦上的位置)、x 座標位置(您想在 x 軸哪個位置顯示圖像)、y 座標位置(您想在 y 軸哪個位置顯示圖像)、圖像的寬度和高度(您希望圖像顯示的大小,畢竟您或許會想要調整原始圖像尺寸)。我們也曾在第四章〈資料擷取〉中,藉由此功能將擷取的視訊饋送顯示為圖像。
**豆腐的形狀**:我們再度使用了第二章〈變數幾何〉中所介紹的相關語法,例如 `rect()`、`vertex()`、`beingShape()` 和 `endShape()` 等。現在,我們也將透過 `text()` 和 `textSize()` 函式,使用印刷體字符來表達情感。
**遊戲邏輯**:這個程式是一個典型、有最終結果的遊戲,以函式 `checkEating()` 和`checkResult()` 來統計有多少塊豆腐被吃掉(以變數 `score` 表示),以及多少豆腐沒有被吃掉,並被視為浪費(以變數 `lose` 表示),最後再比較上述兩個變數,如果浪費的豆腐數量高於吃掉的豆腐數量(`lose > score`),則會顯示「遊戲結束」,此外,此遊戲在條件語句中使用符號/運算子 `&&`寫出了以下程式:`if (lose > score && lose > 2) {}`,因此,在小精靈沒有吃到任何一塊豆腐,「並且」有兩塊豆腐被浪費掉的情況下(lose = 2並且score = 0),遊戲還是會提供額外機會,讓玩家可以繼續遊玩。`noLoop()` 的功能則是停止程式的迴圈,讓畫布凍結在「遊戲結束」的場景。
**算術運算子**:除了簡單的 `=`、`+`、`-`、`*`、`/` 之外,還有新的算術語法,例如 `tableX+=texture_width;`; 和 `edgeX+=texture_width;` 中的 `+=` 等。請參閱以下列表:
| 運算子 | 使用案例 | 等同於 |
|----------|----------|---------|
| += | x+=y | x=x+y |
| -= | x-=y | x=x-y |
## While()
仔細審視豆腐的示例,可以清楚看到,即使物件是抽象的,物件導向的程式設計仍然很具體且高度組織化。另外,值得重申的是,OOP 的目的,是反映人們組織並想像世界的方式,至少從電腦程式設計師的角度看來是如此。這個例子可以協助我們理解相對獨立的物件,是以什麼樣的方式,透過自己本身與其他物件的關係來運作。
身兼學者和電子遊戲設計師的伊安・伯格斯(Ian Bogost)將這些互動過程稱為「單元操作」,並提出這類操作「以更為壓縮的表示形式為特點,從結構主義人類學到運算領域,這可謂二十世紀普遍趨勢。我以這個術語來指涉一般意義上的過程,而文化過程與其運算表示之間的結合便是一例。」[^Bogost]伯格斯從文學理論和運算的結合中得到啟發,認為文化現象(不僅僅是電腦遊戲)可以被解讀為一個由離散卻又互相連鎖的意義單元組成的配置系統,而這個論點放在此處有諸多含義。正如我們在本章中所看到的,每個物件都包含了資料和函式,程式設計師可以在不同對象之間建立關係,而物件又能進一步繼承其他物件的特性。
如上所述,這種物件導向的方法,與其他許多學科理解離散物件及其互動的方式非常類似。其中最明顯的,就是「物件導向程式設計」這個名稱,與「物件導向本體論」(object-oriented ontology,OOO)這種關乎物件如何存在並互動的哲學性「推測」之間的連結。不過,我們應該仔細留意,不要在物件導向程式設計(object-oriented programming,OOP)和 OOO 之間建立過於簡單的聯繫,而早期物件導向本體論又有「物件導向哲學」(object-oriented philosophy,簡寫亦為 OOP)之稱,這更加劇了理解上的混亂。簡而言之,物件導向本體論完全反對物件是透過人類主體的感知而生的觀點,並提倡無論人類或非人類,只要是物件,都具有自主性。[^Harman]
再次重申,雖說哲學問題不適合在本書這類著作中深入探討,我們仍應在這邊指出,物件導向本體論受到海德格的學說所影響,批判康德的形而上學,並且反對將人類存在(human existence)的地位,置於非人類物件之上。哈曼(Graham Harman)的著作《工具存有:海德格和物件形而上學》(Tool-Being: Heidegger and the Metaphysics of Objects),便建立於「手前狀態」(present-at-hand)和「及手狀態」(ready-to-hand)之間的區別上(前者指我們在理論上如何理解由物件組成的世界,後者則描述我們與實用或便於使用的事物之間的實際關係),此二概念的差異經常被引用,而這本書也藉此明確地闡述了此論與海德格思想之間的連結。[^Harman2]此處的論點,是實踐先於理論,而「及手狀態」的人類時間,又先於「手前狀態」,哈曼將此概念延伸到物件本身的實務操作,以闡釋他的「物件本體論」。透過這種方式來思考程式設計也許能帶來有用的見解,我們將程式設計看作是一種以工具為基礎的實務操作,而其中物件本身的存在乃是獨立於人類活動之外,且正如哈曼所言,在本體論上,物件的意義並不以其與人類或其他物件的關係而窮盡。
將政治納入此處的相關討論,是其中一個明顯的難題。舉例來說,珍・班妮特(Jane Bennett)所著的《顫動物質》(Vibrant Matter),便嘗試針對超越人類範疇的群體(assemblage)採取政治立場,這些群體質疑了人類(通常是男性)對世界的主權,而在〈可食之物〉一章中,班妮特甚至還談及食物的能動性(agency)。[^Bennett]根據她所寫的內容,該書之目的,是「闡明一種充滿活力、與人類並存,也同時在人類內部運作的物質性,藉此了解若事物被賦予了更多力量/勢(force),我們對政治事件的分析可能會產生怎樣的變化。[^Bennett2]」班妮特在這裡部分參考了布魯諾・拉圖(Bruno Latour)的著作,以及他的「行動者」(actant)概念,此短語強調的,是一種複雜、多樣、相互關聯的能動者的聚合體(群體,assemblage)[^Latour],正如拉圖所說,物件(object)於此又再次成為了事物(thing)。
若我們將這一論點延伸到作業系統,並像拉圖一樣進一步將之擴展到社會領域,那此處便會出現一個問題,就是該如何最有效地促進物件的產出及行為。舉例來說,免費開源軟體的生產,便是基於某些特定的散佈原則,以及其物件之間的互相交換(類似於程式設計環境中物件的互動)。物件的運作方式,在運算和文化決策,以及表示模型之中都是政治性的。馬修・富勒(Matthew Fuller)和安德魯・高菲(Andrew Goffey)認為,這種物件導向的世界建模是一種社會的技術性實踐,「對在不同尺度的現實上運作的關係,進行壓縮和抽象,並建構新的能動性形式。[^FullerGoffey]」在他們看來,此種能動性的運作範圍,便是日常中會遇到的運算性和物質性安排。
在對關係進行的抽象之中,抽象現實與具體現實之間存在著相互作用。馬克思在批判資本主義的工作條件時,也採用了抽象和具體的分類,以區別「抽象勞動」和「活勞動」(living labor),前者是生產出讓資本主義維持不墜之商品的勞動力,後者則指工作的能力。此處的抽象,便是勞動力被「納入」資本主義的過程(和資料擷取有些類似)。簡言之,我們想更肯定地強調,這些思想與程式設計的相關知識,會受到具體和抽象狀態之間的來回游移所影響。
這裡將援引更多馬克思主義學說來解釋上述概念:我們可以假設世界上有一個真實而具體的事物,但它一旦受到批判的壓力,就會暴露出自己其實為假,僅是一種抽象。因此,在馬克思的想法中,具體與抽象之間的關係,是狀態之間的來回辯證,務求減少抽象以直抵現實,而這樣的現實必須表現出豐富的整體關係(例如階級鬥爭)。馬克思所說的抽象決定,推動了以根植於現實和生活條件的批判性思考為手段,對具體事物的再生產。與黑格爾的唯心主義不同,上述概念的政治意義,在於抽象乃是依賴具體而生,並會回歸具體,這理應是一個可以不斷反覆的過程。馬克思舉的例子,乃是透過抽象勞動,對交換價值進行抽象,因為此種抽象與具體社會關係之間的辯證關係,正是它唯一能夠存在的地方。[^Manifesto]
要將上述概念應用到運算物件及其抽象(屬性和方法的辨識)之上,必須將之置於更廣泛的物件關係層面,並充分認識其操作條件(程式、程式設計師的勞動、作業系統、基礎設施等),此外,只有在揭露這些操作條件,方能對其進行改進(尤其是透過更出色的抽象)時,這樣才有意義。在馬克思的方式下,運算物件為我們開啟了不同的視角,用以看待生活條件,以及我們如何感知世界。世界觀往往是不道德的,只需要想想遊戲世界,不難發現到諸多種族和性別抽象的糟糕例子,而這些例子顯示了關於世界的一些假設,以及遊戲角色被定義的屬性和方法。承上所述,本章的部分動機,便是理解物件的設計,乃是立基於某些假設、偏見和世界觀之上,並期待能以此設計進行更好、目的更清晰的物件抽象。
## 迷你習作:包含物件的遊戲
**目標:**
* 透過設計物件的屬性和方法並進行抽象,來實現以類別為基礎的物件導向草稿碼。
* 反思數位文化生活條件下的物件抽象。
**更多靈感:**
* "p5.js - Array Objects," <https://p5js.org/examples/arrays-array-objects.html>.
* *daily coding* by Tomokazu Nagayama (2020), <https://twitter.com/nagayama/status/1246086230497845250?s=19>,原始碼:<https://github.com/nagayama/dailycoding/blob/master/2020/04/03.html>.
* *Eat Food Not Bombs*(含原始碼)by Benjamin Grosser (2019), <https://editor.p5js.org/bengrosser/full/Ml3Nj2X6w>.
* *lifeline* by Junichiro Horikawa (2020), <https://www.openprocessing.org/sketch/891619>.
* "p5.js coding challenge #31: Flappy Bird" by Daniel Shiffman, <https://www.youtube.com/watch?v=cXgA1d_E-jY>.
* "p5.js coding challenge #3: The Snake Game" by Daniel Shiffman, <https://www.youtube.com/watch?v=AaGK-fj-BAM>.
**任務(RunMe)**
想像一個您想要設計和實現的簡單遊戲。這個遊戲需要哪些物件?它們的性質和方法是什麼?在最基礎的層面上,您必須使用基於類別、物件導向的方法來設計遊戲組成,而它們又可以表現出某些行為,這表示您至少需要建立一個類別、一個建構函式和一個方法。
理解物件和類別後,您便可以繼續開發與物件互動的迷你游戲。在思考或重新利用規則、互動和檢查步驟時,請從較簡單的方面著手。對於練習建構邏輯和將較細碎的步驟組合在一起而言,上文中的《吃豆腐》範例程式碼和其他遊戲將是相當實用的例子。
**在您的 ReadMe 檔案中可供思考的問題:**
> * 請描述您的遊戲/遊戲物件的運作方式。
> * 請描述您是如何為物件和其相關屬性編寫程式,以及您在遊戲中使用的方法。
> * 參考我們提供的閱讀內容,物件導向程式設計的特色為何?抽象更廣泛的含義又是什麼?
> * 請將您的遊戲項目連結到更廣的文化脈絡上,並想出一個例子,來描述複雜的細節和作業是如何被「抽象」的。
## 指定閱讀
* Matthew Fuller and Andrew Goffey, "The Obscure Objects of Object Orientation," in Matthew Fuller, *How to be a Geek: Essays on the Culture of Software* (Cambridge: Polity, 2017).
* "p5.js examples - Objects," <https://p5js.org/examples/objects-objects.html>.
* "p5.js examples - Array of Objects," <https://p5js.org/examples/objects-array-of-objects.html>.
* Daniel Shiffman, "Code! Programming with p5.js," *The Coding Train* (watch: 2.3, 6.1, 6.2, 6.3, 7.1, 7.2, 7.3), <https://www.youtube.com/watch?v=8j0UDiN7my4&list=PLRqwX-V7Uu6Zy51Q-x9tMWIv9cueOFTFA>.
## 延伸閱讀
* Evan Narcisse, "The Natural: The Parameters of Afro," in Goldberg & Larsson, eds., *The State of Play: Creators and Critics on Video Game Culture* (New York & Oakland: Seven Stories Press, 2015).
* Cecile Crutzen and Erna Kotkamp, "Object Orientation," in Fuller, ed., *Software Studies*, 200-207.
* Roger Y. Lee, "Object-Oriented Concepts," in *Software Engineering: A Hands-On Approach* (Springer, 2013), 17-24, 35-37.
* Daniel Shiffman, "16.17 Inheritance in JavaScript - Topics of JavaScript/ES6," <https://www.youtube.com/watch?v=MfxBfRD0FVU&feature=youtu.be&fbclid=IwAR14JwOuRnCXYUIKV7DxML3ORwPIttOPPKhqTCKehbq4EcxbtdZDXJDr4b0>.
* Andrew P. Black, "Object-Oriented Programming: Some history, and challenges for the next fifty years" (2013), <https://arxiv.org/abs/1303.0427>.
## 註釋
[^history]: 「Simula」是由奧利-約翰・達爾(Ole-Johan Dahl)和克利斯登・奈加特(Kristen Nygaard)在二十世紀六〇年代於奧斯陸的挪威運算中心開發的,被認為是第一個物件導向的程式語言。「Smalltalk」最初是在二十世紀六〇年代末,由 Xerox 公司的帕洛奧圖研究中心開發,並於 1972 年發布,原是用於教育目的,較 Simula 更常被引用。有關物件導向程式語言歷史的更多資訊,請參閱:Ole-Johan Dahl, "The Birth of Object Orientation: the Simula Languages," *Object-Orientation to Formal Methods*, Olaf Owe, Stein Krogdahl, Tom Lyche, eds., *Lecture Notes in Computer Science* 2635 (Berlin/Heidelberg: Springer, 2004), <https://link.springer.com/chapter/10.1007/978-3-540-39993-3_3>。
[^Fazi]: Beatrice M. Fazi and Matthew Fuller, "Computational Aesthetics," in Christiane Paul, ed., *A Companion to Digital Art* (Hoboken, NJ: Wiley Blackwell, 2016), 281-296.
[^Fuller]: Matthew Fuller & Andrew Goffey, "The Obscure Objects of Object Orientation," in Matthew Fuller, ed., *How to be a Geek: Essays on the Culture of Software* (Cambridge: Polity, 2017).
[^binary]: Limor Fried & Federico Gomez Suarez (n.d), "Binary & Data," Khan Academy. 請參閱:<https://www.khanacademy.org/computing/computer-science/how-computers-work2/v/khan-academy-and-codeorg-binary-data>。
[^color]:電腦理解顏色的方式就是一個數字邏輯的例子。各個顏色都包含數值 0-255 之間的範圍,因此一共有 256 個可能值。每種顏色(紅、綠、藍)在電腦記憶體中儲存圖像資訊時,皆使用 8 位元色彩圖形。具體而言,紅色是以 8 位元色彩圖形的形式呈現,每一個位元都有兩種可能性(二進制),因此得出可能值的數量為 2^8,即 256。
[^chun]: 正如第一章〈入門〉中對高階程式語言通常性理解的討論,全喜卿尖銳地指出了軟體在刪除和可重複使用性方面的政治意涵。高階程式語言不會揭露詳細的機器作業/指令,因此軟/硬體之間被強制地二元分離,機器的物理性和具體性也遭到忽略。程式設計的專業,便是建立在機器的隱藏性之上。請參見:Wendy Hui Kyong Chun, “On Software, or the Persistence of Visual Knowledge,” *Grey Room* 18 (2005): 26–51, <https://doi.org/10.1162/1526381043320741>。
[^cecile]: Cecile Crutzen, and Erna Kotkamp, "Object Orientation," in Fuller, ed., *Software Studies* (Cambridge, MA: MIT Press, 2008), 202-203.
[^Madsen]: Ole Madsen, Birger Møller-Pedersen, and Kirsten Nygaard, "Object-Oriented Programming in the BETA Programming Language," (1993), 16-18.
[^cecile2]: Crutzen and Kotkamp, "Object Orientation," 202-203.
[^francis]: 《Tofu Go!》可在 iPhone 和 iPad 的 Apple App store 上免費取得,請見:<https://apps.apple.com/us/app/tofu-go/id441704812>,以及影片演示:<https://www.youtube.com/watch?v=V9NirY55HfU>。
[^francis2]: 請參閱Francis Lam 2012 年的採訪:<https://www.design-china.org/post/35833433475/francis-lam>。
[^pacman]: 《小精靈》(Pac-Man)遊戲最初名稱為「PuckMan」,指的是流行的日文短語「Paku paku taberu」,其中「paku paku」是形容咀嚼的狀聲詞,而 taberu 則是「吃」的意思。請參閱:Jacopo Prisco, "Pac-Man at 40: The eating icon that changed gaming history," *CNN*, <https://edition.cnn.com/style/article/pac-man-40-anniversary-history/>。
[^tofu2]: 參見:<https://editor.p5js.org/siusoon/sketches/HAYWF3gv>。
[^Bogost]: Ian Bogost, *Persuasive Games: The Expressive Power of Videogames* (Cambridge, MA: MIT Press, 2007), 8; also, Ian Bogost, *Unit Operations: An Approach to Videogame Criticism* (Cambridge, MA: MIT Press, 2006).
[^Harman]: Graham Harman, *Object-Oriented Ontology: A New Theory of Everything* (London: Pelican/Penguin, 2018).
[^Harman2]: Graham Harman, *Tool-Being: Heidegger and the Metaphysics of Objects* (Chicago: Open Court Publishing, 2002).
[^Bennett]: Jane Bennett, *Vibrant Matter: A Political Ecology of Things* (Durham, NC: Duke University Press, 2009).
[^Bennett2]: Bennett, *Vibrant Matter*, viii.
[^Latour]: 這段是對「行動者網路」的描述。請參見 Bruno Latour, *Reassembling the Social: An Introduction to Actor-Network-Theory* (Oxford: Oxford University Press, 2005)。
[^FullerGoffey]: Fuller and Goffey, "The Obscure Objects of Object Orientation," 21.
[^Manifesto]: 「迄今為止,哲學家一直在尋求理解世界;然而,重點是要改變它。」馬克思與恩格斯,《共產黨宣言》(1848 年),<https://www.marxists.org/archive/marx/works/1848/communist-manifesto/>。
[^splice]: `splice()` 是 p5.js 的函式,請參閱:<https://p5js.org/reference/#/p5/splice>。
[^push]: `push()` 是一個用於 JavaScript 陣列的函式,用於在陣列末端加入一個或多個元素,請參閱:<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push>。