السلام عليكم ورحمه الله وبركاته ،،
من المعروف والمسَلَم به في عالم البرمجيات القاعده " Software Requirement Always change "، هذه القاعده تنص على أن متطلبات البرنامج سواء كانت General Software موجه للعامه أو Custom Software موجه لزبون معين سوف تتغير بمرور الزمن ، فالبرنامج الذي كان يشغل صوتيات mp3 يجب أن يشغل في الاصداره القادمه صوتيات ram,rm والا بالتأكيد سوف يتجاهل المستخدمين هذا البرنامج ويذهبوا الى ما هو أفضل ، بنفس الأمر البرنامج الذي صممته لاداره المدرسه القريبه منكم تغيرت متطلباتها حيث تريد الاداره أن يقوم البرنامج باصدار شهادات للطلاب بالاضافه الى امكانيه استعلام الطالب عن نتيجته من خلال موقع المدرسه ،،
مع كل هذه التغييرات التي لا مفر بها ، عرف المبرمجين ومهندسي البرمجيات ضروره تصميم البرمجيات باستخدام أساليب جيده تجعل عمليه التغيير في البرنامج أمر سهل وغير مكلف بتاتا (ونقصد لا يحتاج الى تغيير في الكود الأصلي ، فقط اضافه المزيد) ، وبدأت تظهر منهجيات Methodology لا تخاف أبدا من التغيير بل تشجع الزبون على تغيير متطلباته أيضا . هذه المنهجيات هي عباره عن مجموعه من الخطوات التي يجب أن تلتزم بها عندما تبدأ في تصميم برمجياتك باستخدام المنهج المعين ، كما أنها تحتوي على مفاهيم وطرق تختلف من منهجيه لأخرى ،
وبغض النظر عن المنهجيه المستخدمه في التطوير ، فما زالت هناك مشاكل تحدث دائما عند تصميم البرامج (أتحدث عن البرامج التي تستخدم أسلوب البرمجه الكائنيه Object Oriented ) فمثلا قمت بانشاء كائننين في البرنامج السؤال هو ما هو الكائن المسؤول عن توليد الأخر ، من الذي سوف تنشئه أولا ومن الذي ستنهيه أخرا ، مثال أخر لديك مثلا كائن عباره عن timer للعبه ما ، وأنت تريد أن تكون هناك timer واحده فقط في البرنامج ، كيف يمكن أن تمنع المستخدم للكائن Timer من أن ينشئ كائن أخر ،،
هذه المشاكل وغيرها الكثير هي مشاكل في تصميم Design البرنامج أو الكلاسات في البرنامج ، ومع تقدم الوقت بدأت خبره المبرمجين تزداد وظهرت حلول عمليه لأشهر هذه المشاكل وكيف يمكن أن تحلها بأسلوب مبسط وأكثر كفائه ، هذه الحلول للمشاكل أصبح يطلق عليها حاليا في مصطلح جديد الا وهو الdesign pattern .
علم تصميم الأنماط Design Pattern هو عباره عن تصاميم معينه لمشاكل لطالما سوف تظهر في أي مشروع برمجي (يستخدم بالطبع مفهوم OO) ، وهذه الحلول قدمها مهندسي البرمجيات والمبرمجين المخترفين على مر الزمن وأصبح من الأفضل للمبتدئ مباشره استخدام هذه الانماط في برنامجه لحل المشكله بدلا من التفكيير في حل قد يكون غير صحيح أو قد يُظهر مشاكل أخرى فيما بعد ،
سنتناول في هذه المقاله البسيطه واحد من أشهر الأنماط ويستخدم بشكل كبير في تصميم واجهات البرامج OO GUI Desgin الا وهو نمط Model-View-Controller واختصارا MVC ،
ما هو الMVC ؟
=-=-=-=-=-=-=
نمط MVC يقوم على أساس فصل الواجهه Interface (سواء GUI,console) وتسمى View عن التطبيق والذي هو مشكله البرنامج الذي تقوم على حلها Busniess Logic وتسمى Model وعن التحكم في الواجهه (مثلا الحدث الفلاني عندما يتم التعامل مع أحد الأزرار في الواجهه) ويسمى Controller .
بعباره مبسطه ينص نمط MVC على فصل الواجهه Interface من التطبيق نفسه ، بحيث في حال تغيير المتطلبات وتم تعديل الBusniess Logic فان الواجهه تكون كما هي بدون أي تغيير ، وبنفس الأمر في حال تغييرت المتطلبات وتم تحديث الواجهه (اضافه واجهه أكثر احترافيه) فإن طبقه الBusiness Logic تكون كما هي بدون تغيير ،،
لنأخذ مثال بسيط لكي نوضح الفكره في تقسم البرنامج لعده طبقات ، مثلا تريد كتابه تطبيق شبكي يتصل بجهه معينه ويقوم بأخذ بيانات ما ويخرجها لديه في الشاشه ،،الطريقه الأعتياديه التي يقوم بها المبرمجين المبتدئين وهي كتابه التطبيق بالكامل في ملف واحد (التعامل الشبكي ، الواجهه ، الأحداث والتعامل معها) وبمجرد انتهاء البرنامج ستجد هذا المبرمج -المسكين- في قمه السعاده لأنه باع أول برنامج قام به وأخذ عمولته بالكامل $$ ، لكن ولنفرض بمرور أيام اتصل العميل وطلب من المبرمج أن يغير طريقه طلب الخدمه أو الأتصال الشبكي ،، من هنا ستبدأ رحله معاناه مبرمجنا المسكين حيث سيتوجب عليه تغيير طريقه تقديم الخدمه في الجزء الشبكي وليس هذا فقط بل تغيير جزء الواجهه المرتبط بالجزء القديم بالاضافه الى الجزء المتحكم والذي هو event-handler يجب أن يتغير أيضا ،، كل هذا بسبب التزواج والأعتماديه الخاطئه في تصميم الكلاسات من البدايه ،،
لكن ماذا لو قام المبرمج من البدايه باتباع اسلوب MVC ، فقط عليه التغيير في الجزء Model (المتعلق بالتعامل الشبكي) ولن يحتاج لأي تغيير في الواجهه والتحكم VC ، والسبب أن Model لا يعلم أصلا كيف سيتم عرضه (GUI,Console) والجزء المتعلق بالعرض هو View . لذلك استخدام هذا الأسلوب من البدايه يجعل البرنامج قابل للتغيير والتطوير بشكل أفضل بكثير ،،
بعض الأحيان أو في الكثير من الأحيان الجزء View والجزء Controller يكونان مع بعض والسبب أنهم قريبين جدا ويصعب الفصل بينهم ، فالزر Button يمثل الواجهه ، والحدث الخاص بالضغط عليه يمثل المتحكم وهو يقوم بالوظيفه المناسبه ، وحقيقه الفصل بينهم كل منهم في طبقه أمر يعقد البرنامج أكثر لذلك الكثير يجعل VC مع بعض وهكذا سوف نستخدم النمط M-VC .
نأخذ مثال بسيط بلغه الجافا لكي يتضح المفهوم بشكل أكبر ، المثال عن برنامج لتربيع العدد في نفسه فقط ، وسنقوم أولا بعمل مثال بالطريقه العاديه لكي نرى محدوديه الطرق العاديه واستخدام MVC أو M-VC أفضل بكثير (المثال Toy example ولكن المفهوم واحد ، وفي الأمثله الكبيره ستتجلي فائده MVC بشكل أكبر) .
CODE: تحديد الكل
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.math.BigInteger;
public class BigBlob {
public static void main(String[] args) {
JFrame window = new BigBlobGUI();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLO SE);
window.setTitle("Simple Calc");
window.setVisible(true);
}
}
class BigBlobGUI extends JFrame {
//... Constants
private static final String INITIAL_VALUE = "1";
//... Components
private JTextField m_totalTf = new JTextField(10);
private JTextField m_userInputTf = new JTextField(10);
private JButton m_multiplyBtn = new JButton("Multiply");
private JButton m_clearBtn = new JButton("Clear");
private BigInteger m_total; // The total current value state.
/** Constructor */
BigBlobGUI() {
//... Initialize components and model
m_total = new BigInteger(INITIAL_VALUE);
m_totalTf.setText(INITIAL_VALUE);
m_totalTf.setEditable(false);
//... Layout the components.
JPanel content = new JPanel();
content.setLayout(new FlowLayout());
content.add(new JLabel("Input"));
content.add(m_userInputTf);
content.add(m_multiplyBtn);
content.add(new JLabel("Total"));
content.add(m_totalTf);
content.add(m_clearBtn);
//... finalize layout
this.setContentPane(content);
this.pack();
//... Listener to do multiplication
m_multiplyBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
m_total = m_total.multiply(new BigInteger(m_userInputTf.getText()));
m_totalTf.setText(m_total.toString());
} catch (NumberFormatException nex) {
JOptionPane.showMessageDialog(BigBlobGUI.this, "Bad Number");
}
}
});
//... Listener to clear.
m_clearBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
m_total = new BigInteger(INITIAL_VALUE);
m_totalTf.setText(INITIAL_VALUE);
}
});
}
}
المخرج من البرنامج بعدتشغيله :
الان ماذا لو طلب منك تغيير الرقم من BigInteger الى BigDecimal ، هل يمكن التغيير بسهوله ؟
ماذا لو طلب تغيير الواجهه الى console أو تطبيق يعمل في الويب ، هل يمكن التغيير بسهوله ؟
الاجابه لا ، بسبب الاعتماديه في التصميم ..
من هنا كان الفصل بين الModel والتطبيق مهم جدا ، ففي حال كنا نريد تغيير الواجهه ، فلن تكون هناك أي علاقه مع الModel والعكس أيضا ، نأخذ المثال باستخدام M-VC :
ملف Main :
CODE: تحديد الكل
import javax.swing.*;
public class CalcV3 {
public static void main(String[] args) {
JFrame presentation = new CalcViewController();
presentation.setVisible(true);
}
}
ملف CalcViewController (الواجهه The User Interface ) :
CODE: تحديد الكل
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CalcViewController extends JFrame {
//Constants
private static final String INITIAL_VALUE = "1";
//instance vars
//... The Model.
private CalcModel m_logic;
//... Components
private JTextField m_userInputTf = new JTextField(5);
private JTextField m_totalTf = new JTextField(20);
private JButton m_multiplyBtn = new JButton("Multiply");
private JButton m_clearBtn = new JButton("Clear");
//constructor
/** Constructor */
CalcViewController() {
//... Set up the logic
m_logic = new CalcModel();
m_logic.setValue(INITIAL_VALUE);
//... Initialize components
m_totalTf.setText(m_logic.getValue());
m_totalTf.setEditable(false);
//... Layout the components.
JPanel content = new JPanel();
content.setLayout(new FlowLayout());
content.add(new JLabel("Input"));
content.add(m_userInputTf);
content.add(m_multiplyBtn);
content.add(new JLabel("Total"));
content.add(m_totalTf);
content.add(m_clearBtn);
//... Add button listeners.
m_multiplyBtn.addActionListener(new MultiplyListener());
m_clearBtn.addActionListener(new ClearListener());
//... finalize layout and set window parameters.
this.setContentPane(content);
this.pack();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE );
this.setTitle("Simple Calc - Presentation-Model");
}//end constructor
////////////////////////////////////////// inner class MultiplyListener
/** When a multiplication is requested.
* 1. Get the user input number.
* 2. Call the model to mulitply by this number.
* 3. Get the result from the Model.
* 4. Set the Total textfield to this result.
* If there was an error, display it in a JOptionPane.
*/
class MultiplyListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
String userInput = "";
try {
userInput = m_userInputTf.getText();
m_logic.multiplyBy(userInput);
m_totalTf.setText(m_logic.getValue());
} catch (NumberFormatException nfex) {
JOptionPane.showMessageDialog(CalcViewController.t his,
"Bad input: '" + userInput + "'");
}
}
}//end inner class MultiplyListener
//////////////////////////////////////////// inner class ClearListener
/** 1. Reset model.
* 2. Put model's value into Total textfield.
*/
class ClearListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
m_logic.reset();
m_totalTf.setText(m_logic.getValue());
}
}
}
ملف الModel :
CODE: تحديد الكل
import java.math.BigInteger;
public class CalcModel {
//... Constants
private static final String INITIAL_VALUE = "0";
//... Member variable defining state of calculator.
private BigInteger m_total; // The total current value state.
//constructor
/** Constructor */
public CalcModel() {
reset();
}
//reset
/** Reset to initial value. */
public void reset() {
m_total = new BigInteger(INITIAL_VALUE);
}
//multiplyBy
/** Multiply current total by a number.
*@param operand Number (as string) to multiply total by.
*/
public void multiplyBy(String operand) {
m_total = m_total.multiply(new BigInteger(operand));
}
//setValue
/** Set the total value.
*@param value New value that should be used for the calculator total.
*/
public void setValue(String value) {
m_total = new BigInteger(value);
}
//getValue
/** Return current calculator total. */
public String getValue() {
return m_total.toString();
}
}
الأن تغير المتطلب وأردت أن تستخدم BigDecmial في Model ، فقط كل ما عليك التغيير في ملف Model فقط ، ولن يؤثر ذلك في الواجهه ، وبنفس الفكره لو طلب منك الواجهه تكون نصيه console فقط كل ما عليك استبدال ملف الواجهه بملف للتعامل مع الواجهه النصيه console ويكون الModel كما هو بدون أي تغيير .
هناك الكثير من المكتبات تستخدم هذا النمط كقاعده اساسيه مثل مكتبات java swing و Qt و GTK وغيرهم الكثير ممن عرفوا فائده هذه النمط ،، أيضا هذا المفهوم يمكن أن يمتد لاسلوب تقسيم البرنامج لثلاثه طبقات تعمل كل منهم في جهاز منفصل (جهاز المستخدم الواجهه ، طبقه في الوسط busniess logic ، وطبقه بيانات في السيرفر للحفظ) ويستخدم في الغالب لدى مطورين الويب، سوف نتكلم عنها فيما بعد ان شاء الله ،،
أرجوا أن تكون المقاله مفهومه ، وأن تستخدموا هذا النمط في جميع برامجكم حيث سيضمن لكم القابليه للتغيير والتعديل بأبسط وأسهل شكل ممكن ،،
المصادر :
Model-view-controller
Big Blob Structure
Applying MVC in VisualAge for Java
والسلام عليكم ،،