大家好,小弟接觸程式設計也一陣子了,但關於以下的問題真的還是有點難以理解
類別(Class)跟物件(Object)的關係,真的很像"先有雞還是先有蛋"這問題
類別裡面可以有物件?物件裡面可以有類別?實體?靜態?
有沒有誰可以專業的用比喻的方式來形容他們的關係???用設計圖&房子的例子形容總覺得稍稍沒有抓到精隨,希望各位IT界已經是老鳥們一起討論看看~~~~
過了幾年再回來看看自己問的蠢問題
,真的覺得自己進步好多阿,從一個物件類別搞不清楚的新手,到了現在變成微軟的技術顧問,
中間的努力,但概只有自己能懂
akitosun提到:
類別裡面可以有物件?物件裡面可以有類別?
akitosun提到:
先有雞還是先有蛋
iT邦幫忙MVPwiselou提到:
『原雞』
akitosun提到:
「原型雞」==>「原型雞蛋」==>第一隻雞==>第一顆雞蛋,故先有雞。
關於這個齁..我是這樣想啦
類別跟物件並不是先有雞還是先有蛋的問題
而是你要用什麼角度去看待這個世界
如果我們現在要寫個遊戲, 每個角色有三個屬性: str, int, hp
攻擊力計算公式: atk = (str * 2) + int
防禦力計算公式: def = str + (int * 2)
傷害計算公式: dmg = atk - def
那麼如果有 a 與 b 兩個角色互相攻擊對方, 可以這樣寫:
<pre class="c" name="code">
atk_a = str_a * 2 + int_a;
def_a = str_a + int_a * 2;
atk_b = str_b * 2 + int_b;
def_b = str_b + int_b * 2;
hp_a -= atk_b - def_a;
hp_b -= atk_a - def_b;
當然這樣子寫, 如果要動態增加角色就麻煩了, 所以針對動態增加角色做點改進
我們把每一種屬性都用一個陣列來存放: str_array, int_array, hp_array
所以上面的程式可以修改一下, 這樣就算要再冒出個 c 或 d 都不是問題了:
<pre class="c" name="code">
atk_a = str_array[a] * 2 + int_array[a];
def_a = str_array[a] + int_array[a] * 2;
atk_b = str_array[b] * 2 + int_array[b];
def_b = str_array[b] + int_array[b] * 2;
hp_array[a] -= atk_b - def_a;
hp_array[b] -= atk_a - def_b;
這樣的寫法有個缺點, 公式散落在程式裡並且重複出現了, 若是要修改公式就得每個都找出來
為了避免修改公式發生遺漏, 我們將各個公式使用函式來計算:
<pre class="c" name="code">
function calc_atk(str, int) {
return str * 2 + int;
}
function calc_def(str, int) {
return str + int * 2;
}
function calc_dmg(str_a, int_a, str_b, int_b) {
return calc_atk(str_a, str_b) - calc_def(str_b, int_b);
}
hp_array[a] -= calc_dmg(str_array[b], int_array[b], str_array[a], int_array[a]);
hp_array[b] -= calc_dmg(str_array[a], int_array[a], str_array[b], int_array[b]);
改成這樣還是有個明顯的缺點, 如果我們想要增加更多的屬性怎麼辦?
若是公式需要三個屬性, 就得丟六個參數進去 calc_dmg, 這已經是會讓人看了頭暈的數量了
所以我們建立了一個資料結構, 就叫做 character
用 C 語言寫長這樣:
<pre class="c" name="code">
typedef struct character {
int str;
int intellect; // int是保留字, 所以改成 intellect
int hp;
} character;
// 然後在主程式裡就這樣寫:
character a, b;
PHP 或其它語言可以用 array 或 hash:
<pre class="c" name="code">
$a = array(
"str" => 0,
"int" => 0,
"hp" => 0,
);
然後就可以修改我們的 function, 整個程式改成這樣:
<pre class="c" name="code">
function calc_atk(char) {
return char.str * 2 + char.int;
}
function calc_def(char) {
return char.int * 2 + char.str;
}
function calc_dmg(attacker, defender) {
return calc_atk(attacker) - calc_def(defender);
}
a.hp -= calc_dmg(b, a);
b.hp -= calc_dmg(a, b);
到目前為止還沒用到什麼物件與類別, 程式一樣寫得很開心
可是現在寫的這些函式其實專門用來處理角色的資料結構, 而且別的地方又用不到, 看著心煩
那我們有沒有辦法把這些函式黏到那資料結構上面去? 於是類別派上用場了:
<pre class="c" name="code">
class Character {
str;
int;
hp;
function calc_atk() {
return str * 2 + int;
}
function calc_def() {
return int * 2 + str;
}
function calc_dmg_to(defender) {
return calc_atk() - defender.calc_def();
}
function attack_to(defender) {
defender.hp -= calc_dmg_to(defender);
}
}
a.attack_to(b)
b.attack_to(a)
這樣子, 我們的主程式只剩下兩行, 乾淨清爽
可以說類別的概念, 就是把一個資料結構與操作此資料結構的函式給綁定在一起, 是一種分門別類的概念
類別內的資料結構就是一些欄位, 可以填資料進去
而物件的本質呢, 其實就是這些欄位實際填進去的資料了
所以物件與類別是一體兩面, 是一種組織程式的方法
當你決定這個地方要套用物件導向的概念去做的時候, 它們就同時產生了
從這個角度來延伸, 就能夠討論類別與物件裡面到底能放什麼東西
類別既然是一種組織資料與函式的方法, 那我們當然可以在類別內再分成細項
就像將流程切割成幾個子流程, 或增加別的流程那樣
物件代表的是實際上的資料, 那資料內也當然可能再包含其它種類的資料
接著來談談你提到的兩個詞
實體這詞就是物件的意思, 基本上可當成同義字去理解
而靜態呢, 說白了就是一種全域變數或函式, 只是綁定在某個類別裡面
例如我們想要給每個新建立的角色一個編號, 可以這樣做:
<pre class="c" name="code">
char_id = 0;
a = new Character(char_id++);
b = new Character(char_id++);
這樣子 a 與 b 都有一個代表自己的 id
但 char_id 這個全域變數我們只用來給角色做編號, 有沒有辦法把它也黏到 Character 類別裡呢?
靜態欄位就是用來做這事情的:
<pre class="c" name="code">
class Character {
static _id = 0;
char_id;
// 然後在建構式:
Character() {
char_id = _id++;
}
......
// 接著我們就不需要從外部給 id 了:
a = new Character(); // id = 0
b = new Character(); // id = 1
靜態方法呢, 其實也是相同的道理, 它是一個函式, 而且跟一個類別關係密切
例如我們想要知道兩個角色誰比較硬, 可以寫一個函式去處理:
<pre class="c" name="code">
function get_harder(a, b) {
return a.calc_def() > b.calc_def() ? a : b;
}
可是這函式跟類別關係太密切了, 我又想要把這函式黏到類別上去
而且希望保持不用建立物件就能呼叫, 於是這樣處理:
<pre class="c" name="code">
class Character {
static function get_harder(char1, char2) {
return char1.calc_def() > char2.calc_def() ? char1 : char2;
}
......
// 然後在程式裡這樣使用:
use_char = Character.get_harder(a, b);
整篇下來, 都在把相關的東西圈一圈黏在一起 xD
希望看完不會害你更混亂~
類別像是設計圖
物件則是實際的成品
當一個東西,自己沒有用到、自己沒有感覺的時候
別人講的天花亂墜,還是不懂(無法體會)
很多初學者連基本的程式都還不會寫(不會動),
這時候就去碰OOP,也是霧裡看花
如果您寫好一支程式,然後請「前輩」幫你看看
哪裡能改進?
這時候您就可以發覺:原來如此,可以變成這樣
有段佛經故事:
師父說:你要找一個「能讓你懂」的人
徒弟說:師父,那就是你,不是嗎?
師父說:不對!那個人.....其實是你自己!
OOP,市面上有很多書,很多老師在講(包含學校也上過課)
但聽不懂就是聽不懂,沒有感覺啊!
我建議你,也去找一個「能讓你懂」的人
就像寫作文,你得先寫出來,然後請老師幫你「改」!
用「修改前」「修改後」的比較,讓您懂得:OOP的寫法有哪些好處?不這樣寫有哪些壞處?
我在這邊上課(不是教課喔,我在上這位老師的課)
http://www.allenkuo.com/edm/OOP201404.html
另外,一個ASP.NET MVC的前輩給初學者的建議:
學習MVC以前,先練好OOP
http://www.dotblogs.com.tw/mis2000lab/archive/2014/06/16/mvc_or_web_form_20140616.aspx
以我們自已為例
人類就是 class(類別)
而你我就是 instance(實體),
你我是屬於人類,但人類並不代表你或是我,
你我終有一天會消失,但只要有人存在,人類就會一直存在。
至於靜態,那是指上帝,
你無法說上帝是class或是instance,
因為以我們的角度那是無法理解的,
在instance的眼中,只知道那原本就是一直存在那裡
public class Car
{
public int speed{};
public float gas{};
}
public void Main(....)
{
Car MyCar=new Car();
Car girlfirendCar=new Car();
}
................
這樣什麼是Class,什麼是 instance??
是先雞,先蛋??