أهلاً! اليوم سوف نتطرق لموضوع جديد مهم وهو أنماط التصميم . ما هي هذه الأنماط؟ أعتقد أنك يجب أن تعرف عبارة " لا تعيد اختراع العجلة ". في البرمجة، كما هو الحال في العديد من المجالات الأخرى، هناك عدد كبير من المواقف الشائعة. ومع تطور تطوير البرمجيات، تم إنشاء حلول جاهزة صالحة لكل منها. تسمى هذه الحلول أنماط التصميم. وفقًا للاتفاقية، النمط هو عبارة عن حل تمت صياغته على النحو التالي: "إذا كنت بحاجة إلى تنفيذ X في برنامجك، فهذه هي أفضل طريقة للقيام بذلك". هناك الكثير من الأنماط. الكتاب الممتاز "Head First Design Patterns"، والذي يجب أن تتعرف عليه بالتأكيد، مخصص لهم. باختصار، يتكون النموذج من مشكلة شائعة وحل مناظر يمكن اعتباره نوعًا من المعايير. في درس اليوم سنتعرف على أحد هذه الأنماط: المحول. الاسم يقول كل شيء، وقد واجهت محولات عدة مرات في الحياة الواقعية. بعض المحولات الأكثر شيوعًا هي قارئات البطاقات الموجودة في العديد من أجهزة الكمبيوتر وأجهزة الكمبيوتر المحمولة. لنفترض أن لدينا نوعًا من بطاقة الذاكرة. إذا ما هي المشكلة؟ ولا يعرف كيفية التفاعل مع الكمبيوتر. لا يشتركون في واجهة مشتركة. يحتوي الكمبيوتر على منفذ USB، لكن لا يمكننا إدخال بطاقة الذاكرة فيه. لا يمكن توصيل البطاقة بالكمبيوتر، لذلك لا يمكننا حفظ الصور ومقاطع الفيديو والبيانات الأخرى. قارئ البطاقة هو محول يحل هذه المشكلة. بعد كل شيء، لديه كابل USB! على عكس البطاقة نفسها، يمكن توصيل قارئ البطاقة بالكمبيوتر. يتشاركون في واجهة مشتركة مع الكمبيوتر: USB. دعونا نرى كيف يبدو هذا في الممارسة العملية:
public interface USB {
void connectWithUsbCable();
}
هذه هي واجهة USB الخاصة بنا مع طريقة واحدة فقط للاتصال عبر USB.
public class MemoryCard {
public void insert() {
System.out.println("Memory card successfully inserted!");
}
public void copyData() {
System.out.println("The data has been copied to the computer!");
}
}
هذا هو صفنا الذي يمثل بطاقة الذاكرة. إنه يحتوي بالفعل على الطريقتين اللتين نحتاجهما، ولكن هنا تكمن المشكلة: فهو لا يطبق واجهة USB. لا يمكن إدخال البطاقة في منفذ USB.
public class CardReader implements USB {
private MemoryCard memoryCard;
public CardReader(MemoryCard memoryCard) {
this.memoryCard = memoryCard;
}
@Override
public void connectWithUsbCable() {
this.memoryCard.insert();
this.memoryCard.copyData();
}
}
وهنا محول لدينا! ماذا CardReader
يفعل الفصل وما الذي يجعله محولًا بالضبط؟ كل شيء بسيط. تصبح الفئة التي يتم تكييفها (MemoryCard) أحد حقول المحول. هذا يبدو منطقيا. عندما نضع بطاقة ذاكرة داخل قارئ البطاقات في الحياة الواقعية، فإنها تصبح أيضًا جزءًا منه. وعلى عكس بطاقة الذاكرة، يشترك المحول في واجهة مع الكمبيوتر. يحتوي على كابل USB، أي أنه يمكن توصيله بأجهزة أخرى عبر USB. ولهذا السبب تقوم فئة CardReader الخاصة بنا بتنفيذ واجهة USB. ولكن ماذا يحدث بالضبط داخل هذه الطريقة؟ بالضبط ما نحتاج أن يحدث! يقوم المحول بتفويض العمل إلى بطاقة الذاكرة الخاصة بنا. في الواقع، المحول لا يفعل أي شيء بنفسه. لا يحتوي قارئ البطاقة على أي وظيفة مستقلة. وظيفتها هي فقط توصيل الكمبيوتر وبطاقة الذاكرة للسماح للبطاقة بالقيام بعملها - نسخ الملفات! يتيح المحول الخاص بنا ذلك من خلال توفير الواجهة الخاصة به ( connectWithUsbCable()
الطريقة) لتلبية "احتياجات" بطاقة الذاكرة. لنقم بإنشاء برنامج عميل يحاكي الشخص الذي يريد نسخ البيانات من بطاقة الذاكرة:
public class Main {
public static void main(String[] args) {
USB cardReader = new CardReader(new MemoryCard());
cardReader.connectWithUsbCable();
}
}
وذلك ما لم نحصل؟ إخراج وحدة التحكم:
Memory card successfully inserted!
The data has been copied to the computer!
ممتاز. لقد حققنا هدفنا! إليك رابط لمقطع فيديو يحتوي على معلومات حول نمط المحول:
فئات مجردة القارئ والكاتب
سنعود الآن إلى نشاطنا المفضل: التعرف على فصلين جديدين للعمل مع الإدخال والإخراج :) وأتساءل عن عدد الفصول التي تعلمنا عنها بالفعل. اليوم سنتحدث عن الطبقاتReader
والطبقات Writer
. ولماذا تلك الفئات تحديداً؟ لأنها مرتبطة بقسمنا السابق حول المحولات. دعونا نفحصها بمزيد من التفصيل. سنبدأ ب Reader
. Reader
هي فئة مجردة، لذلك لن نكون قادرين على إنشاء كائنات بشكل صريح. لكنك في الواقع على دراية به بالفعل! بعد كل شيء، أنت على دراية جيدة بالفئات BufferedReader
والطبقات InputStreamReader
التي هي من نسلها :)
public class BufferedReader extends Reader {
…
}
public class InputStreamReader extends Reader {
…
}
الفصل InputStreamReader
عبارة عن محول كلاسيكي. كما تتذكر على الأرجح، يمكننا تمرير InputStream
كائن إلى منشئه. للقيام بذلك، نستخدم عادة المتغير System.in
:
public static void main(String[] args) {
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
}
ولكن ماذا InputStreamReader
يفعل؟ مثل كل محول، فهو يحول واجهة إلى أخرى. في هذه الحالة، InputStream
الواجهة إلى Reader
الواجهة. في البداية، لدينا InputStream
الطبقة. إنه يعمل بشكل جيد، ولكن يمكنك استخدامه فقط لقراءة وحدات البايت الفردية. وبالإضافة إلى ذلك، لدينا Reader
فئة مجردة. لديه بعض الوظائف المفيدة جدًا - فهو يعرف كيفية قراءة الأحرف! نحن بالتأكيد بحاجة إلى هذه القدرة. ولكن هنا نواجه المشكلة الكلاسيكية التي يتم حلها عادة عن طريق المحولات - الواجهات غير المتوافقة. ماذا يعني ذالك؟ دعونا نلقي نظرة على وثائق أوراكل. فيما يلي أساليب الفصل InputStream
. مجموعة الأساليب هي بالضبط الواجهة. كما ترون، هذه الفئة لديها read()
طريقة (في الواقع، عدد قليل من المتغيرات)، ولكن يمكنها قراءة البايتات فقط: إما بايتات فردية، أو عدة بايتات باستخدام المخزن المؤقت. لكن هذا الخيار لا يناسبنا، فنحن نريد قراءة الشخصيات. نحن بحاجة إلى الوظيفة التي تم تنفيذها بالفعل في Reader
فئة مجردة . يمكننا أن نرى هذا أيضًا في الوثائق. ومع ذلك، فإن InputStream
الواجهات Reader
غير متوافقة! كما ترون، كل تطبيق لهذه read()
الطريقة له معلمات وقيم إرجاع مختلفة. وهذا هو المكان الذي نحتاجه InputStreamReader
! سيكون بمثابة محول بين فصولنا. كما في مثال قارئ البطاقة، الذي تناولناه أعلاه، وضعنا مثيلًا للفئة التي تم تكييفها "داخل" فئة المحول، أي أننا قمنا بتمرير واحدة إلى مُنشئها. في المثال السابق، قمنا بوضع MemoryCard
كائن داخل CardReader
. الآن نقوم بتمرير InputStream
كائن إلى InputStreamReader
المنشئ! نستخدم System.in
المتغير المألوف لدينا كـ InputStream
:
public static void main(String[] args) {
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
}
وبالفعل، بالنظر إلى الوثائق الخاصة بـ InputStreamReader
، يمكننا أن نرى أن التعديل نجح :) الآن لدينا طرق لقراءة الأحرف المتاحة لنا. وعلى الرغم من أن System.in
كائننا (الدفق المرتبط بلوحة المفاتيح) لم يسمح بذلك في البداية، إلا أن منشئي اللغة قاموا بحل هذه المشكلة من خلال تطبيق نمط المحول. الطبقة Reader
المجردة، مثل معظم فئات الإدخال/الإخراج، لها شقيق توأم - Writer
. إنه يتمتع بنفس الميزة الكبيرة Reader
- فهو يوفر واجهة ملائمة للعمل مع الشخصيات. مع تدفقات الإخراج، تبدو المشكلة وحلها كما هو الحال مع تدفقات الإدخال. هناك OutputStream
فئة يمكنها كتابة البايتات فقط، وهناك Writer
فئة مجردة تعرف كيفية العمل مع الأحرف، وهناك واجهتان غير متوافقتين. تم حل هذه المشكلة مرة أخرى عن طريق نمط المحول. نحن نستخدم OutputStreamWriter
الفصل لتكييف الواجهتين بسهولة مع Writer
بعضهما OutputStream
البعض. بعد تمرير OutputStream
دفق بايت إلى المنشئ، يمكننا استخدام OutputStreamWriter
لكتابة الأحرف بدلاً من البايتات!
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
OutputStreamWriter streamWriter = new OutputStreamWriter(new FileOutputStream("C:\\Users\\Username\\Desktop\\test.txt"));
streamWriter.write(32144);
streamWriter.close();
}
}
لقد كتبنا الحرف بالكود 32144 (綐) في ملفنا، مما يلغي الحاجة إلى العمل بالبايتات :) هذا كل ما لدينا اليوم. نراكم في الدروس القادمة! :)
GO TO FULL VERSION