الفرق بين المراجعتين لصفحة: «Design Patterns/composite»

من موسوعة حسوب
1.0: عنوان الصفحة
 
2.0 محتوى
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:نمط المركَّب}}</noinclude>
<noinclude>{{DISPLAYTITLE:نمط المركَّب}}</noinclude>
نمط المركَّب هو نمط تصميم هيكلي يسمح لك بتركيب كائنات في هياكل شجرية لتعاملها على أنها وحدة واحدة أو كائن واحد.
== المشكلة ==
يستخدم نمط المركَّب في حالة واحدة فقط، وهي حين يمكن تمثيل النموذج الذي يقوم عليه تطبيقك في هيئة شجرية، كأن يكون لديك نوعين من الكائنات هما المنتجات <code>Products</code> والصناديق <code>Boxes</code>. ويستطيع الأخير استيعاب منتجات بداخله وصناديق أخرى كذلك أصغر منه في الحجم، وتلك الصناديق الجديدة يمكنها احتواء صناديق أخرى بداخلها، وهكذا دواليك. ولنقل أنك تريد إنشاء نظام طلبات يستخدم تلك الفئات، تحتوي فيه الطلبات المختلفة على منتجات بسيطة بدون أي تغليف إضافة إلى صناديق ملأى بالمنتجات والصناديق الأخرى الأصغر منها، وتريد الآن أن تحسب الثمن الكلي لطلب به صناديق بداخلها صناديق أخرى ومنتجات بدون تغليف، كيف تفعل ذلك؟
'''ضع الصورة: قد يتكون الطلب الواحد من عدة منتجات مختلفة مغلفة في صناديق وغير مغلفة، وتغلف كلها في صناديق أكبر منها تحتويها، وهكذا ليبدو الشكل الكلي كشجرة مقلوبة.'''
إن سلكنا الطريق المباشرة فهذا يعني فتح الصناديق جميعها وحساب ثمن كل منتج بسيط ثم جمع تلك الأثمان لنحصل على الثمن الكلي للطلب كاملًا، لكن هذا الأسلوب وإن كان يصلح أحيانًا في الأمثلة التي نراها في الحياة اليومية، لكنها لا تصلح في البرمجة، إذ -وفقًا لهذا المنظور- يجب أن تعرف فئات كل المنتجات <code>Products</code> والصناديق <code>Boxes</code> التي ستفحصها، ومستوى تداخل الصناديق وأمور أخرى غير ذلك، وذلك يجعل المنظور المباشر غير عملي وقريبًا من المستحيل لحل مشكلة كهذه.
== الحل ==
يقترح نمط المركَّب أن تعمل مع <code>Boxes</code> و <code>Products</code> من خلال واجهة مشتركة تصرح عن أسلوب لحساب الثمن الكلي للطلب، وسيفحص ذلك الأسلوب كل عنصر في الصندوق ويطلب ثمنه ثم يعيد إجمالي الصندوق، وإن كان أحد عناصر الصندوق صندوقًا أصغر منه فإن الأسلوب يكرر نفس الخطوات في فحص كل عنصر فيه وطلب ثمنه، وهكذا يعامل كل صندوق فرعي معاملةَ الصندوق الأول حتى يصل إلى آخر تلك السلسلة ويحسب أثمان كل المنتجات الفرعية، بل قد يحسب أثمان الصناديق أنفسها أيضًا في بعض الحالات التي تُحسب فيها قيمة الصناديق من تكلفة التغليف.
'''ضع الصورة: يسمح لك نمط المركَّب بتشغيل أسلوب ما بشكل تكراري في عناصر شجرة من الكائنات.'''
وأعظم فائدة لهذا الأسلوب في الحل أنك لا تحتاج إلى النظر في الفئات الحقيقية للكائنات التي تتكون منها الشجرة، فلا تحتاج إلى معرفة ما إن كان الكائن منتجًا بسيطًا أو صندوقًا معقدًا إذ يمكنك معالجتها جميعًا بنفس الأسلوب من خلال الواجهة المشتركة، ذلك أن الكائنات أنفسها تمرر الطلب إلى ما تحتها في كامل الشجرة كلما استدعيت أسلوبًا ما.
== مثال واقعي ==
'''ضع الصورة: مثال لهيكلة أحد الجيوش.'''
تُشكَّل جيوش أغلب الدول بصورة هرمية يتكون فيها الجيش من أقسام رئيسية، في كل قسم مجموعة ألوية، في كل لواء منها مجموعة كتائب، ثم كل كتيبة بها مجموعة سرايا، وكل سرية فيها مجموعات من الجنود الذين تُمرر إليهم الأوامر من القيادات العليا للجيش لتعرف المهام التي عليها تنفيذها.
== البُنية ==
'''ضع الصورة'''
# تصف واجهةُ الجزء (Component) العمليات المشتركةَ في كل من العناصر البسيطة والمعقدة في الشجرة.
# الورقة (Leaf) هي عنصر بسيط في الشجرة ليس له عناصر فرعية، وعادة ما تنفذ الورقة أغلب المهام الحقيقية بما أنها ليس لها عناصر تحتها تفوض إليه تلك المهام.
# الحاوية (المركَّب أيضًا) هي عنصر في الشجرة تحته عناصر فرعية سواء كانت حاويات أخرى أو أوراقًا، ولا تعرف الحاوية الفئات الحقيقية لما تحتها من فروع، وهي تعمل مع كل العناصر الفرعية من خلال واجهة الجزء فقط، وتفوض العمل إلى عناصرها الفرعية ثم تعالج النتائج الأولية وتعيد النتيجة النهائية للعميل.
# يعمل العميل مع كل العناصر من خلال واجهة الجزء، ويستطيع العمل نتيجة لهذا بنفس الطريقة مع العناصر البسيطة والمعقدة من الشجرة.
== مثال توضيحي ==
يسمح لك نمط المركَّب في هذا المثال بتنفيذ تكديسات لأشكال هندسية (Geometric) داخل محرِّر مرئي.
'''ضع الصورة: مثال محرر الأشكال الهندسية.'''
فئة <code>CompoundGraphic</code> هي الحاوية التي تشمل أي عدد من الأشكال الفرعية بما في ذلك الأشكال المجمَّعة الأخرى، والشكل المجمَّع له نفس الأساليب كالشكل البسيط، لكن بدلًا من فعل أي شيء بنفسه فإنه -أي المجمَّع- يمرر الطلب بشكل تكراري إلى كل فروعه ويجمع النتيجة في النهاية.
وتعمل شيفرة العميل مع الأشكال كلها من خلال واجهة واحدة مشتركة بين كل فئات الأشكال، لهذا لا يعرف العميل ما إن كان يعمل مع شكل بسيط أو مجمع، ويعمل العميل لهذا أيضًا مع هياكل لأشكال معقدة جدًا دون التقيد بالفئات الحقيقية التي تكوِّن تلك الهياكل.<syntaxhighlight lang="java">
// تصرح واجهة الجزء عن عمليات مشتركة للأشكال المجمعة والبسيطة لمركب ما.
interface Graphic is
    method move(x, y)
    method draw()
// تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا يمكن لكائن الورقة أن يحتوي على كائنات فرعية
// وعادة ما ينفذ المهام الحقيقية لذلك، بينما تفوض الكائنات المجمعة المهام إلى ما دونها.
class Dot implements Graphic is
    field x, y
    constructor Dot(x, y) { ... }
    method move(x, y) is
        this.x += x, this.y += y
    method draw() is
        // Y و X ارسم نقطة عند.
// جميع فئات الجزء تستطيع توسعة الأجزاء الأجزاء الأخرى.
class Circle extends Dot is
    field radius
    constructor Circle(x, y, radius) { ... }
    method draw() is
        // R وبنصف قطر Y و X ارسم دائرة عند.
// تمثل فئة المركب أجزاءً معقدة قد تكون لها كائنات فرعية، وتفوض كائناتُ المركبُ المهامَ عادة لما دونها
// ثم تجمع النتيجة.
class CompoundGraphic implements Graphic is
    field children: array of Graphic
    // يمكن أن يضيف كائن المركب أو يحذف أجزاء أخرى -بسيطة أومعقدة- إلى أو من قائمته الفرعية.
    method add(child: Graphic) is
        // إضافة جزء فرعي إلى مصفوفة الفروع.
    method remove(child: Graphic) is
        // حذف جزء فرعي إلى مصفوفة الفروع.
    method move(x, y) is
        foreach (child in children) do
            child.move(x, y)
    // ينفذ المركب منطقه الأساسي بشكل معين، إذ يمر مرة بعد مرة على فروعه كلها ويجمع نتائجها، وبما أن فروع
    // المركب تمرر استدعاءتها إلى فروعها فإن المركب يمر على الشجرة كلها.
    method draw() is
        // 1. لكل جزء فرعي
        //  - ارسم الجزء
        //  - (Bounding Rectangle) حدِّث المستطيل المحدِّد
        // 2. (Bounding Coordinates) باستخدام إحداثيات الحدود (Dashes) ارسم مستطيلًا خطه مكون من شُرط مقطعة .
// تعمل شيفرة العميل مع كل الأجزاء من خلال واجهتها الأساسية، وهكذا تستطيع شيفرة العميل أن تدعم أجزاء الورقة
// البسيطة والمركبات المعقدة على حد سواء.
class ImageEditor is
    method load() is
        all = new CompoundGraphic()
        all.add(new Dot(1, 2))
        all.add(new Circle(5, 3, 10))
        // ...
    // اجمع الأجزاء المختارة في جزءٍ مركب معقد واحد.
    method groupSelected(components: array of Graphic) is
        group = new CompoundGraphic()
        group.add(components)
        all.remove(components)
        all.add(group)
        // ستُرسم كل الأجزاء.
        all.draw()
</syntaxhighlight>
== قابلية التنفيذ ==
* '''استخدم نمط المركَّب عند تنفيذ هيكل شجري لمجموعة كائنات.'''
يزودك نمط المركَّب بنوعين أساسيين من العناصر التي تشترك في واجهة واحدة، وهي الأوراق (Leaves) البسيطة، والحاويات (Containers) المعقدة، وقد تتكون الحاوية من الأوراق والحاويات الأخرى كذلك، ويسمح لك هذا بإنشاء هيكلة تكرارية متداخلة تشبه الشجرة.
* '''استخدم نمط المركَّب حين تريد لشيفرة العميل أن تعامل العناصر البسيطة والمعقدة بنفس الأسلوب.'''
تشترك كل العناصر المعرَّفة من قبل نمط المركَّب في واجهة واحدة، ولا يحتاج العميل أن يعرف الفئة الحقيقية للكائن الذي يتعامل معه.
== كيفية الاستخدام ==
# تأكد أن النموذج الأساسي لتطبيقك يمكن تمثيله بهيكل شجري، ثم جزّء ذلك إلى عناصر بسيطة وحاويات. تذكر أن الحاويات يجب أن تكون قادرة على احتواء العناصر البسيطة إضافة إلى الحاويات الأخرى كذلك.
# صرِّح عن واجهة الجزء مع قائمة أساليب تصلح للعناصر البسيطة والحاويات المعقدة على حد سواء.
# أنشئ فئة ورقة (Leaf) لتمثل العناصر البسيطة، قد يحتوي البرنامج فئات مختلفة للأوراق.
# أنشئ فئة حاوية (Container) لتمثل العناصر المعقدة في تلك الفئة، وأضف حقل مصفوفة لتخزين المراجع إلى العناصر الفرعية، يجب أن تكون المصفوفة قادرة على تخزين الأوراق والحاويات، لذا تأكد أنها مصرح عنها مع نوع واجهة الجزء. تذكر أثناء تنفيذ أساليب واجهة الجزء أن الحاوية يفترض بها تفويض أغلب مهامها إلى العناصر الفرعية لها.
# أخيرًا، عرّف أساليب إضافة وحذف العناصر الفرعية في الحاوية.
لاحظ أن هذه العمليات يمكن التصريح عنها في واجهة الجزء (Component Interface)، وذاك قد يخرق مبدأ عزل الواجهة (Interface Segregation Principle) لأن الأساليب ستكون فارغة في فئة الورقة، لكن العميل سيتمكن من معاملة كل العناصر بالتساوي حتى عند تركيب (Composing) الشجرة.
== المزايا والعيوب ==
=== المزايا ===
* تستطيع العمل مع هياكل شجرية معقدة بشكل أسهل: استخدم تعدد الأشكال (polymorphism) والتكرار المستمر (recursion) لصالحك.
* مبدأ المفتوح/المغلق. تستطيع إدخال أنواعًا جديدة من العناصر في التطبيق دون تعطيل الشيفرة الحالية، والتي تعمل الآن مع شجرة كائنات.
=== العيوب ===
* قد يكون من الصعب توفير واجهة مشتركة للفئات التي تختلف وظائفها عن بعضها بشكل كبير، فستحتاج في حالات معينة إلى المبالغة في تعميم واجهة الجزء إلى الحد الذي سيجعلها تستعصي على الفهم.
== العلاقات مع الأنماط الأخرى ==
* يمكنك استخدام نمط [https://refactoring.guru/design-patterns/builder الباني] عند إنشاء أشجار مركبات معقدة لأنك تستطيع برمجة خطوات هيكلتها لتعمل بتكرارية.
* تُستخدم [[Design Patterns/chain of responsibility|سلسلة المسؤوليات]] عادة بالتزامن مع المركَّب، وفي تلك الحالة تمرر الورقة الطلب الذي تحصل عليه خلال الأجزاء التي تعلوها في السلسلة الهرمية حتى تصل إلى جذر شجرة الكائنات.
* يمكنك استخدام [[Design Patterns/iterator|المكررات]] لاجتياز أشجار المركبات.
* يمكنك استخدام نمط [https://refactoring.guru/design-patterns/visitor الزائر] لتنفيذ عملية على شجرة مركب بكاملها.
* يمكنك تنفيذ عُقد ورقية مشتركة (Shared Leaf Nodes) لشجرة المركب لتكوّن أنماطًا من نوع [[Design Patterns/flyweight|وزن الذبابة]]، من أجل توفير الذاكرة العشوائية RAM.
* لنمطيْ المركب [[Design Patterns/decorator|والمزخرِف]] هياكل متشابهة بما أنهما يعتمدان على التركيب التكراري لتنظيم عدد مفتوح النهاية (open-ended) من الكائنات.
* [[Design Patterns/decorator|المزخرف]] مثل المركب لكنه لا يملك إلا جزءًا فرعيًا واحدًا، ويضيف المزخرف مسؤوليات إضافية إلى الكائن المُغلَّف، بينما ما يقوم به المركَّب هو جمع نتائج الفروع فقط. لكن بأي حال فإن هذه الأنماط تتعاون فيما بينها، فتستطيع استخدام المزخرِف لتوسيع سلوك كائن معين في شجرة مركَّب ما.
* التصميمات التي تستخدم المركب والمزخرف بكثرة يمكنها الاستفادة من نمط [[Design Patterns/prototype|النموذج الأولي]]، إذ استخدامه يسمح باستنساخ هياكل معقدة بدلًا من إعادة بنائهم من الصفر.
== الاستخدام في لغة جافا ==
'''المستوى''': ★ ★ ☆
'''الانتشار''':  ★ ★ ★
'''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة بايثون، إذ يُستخدم لتمثيل هرميات لأجزاء من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs). إليك بعض الأمثلة على نمط المركب من مكتبات جافا قياسية:
[http://docs.oracle.com/javase/8/docs/api/java/awt/Container.html#add-java.awt.Component- (java.awt.Container#add(Component] تقريبًا في كل أجزاء واجهة Swing.
[http://docs.oracle.com/javaee/7/api/javax/faces/component/UIComponent.html#getChildren-- ()javax.faces.component.UIComponent#getChildren] تقريبًا في كل أجزاء واجهات JSF.
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري.
=== مثال: أشكال مرئية بسيطة ومجمَّعة ===
==== أشكال ====
===== shapes/Shape.java: واجهة مشتركة للشكل =====
<syntaxhighlight lang="java">
package refactoring_guru.composite.example.shapes;
import java.awt.*;
public interface Shape {
    int getX();
    int getY();
    int getWidth();
    int getHeight();
    void move(int x, int y);
    boolean isInsideBounds(int x, int y);
    void select();
    void unSelect();
    boolean isSelected();
    void paint(Graphics graphics);
}
</syntaxhighlight>
===== shapes/BaseShape.java: شكل تجريدي بوظائف بسيطة =====
<syntaxhighlight lang="java">
package refactoring_guru.composite.example.shapes;
import java.awt.*;
abstract class BaseShape implements Shape {
    public int x;
    public int y;
    public Color color;
    private boolean selected = false;
    BaseShape(int x, int y, Color color) {
        this.x = x;
        this.y = y;
        this.color = color;
    }
    @Override
    public int getX() {
        return x;
    }
    @Override
    public int getY() {
        return y;
    }
    @Override
    public int getWidth() {
        return 0;
    }
    @Override
    public int getHeight() {
        return 0;
    }
    @Override
    public void move(int x, int y) {
        this.x += x;
        this.y += y;
    }
    @Override
    public boolean isInsideBounds(int x, int y) {
        return x > getX() && x < (getX() + getWidth()) &&
                y > getY() && y < (getY() + getHeight());
    }
    @Override
    public void select() {
        selected = true;
    }
    @Override
    public void unSelect() {
        selected = false;
    }
    @Override
    public boolean isSelected() {
        return selected;
    }
    void enableSelectionStyle(Graphics graphics) {
        graphics.setColor(Color.LIGHT_GRAY);
        Graphics2D g2 = (Graphics2D) graphics;
        float dash1[] = {2.0f};
        g2.setStroke(new BasicStroke(1.0f,
                BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_MITER,
                2.0f, dash1, 0.0f));
    }
    void disableSelectionStyle(Graphics graphics) {
        graphics.setColor(color);
        Graphics2D g2 = (Graphics2D) graphics;
        g2.setStroke(new BasicStroke());
    }
    @Override
    public void paint(Graphics graphics) {
        if (isSelected()) {
            enableSelectionStyle(graphics);
        }
        else {
            disableSelectionStyle(graphics);
        }
        // ...
    }
}
</syntaxhighlight>
===== shapes/Dot.java: نقطة =====
<syntaxhighlight lang="java">
package refactoring_guru.composite.example.shapes;
import java.awt.*;
public class Dot extends BaseShape {
    private final int DOT_SIZE = 3;
    public Dot(int x, int y, Color color) {
        super(x, y, color);
    }
    @Override
    public int getWidth() {
        return DOT_SIZE;
    }
    @Override
    public int getHeight() {
        return DOT_SIZE;
    }
    @Override
    public void paint(Graphics graphics) {
        super.paint(graphics);
        graphics.fillRect(x - 1, y - 1, getWidth(), getHeight());
    }
}
</syntaxhighlight>
===== shapes/Circle.java: دائرة =====
<syntaxhighlight lang="java">
package refactoring_guru.composite.example.shapes;
import java.awt.*;
public class Circle extends BaseShape {
    public int radius;
    public Circle(int x, int y, int radius, Color color) {
        super(x, y, color);
        this.radius = radius;
    }
    @Override
    public int getWidth() {
        return radius * 2;
    }
    @Override
    public int getHeight() {
        return radius * 2;
    }
    public void paint(Graphics graphics) {
        super.paint(graphics);
        graphics.drawOval(x, y, getWidth() - 1, getHeight() - 1);
    }
}
</syntaxhighlight>
===== shapes/Rectangle.java: مستطيل =====
<syntaxhighlight lang="java">
package refactoring_guru.composite.example.shapes;
import java.awt.*;
public class Rectangle extends BaseShape {
    public int width;
    public int height;
    public Rectangle(int x, int y, int width, int height, Color color) {
        super(x, y, color);
        this.width = width;
        this.height = height;
    }
    @Override
    public int getWidth() {
        return width;
    }
    @Override
    public int getHeight() {
        return height;
    }
    @Override
    public void paint(Graphics graphics) {
        super.paint(graphics);
        graphics.drawRect(x, y, getWidth() - 1, getHeight() - 1);
    }
}
</syntaxhighlight>
===== shapes/CompoundShape.java: شكل مجمع يتكون من كائنات أشكال أخرى =====
<syntaxhighlight lang="java">
package refactoring_guru.composite.example.shapes;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CompoundShape extends BaseShape {
    protected List<Shape> children = new ArrayList<>();
    public CompoundShape(Shape... components) {
        super(0, 0, Color.BLACK);
        add(components);
    }
    public void add(Shape component) {
        children.add(component);
    }
    public void add(Shape... components) {
        children.addAll(Arrays.asList(components));
    }
    public void remove(Shape child) {
        children.remove(child);
    }
    public void remove(Shape... components) {
        children.removeAll(Arrays.asList(components));
    }
    public void clear() {
        children.clear();
    }
    @Override
    public int getX() {
        if (children.size() == 0) {
            return 0;
        }
        int x = children.get(0).getX();
        for (Shape child : children) {
            if (child.getX() < x) {
                x = child.getX();
            }
        }
        return x;
    }
    @Override
    public int getY() {
        if (children.size() == 0) {
            return 0;
        }
        int y = children.get(0).getY();
        for (Shape child : children) {
            if (child.getY() < y) {
                y = child.getY();
            }
        }
        return y;
    }
    @Override
    public int getWidth() {
        int maxWidth = 0;
        int x = getX();
        for (Shape child : children) {
            int childsRelativeX = child.getX() - x;
            int childWidth = childsRelativeX + child.getWidth();
            if (childWidth > maxWidth) {
                maxWidth = childWidth;
            }
        }
        return maxWidth;
    }
    @Override
    public int getHeight() {
        int maxHeight = 0;
        int y = getY();
        for (Shape child : children) {
            int childsRelativeY = child.getY() - y;
            int childHeight = childsRelativeY + child.getHeight();
            if (childHeight > maxHeight) {
                maxHeight = childHeight;
            }
        }
        return maxHeight;
    }
    @Override
    public void move(int x, int y) {
        for (Shape child : children) {
            child.move(x, y);
        }
    }
    @Override
    public boolean isInsideBounds(int x, int y) {
        for (Shape child : children) {
            if (child.isInsideBounds(x, y)) {
                return true;
            }
        }
        return false;
    }
    @Override
    public void unSelect() {
        super.unSelect();
        for (Shape child : children) {
            child.unSelect();
        }
    }
    public boolean selectChildAt(int x, int y) {
        for (Shape child : children) {
            if (child.isInsideBounds(x, y)) {
                child.select();
                return true;
            }
        }
        return false;
    }
    @Override
    public void paint(Graphics graphics) {
        if (isSelected()) {
            enableSelectionStyle(graphics);
            graphics.drawRect(getX() - 1, getY() - 1, getWidth() + 1, getHeight() + 1);
            disableSelectionStyle(graphics);
        }
        for (refactoring_guru.composite.example.shapes.Shape child : children) {
            child.paint(graphics);
        }
    }
}
</syntaxhighlight>
==== المحرر ====
===== editor/ImageEditor.java: محرر الشكل =====
<syntaxhighlight lang="java">
package refactoring_guru.composite.example.editor;
import refactoring_guru.composite.example.shapes.CompoundShape;
import refactoring_guru.composite.example.shapes.Shape;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class ImageEditor {
    private EditorCanvas canvas;
    private CompoundShape allShapes = new CompoundShape();
    public ImageEditor() {
        canvas = new EditorCanvas();
    }
    public void loadShapes(Shape... shapes) {
        allShapes.clear();
        allShapes.add(shapes);
        canvas.refresh();
    }
    private class EditorCanvas extends Canvas {
        JFrame frame;
        private static final int PADDING = 10;
        EditorCanvas() {
            createFrame();
            refresh();
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    allShapes.unSelect();
                    allShapes.selectChildAt(e.getX(), e.getY());
                    e.getComponent().repaint();
                }
            });
        }
        void createFrame() {
            frame = new JFrame();
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            frame.setLocationRelativeTo(null);
            JPanel contentPanel = new JPanel();
            Border padding = BorderFactory.createEmptyBorder(PADDING, PADDING, PADDING, PADDING);
            contentPanel.setBorder(padding);
            frame.setContentPane(contentPanel);
            frame.add(this);
            frame.setVisible(true);
            frame.getContentPane().setBackground(Color.LIGHT_GRAY);
        }
        public int getWidth() {
            return allShapes.getX() + allShapes.getWidth() + PADDING;
        }
        public int getHeight() {
            return allShapes.getY() + allShapes.getHeight() + PADDING;
        }
        void refresh() {
            this.setSize(getWidth(), getHeight());
            frame.pack();
        }
        public void paint(Graphics graphics) {
            allShapes.paint(graphics);
        }
    }
}
</syntaxhighlight>
===== Demo.java: شيفرة العميل =====
<syntaxhighlight lang="java">
package refactoring_guru.composite.example;
import refactoring_guru.composite.example.editor.ImageEditor;
import refactoring_guru.composite.example.shapes.Circle;
import refactoring_guru.composite.example.shapes.CompoundShape;
import refactoring_guru.composite.example.shapes.Dot;
import refactoring_guru.composite.example.shapes.Rectangle;
import java.awt.*;
public class Demo {
    public static void main(String[] args) {
        ImageEditor editor = new ImageEditor();
        editor.loadShapes(
                new Circle(10, 10, 10, Color.BLUE),
                new CompoundShape(
                    new Circle(110, 110, 50, Color.RED),
                    new Dot(160, 160, Color.RED)
                ),
                new CompoundShape(
                        new Rectangle(250, 250, 100, 100, Color.GREEN),
                        new Dot(240, 240, Color.GREEN),
                        new Dot(240, 360, Color.GREEN),
                        new Dot(360, 360, Color.GREEN),
                        new Dot(360, 240, Color.GREEN)
                )
        );
    }
}
</syntaxhighlight>
==== OutputDemo.png: نتائج التنفيذ ====
'''ضع الصورة (ملف:dpcj.OutputDemo.png)'''
== الاستخدام في لغة #C ==
'''المستوى''': ★ ★ ☆
'''الانتشار''':  ★ ★ ★
'''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة #C، إذ يُستخدم لتمثيل هرميات لأجزاء من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs).
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري.
=== مثال: مثال تصوري ===
يوضح هذا المثال بنية نمط المركَّب، ويركز على إجابة الأسئلة التالية:
* ما الفئات التي يتكون منها؟
* ما الأدوار التي تلعبها هذه الفئات؟
* كيف ترتبط عناصر النمط ببعضها؟
==== Program.cs: مثال تصوري ====
<syntaxhighlight lang="c#">
using System;
using System.Collections.Generic;
namespace RefactoringGuru.DesignPatterns.Composite.Conceptual
{
    // The base Component class declares common operations for both simple and
    // complex objects of a composition.
    abstract class Component
    {
        public Component() { }
        // The base Component may implement some default behavior or leave it to
        // concrete classes (by declaring the method containing the behavior as
        // "abstract").
        public abstract string Operation();
        // In some cases, it would be beneficial to define the child-management
        // operations right in the base Component class. This way, you won't
        // need to expose any concrete component classes to the client code,
        // even during the object tree assembly. The downside is that these
        // methods will be empty for the leaf-level components.
        public virtual void Add(Component component)
        {
            throw new NotImplementedException();
        }
        public virtual void Remove(Component component)
        {
            throw new NotImplementedException();
        }
        // You can provide a method that lets the client code figure out whether
        // a component can bear children.
        public virtual bool IsComposite()
        {
            return true;
        }
    }
    // The Leaf class represents the end objects of a composition. A leaf can't
    // have any children.
    //
    // Usually, it's the Leaf objects that do the actual work, whereas Composite
    // objects only delegate to their sub-components.
    class Leaf : Component
    {
        public override string Operation()
        {
            return "Leaf";
        }
        public override bool IsComposite()
        {
            return false;
        }
    }
    // The Composite class represents the complex components that may have
    // children. Usually, the Composite objects delegate the actual work to
    // their children and then "sum-up" the result.
    class Composite : Component
    {
        protected List<Component> _children = new List<Component>();
       
        public override void Add(Component component)
        {
            this._children.Add(component);
        }
        public override void Remove(Component component)
        {
            this._children.Remove(component);
        }
        // The Composite executes its primary logic in a particular way. It
        // traverses recursively through all its children, collecting and
        // summing their results. Since the composite's children pass these
        // calls to their children and so forth, the whole object tree is
        // traversed as a result.
        public override string Operation()
        {
            int i = 0;
            string result = "Branch(";
            foreach (Component component in this._children)
            {
                result += component.Operation();
                if (i != this._children.Count - 1)
                {
                    result += "+";
                }
                i++;
            }
           
            return result + ")";
        }
    }
    class Client
    {
        // The client code works with all of the components via the base
        // interface.
        public void ClientCode(Component leaf)
        {
            Console.WriteLine($"RESULT: {leaf.Operation()}\n");
        }
        // Thanks to the fact that the child-management operations are declared
        // in the base Component class, the client code can work with any
        // component, simple or complex, without depending on their concrete
        // classes.
        public void ClientCode2(Component component1, Component component2)
        {
            if (component1.IsComposite())
            {
                component1.Add(component2);
            }
           
            Console.WriteLine($"RESULT: {component1.Operation()}");
        }
    }
   
    class Program
    {
        static void Main(string[] args)
        {
            Client client = new Client();
            // This way the client code can support the simple leaf
            // components...
            Leaf leaf = new Leaf();
            Console.WriteLine("Client: I get a simple component:");
            client.ClientCode(leaf);
            // ...as well as the complex composites.
            Composite tree = new Composite();
            Composite branch1 = new Composite();
            branch1.Add(new Leaf());
            branch1.Add(new Leaf());
            Composite branch2 = new Composite();
            branch2.Add(new Leaf());
            tree.Add(branch1);
            tree.Add(branch2);
            Console.WriteLine("Client: Now I've got a composite tree:");
            client.ClientCode(tree);
            Console.Write("Client: I don't need to check the components classes even when managing the tree:\n");
            client.ClientCode2(tree, leaf);
        }
    }
}
</syntaxhighlight>
==== Output.txt: المخرجات ====
<syntaxhighlight>
Client: I get a simple component:
RESULT: Leaf
Client: Now I've got a composite tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))
Client: I don't need to check the components classes even when managing the tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)
</syntaxhighlight>
== الاستخدام في لغة PHP ==
'''المستوى''': ★ ★ ☆
'''الانتشار''':  ★ ★ ★
'''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة PHP عند التعامل مع أشجار الكائنات، وأبسط مثال له قد يكون تطبيقه على عناصر شجرة DOM لتعمل مع نمط المركب والعناصر البسيطة للشجرة بنفس الأسلوب.
=== مثال: مثال تصوري ===
يوضح هذا المثال بنية نمط المركَّب، ويركز على إجابة الأسئلة التالية:
* ما الفئات التي يتكون منها؟
* ما الأدوار التي تلعبها هذه الفئات؟
* كيف ترتبط عناصر النمط ببعضها؟
==== index.php: مثال تصوري ====
<syntaxhighlight lang="php">
<?php
namespace RefactoringGuru\Composite\Conceptual;
/**
* The base Component class declares common operations for both simple and
* complex objects of a composition.
*/
abstract class Component
{
    /**
    * @var Component
    */
    protected $parent;
    /**
    * Optionally, the base Component can declare an interface for setting and
    * accessing a parent of the component in a tree structure. It can also
    * provide some default implementation for these methods.
    */
    public function setParent(Component $parent)
    {
        $this->parent = $parent;
    }
    public function getParent(): Component
    {
        return $this->parent;
    }
    /**
    * In some cases, it would be beneficial to define the child-management
    * operations right in the base Component class. This way, you won't need to
    * expose any concrete component classes to the client code, even during the
    * object tree assembly. The downside is that these methods will be empty
    * for the leaf-level components.
    */
    public function add(Component $component): void { }
    public function remove(Component $component): void { }
    /**
    * You can provide a method that lets the client code figure out whether a
    * component can bear children.
    */
    public function isComposite(): bool
    {
        return false;
    }
    /**
    * The base Component may implement some default behavior or leave it to
    * concrete classes (by declaring the method containing the behavior as
    * "abstract").
    */
    abstract public function operation(): string;
}
/**
* The Leaf class represents the end objects of a composition. A leaf can't have
* any children.
*
* Usually, it's the Leaf objects that do the actual work, whereas Composite
* objects only delegate to their sub-components.
*/
class Leaf extends Component
{
    public function operation(): string
    {
        return "Leaf";
    }
}
/**
* The Composite class represents the complex components that may have children.
* Usually, the Composite objects delegate the actual work to their children and
* then "sum-up" the result.
*/
class Composite extends Component
{
    /**
    * @var \SplObjectStorage
    */
    protected $children;
   
    public function __construct()
    {
        $this->children = new \SplObjectStorage;
    }
    /**
    * A composite object can add or remove other components (both simple or
    * complex) to or from its child list.
    */
    public function add(Component $component): void
    {
        $this->children->attach($component);
        $component->setParent($this);
    }
    public function remove(Component $component): void
    {
        $this->children->detach($component);
        $component->setParent(null);
    }
    public function isComposite(): bool
    {
        return true;
    }
    /**
    * The Composite executes its primary logic in a particular way. It
    * traverses recursively through all its children, collecting and summing
    * their results. Since the composite's children pass these calls to their
    * children and so forth, the whole object tree is traversed as a result.
    */
    public function operation(): string
    {
        $results = [];
        foreach ($this->children as $child) {
            $results[] = $child->operation();
        }
        return "Branch(" . implode("+", $results) . ")";
    }
}
/**
* The client code works with all of the components via the base interface.
*/
function clientCode(Component $component)
{
    // ...
    echo "RESULT: " . $component->operation();
    // ...
}
/**
* This way the client code can support the simple leaf components...
*/
$simple = new Leaf;
echo "Client: I've got a simple component:\n";
clientCode($simple);
echo "\n\n";
/**
* ...as well as the complex composites.
*/
$tree = new Composite;
$branch1 = new Composite;
$branch1->add(new Leaf);
$branch1->add(new Leaf);
$branch2 = new Composite;
$branch2->add(new Leaf);
$tree->add($branch1);
$tree->add($branch2);
echo "Client: Now I've got a composite tree:\n";
clientCode($tree);
echo "\n\n";
/**
* Thanks to the fact that the child-management operations are declared in the
* base Component class, the client code can work with any component, simple or
* complex, without depending on their concrete classes.
*/
function clientCode2(Component $component1, Component $component2)
{
    // ...
    if ($component1->isComposite()) {
        $component1->add($component2);
    }
    echo "RESULT: " . $component1->operation();
    // ...
}
echo "Client: I don't need to check the components classes even when managing the tree:\n";
clientCode2($tree, $simple);
</syntaxhighlight>
==== Output.txt: المخرجات ====
<syntaxhighlight>
Client: I get a simple component:
RESULT: Leaf
Client: Now I get a composite tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))
Client: I don't need to check the components classes even when managing the tree::
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)
</syntaxhighlight>
=== مثال: حالة واقعية ===
يستطيع نمط المركب أن ينظم العمل مع أي هيكل تكراري شجري، كما في مثال شجرة HTML DOM، ففي حين تتصرف عناصر الإدخال كالأوراق، تلعب العناصر المعقدة مثل الاستمارات ووسوم fieldsets دور المركَّبات.
ويمكنك استخدام نمط المركب لتطبيق سلوكيات مختلفة إلى كامل شجرة HTML بنفس أسلوب تطبيقها على عناصرها الداخلية دون ربط شيفرتك بالفئات الحقيقية لشجرة DOM ولا تصديرها إلى صيغ مختلفة أو التأكد من صحة أجزائها، إلخ.
ولا تحتاج كذلك مع نمط المركب إلى تفقد ما إن كان العنصر من النوع البسيط أو المعقد قبل تنفيذ السلوك، إذ يُنفَّذ مباشرة أو يُمرر إلى العناصر الفرعية للعنصر، اعتمادًا على نوع العنصر نفسه.
==== index.php: حالة واقعية ====
<syntaxhighlight lang="php">
<?php
namespace RefactoringGuru\Composite\RealWorld;
/**
* The base Component class declares an interface for all concrete components,
* both simple and complex.
*
* In our example, we'll be focusing on the rendering behavior of DOM elements.
*/
abstract class FormElement
{
    /**
    * We can anticipate that all DOM elements require these 3 fields.
    */
    protected $name;
    protected $title;
    protected $data;
    public function __construct(string $name, string $title)
    {
        $this->name = $name;
        $this->title = $title;
    }
    public function getName(): string
    {
        return $this->name;
    }
    public function setData($data): void
    {
        $this->data = $data;
    }
    public function getData(): array
    {
        return $this->data;
    }
    /**
    * Each concrete DOM element must provide its rendering implementation, but
    * we can safely assume that all of them are returning strings.
    */
    abstract public function render(): string;
}
/**
* This is a Leaf component. Like all the Leaves, it can't have any children.
*/
class Input extends FormElement
{
    private $type;
    public function __construct(string $name, string $title, string $type)
    {
        parent::__construct($name, $title);
        $this->type = $type;
    }
    /**
    * Since Leaf components don't have any children that may handle the bulk of
    * the work for them, usually it is the Leaves who do the most of the heavy-
    * lifting within the Composite pattern.
    */
    public function render(): string
    {
        return "<label for=\"{$this->name}\">{$this->title}</label>\n" .
            "<input name=\"{$this->name}\" type=\"{$this->type}\" value=\"{$this->data}\">\n";
    }
}
/**
* The base Composite class implements the infrastructure for managing child
* objects, reused by all Concrete Composites.
*/
abstract class FieldComposite extends FormElement
{
    /**
    * @var FormElement[]
    */
    protected $fields = [];
    /**
    * The methods for adding/removing sub-objects.
    */
    public function add(FormElement $field): void
    {
        $name = $field->getName();
        $this->fields[$name] = $field;
    }
    public function remove(FormElement $component): void
    {
        $this->fields = array_filter($this->fields, function ($child) use ($component) {
            return $child == $component;
        });
    }
    /**
    * Whereas a Leaf's method just does the job, the Composite's method almost
    * always has to take its sub-objects into account.
    *
    * In this case, the composite can accept structured data.
    *
    * @param array $data
    */
    public function setData($data): void
    {
        foreach ($this->fields as $name => $field) {
            if (isset($data[$name])) {
                $field->setData($data[$name]);
            }
        }
    }
    /**
    * The same logic applies to the getter. It returns the structured data of
    * the composite itself (if any) and all the children data.
    */
    public function getData(): array
    {
        $data = [];
       
        foreach ($this->fields as $name => $field) {
            $data[$name] = $field->getData();
        }
       
        return $data;
    }
    /**
    * The base implementation of the Composite's rendering simply combines
    * results of all children. Concrete Composites will be able to reuse this
    * implementation in their real rendering implementations.
    */
    public function render(): string
    {
        $output = "";
       
        foreach ($this->fields as $name => $field) {
            $output .= $field->render();
        }
       
        return $output;
    }
}
/**
* The fieldset element is a Concrete Composite.
*/
class Fieldset extends FieldComposite
{
    public function render(): string
    {
        // Note how the combined rendering result of children is incorporated
        // into the fieldset tag.
        $output = parent::render();
       
        return "<fieldset><legend>{$this->title}</legend>\n$output</fieldset>\n";
    }
}
/**
* And so is the form element.
*/
class Form extends FieldComposite
{
    protected $url;
    public function __construct(string $name, string $title, string $url)
    {
        parent::__construct($name, $title);
        $this->url = $url;
    }
    public function render(): string
    {
        $output = parent::render();
        return "<form action=\"{$this->url}\">\n<h3>{$this->title}</h3>\n$output</form>\n";
    }
}
/**
* The client code gets a convenient interface for building complex tree
* structures.
*/
function getProductForm(): FormElement
{
    $form = new Form('product', "Add product", "/product/add");
    $form->add(new Input('name', "Name", 'text'));
    $form->add(new Input('description', "Description", 'text'));
    $picture = new Fieldset('photo', "Product photo");
    $picture->add(new Input('caption', "Caption", 'text'));
    $picture->add(new Input('image', "Image", 'file'));
    $form->add($picture);
    return $form;
}
/**
* The form structure can be filled with data from various sources. The Client
* doesn't have to traverse through all form fields to assign data to various
* fields since the form itself can handle that.
*/
function loadProductData(FormElement $form)
{
    $data = [
        'name' => 'Apple MacBook',
        'description' => 'A decent laptop.',
        'photo' => [
            'caption' => 'Front photo.',
            'image' => 'photo1.png',
        ],
    ];
    $form->setData($data);
}
/**
* The client code can work with form elements using the abstract interface.
* This way, it doesn't matter whether the client works with a simple component
* or a complex composite tree.
*/
function renderProduct(FormElement $form)
{
    // ..
    echo $form->render();
    // ..
}
$form = getProductForm();
loadProductData($form);
renderProduct($form);
</syntaxhighlight>
==== Output.txt: المخرجات ====
<syntaxhighlight>
<form action="/product/add">
<h3>Add product</h3>
<label for="name">Name</label>
<input name="name" type="text" value="Apple MacBook">
<label for="description">Description</label>
<input name="description" type="text" value="A decent laptop.">
<fieldset><legend>Product photo</legend>
<label for="caption">Caption</label>
<input name="caption" type="text" value="Front photo.">
<label for="image">Image</label>
<input name="image" type="file" value="photo1.png">
</fieldset>
</form>
</syntaxhighlight>
== الاستخدام في لغة بايثون ==
'''المستوى''': ★ ★ ☆
'''الانتشار''':  ★ ★ ★
'''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة بايثون، إذ يُستخدم لتمثيل هرميات لأجزاء من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs).
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري.
=== مثال: مثال تصوري ===
يوضح هذا المثال بنية نمط الجسر، ويركز على إجابة الأسئلة التالية:
* ما الفئات التي يتكون منها؟
* ما الأدوار التي تلعبها هذه الفئات؟
* كيف ترتبط عناصر النمط ببعضها؟
==== main.py: مثال تصوري ====
<syntaxhighlight lang="python">
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List
class Component(ABC):
    """
    The base Component class declares common operations for both simple and
    complex objects of a composition.
    """
    @property
    def parent(self) -> Component:
        return self._parent
    @parent.setter
    def parent(self, parent: Component):
        """
        Optionally, the base Component can declare an interface for setting
        and accessing a parent of the component in a tree structure. It can
        also provide some default implementation for these methods.
        """
        self._parent = parent
    """
    In some cases, it would be beneficial to define the child-management
    operations right in the base Component class. This way, you won't need to
    expose any concrete component classes to the client code, even during the
    object tree assembly. The downside is that these methods will be empty
    for the leaf-level components.
    """
    def add(self, component: Component) -> None:
        pass
    def remove(self, component: Component) -> None:
        pass
    def is_composite(self) -> bool:
        """
        You can provide a method that lets the client code figure out whether
        a component can bear children.
        """
        return False
    @abstractmethod
    def operation(self) -> str:
        """
        The base Component may implement some default behavior or leave it to
        concrete classes (by declaring the method containing the behavior as
        "abstract").
        """
        pass
class Leaf(Component):
    """
    The Leaf class represents the end objects of a composition. A leaf can't
    have any children.
   
    Usually, it's the Leaf objects that do the actual work, whereas Composite
    objects only delegate to their sub-components.
    """
    def operation(self) -> str:
        return "Leaf"
class Composite(Component):
    """
    The Composite class represents the complex components that may have
    children. Usually, the Composite objects delegate the actual work to
    their children and then "sum-up" the result.
    """
    def __init__(self) -> None:
        self._children: List[Component] = []
    """
    A composite object can add or remove other components (both simple or
    complex) to or from its child list.
    """
    def add(self, component: Component) -> None:
        self._children.append(component)
        component.parent = self
    def remove(self, component: Component) -> None:
        self._children.remove(component)
        component.parent = None
    def is_composite(self) -> bool:
        return True
    def operation(self) -> str:
        """
        The Composite executes its primary logic in a particular way. It
        traverses recursively through all its children, collecting and
        summing their results. Since the composite's children pass these
        calls to their children and so forth, the whole object tree is
        traversed as a result.
        """
        results = []
        for child in self._children:
            results.append(child.operation())
        return f"Branch({'+'.join(results)})"
def client_code(component: Component) -> None:
    """
    The client code works with all of the components via the base interface.
    """
    print(f"RESULT: {component.operation()}", end="")
def client_code2(component1: Component, component2: Component) -> None:
    """
    Thanks to the fact that the child-management operations are declared in
    the base Component class, the client code can work with any component,
    simple or complex, without depending on their concrete classes.
    """
    if component1.is_composite():
        component1.add(component2)
    print(f"RESULT: {component1.operation()}", end="")
if __name__ == "__main__":
    # This way the client code can support the simple leaf components...
    simple = Leaf()
    print("Client: I've got a simple component:")
    client_code(simple)
    print("\n")
    # ...as well as the complex composites.
    tree = Composite()
    branch1 = Composite()
    branch1.add(Leaf())
    branch1.add(Leaf())
    branch2 = Composite()
    branch2.add(Leaf())
    tree.add(branch1)
    tree.add(branch2)
    print("Client: Now I've got a composite tree:")
    client_code(tree)
    print("\n")
    print("Client: I don't need to check the components classes even when managing the tree:")
    client_code2(tree, simple)
</syntaxhighlight>
==== Output.txt: المخرجات ====
<syntaxhighlight>
Client: I've got a simple component:
RESULT: Leaf
Client: Now I've got a composite tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))
Client: I don't need to check the components classes even when managing the tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)
</syntaxhighlight>
== انظر أيضًا ==
* [[Design Patterns/factory method|نمط أسلوب المصنع.]]
* [[Design Patterns/abstract factory|نمط المصنع المجرد]].
* [[Design Patterns/singleton|نمط المفردة]].
== مصادر ==
* [https://refactoring.guru/design-patterns/composite توثيق نمط المركَّب في موقع refactoring.guru.]

مراجعة 06:01، 18 فبراير 2019

نمط المركَّب هو نمط تصميم هيكلي يسمح لك بتركيب كائنات في هياكل شجرية لتعاملها على أنها وحدة واحدة أو كائن واحد.

المشكلة

يستخدم نمط المركَّب في حالة واحدة فقط، وهي حين يمكن تمثيل النموذج الذي يقوم عليه تطبيقك في هيئة شجرية، كأن يكون لديك نوعين من الكائنات هما المنتجات Products والصناديق Boxes. ويستطيع الأخير استيعاب منتجات بداخله وصناديق أخرى كذلك أصغر منه في الحجم، وتلك الصناديق الجديدة يمكنها احتواء صناديق أخرى بداخلها، وهكذا دواليك. ولنقل أنك تريد إنشاء نظام طلبات يستخدم تلك الفئات، تحتوي فيه الطلبات المختلفة على منتجات بسيطة بدون أي تغليف إضافة إلى صناديق ملأى بالمنتجات والصناديق الأخرى الأصغر منها، وتريد الآن أن تحسب الثمن الكلي لطلب به صناديق بداخلها صناديق أخرى ومنتجات بدون تغليف، كيف تفعل ذلك؟

ضع الصورة: قد يتكون الطلب الواحد من عدة منتجات مختلفة مغلفة في صناديق وغير مغلفة، وتغلف كلها في صناديق أكبر منها تحتويها، وهكذا ليبدو الشكل الكلي كشجرة مقلوبة.

إن سلكنا الطريق المباشرة فهذا يعني فتح الصناديق جميعها وحساب ثمن كل منتج بسيط ثم جمع تلك الأثمان لنحصل على الثمن الكلي للطلب كاملًا، لكن هذا الأسلوب وإن كان يصلح أحيانًا في الأمثلة التي نراها في الحياة اليومية، لكنها لا تصلح في البرمجة، إذ -وفقًا لهذا المنظور- يجب أن تعرف فئات كل المنتجات Products والصناديق Boxes التي ستفحصها، ومستوى تداخل الصناديق وأمور أخرى غير ذلك، وذلك يجعل المنظور المباشر غير عملي وقريبًا من المستحيل لحل مشكلة كهذه.

الحل

يقترح نمط المركَّب أن تعمل مع Boxes و Products من خلال واجهة مشتركة تصرح عن أسلوب لحساب الثمن الكلي للطلب، وسيفحص ذلك الأسلوب كل عنصر في الصندوق ويطلب ثمنه ثم يعيد إجمالي الصندوق، وإن كان أحد عناصر الصندوق صندوقًا أصغر منه فإن الأسلوب يكرر نفس الخطوات في فحص كل عنصر فيه وطلب ثمنه، وهكذا يعامل كل صندوق فرعي معاملةَ الصندوق الأول حتى يصل إلى آخر تلك السلسلة ويحسب أثمان كل المنتجات الفرعية، بل قد يحسب أثمان الصناديق أنفسها أيضًا في بعض الحالات التي تُحسب فيها قيمة الصناديق من تكلفة التغليف.

ضع الصورة: يسمح لك نمط المركَّب بتشغيل أسلوب ما بشكل تكراري في عناصر شجرة من الكائنات.

وأعظم فائدة لهذا الأسلوب في الحل أنك لا تحتاج إلى النظر في الفئات الحقيقية للكائنات التي تتكون منها الشجرة، فلا تحتاج إلى معرفة ما إن كان الكائن منتجًا بسيطًا أو صندوقًا معقدًا إذ يمكنك معالجتها جميعًا بنفس الأسلوب من خلال الواجهة المشتركة، ذلك أن الكائنات أنفسها تمرر الطلب إلى ما تحتها في كامل الشجرة كلما استدعيت أسلوبًا ما.

مثال واقعي

ضع الصورة: مثال لهيكلة أحد الجيوش.

تُشكَّل جيوش أغلب الدول بصورة هرمية يتكون فيها الجيش من أقسام رئيسية، في كل قسم مجموعة ألوية، في كل لواء منها مجموعة كتائب، ثم كل كتيبة بها مجموعة سرايا، وكل سرية فيها مجموعات من الجنود الذين تُمرر إليهم الأوامر من القيادات العليا للجيش لتعرف المهام التي عليها تنفيذها.

البُنية

ضع الصورة

  1. تصف واجهةُ الجزء (Component) العمليات المشتركةَ في كل من العناصر البسيطة والمعقدة في الشجرة.
  2. الورقة (Leaf) هي عنصر بسيط في الشجرة ليس له عناصر فرعية، وعادة ما تنفذ الورقة أغلب المهام الحقيقية بما أنها ليس لها عناصر تحتها تفوض إليه تلك المهام.
  3. الحاوية (المركَّب أيضًا) هي عنصر في الشجرة تحته عناصر فرعية سواء كانت حاويات أخرى أو أوراقًا، ولا تعرف الحاوية الفئات الحقيقية لما تحتها من فروع، وهي تعمل مع كل العناصر الفرعية من خلال واجهة الجزء فقط، وتفوض العمل إلى عناصرها الفرعية ثم تعالج النتائج الأولية وتعيد النتيجة النهائية للعميل.
  4. يعمل العميل مع كل العناصر من خلال واجهة الجزء، ويستطيع العمل نتيجة لهذا بنفس الطريقة مع العناصر البسيطة والمعقدة من الشجرة.

مثال توضيحي

يسمح لك نمط المركَّب في هذا المثال بتنفيذ تكديسات لأشكال هندسية (Geometric) داخل محرِّر مرئي.

ضع الصورة: مثال محرر الأشكال الهندسية.

فئة CompoundGraphic هي الحاوية التي تشمل أي عدد من الأشكال الفرعية بما في ذلك الأشكال المجمَّعة الأخرى، والشكل المجمَّع له نفس الأساليب كالشكل البسيط، لكن بدلًا من فعل أي شيء بنفسه فإنه -أي المجمَّع- يمرر الطلب بشكل تكراري إلى كل فروعه ويجمع النتيجة في النهاية.

وتعمل شيفرة العميل مع الأشكال كلها من خلال واجهة واحدة مشتركة بين كل فئات الأشكال، لهذا لا يعرف العميل ما إن كان يعمل مع شكل بسيط أو مجمع، ويعمل العميل لهذا أيضًا مع هياكل لأشكال معقدة جدًا دون التقيد بالفئات الحقيقية التي تكوِّن تلك الهياكل.

// تصرح واجهة الجزء عن عمليات مشتركة للأشكال المجمعة والبسيطة لمركب ما.
interface Graphic is
    method move(x, y)
    method draw()

// تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا يمكن لكائن الورقة أن يحتوي على كائنات فرعية
// وعادة ما ينفذ المهام الحقيقية لذلك، بينما تفوض الكائنات المجمعة المهام إلى ما دونها.
class Dot implements Graphic is
    field x, y

    constructor Dot(x, y) { ... }

    method move(x, y) is
        this.x += x, this.y += y

    method draw() is
        // Y و X ارسم نقطة عند.

// جميع فئات الجزء تستطيع توسعة الأجزاء الأجزاء الأخرى.
class Circle extends Dot is
    field radius

    constructor Circle(x, y, radius) { ... }

    method draw() is
        // R وبنصف قطر Y و X ارسم دائرة عند.

// تمثل فئة المركب أجزاءً معقدة قد تكون لها كائنات فرعية، وتفوض كائناتُ المركبُ المهامَ عادة لما دونها 
// ثم تجمع النتيجة.
class CompoundGraphic implements Graphic is
    field children: array of Graphic

    // يمكن أن يضيف كائن المركب أو يحذف أجزاء أخرى -بسيطة أومعقدة- إلى أو من قائمته الفرعية.
    method add(child: Graphic) is
        // إضافة جزء فرعي إلى مصفوفة الفروع.

    method remove(child: Graphic) is
        // حذف جزء فرعي إلى مصفوفة الفروع.

    method move(x, y) is
        foreach (child in children) do
            child.move(x, y)

    // ينفذ المركب منطقه الأساسي بشكل معين، إذ يمر مرة بعد مرة على فروعه كلها ويجمع نتائجها، وبما أن فروع
    // المركب تمرر استدعاءتها إلى فروعها فإن المركب يمر على الشجرة كلها.
    method draw() is
        // 1. لكل جزء فرعي
        //  - ارسم الجزء
        //  - (Bounding Rectangle) حدِّث المستطيل المحدِّد
        // 2. (Bounding Coordinates) باستخدام إحداثيات الحدود (Dashes) ارسم مستطيلًا خطه مكون من شُرط مقطعة .


// تعمل شيفرة العميل مع كل الأجزاء من خلال واجهتها الأساسية، وهكذا تستطيع شيفرة العميل أن تدعم أجزاء الورقة
// البسيطة والمركبات المعقدة على حد سواء.
class ImageEditor is
    method load() is
        all = new CompoundGraphic()
        all.add(new Dot(1, 2))
        all.add(new Circle(5, 3, 10))
        // ...

    // اجمع الأجزاء المختارة في جزءٍ مركب معقد واحد.
    method groupSelected(components: array of Graphic) is
        group = new CompoundGraphic()
        group.add(components)
        all.remove(components)
        all.add(group)
        // ستُرسم كل الأجزاء.
        all.draw()

قابلية التنفيذ

  • استخدم نمط المركَّب عند تنفيذ هيكل شجري لمجموعة كائنات.

يزودك نمط المركَّب بنوعين أساسيين من العناصر التي تشترك في واجهة واحدة، وهي الأوراق (Leaves) البسيطة، والحاويات (Containers) المعقدة، وقد تتكون الحاوية من الأوراق والحاويات الأخرى كذلك، ويسمح لك هذا بإنشاء هيكلة تكرارية متداخلة تشبه الشجرة.

  • استخدم نمط المركَّب حين تريد لشيفرة العميل أن تعامل العناصر البسيطة والمعقدة بنفس الأسلوب.

تشترك كل العناصر المعرَّفة من قبل نمط المركَّب في واجهة واحدة، ولا يحتاج العميل أن يعرف الفئة الحقيقية للكائن الذي يتعامل معه.

كيفية الاستخدام

  1. تأكد أن النموذج الأساسي لتطبيقك يمكن تمثيله بهيكل شجري، ثم جزّء ذلك إلى عناصر بسيطة وحاويات. تذكر أن الحاويات يجب أن تكون قادرة على احتواء العناصر البسيطة إضافة إلى الحاويات الأخرى كذلك.
  2. صرِّح عن واجهة الجزء مع قائمة أساليب تصلح للعناصر البسيطة والحاويات المعقدة على حد سواء.
  3. أنشئ فئة ورقة (Leaf) لتمثل العناصر البسيطة، قد يحتوي البرنامج فئات مختلفة للأوراق.
  4. أنشئ فئة حاوية (Container) لتمثل العناصر المعقدة في تلك الفئة، وأضف حقل مصفوفة لتخزين المراجع إلى العناصر الفرعية، يجب أن تكون المصفوفة قادرة على تخزين الأوراق والحاويات، لذا تأكد أنها مصرح عنها مع نوع واجهة الجزء. تذكر أثناء تنفيذ أساليب واجهة الجزء أن الحاوية يفترض بها تفويض أغلب مهامها إلى العناصر الفرعية لها.
  5. أخيرًا، عرّف أساليب إضافة وحذف العناصر الفرعية في الحاوية.

لاحظ أن هذه العمليات يمكن التصريح عنها في واجهة الجزء (Component Interface)، وذاك قد يخرق مبدأ عزل الواجهة (Interface Segregation Principle) لأن الأساليب ستكون فارغة في فئة الورقة، لكن العميل سيتمكن من معاملة كل العناصر بالتساوي حتى عند تركيب (Composing) الشجرة.

المزايا والعيوب

المزايا

  • تستطيع العمل مع هياكل شجرية معقدة بشكل أسهل: استخدم تعدد الأشكال (polymorphism) والتكرار المستمر (recursion) لصالحك.
  • مبدأ المفتوح/المغلق. تستطيع إدخال أنواعًا جديدة من العناصر في التطبيق دون تعطيل الشيفرة الحالية، والتي تعمل الآن مع شجرة كائنات.

العيوب

  • قد يكون من الصعب توفير واجهة مشتركة للفئات التي تختلف وظائفها عن بعضها بشكل كبير، فستحتاج في حالات معينة إلى المبالغة في تعميم واجهة الجزء إلى الحد الذي سيجعلها تستعصي على الفهم.

العلاقات مع الأنماط الأخرى

  • يمكنك استخدام نمط الباني عند إنشاء أشجار مركبات معقدة لأنك تستطيع برمجة خطوات هيكلتها لتعمل بتكرارية.
  • تُستخدم سلسلة المسؤوليات عادة بالتزامن مع المركَّب، وفي تلك الحالة تمرر الورقة الطلب الذي تحصل عليه خلال الأجزاء التي تعلوها في السلسلة الهرمية حتى تصل إلى جذر شجرة الكائنات.
  • يمكنك استخدام المكررات لاجتياز أشجار المركبات.
  • يمكنك استخدام نمط الزائر لتنفيذ عملية على شجرة مركب بكاملها.
  • يمكنك تنفيذ عُقد ورقية مشتركة (Shared Leaf Nodes) لشجرة المركب لتكوّن أنماطًا من نوع وزن الذبابة، من أجل توفير الذاكرة العشوائية RAM.
  • لنمطيْ المركب والمزخرِف هياكل متشابهة بما أنهما يعتمدان على التركيب التكراري لتنظيم عدد مفتوح النهاية (open-ended) من الكائنات.
  • المزخرف مثل المركب لكنه لا يملك إلا جزءًا فرعيًا واحدًا، ويضيف المزخرف مسؤوليات إضافية إلى الكائن المُغلَّف، بينما ما يقوم به المركَّب هو جمع نتائج الفروع فقط. لكن بأي حال فإن هذه الأنماط تتعاون فيما بينها، فتستطيع استخدام المزخرِف لتوسيع سلوك كائن معين في شجرة مركَّب ما.
  • التصميمات التي تستخدم المركب والمزخرف بكثرة يمكنها الاستفادة من نمط النموذج الأولي، إذ استخدامه يسمح باستنساخ هياكل معقدة بدلًا من إعادة بنائهم من الصفر.

الاستخدام في لغة جافا

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ★

أمثلة الاستخدام: يكثر استخدام نمط المركَّب في لغة بايثون، إذ يُستخدم لتمثيل هرميات لأجزاء من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs). إليك بعض الأمثلة على نمط المركب من مكتبات جافا قياسية:

(java.awt.Container#add(Component تقريبًا في كل أجزاء واجهة Swing.

()javax.faces.component.UIComponent#getChildren تقريبًا في كل أجزاء واجهات JSF.

يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري.

مثال: أشكال مرئية بسيطة ومجمَّعة

أشكال

 shapes/Shape.java: واجهة مشتركة للشكل
package refactoring_guru.composite.example.shapes;

import java.awt.*;

public interface Shape {
    int getX();
    int getY();
    int getWidth();
    int getHeight();
    void move(int x, int y);
    boolean isInsideBounds(int x, int y);
    void select();
    void unSelect();
    boolean isSelected();
    void paint(Graphics graphics);
}
 shapes/BaseShape.java: شكل تجريدي بوظائف بسيطة
package refactoring_guru.composite.example.shapes;

import java.awt.*;

abstract class BaseShape implements Shape {
    public int x;
    public int y;
    public Color color;
    private boolean selected = false;

    BaseShape(int x, int y, Color color) {
        this.x = x;
        this.y = y;
        this.color = color;
    }

    @Override
    public int getX() {
        return x;
    }

    @Override
    public int getY() {
        return y;
    }

    @Override
    public int getWidth() {
        return 0;
    }

    @Override
    public int getHeight() {
        return 0;
    }

    @Override
    public void move(int x, int y) {
        this.x += x;
        this.y += y;
    }

    @Override
    public boolean isInsideBounds(int x, int y) {
        return x > getX() && x < (getX() + getWidth()) &&
                y > getY() && y < (getY() + getHeight());
    }

    @Override
    public void select() {
        selected = true;
    }

    @Override
    public void unSelect() {
        selected = false;
    }

    @Override
    public boolean isSelected() {
        return selected;
    }

    void enableSelectionStyle(Graphics graphics) {
        graphics.setColor(Color.LIGHT_GRAY);

        Graphics2D g2 = (Graphics2D) graphics;
        float dash1[] = {2.0f};
        g2.setStroke(new BasicStroke(1.0f,
                BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_MITER,
                2.0f, dash1, 0.0f));
    }

    void disableSelectionStyle(Graphics graphics) {
        graphics.setColor(color);
        Graphics2D g2 = (Graphics2D) graphics;
        g2.setStroke(new BasicStroke());
    }


    @Override
    public void paint(Graphics graphics) {
        if (isSelected()) {
            enableSelectionStyle(graphics);
        }
        else {
            disableSelectionStyle(graphics);
        }

        // ...
    }
}
 shapes/Dot.java: نقطة
package refactoring_guru.composite.example.shapes;

import java.awt.*;

public class Dot extends BaseShape {
    private final int DOT_SIZE = 3;

    public Dot(int x, int y, Color color) {
        super(x, y, color);
    }

    @Override
    public int getWidth() {
        return DOT_SIZE;
    }

    @Override
    public int getHeight() {
        return DOT_SIZE;
    }

    @Override
    public void paint(Graphics graphics) {
        super.paint(graphics);
        graphics.fillRect(x - 1, y - 1, getWidth(), getHeight());
    }
}
 shapes/Circle.java: دائرة
package refactoring_guru.composite.example.shapes;

import java.awt.*;

public class Circle extends BaseShape {
    public int radius;

    public Circle(int x, int y, int radius, Color color) {
        super(x, y, color);
        this.radius = radius;
    }

    @Override
    public int getWidth() {
        return radius * 2;
    }

    @Override
    public int getHeight() {
        return radius * 2;
    }

    public void paint(Graphics graphics) {
        super.paint(graphics);
        graphics.drawOval(x, y, getWidth() - 1, getHeight() - 1);
    }
}
 shapes/Rectangle.java: مستطيل
package refactoring_guru.composite.example.shapes;

import java.awt.*;

public class Rectangle extends BaseShape {
    public int width;
    public int height;

    public Rectangle(int x, int y, int width, int height, Color color) {
        super(x, y, color);
        this.width = width;
        this.height = height;
    }

    @Override
    public int getWidth() {
        return width;
    }

    @Override
    public int getHeight() {
        return height;
    }

    @Override
    public void paint(Graphics graphics) {
        super.paint(graphics);
        graphics.drawRect(x, y, getWidth() - 1, getHeight() - 1);
    }
}
 shapes/CompoundShape.java: شكل مجمع يتكون من كائنات أشكال أخرى
package refactoring_guru.composite.example.shapes;

import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CompoundShape extends BaseShape {
    protected List<Shape> children = new ArrayList<>();

    public CompoundShape(Shape... components) {
        super(0, 0, Color.BLACK);
        add(components);
    }

    public void add(Shape component) {
        children.add(component);
    }

    public void add(Shape... components) {
        children.addAll(Arrays.asList(components));
    }

    public void remove(Shape child) {
        children.remove(child);
    }

    public void remove(Shape... components) {
        children.removeAll(Arrays.asList(components));
    }

    public void clear() {
        children.clear();
    }

    @Override
    public int getX() {
        if (children.size() == 0) {
            return 0;
        }
        int x = children.get(0).getX();
        for (Shape child : children) {
            if (child.getX() < x) {
                x = child.getX();
            }
        }
        return x;
    }

    @Override
    public int getY() {
        if (children.size() == 0) {
            return 0;
        }
        int y = children.get(0).getY();
        for (Shape child : children) {
            if (child.getY() < y) {
                y = child.getY();
            }
        }
        return y;
    }

    @Override
    public int getWidth() {
        int maxWidth = 0;
        int x = getX();
        for (Shape child : children) {
            int childsRelativeX = child.getX() - x;
            int childWidth = childsRelativeX + child.getWidth();
            if (childWidth > maxWidth) {
                maxWidth = childWidth;
            }
        }
        return maxWidth;
    }

    @Override
    public int getHeight() {
        int maxHeight = 0;
        int y = getY();
        for (Shape child : children) {
            int childsRelativeY = child.getY() - y;
            int childHeight = childsRelativeY + child.getHeight();
            if (childHeight > maxHeight) {
                maxHeight = childHeight;
            }
        }
        return maxHeight;
    }

    @Override
    public void move(int x, int y) {
        for (Shape child : children) {
            child.move(x, y);
        }
    }

    @Override
    public boolean isInsideBounds(int x, int y) {
        for (Shape child : children) {
            if (child.isInsideBounds(x, y)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void unSelect() {
        super.unSelect();
        for (Shape child : children) {
            child.unSelect();
        }
    }

    public boolean selectChildAt(int x, int y) {
        for (Shape child : children) {
            if (child.isInsideBounds(x, y)) {
                child.select();
                return true;
            }
        }
        return false;
    }

    @Override
    public void paint(Graphics graphics) {
        if (isSelected()) {
            enableSelectionStyle(graphics);
            graphics.drawRect(getX() - 1, getY() - 1, getWidth() + 1, getHeight() + 1);
            disableSelectionStyle(graphics);
        }

        for (refactoring_guru.composite.example.shapes.Shape child : children) {
            child.paint(graphics);
        }
    }
}

المحرر

 editor/ImageEditor.java: محرر الشكل
package refactoring_guru.composite.example.editor;

import refactoring_guru.composite.example.shapes.CompoundShape;
import refactoring_guru.composite.example.shapes.Shape;

import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class ImageEditor {
    private EditorCanvas canvas;
    private CompoundShape allShapes = new CompoundShape();

    public ImageEditor() {
        canvas = new EditorCanvas();
    }

    public void loadShapes(Shape... shapes) {
        allShapes.clear();
        allShapes.add(shapes);
        canvas.refresh();
    }

    private class EditorCanvas extends Canvas {
        JFrame frame;

        private static final int PADDING = 10;

        EditorCanvas() {
            createFrame();
            refresh();
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    allShapes.unSelect();
                    allShapes.selectChildAt(e.getX(), e.getY());
                    e.getComponent().repaint();
                }
            });
        }

        void createFrame() {
            frame = new JFrame();
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            frame.setLocationRelativeTo(null);

            JPanel contentPanel = new JPanel();
            Border padding = BorderFactory.createEmptyBorder(PADDING, PADDING, PADDING, PADDING);
            contentPanel.setBorder(padding);
            frame.setContentPane(contentPanel);

            frame.add(this);
            frame.setVisible(true);
            frame.getContentPane().setBackground(Color.LIGHT_GRAY);
        }

        public int getWidth() {
            return allShapes.getX() + allShapes.getWidth() + PADDING;
        }

        public int getHeight() {
            return allShapes.getY() + allShapes.getHeight() + PADDING;
        }

        void refresh() {
            this.setSize(getWidth(), getHeight());
            frame.pack();
        }

        public void paint(Graphics graphics) {
            allShapes.paint(graphics);
        }
    }
}
 Demo.java: شيفرة العميل
package refactoring_guru.composite.example;

import refactoring_guru.composite.example.editor.ImageEditor;
import refactoring_guru.composite.example.shapes.Circle;
import refactoring_guru.composite.example.shapes.CompoundShape;
import refactoring_guru.composite.example.shapes.Dot;
import refactoring_guru.composite.example.shapes.Rectangle;

import java.awt.*;

public class Demo {
    public static void main(String[] args) {
        ImageEditor editor = new ImageEditor();

        editor.loadShapes(
                new Circle(10, 10, 10, Color.BLUE),

                new CompoundShape(
                    new Circle(110, 110, 50, Color.RED),
                    new Dot(160, 160, Color.RED)
                ),

                new CompoundShape(
                        new Rectangle(250, 250, 100, 100, Color.GREEN),
                        new Dot(240, 240, Color.GREEN),
                        new Dot(240, 360, Color.GREEN),
                        new Dot(360, 360, Color.GREEN),
                        new Dot(360, 240, Color.GREEN)
                )
        );
    }
}

 OutputDemo.png: نتائج التنفيذ

ضع الصورة (ملف:dpcj.OutputDemo.png)

الاستخدام في لغة #C

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ★

أمثلة الاستخدام: يكثر استخدام نمط المركَّب في لغة #C، إذ يُستخدم لتمثيل هرميات لأجزاء من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs).

يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري.

مثال: مثال تصوري

يوضح هذا المثال بنية نمط المركَّب، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 Program.cs: مثال تصوري

using System;
using System.Collections.Generic;

namespace RefactoringGuru.DesignPatterns.Composite.Conceptual
{
    // The base Component class declares common operations for both simple and
    // complex objects of a composition.
    abstract class Component
    {
        public Component() { }

        // The base Component may implement some default behavior or leave it to
        // concrete classes (by declaring the method containing the behavior as
        // "abstract").
        public abstract string Operation();

        // In some cases, it would be beneficial to define the child-management
        // operations right in the base Component class. This way, you won't
        // need to expose any concrete component classes to the client code,
        // even during the object tree assembly. The downside is that these
        // methods will be empty for the leaf-level components.
        public virtual void Add(Component component)
        {
            throw new NotImplementedException();
        }

        public virtual void Remove(Component component)
        {
            throw new NotImplementedException();
        }

        // You can provide a method that lets the client code figure out whether
        // a component can bear children.
        public virtual bool IsComposite()
        {
            return true;
        }
    }

    // The Leaf class represents the end objects of a composition. A leaf can't
    // have any children.
    //
    // Usually, it's the Leaf objects that do the actual work, whereas Composite
    // objects only delegate to their sub-components.
    class Leaf : Component
    {
        public override string Operation()
        {
            return "Leaf";
        }

        public override bool IsComposite()
        {
            return false;
        }
    }

    // The Composite class represents the complex components that may have
    // children. Usually, the Composite objects delegate the actual work to
    // their children and then "sum-up" the result.
    class Composite : Component
    {
        protected List<Component> _children = new List<Component>();
        
        public override void Add(Component component)
        {
            this._children.Add(component);
        }

        public override void Remove(Component component)
        {
            this._children.Remove(component);
        }

        // The Composite executes its primary logic in a particular way. It
        // traverses recursively through all its children, collecting and
        // summing their results. Since the composite's children pass these
        // calls to their children and so forth, the whole object tree is
        // traversed as a result.
        public override string Operation()
        {
            int i = 0;
            string result = "Branch(";

            foreach (Component component in this._children)
            {
                result += component.Operation();
                if (i != this._children.Count - 1)
                {
                    result += "+";
                }
                i++;
            }
            
            return result + ")";
        }
    }

    class Client
    {
        // The client code works with all of the components via the base
        // interface.
        public void ClientCode(Component leaf)
        {
            Console.WriteLine($"RESULT: {leaf.Operation()}\n");
        }

        // Thanks to the fact that the child-management operations are declared
        // in the base Component class, the client code can work with any
        // component, simple or complex, without depending on their concrete
        // classes.
        public void ClientCode2(Component component1, Component component2)
        {
            if (component1.IsComposite())
            {
                component1.Add(component2);
            }
            
            Console.WriteLine($"RESULT: {component1.Operation()}");
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            Client client = new Client();

            // This way the client code can support the simple leaf
            // components...
            Leaf leaf = new Leaf();
            Console.WriteLine("Client: I get a simple component:");
            client.ClientCode(leaf);

            // ...as well as the complex composites.
            Composite tree = new Composite();
            Composite branch1 = new Composite();
            branch1.Add(new Leaf());
            branch1.Add(new Leaf());
            Composite branch2 = new Composite();
            branch2.Add(new Leaf());
            tree.Add(branch1);
            tree.Add(branch2);
            Console.WriteLine("Client: Now I've got a composite tree:");
            client.ClientCode(tree);

            Console.Write("Client: I don't need to check the components classes even when managing the tree:\n");
            client.ClientCode2(tree, leaf);
        }
    }
}

 Output.txt: المخرجات

Client: I get a simple component:
RESULT: Leaf

Client: Now I've got a composite tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))

Client: I don't need to check the components classes even when managing the tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)

الاستخدام في لغة PHP

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ★

أمثلة الاستخدام: يكثر استخدام نمط المركَّب في لغة PHP عند التعامل مع أشجار الكائنات، وأبسط مثال له قد يكون تطبيقه على عناصر شجرة DOM لتعمل مع نمط المركب والعناصر البسيطة للشجرة بنفس الأسلوب.

مثال: مثال تصوري

يوضح هذا المثال بنية نمط المركَّب، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 index.php: مثال تصوري

<?php

namespace RefactoringGuru\Composite\Conceptual;

/**
 * The base Component class declares common operations for both simple and
 * complex objects of a composition.
 */
abstract class Component
{
    /**
     * @var Component
     */
    protected $parent;

    /**
     * Optionally, the base Component can declare an interface for setting and
     * accessing a parent of the component in a tree structure. It can also
     * provide some default implementation for these methods.
     */
    public function setParent(Component $parent)
    {
        $this->parent = $parent;
    }

    public function getParent(): Component
    {
        return $this->parent;
    }

    /**
     * In some cases, it would be beneficial to define the child-management
     * operations right in the base Component class. This way, you won't need to
     * expose any concrete component classes to the client code, even during the
     * object tree assembly. The downside is that these methods will be empty
     * for the leaf-level components.
     */
    public function add(Component $component): void { }

    public function remove(Component $component): void { }

    /**
     * You can provide a method that lets the client code figure out whether a
     * component can bear children.
     */
    public function isComposite(): bool
    {
        return false;
    }

    /**
     * The base Component may implement some default behavior or leave it to
     * concrete classes (by declaring the method containing the behavior as
     * "abstract").
     */
    abstract public function operation(): string;
}

/**
 * The Leaf class represents the end objects of a composition. A leaf can't have
 * any children.
 *
 * Usually, it's the Leaf objects that do the actual work, whereas Composite
 * objects only delegate to their sub-components.
 */
class Leaf extends Component
{
    public function operation(): string
    {
        return "Leaf";
    }
}

/**
 * The Composite class represents the complex components that may have children.
 * Usually, the Composite objects delegate the actual work to their children and
 * then "sum-up" the result.
 */
class Composite extends Component
{
    /**
     * @var \SplObjectStorage
     */
    protected $children;
    
    public function __construct()
    {
        $this->children = new \SplObjectStorage;
    }

    /**
     * A composite object can add or remove other components (both simple or
     * complex) to or from its child list.
     */
    public function add(Component $component): void
    {
        $this->children->attach($component);
        $component->setParent($this);
    }

    public function remove(Component $component): void
    {
        $this->children->detach($component);
        $component->setParent(null);
    }

    public function isComposite(): bool
    {
        return true;
    }

    /**
     * The Composite executes its primary logic in a particular way. It
     * traverses recursively through all its children, collecting and summing
     * their results. Since the composite's children pass these calls to their
     * children and so forth, the whole object tree is traversed as a result.
     */
    public function operation(): string
    {
        $results = [];
        foreach ($this->children as $child) {
            $results[] = $child->operation();
        }

        return "Branch(" . implode("+", $results) . ")";
    }
}

/**
 * The client code works with all of the components via the base interface.
 */
function clientCode(Component $component)
{
    // ...

    echo "RESULT: " . $component->operation();

    // ...
}

/**
 * This way the client code can support the simple leaf components...
 */
$simple = new Leaf;
echo "Client: I've got a simple component:\n";
clientCode($simple);
echo "\n\n";

/**
 * ...as well as the complex composites.
 */
$tree = new Composite;
$branch1 = new Composite;
$branch1->add(new Leaf);
$branch1->add(new Leaf);
$branch2 = new Composite;
$branch2->add(new Leaf);
$tree->add($branch1);
$tree->add($branch2);
echo "Client: Now I've got a composite tree:\n";
clientCode($tree);
echo "\n\n";

/**
 * Thanks to the fact that the child-management operations are declared in the
 * base Component class, the client code can work with any component, simple or
 * complex, without depending on their concrete classes.
 */
function clientCode2(Component $component1, Component $component2)
{
    // ...

    if ($component1->isComposite()) {
        $component1->add($component2);
    }
    echo "RESULT: " . $component1->operation();

    // ...
}

echo "Client: I don't need to check the components classes even when managing the tree:\n";
clientCode2($tree, $simple);

 Output.txt: المخرجات

Client: I get a simple component:
RESULT: Leaf

Client: Now I get a composite tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))

Client: I don't need to check the components classes even when managing the tree::
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)

مثال: حالة واقعية

يستطيع نمط المركب أن ينظم العمل مع أي هيكل تكراري شجري، كما في مثال شجرة HTML DOM، ففي حين تتصرف عناصر الإدخال كالأوراق، تلعب العناصر المعقدة مثل الاستمارات ووسوم fieldsets دور المركَّبات.

ويمكنك استخدام نمط المركب لتطبيق سلوكيات مختلفة إلى كامل شجرة HTML بنفس أسلوب تطبيقها على عناصرها الداخلية دون ربط شيفرتك بالفئات الحقيقية لشجرة DOM ولا تصديرها إلى صيغ مختلفة أو التأكد من صحة أجزائها، إلخ.

ولا تحتاج كذلك مع نمط المركب إلى تفقد ما إن كان العنصر من النوع البسيط أو المعقد قبل تنفيذ السلوك، إذ يُنفَّذ مباشرة أو يُمرر إلى العناصر الفرعية للعنصر، اعتمادًا على نوع العنصر نفسه.

 index.php: حالة واقعية

<?php

namespace RefactoringGuru\Composite\RealWorld;

/**
 * The base Component class declares an interface for all concrete components,
 * both simple and complex.
 *
 * In our example, we'll be focusing on the rendering behavior of DOM elements.
 */
abstract class FormElement
{
    /**
     * We can anticipate that all DOM elements require these 3 fields.
     */
    protected $name;
    protected $title;
    protected $data;

    public function __construct(string $name, string $title)
    {
        $this->name = $name;
        $this->title = $title;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setData($data): void
    {
        $this->data = $data;
    }

    public function getData(): array
    {
        return $this->data;
    }

    /**
     * Each concrete DOM element must provide its rendering implementation, but
     * we can safely assume that all of them are returning strings.
     */
    abstract public function render(): string;
}

/**
 * This is a Leaf component. Like all the Leaves, it can't have any children.
 */
class Input extends FormElement
{
    private $type;

    public function __construct(string $name, string $title, string $type)
    {
        parent::__construct($name, $title);
        $this->type = $type;
    }

    /**
     * Since Leaf components don't have any children that may handle the bulk of
     * the work for them, usually it is the Leaves who do the most of the heavy-
     * lifting within the Composite pattern.
     */
    public function render(): string
    {
        return "<label for=\"{$this->name}\">{$this->title}</label>\n" .
            "<input name=\"{$this->name}\" type=\"{$this->type}\" value=\"{$this->data}\">\n";
    }
}

/**
 * The base Composite class implements the infrastructure for managing child
 * objects, reused by all Concrete Composites.
 */
abstract class FieldComposite extends FormElement
{
    /**
     * @var FormElement[]
     */
    protected $fields = [];

    /**
     * The methods for adding/removing sub-objects.
     */
    public function add(FormElement $field): void
    {
        $name = $field->getName();
        $this->fields[$name] = $field;
    }

    public function remove(FormElement $component): void
    {
        $this->fields = array_filter($this->fields, function ($child) use ($component) {
            return $child == $component;
        });
    }

    /**
     * Whereas a Leaf's method just does the job, the Composite's method almost
     * always has to take its sub-objects into account.
     *
     * In this case, the composite can accept structured data.
     *
     * @param array $data
     */
    public function setData($data): void
    {
        foreach ($this->fields as $name => $field) {
            if (isset($data[$name])) {
                $field->setData($data[$name]);
            }
        }
    }

    /**
     * The same logic applies to the getter. It returns the structured data of
     * the composite itself (if any) and all the children data.
     */
    public function getData(): array
    {
        $data = [];
        
        foreach ($this->fields as $name => $field) {
            $data[$name] = $field->getData();
        }
        
        return $data;
    }

    /**
     * The base implementation of the Composite's rendering simply combines
     * results of all children. Concrete Composites will be able to reuse this
     * implementation in their real rendering implementations.
     */
    public function render(): string
    {
        $output = "";
        
        foreach ($this->fields as $name => $field) {
            $output .= $field->render();
        }
        
        return $output;
    }
}

/**
 * The fieldset element is a Concrete Composite.
 */
class Fieldset extends FieldComposite
{
    public function render(): string
    {
        // Note how the combined rendering result of children is incorporated
        // into the fieldset tag.
        $output = parent::render();
        
        return "<fieldset><legend>{$this->title}</legend>\n$output</fieldset>\n";
    }
}

/**
 * And so is the form element.
 */
class Form extends FieldComposite
{
    protected $url;

    public function __construct(string $name, string $title, string $url)
    {
        parent::__construct($name, $title);
        $this->url = $url;
    }

    public function render(): string
    {
        $output = parent::render();
        return "<form action=\"{$this->url}\">\n<h3>{$this->title}</h3>\n$output</form>\n";
    }
}

/**
 * The client code gets a convenient interface for building complex tree
 * structures.
 */
function getProductForm(): FormElement
{
    $form = new Form('product', "Add product", "/product/add");
    $form->add(new Input('name', "Name", 'text'));
    $form->add(new Input('description', "Description", 'text'));

    $picture = new Fieldset('photo', "Product photo");
    $picture->add(new Input('caption', "Caption", 'text'));
    $picture->add(new Input('image', "Image", 'file'));
    $form->add($picture);

    return $form;
}

/**
 * The form structure can be filled with data from various sources. The Client
 * doesn't have to traverse through all form fields to assign data to various
 * fields since the form itself can handle that.
 */
function loadProductData(FormElement $form)
{
    $data = [
        'name' => 'Apple MacBook',
        'description' => 'A decent laptop.',
        'photo' => [
            'caption' => 'Front photo.',
            'image' => 'photo1.png',
        ],
    ];

    $form->setData($data);
}

/**
 * The client code can work with form elements using the abstract interface.
 * This way, it doesn't matter whether the client works with a simple component
 * or a complex composite tree.
 */
function renderProduct(FormElement $form)
{
    // ..

    echo $form->render();

    // ..
}

$form = getProductForm();
loadProductData($form);
renderProduct($form);

 Output.txt: المخرجات

<form action="/product/add">
<h3>Add product</h3>
<label for="name">Name</label>
<input name="name" type="text" value="Apple MacBook">
<label for="description">Description</label>
<input name="description" type="text" value="A decent laptop.">
<fieldset><legend>Product photo</legend>
<label for="caption">Caption</label>
<input name="caption" type="text" value="Front photo.">
<label for="image">Image</label>
<input name="image" type="file" value="photo1.png">
</fieldset>
</form>

الاستخدام في لغة بايثون

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ★

أمثلة الاستخدام: يكثر استخدام نمط المركَّب في لغة بايثون، إذ يُستخدم لتمثيل هرميات لأجزاء من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs).

يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري.

مثال: مثال تصوري

يوضح هذا المثال بنية نمط الجسر، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 main.py: مثال تصوري

from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List


class Component(ABC):
    """
    The base Component class declares common operations for both simple and
    complex objects of a composition.
    """

    @property
    def parent(self) -> Component:
        return self._parent

    @parent.setter
    def parent(self, parent: Component):
        """
        Optionally, the base Component can declare an interface for setting
        and accessing a parent of the component in a tree structure. It can
        also provide some default implementation for these methods.
        """

        self._parent = parent

    """
    In some cases, it would be beneficial to define the child-management
    operations right in the base Component class. This way, you won't need to
    expose any concrete component classes to the client code, even during the
    object tree assembly. The downside is that these methods will be empty
    for the leaf-level components.
    """

    def add(self, component: Component) -> None:
        pass

    def remove(self, component: Component) -> None:
        pass

    def is_composite(self) -> bool:
        """
        You can provide a method that lets the client code figure out whether
        a component can bear children.
        """

        return False

    @abstractmethod
    def operation(self) -> str:
        """
        The base Component may implement some default behavior or leave it to
        concrete classes (by declaring the method containing the behavior as
        "abstract").
        """

        pass


class Leaf(Component):
    """
    The Leaf class represents the end objects of a composition. A leaf can't
    have any children.
    
    Usually, it's the Leaf objects that do the actual work, whereas Composite
    objects only delegate to their sub-components.
    """

    def operation(self) -> str:
        return "Leaf"


class Composite(Component):
    """
    The Composite class represents the complex components that may have
    children. Usually, the Composite objects delegate the actual work to
    their children and then "sum-up" the result.
    """

    def __init__(self) -> None:
        self._children: List[Component] = []

    """
    A composite object can add or remove other components (both simple or
    complex) to or from its child list.
    """

    def add(self, component: Component) -> None:
        self._children.append(component)
        component.parent = self

    def remove(self, component: Component) -> None:
        self._children.remove(component)
        component.parent = None

    def is_composite(self) -> bool:
        return True

    def operation(self) -> str:
        """
        The Composite executes its primary logic in a particular way. It
        traverses recursively through all its children, collecting and
        summing their results. Since the composite's children pass these
        calls to their children and so forth, the whole object tree is
        traversed as a result.
        """

        results = []
        for child in self._children:
            results.append(child.operation())
        return f"Branch({'+'.join(results)})"


def client_code(component: Component) -> None:
    """
    The client code works with all of the components via the base interface.
    """

    print(f"RESULT: {component.operation()}", end="")


def client_code2(component1: Component, component2: Component) -> None:
    """
    Thanks to the fact that the child-management operations are declared in
    the base Component class, the client code can work with any component,
    simple or complex, without depending on their concrete classes.
    """

    if component1.is_composite():
        component1.add(component2)

    print(f"RESULT: {component1.operation()}", end="")


if __name__ == "__main__":
    # This way the client code can support the simple leaf components...
    simple = Leaf()
    print("Client: I've got a simple component:")
    client_code(simple)
    print("\n")

    # ...as well as the complex composites.
    tree = Composite()

    branch1 = Composite()
    branch1.add(Leaf())
    branch1.add(Leaf())

    branch2 = Composite()
    branch2.add(Leaf())

    tree.add(branch1)
    tree.add(branch2)

    print("Client: Now I've got a composite tree:")
    client_code(tree)
    print("\n")

    print("Client: I don't need to check the components classes even when managing the tree:")
    client_code2(tree, simple)

 Output.txt: المخرجات

Client: I've got a simple component:
RESULT: Leaf

Client: Now I've got a composite tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))

Client: I don't need to check the components classes even when managing the tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)

انظر أيضًا

مصادر