4 - Composite
Tags: flutter, dart, oop, programming
Published: 2021-02-08 11:22:01

In the last article, I have analysed the Template Method design pattern. This time I would like to represent the pattern which is pretty simple to understand (comparing to the other design patterns) and is related to the implementation of the Flutter framework itself — the Composite design pattern.

## Table of Contents

* What is the Composite design pattern?
* Analysis
* Implementation
* Your Contribution

## What is the Composite design pattern?

![Cat-box Composition](https://dev-to-uploads.s3.amazonaws.com/i/1rkso1w5qqxetoo4vrk2.jpeg)

The **Composite** is one of the **structural** design patterns. Its intention in the [GoF book](https://en.wikipedia.org/wiki/Design_Patterns) is described as:

> Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

To understand the Composite design pattern, you should be familiar with the tree data structure.

![Tree Data Structure](https://dev-to-uploads.s3.amazonaws.com/i/wp4kca49pnwmk20ddkzh.jpeg)

In simple words, the tree data structure consists of **nodes** (elements) and **edges** (relations between nodes). Each node could have multiple **children** nodes, but each child could have only one **parent** node. The root node is the base of the tree, which has no parent node. The leaf is a tree node, which does not have any children. In the Composite design pattern context, we use two types of nodes — **leaf** (a component, which has no child components) and **composite** (a component, which contains one or more child components). Basically, any hierarchical data could be represented and stored as a tree structure. The main problem — how to implement this kind of structure in code? A very inflexible way is to define leaf and composite objects differently, treat the composite object as a container for the leaf objects by specifying a specific logic, interface for it. This leads to the issue when a client should treat leaf and composite objects differently, hence making the code very complex especially when the data structure is constructed dynamically. That is one of the main reasons why the Composite design pattern is used — to define an abstract class (interface works as well) that represents both leaf and composite objects uniformly hence letting clients treat every element of the tree structure in the same manner.

Doesn’t it sound familiar? “In Flutter, everything is a widget!”, “Flutter widget tree”, no? The Flutter framework builds the UI of the application as a Widget tree, allows you to put widgets inside other widgets or their containers (widgets, which contain the children property, e.g. *Column*, *Row*, *ListView*, etc.). That’s pretty much a Composite design pattern, well, on steroids and with some additional Flutter magic…

## Analysis

The general structure of the Composite design pattern looks like this:

![Composite Class Diagram](https://dev-to-uploads.s3.amazonaws.com/i/1dvx275ym4loo9yxz85j.png)

* *Component* — declares the interface for objects in the composition. This interface allows the client to treat leaf and composite objects uniformly.
* *Leaf* — represents leaf objects in the composition. This object does not have sub-elements (child components), defines behaviour for primitive objects in the composition and does most of the real work since they don’t have anyone to delegate the work to.
* *Composite* — stores sub-elements (children) and implements child-related operations in the *Composite* interface. Differently from the *leaf* component, *composite* object delegates the work to its child elements, processes intermediate results ant then returns the final result to the client.
* *Client* — uses the *Component* interface to interact with objects in the composite structure. This allows the client to work with simple and complex elements of the tree in the same way.

### Applicability

The Composite design pattern should be used when you want to represent part-whole hierarchies of objects and you want clients to be able to ignore the difference between compositions of objects and individual objects. In my opinion, the most difficult part of this pattern is to identify where and when you can apply it in your codebase. A general rule of thumb — if you have a set of groups or collections, this is a big indicator that you might be able to use the Composite design pattern. The easier case to detect — you are using a tree structure. In this case, you should think where you can apply the pattern to make the work with this data structure easier. If you detect these cases in your code, only the implementation details are left, which I will describe next.

## Implementation

This time the implementation of the design pattern is more visual (finally!) and would make more sense in Flutter context (yes, I do consider your feedback, so do not hesitate to share your insights about the series — it helps me improve the quality of the content a lot!). Let’s say, we want to represent the structure of our file system. The file system consists of directories and files of various types: audio, video, images, text files, etc. Files are stored inside directories, also, directories could be stored inside other directories. For instance, our file structure could look like this:

![File Structure Example](https://dev-to-uploads.s3.amazonaws.com/i/a7s6x63dyprvo6cursai.png)

Besides, we want to show the size of each file or directory. It is easy to show it for a concrete file, but the directory size depends on the items inside it and should be calculated. To implement this, the Composite design pattern is a great option!

### Class diagram

The class diagram below shows the implementation of the Composite design pattern.

![Composite Implementation Class Diagram](https://dev-to-uploads.s3.amazonaws.com/i/37mvfzssgua0geb0d8rs.png)

*IFile* defines a common interface for both *File* (leaf) and *Directory* (composite) classes:

* *getSize()* — returns size of the file;
* *render()* — renders the component’s UI.

*File* class implements the *getSize()* and *render()* methods, additionally contains *title*, *size* and *icon* properties. *Directory* implements the same required methods, but it also contains *title*, *isInitiallyExpanded* and *files* list, containing the *IFile* objects, defines *addFile()* method, which allows adding *IFile* objects to the directory (files list). *AudioFile*, *ImageFile*, *TextFile* and *VideoFile* classes extend the *File* class to specify a concrete type of the file.

### IFile

An interface which defines methods to be implemented by leaf and composite components. Dart language does not support the interface as a class type, so we define an interface by creating an abstract class and providing a method header (name, return type, parameters) without the default implementation.

![ifile.dart](https://dev-to-uploads.s3.amazonaws.com/i/8jmztq52didzk1ki84q0.png)

### File

A concrete implementation of the *IFile* interface which matches the *leaf* class in the Composite design pattern. In *File* class, *getSize()* method simply returns the file size, *render()* — returns file’s UI widget which is used in the example screen.

![file.dart](https://dev-to-uploads.s3.amazonaws.com/i/tyi8nvnbwhdxkoqf1gn2.png)

### Concrete classes extending File

All of these classes extend the *File* class and specify the concrete file type by providing a unique icon for the corresponding file type:

* *AudioFile*:

![audio_file.dart](https://dev-to-uploads.s3.amazonaws.com/i/xgyjz2arexc8i4ggg6rz.png)

* *ImageFile*:

![image_file.dart](https://dev-to-uploads.s3.amazonaws.com/i/sxs2i7xtbv10c1i7b6bm.png)

* *TextFile*:

![text_file.dart](https://dev-to-uploads.s3.amazonaws.com/i/iiip1dspyh78posq9a78.png)

* *VideoFile*:

![video_file.dart](https://dev-to-uploads.s3.amazonaws.com/i/nqbj40iojns7l5io3y2j.png)

### Directory

A concrete implementation of the *IFile* interface which matches the *composite* class in the Composite design pattern. Similar as in *File* class, *render()* returns directory’s UI widget which is used in the example screen. However, in this class *getSize()* method calculates the directory size by calling the *getSize()* method for each item in the *files* list and adding up the results. This is the main idea of the Composite design pattern which allows the composite class to treat all the elements in the containing list uniformly as long as they implement the same interface.

![directory.dart](https://dev-to-uploads.s3.amazonaws.com/i/kurv69n2nl2rfwkwqxrt.png)

### FileSizeConverter

To represent the file size in a more appealing format, *FileSizeConverter* helper class was created which provides a static method *bytesToString()* which converts the value of the file size in bytes to the human-readable text.

![file_size_converter.dart](https://dev-to-uploads.s3.amazonaws.com/i/99qs64rkrxrcwha482eb.png)

### Example

First of all, a markdown file is prepared and provided as a pattern’s description:

![Composite Markdown](https://dev-to-uploads.s3.amazonaws.com/i/48xw7nj5lcl2io2zbdq7.gif)

*CompositeExample* widget contains the *buildMediaDirectory()* method which builds the file structure for the example. This method illustrates the Composite design pattern — even though the components are of a different type, they could be handled in the same manner since the implemented interface of *IFile* is the same for all components. This allows us to put *Directory* objects inside other directories, mix them along with concrete *File* objects hence building the tree structure of *IFile* components.

![composite_example.dart](https://dev-to-uploads.s3.amazonaws.com/i/t1obta9cah38r0v680hq.png)

The final result of the Composite design pattern’s implementation looks like this:

![Composite Example](https://dev-to-uploads.s3.amazonaws.com/i/gsf0dh2wp1y9fkxm0jls.gif)

As you can see in the example, the file size is shown for each file directly and for directories, it is calculated by adding up each file size inside the directory.

All of the code changes for the Composite design pattern and its example implementation could be found [here](https://github.com/mkobuolys/flutter-design-patterns/pull/5).

## Your Contribution

💖 or 🦄 this article to show your support and motivate me to write better!
💬 Leave a response to this article by providing your insights, comments or wishes for the next topic.
📢 Share this article with your friends, colleagues in social media.
➕ Follow me on dev.to or any other social media platform.
⭐ Star the [Github](https://github.com/mkobuolys/flutter-design-patterns) repository.