الفرق بين المراجعتين ل"Design Patterns/facade"
أسامه-دمراني (نقاش | مساهمات) (3.0 إضافة صور) |
أسامه-دمراني (نقاش | مساهمات) (3.1 تمام التعديل على الصور.) |
||
سطر 11: | سطر 11: | ||
== مثال واقعي == | == مثال واقعي == | ||
− | + | [[ملف:dpf.live-example-en.png|تصغير|(ش.1) طلب المنتجات عن طريق الهاتف.]] | |
حين تتصل بمتجر لتطلب منتجًا ما، فإن الموظف على الطرف الأخر هو واجهتك التي تتصل من خلالها بالخدمات والأقسام داخل المتجر، بدلًا من التعامل مع المتجر مباشرة، وما يقدمه لك الموظف هو واجهة صوتية بسيطة لنظام الطلبات وطرق الدفع وخدمات التوصيل المختلفة. | حين تتصل بمتجر لتطلب منتجًا ما، فإن الموظف على الطرف الأخر هو واجهتك التي تتصل من خلالها بالخدمات والأقسام داخل المتجر، بدلًا من التعامل مع المتجر مباشرة، وما يقدمه لك الموظف هو واجهة صوتية بسيطة لنظام الطلبات وطرق الدفع وخدمات التوصيل المختلفة. | ||
== البُنية == | == البُنية == | ||
− | [[ملف:dpf.structure.png]] | + | [[ملف:dpf.structure.png|تصغير|(ش.2)]] |
<br> | <br> | ||
# توفر الواجهة وصولًا سهلًا إلى جزء معين من وظائف النظام الفرعي، وهو يعرف إلى أين يوجِّه طلب العميل وكيف يدير كل الأجزاء المتحركة. | # توفر الواجهة وصولًا سهلًا إلى جزء معين من وظائف النظام الفرعي، وهو يعرف إلى أين يوجِّه طلب العميل وكيف يدير كل الأجزاء المتحركة. | ||
سطر 25: | سطر 25: | ||
== مثال توضيحي == | == مثال توضيحي == | ||
في هذا المثال، يبسط نمط الواجهة التفاعل مع إطار عمل معقد لتحويل الفيديو. | في هذا المثال، يبسط نمط الواجهة التفاعل مع إطار عمل معقد لتحويل الفيديو. | ||
− | [[ملف:dpf.example.png | + | [[ملف:dpf.example.png|بدون|تصغير|(ش.3) مثال لعزل عدة اعتماديات داخل فئة واجهة وحيدة.]] |
− | + | فبدلًا من جعل شيفرتك تعمل مع العشرات من فئات إطار العمل مباشرة، فإنك تنشئ فئة واجهة تحتوي هذه الوظيفة وتخفيها من بقية الشيفرة، وهذه البنية تساعدك أيضًا على تقليل الجهد اللازم للترقية إلى نسخ مستقبلية من إطار العمل أو استبداله بغيره، والشيء الوحيد الذي ستحتاج إلى تغييره في تطبيقك هو الاستخدام لأساليب الواجهة.<syntaxhighlight lang="java"> | |
− | |||
− | |||
// هذه بعض من فئات لإطار عمل من طرف ثالث معقد لتحويل الفيديو. | // هذه بعض من فئات لإطار عمل من طرف ثالث معقد لتحويل الفيديو. | ||
// نحن لا نتحكم في هذه الشيفرة، ولهذا لا نستطيع تبسيطها. | // نحن لا نتحكم في هذه الشيفرة، ولهذا لا نستطيع تبسيطها. |
مراجعة 08:21، 16 مارس 2019
نمط الواجهة هو نمط تصميم هيكلي يوفر واجهة مبسطة لمكتبة ما أو إطار عمل أو أي تركيبة معقدة من الفئات.
المشكلة
تخيل أن عليك جعل شيفرتك تعمل مع نطاق واسع من الكائنات التي تنتمي إلى مكتبة معقدة أو إطار عمل معقد، فالمعتاد أنك ستحتاج إلى بدء كل تلك الكائنات وتتابع الاعتماديات وتنفذ الأساليب بالترتيب الصحيح، وهكذا. ونتيجة لهذا يكون المنطق التجاري لفئاتك (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.
الاستخدام في لغة #C
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يشيع استخدام نمط الواجهة في التطبيقات المكتوبة بلغة #C، وهو مفيد خاصة عند العمل مع مكتبات وواجهات برمجة تطبيقات معقدة.
يمكن ملاحظة نمط الواجهة في فئة لديها واجهة بسيطة لكن تفوض أغلب العمل إلى فئات أخرى، وعادة ما تدير الواجهات دورة حياة كاملة للكائنات التي تستخدمها.
مثال تصوري
يوضح هذا المثال بنية نمط الواجهة، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
Program.cs: مثال تصوري
using System;
namespace RefactoringGuru.DesignPatterns.Facade.Conceptual
{
// نمط الواجهة يوفر واجهة بسيطة للمنطق المعقد الذي يكون في نظام فرعي أو أكثر
// وتفوض الواجهة طلبات العميل إلى الكائنات المناسبة داخل النظام الفرعي، كما تكون
// مسؤولة عن إدارة دورات الحياة الخاصة بها.
// كل هذا يحمي العميل من التعقيد غير المرغوب فيه للنظام الفرعي.
public class Facade
{
protected Subsystem1 _subsystem1;
protected Subsystem2 _subsystem2;
public Facade(Subsystem1 subsystem1, Subsystem2 subsystem2)
{
this._subsystem1 = subsystem1;
this._subsystem2 = subsystem2;
}
// أساليب الواجهة هي اختصارات سهلة للوظائف المعقدة للنظم الفرعية، لكن العملاء لا
// يحصلون إلا على جزء بسيط من قدرات النظام الفرعي.
public string Operation()
{
string result = "Facade initializes subsystems:\n";
result += this._subsystem1.operation1();
result += this._subsystem2.operation1();
result += "Facade orders subsystems to perform the action:\n";
result += this._subsystem1.operationN();
result += this._subsystem2.operationZ();
return result;
}
}
// يستطيع النظام الفرعي قبول الطلبات من الواجهة أو العميل مباشرة، وفي كل الحالات
// فإن الواجهة بالنسبة للنظام الفرعي ما هي إلا عميل آخر، وليست جزءًا منه.
public class Subsystem1
{
public string operation1()
{
return "Subsystem1: Ready!\n";
}
public string operationN()
{
return "Subsystem1: Go!\n";
}
}
// تستطيع بعض الواجهات أن تعمل مع عدة نظم فرعية في نفس الوقت.
public class Subsystem2
{
public string operation1()
{
return "Subsystem2: Get ready!\n";
}
public string operationZ()
{
return "Subsystem2: Fire!\n";
}
}
class Client
{
// تعمل شيفرة العميل مع أنظمة معقدة من خلال واجهة بسيطة يوفرها نمط الواجهة.
// وحين تدير واجهة دورة حياة نظام فرعي فإن العميل قد لا يعرف بوجود النظام الفرعي
// أصلًا، وهذا يحافظ على مستوى التعقيد تحت السيطرة.
public static void ClientCode(Facade facade)
{
Console.Write(facade.Operation());
}
}
class Program
{
static void Main(string[] args)
{
// قد تكون بعض كائنات النظام الفرعي منشأة بالفعل داخل شيفرة العميل، وفي تلك
// الحالة، يفضل أن تُبدأ الواجهة بتلك الكائنات بدلًا من ترك الواجهة تنشئ
// كائنات جديدة.
Subsystem1 subsystem1 = new Subsystem1();
Subsystem2 subsystem2 = new Subsystem2();
Facade facade = new Facade(subsystem1, subsystem2);
Client.ClientCode(facade);
}
}
}
Output.txt: نتائج التنفيذ
Facade initializes subsystems:
Subsystem1: Ready!
Subsystem2: Get ready!
Facade orders subsystems to perform the action:
Subsystem1: Go!
Subsystem2: Fire!
الاستخدام في لغة PHP
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يشيع استخدام نمط الواجهة في التطبيقات المكتوبة بلغة PHP، وهو مفيد خاصة عند العمل مع مكتبات وواجهات برمجة تطبيقات معقدة.
مثال تصوري
يوضح هذا المثال بنية نمط الواجهة، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة PHP.
index.php: مثال تصوري
<?php
namespace RefactoringGuru\Facade\Conceptual;
/**
* نمط الواجهة يوفر واجهة بسيطة للمنطق المعقد الذي يكون في نظام فرعي أو أكثر
* وتفوض الواجهة طلبات العميل إلى الكائنات المناسبة داخل النظام الفرعي، كما تكون
* مسؤولة عن إدارة دورات الحياة الخاصة بها.
* كل هذا يحمي العميل من التعقيد غير المرغوب فيه للنظام الفرعي.
*/
class Facade
{
protected $subsystem1;
protected $subsystem2;
/**
* بناءً على احتياجات برنامجك، تستطيع تزويد الواجهة بكائنات من النظام الفرعي أو
* تجبرها على إنشائها بنفسها.
*/
public function __construct(
Subsystem1 $subsystem1 = null,
Subsystem2 $subsystem2 = null
) {
$this->subsystem1 = $subsystem1 ?: new Subsystem1;
$this->subsystem2 = $subsystem2 ?: new Subsystem2;
}
/**
* أساليب الواجهة هي اختصارات سهلة للوظائف المعقدة للنظم الفرعية، لكن العملاء لا
* يحصلون إلا على جزء بسيط من قدرات النظام الفرعي.
*/
public function operation(): string
{
$result = "Facade initializes subsystems:\n";
$result .= $this->subsystem1->operation1();
$result .= $this->subsystem2->operation1();
$result .= "Facade orders subsystems to perform the action:\n";
$result .= $this->subsystem1->operationN();
$result .= $this->subsystem2->operationZ();
return $result;
}
}
/**
* يستطيع النظام الفرعي قبول الطلبات من الواجهة أو العميل مباشرة، وفي كل الحالات
* فإن الواجهة بالنسبة للنظام الفرعي ما هي إلا عميل آخر، وليست جزءًا منه.
*/
class Subsystem1
{
public function operation1(): string
{
return "Subsystem1: Ready!\n";
}
// ...
public function operationN(): string
{
return "Subsystem1: Go!\n";
}
}
/**
* تستطيع بعض الواجهات أن تعمل مع عدة نظم فرعية في نفس الوقت.
*/
class Subsystem2
{
public function operation1(): string
{
return "Subsystem2: Get ready!\n";
}
// ...
public function operationZ(): string
{
return "Subsystem2: Fire!\n";
}
}
/**
* تعمل شيفرة العميل مع أنظمة معقدة من خلال واجهة بسيطة يوفرها نمط الواجهة.
* وحين تدير واجهة دورة حياة نظام فرعي فإن العميل قد لا يعرف بوجود النظام الفرعي
* أصلًا، وهذا يحافظ على مستوى التعقيد تحت السيطرة.
*/
function clientCode(Facade $facade)
{
// ...
echo $facade->operation();
// ...
}
/**
* قد تكون بعض كائنات النظام الفرعي منشأة بالفعل داخل شيفرة العميل، وفي تلك
* الحالة، يفضل أن تُبدأ الواجهة بتلك الكائنات بدلًا من ترك الواجهة تنشئ كائنات جديدة.
*/
$subsystem1 = new Subsystem1;
$subsystem2 = new Subsystem2;
$facade = new Facade($subsystem1, $subsystem2);
clientCode($facade);
Output.txt: نتائج التنفيذ
Facade initializes subsystems:
Subsystem1: Ready!
Subsystem2: Get ready!
Facade orders subsystems to perform the action:
Subsystem1: Go!
Subsystem2: Fire!
مثال واقعي
انظر لنمط الواجهة على أنه محول يستخدم للبساطة في بعض الأنظمة الفرعية المعقدة، فالواجهة تعزل التعقيد داخل فئة واحدة وتسمح لباقي شيفرة التطبيق أن تستخدم واجهة مباشرة وصريحة.
وفي هذا المثال تخفي الواجهة تعقيد واجهة برمجة التطبيقات الخاصة بيوتيوب ومكتبة FFmpeg من شيفرة العميل، وبدلًا من العمل مع عشرات الفئات فإن شيفرة العميل تستخدم أسلوبًا بسيطًا على الواجهة.
index.php: مثال واقعي
<?php
namespace RefactoringGuru\Facade\RealWorld;
/**
* يوفر نمط الواجهة أسلوبًا وحيدًا لتنزيل مقاطع الفيديو من يوتيوب، ويخفي هذا الأسلوب
* (FFmpeg) ومكتبة تحويل الفيديو Youtube API و PHP كل تعقيد طبقة الشبكة في .
*/
class YouTubeDownloader
{
protected $youtube;
protected $ffmpeg;
/**
* من المفيد أن الواجهة تستطيع إدارة دورة حياة النظام الفرعي الذي تستخدمه.
*/
public function __construct(string $youtubeApiKey)
{
$this->youtube = new YouTube($youtubeApiKey);
$this->ffmpeg = new FFMpeg;
}
/**
* يوفر نمط الواجهة أسلوبًا بسيطًا لتنزيل مقاطع الفيديو وترميزها بصيغة معينة (وضعت
* الشيفرة الحقيقية في تعليقات بداعي التبسيط).
*/
public function downloadVideo(string $url): void
{
echo "Fetching video metadata from youtube...\n";
// $title = $this->youtube->fetchVideo($url)->getTitle();
echo "Saving video file to a temporary file...\n";
// $this->youtube->saveAs($url, "video.mpg");
echo "Processing source video...\n";
// $video = $this->ffmpeg->open('video.mpg');
echo "Normalizing and resizing the video to smaller dimensions...\n";
// $video
// ->filters()
// ->resize(new FFMpeg\Coordinate\Dimension(320, 240))
// ->synchronize();
echo "Capturing preview image...\n";
// $video
// ->frame(FFMpeg\Coordinate\TimeCode::fromSeconds(10))
// ->save($title . 'frame.jpg');
echo "Saving video in target formats...\n";
// $video
// ->save(new FFMpeg\Format\Video\X264, $title . '.mp4')
// ->save(new FFMpeg\Format\Video\WMV, $title . '.wmv')
// ->save(new FFMpeg\Format\Video\WebM, $title . '.webm');
echo "Done!\n";
}
}
/**
* نظام واجهة برمجة التطبيقات الفرعي في يوتيوب (Youtube API)
*/
class YouTube
{
public function fetchVideo(): string { /* ... */ }
public function saveAs(string $path): void { /* ... */ }
// ...مزيد من الأساليب والفئات....
}
/**
* الفرعي FFmpeg نظام
* وهو مكتبة تحويل صوت/فيديو معقدة
*/
class FFMpeg
{
public static function create(): FFMpeg { /* ... */ }
public function open(string $video): void { /* ... */ }
// ...مزيد من الأساليب والفئات.
// EN: More methods and Classes.
}
class FFMpegVideo
{
public function filters(): self { /* ... */ }
public function resize(): self { /* ... */ }
public function synchronize(): self { /* ... */ }
public function frame(): self { /* ... */ }
public function save(string $path): self { /* ... */ }
// ...مزيد من الأساليب والفئات.
// EN: More methods and Classes.
}
/**
* لا تعتمد شيفرة العميل على أي من فئات النظام الفرعي، وأي تغييرات في شيفرة النظام
* الفرعي لا تؤثر في شيفرة العميل، ولن تحتاج سوى تحديث الواجهة.
*/
function clientCode(YouTubeDownloader $facade)
{
// ...
$facade->downloadVideo("https://www.youtube.com/watch?v=QH2-TGUlwu4");
// ...
}
$facade = new YouTubeDownloader("APIKEY-XXXXXXXXX");
clientCode($facade);
Output.txt: نتائج التنفيذ
Fetching video metadata from youtube...
Saving video file to a temporary file...
Processing source video...
Normalizing and resizing the video to smaller dimensions...
Capturing preview image...
Saving video in target formats...
Done!
الاستخدام في لغة بايثون
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يشيع استخدام نمط الواجهة في التطبيقات المكتوبة بلغة بايثون، وهو مفيد خاصة عند العمل مع مكتبات وواجهات برمجة تطبيقات معقدة.
يمكن ملاحظة نمط الواجهة في فئة لديها واجهة بسيطة لكن تفوض أغلب العمل إلى فئات أخرى، وعادة ما تدير الواجهات دورة حياة كاملة للكائنات التي تستخدمها.
مثال تصوري
يوضح هذا المثال بنية نمط الواجهة، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.py: مثال تصوري
from __future__ import annotations
class Facade:
"""
نمط الواجهة يوفر واجهة بسيطة للمنطق المعقد الذي يكون في نظام فرعي أو أكثر
وتفوض الواجهة طلبات العميل إلى الكائنات المناسبة داخل النظام الفرعي، كما تكون
مسؤولة عن إدارة دورات الحياة الخاصة بها.
كل هذا يحمي العميل من التعقيد غير المرغوب فيه للنظام الفرعي.
"""
def __init__(self, subsystem1: Subsystem1, subsystem2: Subsystem2) -> None:
"""
بناءً على احتياجات برنامجك، تستطيع تزويد الواجهة بكائنات من النظام الفرعي أو
تجبرها على إنشائها بنفسها.
"""
self._subsystem1 = subsystem1 or Subsystem1()
self._subsystem2 = subsystem2 or Subsystem2()
def operation(self) -> str:
"""
أساليب الواجهة هي اختصارات سهلة للوظائف المعقدة للنظم الفرعية، لكن العملاء لا
يحصلون إلا على جزء بسيط من قدرات النظام الفرعي.
"""
results = []
results.append("Facade initializes subsystems:")
results.append(self._subsystem1.operation1())
results.append(self._subsystem2.operation1())
results.append("Facade orders subsystems to perform the action:")
results.append(self._subsystem1.operation_n())
results.append(self._subsystem2.operation_z())
return "\n".join(results)
class Subsystem1:
"""
يستطيع النظام الفرعي قبول الطلبات من الواجهة أو العميل مباشرة، وفي كل الحالات
فإن الواجهة بالنسبة للنظام الفرعي ما هي إلا عميل آخر، وليست جزءًا منه.
"""
def operation1(self) -> str:
return "Subsystem1: Ready!"
# ...
def operation_n(self) -> str:
return "Subsystem1: Go!"
class Subsystem2:
"""
تستطيع بعض الواجهات أن تعمل مع عدة نظم فرعية في نفس الوقت.
"""
def operation1(self) -> str:
return "Subsystem2: Get ready!"
# ...
def operation_z(self) -> str:
return "Subsystem2: Fire!"
def client_code(facade: Facade) -> None:
"""
تعمل شيفرة العميل مع أنظمة معقدة من خلال واجهة بسيطة يوفرها نمط الواجهة.
وحين تدير واجهة دورة حياة نظام فرعي فإن العميل قد لا يعرف بوجود النظام الفرعي
أصلًا، وهذا يحافظ على مستوى التعقيد تحت السيطرة.
"""
print(facade.operation(), end="")
if __name__ == "__main__":
# قد تكون بعض كائنات النظام الفرعي منشأة بالفعل داخل شيفرة العميل، وفي تلك
# الحالة، يفضل أن تُبدأ الواجهة بتلك الكائنات بدلًا من ترك الواجهة تنشئ كائنات
# جديدة.
subsystem1 = Subsystem1()
subsystem2 = Subsystem2()
facade = Facade(subsystem1, subsystem2)
client_code(facade)
Output.txt: نتائج التنفيذ
Facade initializes subsystems:
Subsystem1: Ready!
Subsystem2: Get ready!
Facade orders subsystems to perform the action:
Subsystem1: Go!
Subsystem2: Fire!
الاستخدام في لغة Swift
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يشيع استخدام نمط الواجهة في التطبيقات المكتوبة بلغة Swift، وهو مفيد خاصة عند العمل مع مكتبات وواجهات برمجة تطبيقات معقدة.
يمكن ملاحظة نمط الواجهة في فئة لديها واجهة بسيطة لكن تفوض أغلب العمل إلى فئات أخرى، وعادة ما تدير الواجهات دورة حياة كاملة للكائنات التي تستخدمها.
مثال تصوري
يوضح هذا المثال بنية نمط الواجهة، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
Example.swift: مثال تصوري
import XCTest
/// نمط الواجهة يوفر واجهة بسيطة للمنطق المعقد الذي يكون في نظام فرعي أو أكثر
/// وتفوض الواجهة طلبات العميل إلى الكائنات المناسبة داخل النظام الفرعي، كما تكون
/// مسؤولة عن إدارة دورات الحياة الخاصة بها.
/// كل هذا يحمي العميل من التعقيد غير المرغوب فيه للنظام الفرعي.
class Facade {
private var subsystem1: Subsystem1
private var subsystem2: Subsystem2
/// بناءً على احتياجات برنامجك، تستطيع تزويد الواجهة بكائنات من النظام الفرعي أو
/// تجبرها على إنشائها بنفسها.
init(subsystem1: Subsystem1 = Subsystem1(),
subsystem2: Subsystem2 = Subsystem2()) {
self.subsystem1 = subsystem1
self.subsystem2 = subsystem2
}
/// أساليب الواجهة هي اختصارات سهلة للوظائف المعقدة للنظم الفرعية، لكن العملاء لا
/// يحصلون إلا على جزء بسيط من قدرات النظام الفرعي.
func operation() -> String {
var result = "Facade initializes subsystems:"
result += " " + subsystem1.operation1()
result += " " + subsystem2.operation1()
result += "\n" + "Facade orders subsystems to perform the action:\n"
result += " " + subsystem1.operationN()
result += " " + subsystem2.operationZ()
return result
}
}
/// يستطيع النظام الفرعي قبول الطلبات من الواجهة أو العميل مباشرة، وفي كل الحالات
/// فإن الواجهة بالنسبة للنظام الفرعي ما هي إلا عميل آخر، وليست جزءًا منه.
class Subsystem1 {
func operation1() -> String {
return "Sybsystem1: Ready!\n"
}
// ...
func operationN() -> String {
return "Sybsystem1: Go!\n"
}
}
/// تستطيع بعض الواجهات أن تعمل مع عدة نظم فرعية في نفس الوقت.
class Subsystem2 {
func operation1() -> String {
return "Sybsystem2: Get ready!\n"
}
// ...
func operationZ() -> String {
return "Sybsystem2: Fire!\n"
}
}
/// تعمل شيفرة العميل مع أنظمة معقدة من خلال واجهة بسيطة يوفرها نمط الواجهة.
/// وحين تدير واجهة دورة حياة نظام فرعي فإن العميل قد لا يعرف بوجود النظام الفرعي
/// أصلًا، وهذا يحافظ على مستوى التعقيد تحت السيطرة.
class Client {
// ...
static func clientCode(facade: Facade) {
print(facade.operation())
}
// ...
}
/// دعنا نرى كيف ستعمل تلك الأجزاء مع بعضها.
class FacadeConceptual: XCTestCase {
func testFacadeConceptual() {
/// قد تكون بعض كائنات النظام الفرعي منشأة بالفعل داخل شيفرة العميل، وفي تلك
/// الحالة، يفضل أن تُبدأ الواجهة بتلك الكائنات بدلًا من ترك الواجهة تنشئ كائنات
/// جديدة.
let subsystem1 = Subsystem1()
let subsystem2 = Subsystem2()
let facade = Facade(subsystem1: subsystem1, subsystem2: subsystem2)
Client.clientCode(facade: facade)
}
}
Output.txt: نتائج التنفيذ
Facade initializes subsystems: Sybsystem1: Ready!
Sybsystem2: Get ready!
Facade orders subsystems to perform the action:
Sybsystem1: Go!
Sybsystem2: Fire!
مثال واقعي
Example.swift: مثال واقعي
import XCTest
/// نمط الواجهة
///
/// الهدف: توفير واجهة مبسطة لمكتبة أو إطار عمل أو أي مجموعة معقدة من الفئات.
class FacadeRealWorld: XCTestCase {
/// في المشروع الحقيقي فإنك ستستخدم مكتبات من طرف ثالث على الأرجح
/// كمكتبات تنزيل الصور مثلًا.
///
/// لهذا فإن الواجهة والتغليف هما طريقتان مناسبتان لاستخدام واجهة برمجة تطبيقات من
/// طرف ثالث في شيفرة العميل، حتى لو كانت المكتبة المتصلة بالمشروع هي مكتبتك أنت.
///
/// وفوائد ذلك هنا هي ما يلي:
///
/// 1. إن كنت تحتاج أن تغير منزِّل الصور الحالي فيجب أن يتم هذا في مكان واحد فقط
/// داخل المشروع، وسيظل جزء من شيفرة العميل فعَّالًا وعاملًا.
///
/// 2. توفر الواجهة وصولًا إلى جزء يسير من الوظائف التي تلبي أغلب احتياجات العميل.
/// الافتراضية أو المستخدمة بكثرة(Parameters) وفوق هذا فإنها تستطيع ضبط المعامِلات.
func testFacedeRealWorld() {
let imageView = UIImageView()
print("Let's set an image for the image view")
clientCode(imageView)
print("Image has been set")
XCTAssert(imageView.image != nil)
}
fileprivate func clientCode(_ imageView: UIImageView) {
let url = URL(string: "www.example.com/logo")
imageView.downloadImage(at: url)
}
}
private extension UIImageView {
/// يلعب هذا الامتداد دور الواجهة.
func downloadImage(at url: URL?) {
print("Start downloading...")
let placeholder = UIImage(named: "placeholder")
ImageDownloader().loadImage(at: url,
placeholder: placeholder,
completion: { image, error in
print("Handle an image...")
/// اقتطع أو احفظ أو ضع فلاتر، أو ما تريد غير ذلك...
self.image = image
})
}
}
private class ImageDownloader {
/// مكتبة طرف ثالث أو الحل الخاص بك -نظامك الفرعي.
typealias Completion = (UIImage, Error?) -> ()
typealias Progress = (Int, Int) -> ()
func loadImage(at url: URL?,
placeholder: UIImage? = nil,
progress: Progress? = nil,
completion: Completion) {
/// ... Network Stack هيئ مكدس الشبكة
/// ... تنزيل صورة.
/// ...
completion(UIImage(), nil)
}
}
Output.txt: نتائج التنفيذ
Let's set an image for the image view
Start downloading...
Handle an image...
Image has been set
الاستخدام في لغة TypeScript
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يشيع استخدام نمط الواجهة في التطبيقات المكتوبة بلغة Swift، وهو مفيد خاصة عند العمل مع مكتبات وواجهات برمجة تطبيقات معقدة.
يمكن ملاحظة نمط الواجهة في فئة لديها واجهة بسيطة لكن تفوض أغلب العمل إلى فئات أخرى، وعادة ما تدير الواجهات دورة حياة كاملة للكائنات التي تستخدمها.
مثال تصوري
يوضح هذا المثال بنية نمط الواجهة، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
index.ts: مثال تصوري
/**
* نمط الواجهة يوفر واجهة بسيطة للمنطق المعقد الذي يكون في نظام فرعي أو أكثر
* وتفوض الواجهة طلبات العميل إلى الكائنات المناسبة داخل النظام الفرعي، كما تكون
* مسؤولة عن إدارة دورات الحياة الخاصة بها.
* كل هذا يحمي العميل من التعقيد غير المرغوب فيه للنظام الفرعي.
*/
class Facade {
protected subsystem1: Subsystem1;
protected subsystem2: Subsystem2;
/**
* بناءً على احتياجات برنامجك، تستطيع تزويد الواجهة بكائنات من النظام الفرعي أو
* تجبرها على إنشائها بنفسها.
*/
constructor(subsystem1: Subsystem1 = null, subsystem2: Subsystem2 = null) {
this.subsystem1 = subsystem1 || new Subsystem1();
this.subsystem2 = subsystem2 || new Subsystem2();
}
/**
* أساليب الواجهة هي اختصارات سهلة للوظائف المعقدة للنظم الفرعية، لكن العملاء لا
* يحصلون إلا على جزء بسيط من قدرات النظام الفرعي.
*/
public operation(): string {
let result = 'Facade initializes subsystems:\n';
result += this.subsystem1.operation1();
result += this.subsystem2.operation1();
result += 'Facade orders subsystems to perform the action:\n';
result += this.subsystem1.operationN();
result += this.subsystem2.operationZ();
return result;
}
}
/**
* يستطيع النظام الفرعي قبول الطلبات من الواجهة أو العميل مباشرة، وفي كل الحالات
* فإن الواجهة بالنسبة للنظام الفرعي ما هي إلا عميل آخر، وليست جزءًا منه.
*/
class Subsystem1 {
public operation1(): string {
return 'Subsystem1: Ready!\n';
}
// ...
public operationN(): string {
return 'Subsystem1: Go!\n';
}
}
/**
* تستطيع بعض الواجهات أن تعمل مع عدة نظم فرعية في نفس الوقت.
*/
class Subsystem2 {
public operation1(): string {
return 'Subsystem2: Get ready!\n';
}
// ...
public operationZ(): string {
return 'Subsystem2: Fire!';
}
}
/**
* تعمل شيفرة العميل مع أنظمة معقدة من خلال واجهة بسيطة يوفرها نمط الواجهة.
* وحين تدير واجهة دورة حياة نظام فرعي فإن العميل قد لا يعرف بوجود النظام الفرعي
* أصلًا، وهذا يحافظ على مستوى التعقيد تحت السيطرة.
*/
function clientCode(facade: Facade) {
// ...
console.log(facade.operation());
// ...
}
/**
* قد تكون بعض كائنات النظام الفرعي منشأة بالفعل داخل شيفرة العميل، وفي تلك
* الحالة، يفضل أن تُبدأ الواجهة بتلك الكائنات بدلًا من ترك الواجهة تنشئ كائنات جديدة.
*/
const subsystem1 = new Subsystem1();
const subsystem2 = new Subsystem2();
const facade = new Facade(subsystem1, subsystem2);
clientCode(facade);
Output.txt: نتائج التنفيذ
Facade initializes subsystems:
Subsystem1: Ready!
Subsystem2: Get ready!
Facade orders subsystems to perform the action:
Subsystem1: Go!
Subsystem2: Fire!