السابقالفهرسالتالي

الفصل 13

مصفوفات الكائنات

 

13.1 الطريق القادم

في الفصول الثلاثة القادمة سنطور برامجاً لتعمل مع لعب الورق ومجموعات ورق اللعب. قبل أن نبدأ، سأقدم لك فكرة عن الخطوات:

  1. في هذا الفصل سنعرف صنف Card ونكتب عمليات تعمل مع أوراق اللعب ومصفوفات الأوراق.
  2. في الفصل 14 سننشئ صنف Deck ونكتب عمليات تشتغل على مجموعات الورق.
  3. في القسم 14.9 سأقدم البرمجة كائنية التوجه (OOP) وسنحول صنفي Card وDeck إلى أسلوب أقرب إلى OOP.
أعتقد أن أسلوب العمل هذا يجعل الطريق أسهل؛ العقبة الوحيدة هي أننا سنرى العديد من النسخ المعدلة لنفس الشفرة، ما قد يسبب بعض الحيرة. إذا ذلك كان سيساعدك، فيمكنك تنزيل شفرة كل فصل لتسهيل العمل. شفرة هذا الفصل متوفرة على: http://thinklikecs.webs.com/resources/code/Card1.java.

13.2 كائنات Card

إذا لم تكن تعرف كيف تلعب الورق، فسيكون الآن وقتاً مناسباً لتشتري مجموعة ورق، وإلا فلن تفهم شيئاً من هذا الفصل. أو اقرأ http://en.wikipedia.org/wiki/Playing_card.

يوجد 52 ورقة لعب في المجموعة؛ كل منها تنتمي لأحد المنظومات الأربعة وواحدة من الرتب الثلاثة عشر. المنظومات هي: الاسباتي، كوبة، ديناري، والزهر (مرتبة تنازلياً بحسب قوتها في لعبة بريدج). الرتب هي آص، 2، 3، 4، 5، 6، 7، 8، 9، 10، صبي، بنت وشيخ. وبحسب اللعبة التي تلعبها، إما أن يعتبر الآص أعلى من الشيخ أو أدنى من 2.

إذا أردت تعريف صنف جديد لتمثيل أوراق اللعب، فمن الواضح أن متغيرات الحالة يجب أن تكون: rank (للرتبة)، وsuite (للمنظومة). لا يبدو نوع هذه المتغيرات واضحاً. من الاحتمالات الممكنة هو نوع String، ويمكن أن يحتوي على شيء مثل "Spade" لمنظومة الاسباتي و"Queen" لورقة البنت. من علل هذا الأسلوب هو صعوبة المقارنة بين الأوراق لنعرف أي منها أعلى رتبة أو منظومة.

من الحلول البديلة استخدام الأعداد الصحيحة لترميز (encode) الرتب والمنظومات. أنا لا أعني بكلمة "ترميز" التشفير أو التحويل إلى شفرة سرية، وهو ما قد يعتقده البعض. ما يعنيه عالم الكمبيوتر بكلمة "ترميز" هو شيء مثل "تحديد خريطة تربط بين سلسلة من الأرقام وبين أشياء أخرى أريد تمثيلها." مثلاً،

اسباتي		←  3
كوبة		←  2
ديناري		←  1
زهر		←  0

الميزة الواضحة لهذا التخطيط هي استبدال المنظومات بالأرقام الصحيحة بالترتيب، حتى نتمكن من مقارنة المنظومات بالمقارنة بين الأعداد الصحيحة. إن خريطة الرتب واضحة فعلاً؛ كل واحدة من رتب الأرقام ترتبط بالعدد الصحيح المقابل، وبالنسبة لأوراق الصور:

صبي		←  11
بنت		←  12
شيخ		←  13

إن سبب استخدامي للتدوين الرياضي لهذه الخرائط هو أنها ليست جزءاً من البرنامج. هي جزء من تصميم البرنامج، لكنها لن تظهر صراحة في الشفرة أبداً. تعريف الصنف للنوع Card يبدو مثل هذا:

class Card
{
	int suit, rank;

	public Card() {
		this.suit = 0; this.rank = 0;
	}

	public Card(int suit, int rank) {
		this.suit = suit; this.rank = rank;
	}
}
كالعادة، أكتب عمليتين بانيتين: واحدة تأخذ معامل لكل متغير حالة؛ والثانية لا تأخذ أية معاملات.

لإنشاء كائن يمثل 3 الزهر، نستدعي new:

Card threeOfClubs = new Card(0, 3);
المتحول الأول، 0 يعبر عن منظومة الزهر.

13.3 عملية printCard

عندما تنشئ صنفاً جديداً، فإن الخطوة الأولى هي التصريح عن متغيرات الحالة وكتابة العمليات البانية. الخطوة الثانية هي كتابة العمليات القياسية التي يجب أن يملكها كل كائن، بما فيها عملية لطباعة الكائن، وواحدة أخرى أو اثنتين للمقارنة بين الكائنات. دعنا نبدأ مع العملية printCard.

لطباعة كائنات Card بطريقة يقدر البشر على قراءتها بسهولة، علينا عمل خريطة تربط بين الرموز العددية وما يقابلها من كلمات. إحدى الطرق الطبيعية لعمل ذلك هي استعمال مصفوفة سلاسل محرفية. يمكنك إنشاء مصفوفة سلاسل محرفية بنفس الطريقة التي تنشئ فيها مصفوفة لأحد الأنواع البسيطة:

String[] suits = new String [4];
بعدها يمكننا ضبط قيم عناصر المصفوفة.
suits[0] = "Clubs";
suits[1] = "Diamonds";
suits[2] = "Hearts";
suits[3] = "Spades";
إن إنشاء مصفوفة وتهيئة عناصرها بالقيم هي عملية شائعة لدرجة أن Java توفر تعليمة خاصة لها:
String[] suits = { "Clubs", "Diamonds", "Hearts", "Spades" };
تكافئ هذه التعليمة إلى التصريح عن المصفوفة بشكل منفصل، وحجز مكان لها في الذاكرة، وتعليمات الإسناد. يبدو مخطط الحالة لهذ المصفوفة كما يلي:

عناصر هذه المصفوفة ليست سلاسلاً محرفية، بل هي مرجعيات (references) للسلاسل المحرفية الفعلية.

سنحتاج الآن لمصفوفة سلاسل محرفية أخرى لفك تشفير الرتب:

String[] ranks = { "narf", "Ace", "2", "3", "4", "5", "6",
       "7", "8", "9", "10", "Jack", "Queen", "King" };
إن سبب وجود "narf" هو حفظ مكان العنصر الصفري من المصفوفة، الذي لن نستعمله أبداً (أو لا يفترض بنا أن نستعمله). الرتب الصالحة هي من 1 حتى 13. كان من الممكن أن نبدأ من الصفر لتجنب هذا العنصر المهدور، لكن الخريطة ستبدو أكثر منطقية لو رمزنا 2 ب2، و3 ب3، الخ.

باستخدام هذه المصفوفات، يمكننا الوصول إلى السلاسل المحرفية المناسبة باستخدام suit وrank كأدلة. في العملية printCard

public static void printCard(Card c) {
	String[] suits = { "Clubs", "Diamonds", "Hearts", "Spades" };
	String[] ranks = { "narf", "Ace", "2", "3", "4", "5", "6",
       			"7", "8", "9", "10", "Jack", "Queen", "King" };
	System.out.println(ranks[c.rank] + " of " + suits[c.suit]);
}
تعني العبارة suits[c.suit] "استخدم متغير الحالة suit من الكائن c كدليل للمصفوفة المدعوة باسم suits، واختر السلسلة المحرفية المناسبة". خرج هذه الشفرة
Card card = new Card(1, 11);
printCard(card);
هو Jack of Diamonds.

13.4 عملية sameCard

كلمة "same" أو "نفس" هي أحد الأشياء التي تطرأ في اللغة الطبيعية وتبدو واضحة تماماً حتى تفكر فيها قليلاً، وعندها ستستنتج أنها تملك أكثر مما تتوقعه منها.

مثلاً، لو قلت "أنا وكريس نملك نفس السيارة"، فأنا أعني أن سيارتينا من نفس الماركة والموديل، لكنهما سيارتين مختلفتين. لو قلت "أنا وكريس لنا نفس الأم"، فأنا أعني أن أمنا هي امرأة واحدة. لذا فإن فكرة "التطابق" تختلف اعتماداً على السياق.

عندما تتكلم عن الكائنات، يوجد غموض مشابه. مثلاً، إذا كانت ورقتين (two Cards) متطابقتين، فهل يعني هذا أنهما يحويان نفس البيانات (الرتبة والمنظومة)، أو أنهما فعلاً كائن Card وحيد؟

لنتحقق فيما إذا كان مرجعين يشيران إلى نفس الكائن، نستخدم عامل ==. مثلاً:

Card card1 = new Card(1, 11);
Card card2 = card1;

if (card1 == card2) {
	System.out.println("card1 and card2 are identical.");
}
المرجعيات التي تشير إلى نفس الكائن متطابقة (identical). أما المرجعيات التي تشير إلى كائنات تحوي نفس البيانات فهي متساوية (equivalent).

من الشائع كتابة عملية للتحقق من المساواة، وتدعى باسم مثل sameCard.

public static boolean sameCard(Card c1, Card c2) {
	return(c1.suit == c2.suit && c1.rank == c2.rank);
}
هذا مثال ينشئ كائنين لهما نفس البيانات، ويستعمل sameCard ليرى إذا كانا متساويين:
Card card1 = new Card(1, 11);
Card card2 = new Card(1, 11);
if (sameCard(card1, card2)) {
	System.out.println("card1 and card2 are equivalent.");
}
إذا كان المرجعين متطابقين، فهما متساويين أيضاً، لكن إذا كانا متساويين، فليس بالضرورة أن يكونا متطابقين.

في هذه الحالة، card1 وcard2 متساويين لكنهما غير متطابقين، لذا سيبدو مخطط الحالة كهذا:

كيف سيدو مخطط الحالة لو كان card1 وcard2 متطابقين؟

في القسم 8.10 أخبرتك ألا تستعمل == مع السلاسل المحرفية لأنه لن يعطيك ما تتوقعه منه. بدلاً من مقارنة محتويات السلسلتين (المساواة)، سيتحقق فيما إذا كانت السلستين نفس الكائن (المطابقة).

13.5 عملية compareCard

بالنسبة للأنواع البسيطة، تقارن العوامل الشرطية القيم وتحدد أيها أكبر أو أصغر من الآخر. هذه العوامل (> و < وغيرها) لا تعمل مع الأنواع الكائنية. بالنسبة للسلاسل المحرفية توفر Java عملية compareTo. بالنسبة لأوراق اللعب علينا كتابة واحدة بنفسنا، والتي سنسميها compareCard. لاحقاً، سنستخدم هذه العملية لترتيب مجموعة أوراق اللعب.

بعض المجموعات مرتبة كلياً، بمعنى أنك تستطيع المقارنة بين أي عنصرين وأن تعرف أيهما أكبر. الأعداد الصحيحة والعشرية هي مجموعات مرتبة كلياً. بعض المجموعات غير مرتبة، ما يعني عدم وجود طريقة منطقية لنقول أن عنصراً ما أكبر من عنصر آخر. الفاكهة هي مجموعة غير مرتبة، ولذلك لا نستطيع المقارنة بين التفاح والبرتقال. في Java، النوع البولياني غير مرتب؛ لا يمكننا القول أن true أكبر من flase.

مجموعة ورق اللعب مرتبة جزئياً، أي أننا نستطيع المقارنة بين الأوراق أحياناً وأحياناً لا نستطيع. مثلاً، أنا أعلم أن 3 الزهر أعلى من 2 زهر، وأن 3 ديناري أعلى من 3 زهر. لكن أيهما أعلى، 3 زهر أم 2 ديناري؟ أحدهما من رتبة أعلى، لكن الآخر منظومته أعلى.

لنجعل الأوراق قابلة للمقارنة، علينا أن نقرر أيهما ذا قيمة أكثر، الرتبة أم المنظومة. الخيار عشوائي، لكن عندما تشتري مجموعة ورق جديدة، تأتي مرتبة وكل أوراق الزهر معاً، تليها أوراق الديناري وهكذا. لذا سنقول أن المنظومة ذات قية أكثر.

بعد أن اعتمدنا ذلك، يمكننا كتابة compareCard. تأخذ ورقتين (كائنين من نوع Card) كمعاملين وتعيد 1 إذا ربحت الورقة أولى، 1- إذا ربحت الورقة الثانية، و0 إذا كانتا متساويتين.

أولاً نقارن المنظومات:

if (c1.suit > c2.suit) return 1;
if (c1.suit < c2.suit) return -1;
إذا لم تتحقق أي من التعليمتين، فلا بد أن المنظومتين متساويتين، وعلينا مقارنة الرتب:
if (c1.rank > c2.rank) return 1;
if (c1.rank < c2.rank) return -1;
إذا لم تتحقق أي من هذه أيضاً، فلا بد أن الرتب متساوية، لذا نعيد القيمة 0.

تمرين 13.1 غلف هذه الشفرة في عملية. ثم عـدلها بحيث تكون ورقة الآص أعلى من الشيخ.

13.6 مصفوفات الأوراق

لقد شاهدنا عدة أمثلة عن التركيب (القدرة على جمع عدة مقومات من اللغة في عدة ترتيبات متنوعة) حتى الآن. من الأمثلة الأولى التي شاهدناها كان استدعاء عملية ضمن عبارة حسابية. مثال آخر كان البنية المتداخلة للتعليمات: يمكنك وضع تعليمة if ضمن حلقة while، أو ضمن تعليمة if أخرى، الخ.

بعد أن شاهدت هذه الأشكال، ودرست المصفوفات والكائنات، لا يجب أن تفاجأ عندما تعلم أنك تستطيع عمل مصفوفات من الكائنات. وأنك تستطيع تعريف كائنات تحوي مصفوفات كمتغيرات حالة؛ يمكنك عمل مصفوفات تحتوي على مصفوفات؛ يمكنك تعريف كائنات تحتوي على كائنات، وهكذا. سنرى في الفصلين القادمين أمثلة عن هذه التراكيب باستخدام كائنات Card.

هذا المثال ينشئ مصفوفة من 52 ورقة:

Card[] cards = new Card [52];
هذا هو مخطط الحالة لهذا الكائن:

تحتوي المصفوفة على مرجعيات للكائنات؛ ولا تحتوي على كائنات Card ذاتها. يتم تهيئة العناصر بالقيمة null. يمكنك الوصول إلى العناصر في المصفوفة بالطريقة المعتادة:

if (cards[0] == null) {
System.out.println("No cards yet!");
}
لكن إذا حاولت الولوج إلى متغير حالة لكائن Card غير موجود، ستحصل على NullPointerException.
cards[0].rank; 		// NullPointerException
لكن هذه هي البنية الصحيحة للوصول إلى رتبة الورقة "الصفرية" من المجموعة. كما أن هذا مثال عن التركيب، يجمع البنية النحوية المستخدمة في الوصول إلى عنصر من مصفوفة ومتغير حالة في كائن.

أسهل طريقة لتعبئة مجموعة اللعب بكائنات Card هي كتابة حلقة متداخلة:

int index = 0;
for (int suit = 0; suit <= 3; suit++) {
	for (int rank = 1; rank <= 13; rank++) {
		cards[index] = new Card(suit, rank);
		index++;
	}
}
تعد الحلقة الخارجية الرتب من 0 إلى 3. ومن أجل لكل منظومة، تعد الحلقة الداخلية الرتب من 1 إلى 13. وبما أن الحلقة الخارجية تشتغل 4 مرات، والداخلية 13 مرة، فسيتم تنفيذ جسم الحلقة 52 مرة.

استخدمت index لمعرفة الموقع الذي يجب أن تذهب إليه الورقة التالية في مجموعة اللعب. يبين مخطط الحالة التالي شكل المجموعة بعد حجز أول ورقتين:

تمرين 13.2 غلف شفرة بناء مجموعة ورق اللعب هذه في عملية باسم makeDeck لا تأخذ أية معاملات وتعيد مصفوفة أوراق ممتلئة بالكامل.

13.7 عملية printDeck

عندما تعمل مع المصفوفات، من المناسب أن تملك عملية لطباعة المحتويات. لقد رأينا نموذج عبور المصفوفة عدة مرات، لذا يجب أن تكون الشفرة التالية مألوفة:

public static void printDeck(Card[] cards) {
	for (int i=0; i < cards.length; i++) {
		printCard(cards[i]);
	}
}
بما أن cards من نوع Card[]، فإن العنصر من cards سيكون من النوع Card. لذا فإن cards[i] سيكون متحولاً مشروعاً للعملية printCard.

13.8 البحث

ستكون العملية التالية التي سأكتبها هي findCard، التي ستبحث في مصفوفة أوراق لترى إذا كانت تحتوي على ورقة معينة. ستعطيني هذه العملية الفرصة لأشرح خوارزميتين: البحث الخطي (linear search) والبحث المجزّأ (bisection search).

البحث الخطي واضح للغاية؛ نجتاز مجموعة الورق ونقارن كل ورقة بالورقة التي نبحث عنها. إذا عثرنا عليها نعيد الدليل الذي تظهر عنده الورقة. إذا لم تكن موجودة في مجموعة الورق، نعيد القيمة 1-.

public static int findCard(Card[] cards, Card card) {
	for (int i = 0; i< cards.length; i++) {
		if (sameCard(cards[i], card)) {
			return i;
		}
	}

	return -1;
}
متحولات العملية findCard هي card وcards. قد يبدو وجود متحول بنفس اسم النوع غريباً (المتغير card من النوع Card). يمكننا أن نميز بينهما لأن المتغير يبدأ بحرف صغير.

ترجع العملية بمجرد اكتشاف الورقة، ما يعني أننا لا نحتاج للمرور على مجموعة الورق بالكامل إذا وجدنا الورقة التي كنا نبحث عنها. إذا وصلنا إلى آخر الحلقة، سنعرف أن الورقة غير موجودة في المجموعة.

إذا لم تكن الأوراق في المجموعة مرتبة، لا توجد أي طريقة أسرع للبحث من هذه. علينا فحص كل ورقة لأننا لا نملك طريقة أخرى تمكننا من التأكد أن الورقة التي نبحث عنها ليست هذه الورقة.

لكن عندما تبحث عن كلمة في قاموس، فأنت لا تبحث عنها خطياً، لأن الكلمات مرتبة أبجدياً. ولذلك ستستخدم خوارزمية مشابهة لخوارزمية البحث المجزأ:

  1. ابدأ في مكان ما في الوسط.
  2. اختر كلمة في الصفحة وقارنها مع الكلمة التي تبحث عنها.
  3. إذا وجدت الكلمة التي تبحث عنها، توقف.
  4. إذا كانت الكلمة التي تبحث عنها تأتي بعد الكلمة الموجودة في هذه الصفحة، قلّب الصفحات إلى مكان لاحق في القاموس وانتقل إلى الخطوة 2.
  5. إذا كانت الكلمة التي تبحث عنها تأتي قبل الكلمة الموجودة في هذه الصفحة، قلّب الصفحات إلى مكان سابق في القاموس وانتقل إلى الخطوة 2.
إذا وصلت إلى نقطة حيث توجد كلمتان متجاورتان في الصفحة وكان من المفروض أن توجد كلمتك بينهما، ستستنتج أن كلمتك غير موجودة في القاموس.

لنعد إلى مجموعة ورق الشدة، إذا علمنا أن الأوراق مرتبة، يمكننا كتابة نسخة أسرع من findCard. أفضل طريقة لكتابة البحث المجزأ هي باستخدام عملية تعاودية، لأن التجزئة تعاودية بطبيعتها.

الحيلة هي كتابة عملية باسم findBisect تأخذ دليلين كمعاملات، low وhigh، يشيران إلى القطعة من المصفوفة التي سيتم البحث فيها (تتضمن العنصر ذا الدليل low وكذا الدليل high).

  1. للبحث في المصفوفة، اختر دليلاً بين low وhigh (سمّه mid) وقارن العنصر الموافق له مع الورقة التي تبحث عنها.
  2. إذا عثرت عليها توقف.
  3. إذا كانت الورقة mid أعلى من الورقة التي تبحث عنها، ابحث في النطاق من low إلى mid-1.
  4. إذا كانت الورقة mid أدنى من ورقتك، ابحث في النطاق من mid+1 إلى high.
تبدو الخطوتين 3 و4 كاستدعاءات تعاودية مريبة. هذا ما ستبدو عليه هذه الخوارزمية بعد ترجمتها إلى شفرة Java:
public static int findBisect(Card[] cards, Card card, int low, int high) {
	// TODO: need a base case
	int mid = (high + low) / 2;
	int comp = compareCard(cards[mid], card);

	if (comp == 0) {
		return mid;
	} else if (comp > 0) {
		return findBisect(cards, card, low, mid-1);
	} else {
		return findBisect(cards, card, mid+1, high);
	}
}
هذه الشفرة تحتوي على لب البحث المجزأ، لكن تنقصها قطعة، ولذلك أضفت تعليق TODO (يلزم عمله).

كما هو مكتوب، ستظل العملية تستدعي نفسها للأبد إذا لم تكن الورقة موجودة في المجموعة. نحتاج إلى حالة قاعدية لمعالجة هذه الحالة.

إذا كان high أصغر من low، فلا توجد أوراق بينهما، كما ترى فقد استنتجنا أن تلك الورقة غير موجودة في المجموعة. إذا عالجنا هذه الحالة، ستعمل العملية بشكل صحيح:

public static int findBisect(Card[] cards, Card card, int low, int high) {
	System.out.println(low + ", " + high);

	if (high < low) return -1;

	int mid = (high + low) / 2;
	int comp = cards[mid].compareCard(card);

	if (comp == 0) {
		return mid;
	} else if (comp > 0) {
		return findBisect(cards, card, low, mid-1);
	} else {
		return findBisect(cards, card, mid+1, high);
	}
}
أضفت تعليمة طباعة حتى أتمكن من متابعة تتابع الاستدعاءات التعاودية. لقد جربت الشفرة التالية:
Card card1 = new Card(1, 11);
System.out.println(findBisect(cards, card1, 0, 51));
وحصلت على الخرج التالي:
0, 51
0, 24
13, 24
19, 24
22, 24
23
ثم اخترعت ورقة غير موجودة في المجموعة (15 ديناري)، وحاولت العثور عليها. حصلت على ما يلي:
0, 51
0, 24
13, 24
13, 17
13, 14
13, 12
-1
هذه الاختبارات لا تثبت أن هذا البرنامج صحيح. في الواقع، لا توجد أية كمية من الاختبارات يمكن أن تبرهن أن برنامجاً ما يعمل بشكل صحيح. من جهة أخرى، بتجربة عدة حالات واختبار الشفرة، قد تقدر على إقناع نفسك بذلك.

عدد الاستدعاءات التعاودية نموذجياً 6 أو 7، لذا فنحن نستدعي compareCard 6 أو 7 مرات، مقارنة بما قد يصل إلى 52 مرة إذا اعتمدنا البحث الخطي. بشكل عام، البحث المجزأ أسرع بكثير من البحث الخطي، ويزداد فرق السرعة كلما ازداد حجم المصفوفة.

خطأين شائعي الحدوث في البرامج التعاودية هما نسيان تضمين حالة قاعدية أو كتابة الاستدعاء التعاودي بحيث لا يصل البرنامج للحالة القاعدية أبداً. كلا الخطأين سيسبب تعاوداً لانهائياً، الذي سيولد StackOverflowEception.

13.9 مجموعات ورق الشدة والمجموعات الفرعية

هذا هو النموذج الأولي للعملية findBisect:

public static int findBisect(Card[] deck, Card card, int low, int high)
يمكننا اعتبار cards، وlow وhigh كمعامل وحيد يمثل مجموعة فرعية (subdeck). هذه الطرقة في التفكير شائعة جداً، وأحياناً أعتبرها كمعامل مجرد (abstract parameter). ما أعنيه بكلمة "مجرد"، هو شيء ليس جزءاً من نص البرنامج بشكل فعلي، لكنه يصف عمل البرنامج على مستوى أعلى.

مثلاً، عندما تستدعي عملية وتمرر لها مصفوفة وlow وhigh اللذان يمثلان الحدود، لا يوجد شيء يمنع العملية المستدعاة من الوصول إلى أجزاء المصفوفة الواقعة خارج النطاق المعطى. لذا فأنت فعلياً لا ترسل مجموعة فرعية من مجموعة الورق؛ بل ترسل المجموعة كاملة. لكن طالما أن المستقبِل يلعب وفق القواعد، فمن المعقول أن تعتبرها مجموعة فرعية، بشكل مجرد.

هذا النمط من التفكير، الذي يعطي البرنامج معنى أبعد مما هو مكتوب فعلياً، جزء مهم من أسلوب التفكير كعالم كمبيوتر.

لقد استخدمت الكلمة "مجرد" في العديد من الأماكن حتى كادت تفقد معناها. مع ذلك، فالتجريد فكرة مركزية في علوم الحاسوب (والعديد من المجالات الأخرى).

لتعريف "التجريد" بشكل أكثر عمومية نقول "هو عملية قولبة لنظام معقد باستخدام وصف مبسط للتخلص من التفاصيل غير الضرورية والحصول على سلوك مناسب".

13.10 المصطلحات

ترميز: تمثيل مجموعة قيم باستخدام مجموعة قيم أخرى، وذلك بصنع خريطة تربط بينهما.

التطابق: تساوي المرجعيات. مرجعين يشيران إلى كائن واحد.

التساوي: تساوي القيم. مرجعين يشيران إلى كائنين يحويان نفس البيانات.

معامل مجرد: مجموعة من المعاملات التي تعمل معاً عمل معامل واحد.

التجريد: عملية تفسير برنامج (أو أي شيء آخر) في مستوى أعلى مما هو ممثل بالشفرة فعلياً.

encode: To represent one set of values using another set of values, by constructing a mapping between them.

identity: Equality of references. Two references that point to the same object.

equivalence: Equality of values. Two references that point to objects that contain the same data.

abstract parameter: A set of parameters that act together as a single parameter.

abstraction: The process of interpreting a program (or anything else) at a higher level than what is literally represented by the code.

13.11 تمرينات

تمرين 13.3

في لعبة بلاك جاك يكون الهدف هو الحصول على ورق تكون نقاطه 21. تحسب نقاط اليد بجمع نقاط كافة الأوراق. الآص نقطة واحدة، أوراق الصور تساوي 10 نقاط، ونقاط بقية الأوراق تكون نفس رتبة الورقة. مثلاً: اليد (آص، 10، صبي، 3) تكون نقاطها 1 + 10 + 10 + 3 = 24.

اكتب عملية باسم handScore تأخذ مصفوفة أوراق كمتحول وتعيد مجموع النقاط.

تمرين 13.4

في البوكر يكون "flush" هو يد تحوي خمس أوراق أو أكثر من نفس المنظومة. يمكن أن تحوي اليد أي عدد من الأوراق.

  1. اكتب عملية باسم suiHist تأخذ مصفوفة أوراق كمعامل وتعيد مخطط أعمدة لمنظومات الأوراق في اليد. يجب على الحل أن يجتاز المصفوفة مرة واحدة.
  2. اكتب عملية باسم hasFlush تأخذ مصفوفة أوراق كمعامل وتعيد true إذا احتوت اليد على flush، وfalse فيما عدا ذلك.

تمرين 13.5

سيكون العمل مع الأوراق ممتعاً أكثر لو كنت تستطيع عرضها على الشاشة. إذا لم تكن قد عبثت مع الأمثلة الرسومية في الملحق A، فقد ترغب بعمل ذلك الآن.

بعدها نزل http://thinkapjava.com/code/CardTable.java و http://thinkapjava.com/code/cardset.7z.

فك الضغط عن cardset.7z ثم شغل CardTable.java. يجب أن ترى حزمة من الأوراق تجلس على "طاولة" خضراء.

يمكنك استعمال هذا الصنف كنقطة انطلاق لصنع ألعاب الورق التي تريدها.

السابقالفهرسالتالي