الحالة.
الحالة (State) هو نمط تصميم سلوكي يتيح لكائنٍ تغيير سلوكه عند تغيّر حالته الداخلية. ويبدو الأمر كما لو أنّ الكائن قد غيّر فئته.
##Intent
الحالة (State) هو نمط تصميم سلوكي يتيح لكائنٍ تغيير سلوكه عند تغيّر حالته الداخلية. ويبدو الأمر كما لو أنّ الكائن قد غيّر فئته.

##Problem
يرتبط نمط الحالة ارتباطاً وثيقاً بمفهوم آلة الحالات المنتهية (Finite-State Machine) .

آلة الحالات المنتهية.
الفكرة الأساسية هي أنه، في أي لحظة معيّنة، يوجد عدد منتهٍ من الحالات التي يمكن أن يكون البرنامج فيها. وضمن أي حالة فريدة، يتصرّف البرنامج بشكل مختلف، ويمكن تبديل البرنامج من حالة إلى أخرى في الحال. غير أنه، تبعاً للحالة الراهنة، قد ينتقل البرنامج أو لا ينتقل إلى حالات أخرى معيّنة. وهذه القواعد للتبديل، التي تُسمّى الانتقالات (transitions)، منتهية ومحدّدة سلفاً أيضاً.
يمكنك تطبيق هذا الأسلوب على الكائنات أيضاً. تخيّل أن لدينا فئة Document. يمكن أن يكون المستند في إحدى ثلاث حالات: Draft وModeration وPublished. ويعمل الأسلوب publish الخاص بالمستند بشكل مختلف قليلاً في كل حالة:
- في
Draft، ينقل المستند إلى المراجعة. - في
Moderation، يجعل المستند علنياً، لكن فقط إذا كان المستخدم الحالي مسؤولاً (administrator). - في
Published، لا يفعل أي شيء على الإطلاق.

الحالات والانتقالات الممكنة لكائن المستند.
عادةً ما تُطبَّق آلات الحالات باستخدام الكثير من عبارات الشرط (if أو switch) التي تختار السلوك المناسب تبعاً للحالة الراهنة للكائن. وغالباً ما تكون هذه «الحالة» مجرّد مجموعة من قيم حقول الكائن. وحتى لو لم تكن قد سمعت من قبل عن آلات الحالات المنتهية، فمن المرجّح أنك طبّقت حالةً مرة واحدة على الأقل. ألا يذكّرك هيكل الكود التالي بشيء؟
class Document is
field state: string
// ...
method publish() is
switch (state)
"draft":
state = "moderation"
break
"moderation":
if (currentUser.role == "admin")
state = "published"
break
"published":
// لا تفعل شيئاً.
break
// ...
تظهر أكبر نقطة ضعف في آلة الحالات القائمة على الشروط بمجرّد أن نبدأ بإضافة المزيد والمزيد من الحالات والسلوكيات المعتمدة على الحالة إلى الفئة Document. إذ ستحتوي معظم الأساليب على شروط ضخمة تنتقي السلوك المناسب للأسلوب وفقاً للحالة الراهنة. وكودٌ كهذا من الصعب جداً صيانته، لأن أي تغيير في منطق الانتقال قد يتطلّب تغيير شروط الحالة في كل أسلوب.
وتميل المشكلة إلى التفاقم مع تطوّر المشروع. فمن الصعب جداً التنبّؤ بجميع الحالات والانتقالات الممكنة في مرحلة التصميم. ومن ثمّ، يمكن لآلة حالات رشيقة بُنيت بمجموعة محدودة من الشروط أن تتحوّل بمرور الوقت إلى فوضى متضخّمة.
##Solution
يقترح نمط الحالة أن تنشئ فئات جديدة لجميع الحالات الممكنة لكائنٍ ما وأن تستخرج كل السلوكيات الخاصة بكل حالة إلى هذه الفئات.
بدلاً من تنفيذ كل السلوكيات بنفسه، يخزّن الكائن الأصلي، الذي يُسمّى السياق (context)، مرجعاً إلى أحد كائنات الحالة التي تمثّل حالته الراهنة، ويفوّض كل العمل المتعلّق بالحالة إلى ذلك الكائن.

المستند يفوّض العمل إلى كائن حالة.
لنقل السياق إلى حالة أخرى، استبدِل كائن الحالة النشط بكائن آخر يمثّل تلك الحالة الجديدة. ولا يكون ذلك ممكناً إلا إذا اتّبعت جميع فئات الحالة الواجهة نفسها وعمل السياق نفسه مع هذه الكائنات عبر تلك الواجهة.
قد تبدو هذه البنية شبيهة بنمط الاستراتيجية (Strategy)، لكن هناك فرقاً جوهرياً واحداً. ففي نمط الحالة، قد تكون الحالات المعيّنة مدركة لبعضها وتبادر إلى الانتقالات من حالة إلى أخرى، في حين أن الاستراتيجيات لا تعرف بعضها تقريباً أبداً.
##Structure
-
السياق (Context) يحتفظ بمرجع إلى أحد كائنات الحالة المحسوسة ويفوّض إليه كل العمل الخاص بالحالة. يتواصل السياق مع كائن الحالة عبر واجهة الحالة. ويعرّض السياق دالة ضبط (setter) لتمرير كائن حالة جديد إليه.
-
واجهة الحالة (State) تُعلِن عن الأساليب الخاصة بالحالة. وينبغي أن تكون هذه الأساليب منطقية بالنسبة لجميع الحالات المحسوسة، لأنك لا تريد أن تحتوي بعض حالاتك على أساليب عديمة الفائدة لن تُستدعى أبداً.
-
الحالات المحسوسة (Concrete States) توفّر تطبيقاتها الخاصة للأساليب الخاصة بالحالة. ولتجنّب تكرار كود متشابه عبر حالات متعدّدة، يمكنك توفير فئات مجرّدة وسيطة تغلّف بعض السلوك المشترك.
قد تخزّن كائنات الحالة مرجعاً عكسياً إلى كائن السياق. ومن خلال هذا المرجع، يمكن للحالة جلب أي معلومات مطلوبة من كائن السياق، فضلاً عن بدء انتقالات الحالة.
-
يمكن لكلٍّ من السياق والحالات المحسوسة ضبط الحالة التالية للسياق وتنفيذ انتقال الحالة الفعلي باستبدال كائن الحالة المرتبط بالسياق.
##Pseudocode
في هذا المثال، يتيح نمط الحالة (State) لعناصر التحكّم نفسها في مشغّل الوسائط أن تتصرّف بشكل مختلف، تبعاً لحالة التشغيل الراهنة.

مثال على تغيير سلوك الكائن باستخدام كائنات الحالة.
يكون الكائن الرئيسي للمشغّل مرتبطاً دائماً بكائن حالة يؤدّي معظم العمل نيابةً عن المشغّل. وتستبدل بعض الإجراءات كائن الحالة الحالي للمشغّل بآخر، مما يغيّر طريقة استجابة المشغّل لتفاعلات المستخدم.
// The AudioPlayer class acts as a context. It also maintains a
// reference to an instance of one of the state classes that
// represents the current state of the audio player.
class AudioPlayer is
field state: State
field UI, volume, playlist, currentSong
constructor AudioPlayer() is
this.state = new ReadyState(this)
// يفوّض السياق معالجة مُدخلات المستخدم إلى كائن
// حالة. وبطبيعة الحال، تعتمد النتيجة على الحالة
// النشطة حالياً، لأن كل حالة يمكنها معالجة
// المُدخلات بشكل مختلف.
UI = new UserInterface()
UI.lockButton.onClick(this.clickLock)
UI.playButton.onClick(this.clickPlay)
UI.nextButton.onClick(this.clickNext)
UI.prevButton.onClick(this.clickPrevious)
// يجب أن تتمكّن الكائنات الأخرى من تبديل الحالة
// النشطة لمشغّل الصوت.
method changeState(state: State) is
this.state = state
// تفوّض أساليب واجهة المستخدم التنفيذ إلى الحالة النشطة.
method clickLock() is
state.clickLock()
method clickPlay() is
state.clickPlay()
method clickNext() is
state.clickNext()
method clickPrevious() is
state.clickPrevious()
// قد تستدعي الحالة بعض أساليب الخدمة على السياق.
method startPlayback() is
// ...
method stopPlayback() is
// ...
method nextSong() is
// ...
method previousSong() is
// ...
method fastForward(time) is
// ...
method rewind(time) is
// ...
// تُعلِن فئة الحالة الأساسية عن الأساليب التي ينبغي أن
// تنفّذها جميع الحالات المحسوسة، كما توفّر مرجعاً عكسياً إلى
// كائن السياق المرتبط بالحالة. ويمكن للحالات استخدام المرجع
// العكسي لنقل السياق إلى حالة أخرى.
abstract class State is
protected field player: AudioPlayer
// يمرّر السياق نفسه عبر مُنشئ الحالة. وقد يساعد ذلك
// الحالة على جلب بعض بيانات السياق المفيدة إذا
// لزم الأمر.
constructor State(player) is
this.player = player
abstract method clickLock()
abstract method clickPlay()
abstract method clickNext()
abstract method clickPrevious()
// تُنفّذ الحالات المحسوسة سلوكيات متنوعة مرتبطة بحالة
// معيّنة من حالات السياق.
class LockedState extends State is
// عند إلغاء قفل مشغّل مقفل، قد يتّخذ إحدى
// حالتين.
method clickLock() is
if (player.playing)
player.changeState(new PlayingState(player))
else
player.changeState(new ReadyState(player))
method clickPlay() is
// مقفل، لذا لا تفعل شيئاً.
method clickNext() is
// مقفل، لذا لا تفعل شيئاً.
method clickPrevious() is
// مقفل، لذا لا تفعل شيئاً.
// يمكنها أيضاً إطلاق انتقالات الحالة في السياق.
class ReadyState extends State is
method clickLock() is
player.changeState(new LockedState(player))
method clickPlay() is
player.startPlayback()
player.changeState(new PlayingState(player))
method clickNext() is
player.nextSong()
method clickPrevious() is
player.previousSong()
class PlayingState extends State is
method clickLock() is
player.changeState(new LockedState(player))
method clickPlay() is
player.stopPlayback()
player.changeState(new ReadyState(player))
method clickNext() is
if (event.doubleclick)
player.nextSong()
else
player.fastForward(5)
method clickPrevious() is
if (event.doubleclick)
player.previous()
else
player.rewind(5)
##Applicability
استخدم نمط الحالة (State) عندما يكون لديك كائن يتصرّف بشكل مختلف تبعاً لحالته الحالية، ويكون عدد الحالات هائلاً، ويتغيّر الكود الخاص بكل حالة بشكل متكرّر.
يقترح النمط أن تستخرج كل الكود الخاص بحالة معيّنة إلى مجموعة من الفئات المنفصلة. ونتيجة لذلك، يمكنك إضافة حالات جديدة أو تعديل الحالات القائمة بشكل مستقلّ عن بعضها، مما يقلّل من تكلفة الصيانة.
استخدم النمط عندما يكون لديك فئة مليئة بالشروط الضخمة التي تغيّر سلوك الفئة تبعاً للقيم الحالية لحقولها.
يتيح لك نمط الحالة استخراج فروع هذه الشروط إلى أساليب في فئات الحالة المقابلة. وأثناء قيامك بذلك، يمكنك أيضاً تنظيف الحقول المؤقتة والأساليب المساعدة المتعلّقة بالكود الخاص بكل حالة من فئتك الرئيسية.
استخدم نمط الحالة عندما يكون لديك الكثير من الكود المكرّر عبر حالات وانتقالات متشابهة في آلة حالات قائمة على الشروط.
يتيح لك نمط الحالة تأليف تسلسلات هرمية من فئات الحالة وتقليل التكرار باستخراج الكود المشترك إلى فئات أساسية مجرّدة.
##How to Implement
-
قرّر أي فئة ستؤدّي دور السياق. قد تكون فئة موجودة تحتوي بالفعل على الكود المعتمد على الحالة؛ أو فئة جديدة، إذا كان الكود الخاص بالحالة موزّعاً عبر فئات متعدّدة.
-
أعلِن عن واجهة الحالة. ومع أنها قد تعكس جميع الأساليب المُعلَنة في السياق، فاستهدف فقط تلك التي قد تحتوي على سلوك خاص بالحالة.
-
لكل حالة فعلية، أنشئ فئة تشتقّ من واجهة الحالة. ثم تصفّح أساليب السياق واستخرج كل الكود المتعلّق بتلك الحالة إلى فئتك المنشأة حديثاً.
أثناء نقل الكود إلى فئة الحالة، قد تكتشف أنه يعتمد على أعضاء خاصّة في السياق. وهناك عدّة حلول بديلة:
- اجعل هذه الحقول أو الأساليب عامّة (public).
- حوّل السلوك الذي تستخرجه إلى أسلوب عام في السياق واستدعِه من فئة الحالة. هذه الطريقة غير أنيقة لكنها سريعة، ويمكنك دائماً إصلاحها لاحقاً.
- ضمّن فئات الحالة داخل فئة السياق، لكن فقط إذا كانت لغة البرمجة لديك تدعم تضمين الفئات.
-
في فئة السياق، أضِف حقل مرجع من نوع واجهة الحالة ودالة ضبط عامّة تسمح بإعادة كتابة قيمة ذلك الحقل.
-
تصفّح أسلوب السياق مرة أخرى واستبدل شروط الحالة الفارغة باستدعاءات للأساليب المقابلة في كائن الحالة.
-
لتبديل حالة السياق، أنشئ نسخة من إحدى فئات الحالة ومرّرها إلى السياق. يمكنك القيام بذلك داخل السياق نفسه، أو في حالات مختلفة، أو في العميل. وأينما جرى ذلك، تصبح الفئة معتمِدة على فئة الحالة المحسوسة التي تنشئها.
##Pros & Cons
- مبدأ المسؤولية الواحدة (Single Responsibility Principle). تنظيم الكود المتعلّق بحالات معيّنة في فئات منفصلة.
- مبدأ الفتح/الإغلاق (Open/Closed Principle). إدخال حالات جديدة دون تغيير فئات الحالة القائمة أو السياق.
- تبسيط كود السياق بإزالة شروط آلة الحالات الضخمة.
- قد يكون تطبيق النمط مبالغة إذا كانت آلة الحالات تحتوي على عدد قليل فقط من الحالات أو نادراً ما تتغيّر.
##Relations with Other Patterns
-
تتشارك أنماط الجسر (Bridge) والحالة (State) والاستراتيجية (Strategy) (وإلى حدٍّ ما المُكيِّف (Adapter)) في بنى متشابهة جداً. فجميع هذه الأنماط تقوم على التركيب (composition)، أي تفويض العمل إلى كائنات أخرى. غير أنها جميعاً تحلّ مشكلات مختلفة. فالنمط ليس مجرّد وصفة لهيكلة كودك بطريقة معيّنة، بل يمكنه أيضاً أن ينقل إلى المطوّرين الآخرين المشكلة التي يحلّها النمط.
-
يمكن اعتبار نمط الحالة (State) امتداداً لنمط الاستراتيجية (Strategy). فكلا النمطين يقوم على التركيب: إذ يغيّران سلوك السياق بتفويض بعض العمل إلى كائنات مساعدة. تجعل الاستراتيجية هذه الكائنات مستقلّة تماماً وغير مدركة لبعضها. أما الحالة فلا تقيّد الاعتماديات بين الحالات المحسوسة، إذ تتيح لها تغيير حالة السياق كما تشاء.