وبغض النظر عن المنهجيه المستخدمه في التطوير ، فما زالت هناك مشاكل تحدث دائما عند تصميم البرامج (أتحدث عن البرامج التي تستخدم أسلوب البرمجه الكائنيه Object Oriented ) فمثلا قمت بانشاء كائننين في البرنامج السؤال هو ما هو الكائن المسؤول عن توليد الأخر ، من الذي سوف تنشئه أولا ومن الذي ستنهيه أخرا ، مثال أخر لديك مثلا كائن عباره عن timer للعبه ما ، وأنت تريد أن تكون هناك timer واحده فقط في البرنامج ، كيف يمكن أن تمنع المستخدم للكائن Timer من أن ينشئ كائن أخر ،،
هذه المشاكل وغيرها الكثير هي مشاكل في تصميم Design البرنامج أو الكلاسات في البرنامج ، ومع تقدم الوقت بدأت خبره المبرمجين تزداد وظهرت حلول عمليه لأشهر هذه المشاكل وكيف يمكن أن تحلها بأسلوب مبسط وأكثر كفائه ، هذه الحلول للمشاكل أصبح يطلق عليها حاليا في مصطلح جديد الا وهو الdesign pattern .
علم تصميم الأنماط Design Pattern هو عباره عن تصاميم معينه لمشاكل لطالما سوف تظهر في أي مشروع برمجي (يستخدم بالطبع مفهوم OO) ، وهذه الحلول قدمها مهندسي البرمجيات والمبرمجين المخترفين على مر الزمن وأصبح من الأفضل للمبتدئ مباشره استخدام هذه الانماط في برنامجه لحل المشكله بدلا من التفكيير في حل قد يكون غير صحيح أو قد يُظهر مشاكل أخرى فيما بعد ،
سنتناول في هذه المقاله البسيطه واحد من أشهر الأنماط ويستخدم بشكل كبير في تصميم واجهات البرامج OO GUI Design الا وهو نمط Model-View-Controller واختصارا MVC
ما هو الMVC ؟
نمط MVC يقوم على أساس فصل الواجهه Interface (سواء GUI,console) وتسمى View عن التطبيق والذي هو مشكله البرنامج الذي تقوم على حلها Business 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 بشكل أكبر) .
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_CLOSE); 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 :
import javax.swing.*; public class CalcV3 { public static void main(String[] args) { JFrame presentation = new CalcViewController(); presentation.setVisible(true); } }
ملف CalcViewController (الواجهه The User Interface ) :
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.this, "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 :
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 كما هو بدون أي تغيير .
أرجوا أن تكون المقاله مفهومه ، وأن تستخدموا هذا النمط في جميع برامجكم حيث سيضمن لكم القابليه للتغيير والتعديل بأبسط وأسهل شكل ممكن ،،
المصادر :
Model-view-controller
Big Blob Structure
Applying MVC in Visual Age for Java
Java Server Faces , Struts , Spring و هي تقنيات تستخدم في بناء تطبيقات للشركات ضمن Java EE كلها عبارة عن تحقيق للـ MVC