نمط الواجهة
نمط الواجهة هو نمط تصميم هيكلي يوفر واجهة مبسطة لمكتبة ما أو إطار عمل أو أي تركيبة معقدة من الفئات.
المشكلة
تخيل أن عليك جعل شيفرتك تعمل مع نطاق واسع من الكائنات التي تنتمي إلى مكتبة معقدة أو إطار عمل معقد، فالمعتاد أنك ستحتاج إلى بدء كل تلك الكائنات وتتابع الاعتماديات وتنفذ الأساليب بالترتيب الصحيح، وهكذا. ونتيجة لهذا يكون المنطق التجاري لفئاتك (Business Logic) مرتبطًا بشدة بتفاصيل الاستخدام لفئات الطرف الثالث، مما يجعل من الصعب عليك إدراكه وصيانته.
الحل
ويأتي نمط الواجهة (Facade) هنا كفئة توفر واجهة بسيطة لنظام فرعي معقد يحتوي الكثير من الأجزاء المتحركة، وقد يكون ما يقدمه نمط الواجهة محدودًا مقارنة بالعمل مع النظام الفرعي مباشرة، لكنها ستحتوي فقط على المزايا التي يرغب فيها العملاء.
سيكون نمط الواجهة مفيدًا أيضًا عندما تحتاج إلى تكامل تطبيقك مع مكتبة معقدة بها عشرات المزايا، لكنك لا تريد إلا جزءًا صغيرًا من وظائفها. فقد يستخدم تطبيق يرفع مقاطع مضحكة للقطط إلى الشبكات الاجتماعية مكتبة تحويل فيديو احترافية، لكنه لا يحتاج حقيقة إلا إلى فئة مع أسلوب (encode(filename, format
وحيد، وبعد إنشاء هذه الفئة وتوصيلها بمكتبة تحويل الفيديو، تكون بهذا قد صنعت أول واجهة لك.
مثال واقعي
ضع الصورة. طلب المنتجات عن طريق الهاتف.
حين تتصل بمتجر لتطلب منتجًا ما، فإن الموظف على الطرف الأخر هو واجهتك التي تتصل من خلالها بالخدمات والأقسام داخل المتجر، بدلًا من التعامل مع المتجر مباشرة، وما يقدمه لك الموظف هو واجهة صوتية بسيطة لنظام الطلبات وطرق الدفع وخدمات التوصيل المختلفة.
البُنية
- توفر الواجهة وصولًا سهلًا إلى جزء معين من وظائف النظام الفرعي، وهو يعرف إلى أين يوجِّه طلب العميل وكيف يدير كل الأجزاء المتحركة.
- يمكن إنشاء فئة واجهة إضافية لمنع تلويث واجهة ما بمزايا خارج نطاق وظيفتها تجعلها نظامًا فرعيًا معقدًا لا يختلف عن النظام الذي أنشئت من أجل تبسيطه، ويمكن استخدام الواجهات الإضافية من قبل العملاء والواجهات الأخرى على حد سواء.
- يتكون النظام المعقد من عشرات الكائنات المختلفة، وعليك أن تتعمق في تفاصيل استخدام النظام الفرعي من أجل جعلهم جميعًا ينفذون شيئًا مفيدًا. هذه التفاصيل قد تكون بدء الكائنات بالترتيب الصحيح، وتزويدها بالبيانات في صيغ مناسبة. ولا تكون فئات النظام الفرعي مدركة لوجود الواجهة، فهي تعمل داخل النظام وتتعامل مع بعضها مباشرة.
- يستخدم العميل الواجهة بدلًا من استدعاء كائنات النظام الفرعي مباشرة.
مثال توضيحي
في هذا المثال، يبسط نمط الواجهة التفاعل مع إطار عمل معقد لتحويل الفيديو.
ضع الصورة. مثال لعزل عدة اعتماديات داخل فئة واجهة وحيدة.
بدلًا من جعل شيفرتك تعمل مع العشرات من فئات إطار العمل مباشرة، فإنك تنشئ فئة واجهة تحتوي هذه الوظيفة وتخفيها من بقية الشيفرة، وهذه البنية تساعدك أيضًا على تقليل الجهد اللازم للترقية إلى نسخ مستقبلية من إطار العمل أو استبداله بغيره، والشيء الوحيد الذي ستحتاج إلى تغييره في تطبيقك هو الاستخدام لأساليب الواجهة.
// هذه بعض من فئات لإطار عمل من طرف ثالث معقد لتحويل الفيديو.
// نحن لا نتحكم في هذه الشيفرة، ولهذا لا نستطيع تبسيطها.
class VideoFile
// ...
class OggCompressionCodec
// ...
class MPEG4CompressionCodec
// ...
class CodecFactory
// ...
class BitrateReader
// ...
class AudioMixer
// ...
// سننشئ فئة واجهة تخفي تعقيد إطار العمل خلف واجهة بسيطة.
// هذه مقايضة بين الوظيفة والبساطة.
class VideoConverter is
method convert(filename, format):File is
file = new VideoFile(filename)
sourceCodec = new CodecFactory.extract(file)
if (format == "mp4")
destinationCodec = new MPEG4CompressionCodec()
else
destinationCodec = new OggCompressionCodec()
buffer = BitrateReader.read(filename, sourceCodec)
result = BitrateReader.convert(buffer, destinationCodec)
result = (new AudioMixer()).fix(result)
return new File(result)
// لا تعتمد فئات التطبيق على ملايين الفئات التي يوفرها إطار العمل المعقد.
// وكذلك، فإن قررت تبديل أطر العمل فلا تحتاج إلا إلى كتابة فئة الواجهة فقط.
class Application is
method main() is
convertor = new VideoConverter()
mp4 = convertor.convert("youtubevideo.ogg", "mp4")
mp4.save()
قابلية التطبيق
استخدم نمط الواجهة عندما ترغب في واجهة محدودة ومباشرة في نفس الوقت تربطك بنظام فرعي معقد.
عادة ما يزيد تعقيد الأنظمة الفرعية مع الوقت حتى أن مجرد استخدام أنماط التصميم فيها أحيانًا يقود إلى مزيد من الفئات، ورغم أنه قد تكون تلك الأنظمة الفرعية حين يزيد حجمها أكثر مرونة وسهولة في إعادة الاستخدام، إلا أن كم الإعدادات والنصوص المعيارية (Boilerplates) المطلوبة من العميل تزيد أيضًا بمقدار ربما أكبر من زيادة النظام الفرعي نفسه. ويحاول نمط الواجهة حل هذه المشكلة بتوفير اختصارات لأغلب المزايا التي يكثر استخدامها في النظام الفرعي، والتي تناسب أغلب احتياجات العملاء.
استخدم نمط الواجهة حين تريد هيكلة نظام فرعي في هيئة طبقات.
أنشئ واجهات لتعريف نقاط الدخول (entry points) لكل مستوى في نظام فرعي ما، وهكذا تقلل الارتباط بين عدة أنظمة فرعية من خلال إجبارها على التواصل فيما بينها من خلال الواجهات فقط.
فمثلًا، بالعودة إلى مثال إطار العمل الخاص بتحويل الفيديو المذكور أعلاه، فإنك تقسِّمه إلى طبقتين، إحداهما تتعلق بالفيديو والأخرى بالصوت، وتنشئ واجهة لكل طبقة وتجعل فئاتها تتواصل مع فئات الطبقة الأخرى من خلال الواجهات التي أنشأتها. يشبه هذا الأسلوب في تبسيط حل تعقيد المثال أسلوبَ نمط الوسيط (Mediator).
كيفية الاستخدام
- تأكد من إمكانية توفير واجهة أبسط من التي يوفرها النظام الفرعي الموجود حاليًا، فإن كانت الواجهة ستجعل شيفرة العميل مستقلة من فئات النظام الفرعي تكون أفضل من واجهة النظام الموجودة.
- صرِّح عن هذه الواجهة واستخدمها في فئة واجهة (facade) جديدة، وينبغي لهذه الواجهة أن تعيد توجيه الاستدعاءات من شيفرة العميل إلى الكائنات المناسبة في النظام الفرعي، ويجب أن تكون الواجهة مسؤولة عن بدء النظام الفرعي وإدارة دورة حياته ما لم تكن شيفرة العميل تقوم بهذا فعلًا.
- للحصول على أقصى فائدة من النمط، اجعل شيفرة العميل تتواصل مع النظام الفرعي من خلال الواجهة فقط، وهكذا تكون شيفرة العميل محصنة من أي تغيير يحدث في شيفرة النظام الفرعي. فمثلًا حين يحصل نظام فرعي على ترقية إلى إصدار أعلى، فلا تحتاج إلى تغيير شيء سوى الشيفرة التي في الواجهة.
- إن أصبحت الواجهة كبيرة جدًا، فابحث إمكانية استخراج جزء من سلوكها إلى فئة واجهة جديدة منقحة.
المزايا والعيوب
المزايا
يمكنك عزل شيفرتك من تعقيد النظام الفرعي.
العيوب
يمكن أن تتحول الواجهة إلى كائن إلهي (God Object) يرتبط بكل الفئات في التطبيق.
العلاقات مع الأنماط الأخرى
يصنع نمط الواجهةِ واجهةً جديدة لكائنات موجودة فعلًا، في حين يحاول نمط المحول أن يجعل الواجهة الحالية قابلة للاستخدام. وبينما يغلف نمط المحول كائنًا واحدًا، تعمل الواجهة مع نظام فرعي كامل من الكائنات.
يستطيع المصنع المجرد أن يكون بديلًا عن الواجهة حين تريد إخفاء الطريقة التي تُنشأ بها كائنات النظام الفرعي عن شيفرة العميل.
يوضح نمط وزن الذبابة (Flyweight) كيف تصنع الكثير من الكائنات الصغيرة، بينما تشرح الواجهة كيف تصنع كائنًا وحيدًا يمثل نظامًا فرعيًا كاملًا.
وظيفة نمط الواجهة تشبه الوظيفة الخاصة بنمط الوسيط، إذ يحاول كل منهما تنظيم التعاون بين فئات كثيرة مرتبطة ببعضها بشدة.
يحدد نمط الواجهة واجهة مبسطة لنظام فرعي من الكائنات، لكنه لا يقدم أي وظيفة جديدة، والنظام الفرعي نفسه لا يكون مدركًا للواجهة تلك، وتستطيع الكائنات التي بداخل النظام الفرعي أن تتواصل بشكل مباشر.
يمركز نمط الوسيط التواصل بين مكونات النظام، ولا تعرف هذه المكونات إلا كائنَ الوسيط، ولا تتواصل بشكل مباشر.
تستطيع فئة الواجهة أن تتحول إلى مفردة بما أن كائن واجهة وحيد يكفي في أغلب الأحيان.
يتشابه نمط الواجهة مع نمط الوكيل في أن كليهما يبسِّط كيانًا معقدًا ويبدؤه بشكل مستقل، وعلى عكس نمط الواجهة فإن نمط الوكيل لديه نفس الواجهة التي لدى كائن الخدمة الخاص به، مما يجعلهما قابلين للتبادل.
الاستخدام في لغة جافا
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يشيع استخدام نمط الواجهة في التطبيقات المكتوبة بلغة جافا، وهو مفيد خاصة عند العمل مع مكتبات وواجهات برمجة تطبيقات معقدة. إليك بعض أمثلة الواجهة في مكتبات جافا:
تستخدم javax.faces.context.FacesContext
فئات LifeCycle
و ViewHandler
و NavigationHandler
تحت الغطاء، لكن أغلب العملاء لا تدرك هذا.
تستخدم مكتبة javax.faces.context.ExternalContext
فئات ServletContext
و HttpSession
و HttpServletRequest
و HttpServletResponse
وغيرها.
يمكن ملاحظة نمط الواجهة في فئة لديها واجهة بسيطة لكن يفوض أغلب العمل إلى فئات أخرى، وعادة ما تدير الواجهات دورة حياة كاملة للكائنات التي تستخدمها.
واجهة بسيطة لمكتبة معقدة لتحويل الفيديو
تبسط الواجهة في هذا المثال عملية التواصل مع إطار عمل معقد لتحويل الفيديو، إذ توفر فئة واحدة فيها أسلوب واحد يعالج كل التعقيد الموجود في إعدادات الفئات المناسبة لإطار العمل وجلب النتيجة في صيغة صحيحة.
some_complex_media_library: مكتبة معقدة لتحويل الفيديو
some_complex_media_library/VideoFile.java
package refactoring_guru.facade.example.some_complex_media_library;
public class VideoFile {
private String name;
private String codecType;
public VideoFile(String name) {
this.name = name;
this.codecType = name.substring(name.indexOf(".") + 1);
}
public String getCodecType() {
return codecType;
}
public String getName() {
return name;
}
}
some_complex_media_library/Codec.java
package refactoring_guru.facade.example.some_complex_media_library;
public interface Codec {
}
some_complex_media_library/MPEG4CompressionCodec.java
package refactoring_guru.facade.example.some_complex_media_library;
public class MPEG4CompressionCodec implements Codec {
public String type = "mp4";
}
some_complex_media_library/OggCompressionCodec.java
package refactoring_guru.facade.example.some_complex_media_library;
public class OggCompressionCodec implements Codec {
public String type = "ogg";
}
some_complex_media_library/CodecFactory.java
package refactoring_guru.facade.example.some_complex_media_library;
public class CodecFactory {
public static Codec extract(VideoFile file) {
String type = file.getCodecType();
if (type.equals("mp4")) {
System.out.println("CodecFactory: extracting mpeg audio...");
return new MPEG4CompressionCodec();
}
else {
System.out.println("CodecFactory: extracting ogg audio...");
return new OggCompressionCodec();
}
}
}
some_complex_media_library/BitrateReader.java
package refactoring_guru.facade.example.some_complex_media_library;
public class BitrateReader {
public static VideoFile read(VideoFile file, Codec codec) {
System.out.println("BitrateReader: reading file...");
return file;
}
public static VideoFile convert(VideoFile buffer, Codec codec) {
System.out.println("BitrateReader: writing file...");
return buffer;
}
}
some_complex_media_library/AudioMixer.java
package refactoring_guru.facade.example.some_complex_media_library;
import java.io.File;
public class AudioMixer {
public File fix(VideoFile result){
System.out.println("AudioMixer: fixing audio...");
return new File("tmp");
}
}
الواجهة
facade/VideoConversionFacade.java: نمط الواجهة يوفر واجهة بسيطة لتحويل الفيديو
package refactoring_guru.facade.example.facade;
import refactoring_guru.facade.example.some_complex_media_library.*;
import java.io.File;
public class VideoConversionFacade {
public File convertVideo(String fileName, String format) {
System.out.println("VideoConversionFacade: conversion started.");
VideoFile file = new VideoFile(fileName);
Codec sourceCodec = CodecFactory.extract(file);
Codec destinationCodec;
if (format.equals("mp4")) {
destinationCodec = new OggCompressionCodec();
} else {
destinationCodec = new MPEG4CompressionCodec();
}
VideoFile buffer = BitrateReader.read(file, sourceCodec);
VideoFile intermediateResult = BitrateReader.convert(buffer, destinationCodec);
File result = (new AudioMixer()).fix(intermediateResult);
System.out.println("VideoConversionFacade: conversion completed.");
return result;
}
}
Demo.java: شيفرة العميل
package refactoring_guru.facade.example;
import refactoring_guru.facade.example.facade.VideoConversionFacade;
import java.io.File;
public class Demo {
public static void main(String[] args) {
VideoConversionFacade converter = new VideoConversionFacade();
File mp4Video = converter.convertVideo("youtubevideo.ogg", "mp4");
// ...
}
}
OutputDemo.txt: نتائج التنفيذ
VideoConversionFacade: conversion started.
CodecFactory: extracting ogg audio...
BitrateReader: reading file...
BitrateReader: writing file...
AudioMixer: fixing audio...
VideoConversionFacade: conversion completed.