Project – BookStore – Structural Patterns-1

Context: We built a program in the past to support a bookstore. However, the store is going so well that our little program is not enough anymore. Our fictional company now owns multiple stores. They want to divide those stores into sections and manage book sets and single books. After a few minutes of gathering information and asking them questions, we realize they can have sets of sets, subsections, and think of creating sub-stores, so we need a flexible design.We have decided to use the Composite pattern to solve this problem. Here’s our class hierarchy:

 Figure 11.8: the BookStore project composite class hierarchyFigure 11.8: the BookStore project composite class hierarchy 

Due to the complexity of our class hierarchy and the uncertainty of a project in an early stage, we decided that a factory would be adequate to create our class hierarchy, showcase our design, and validate it with the customer. Here’s the high-level design:

 Figure 11.9: high-level design of the BookStore projectFigure 11.9: high-level design of the BookStore project 

We decided to aim for the smallest possible interface to get the ball rolling. Since we want to know how many items are available in any part of the store and what type of component we are interacting with, we created the following interface:

namespace Composite.Models;
public interface IComponent
{
    int Count { get; }
    string Type { get; }
}

The Count property allows us to calculate how many items are available under the corporation, a store, a section, a set, or any other composite component we create in the future. The Type property forces each component to display its type linearly.

We can create such a minimal interface because we are not executing any operations on the data structure but counting the elements, then serializing it to JSON. The serializer will take care of navigating the class hierarchy for us. In another context, the minimal subset of properties might be more than this. For example, in this case, we could have added a Name property to the interface, but the book’s name is its title, so I decided not to include it.

Next, let’s create our composite structure, starting with the Book class (the Component):

namespace Composite.Models;
public class Book : IComponent
{
    public Book(string title)
    {
        Title = title ??
throw new ArgumentNullException(nameof(title));
    }
    public string Title { get; }
    public string Type => “Book”;
    public int Count { get; } = 1;
}

The preceding Book class implements the interface by always returning a count of 1 because it is a single book, a leaf in the tree. The Type property is also hard-coded. As a book, the class requires a title upon construction that it stores in the Title property (not inherited and only available to Book instances).

In a real scenario, we’d have more properties, like the ISBN and author, but doing so here would just clutter the example. We are not designing a real bookstore but learning about the Composite pattern.

Next, let’s create our composite component, the BookComposite class:

using System.Collections;
using System.Collections.ObjectModel;
namespace Composite.Models;
public abstract class BookComposite : IComponent
{
    protected readonly List<IComponent> children = new();
    public BookComposite(string name)
    {
        Name = name ??
throw new ArgumentNullException(nameof(name));
    }
    public string Name { get; }
    public virtual string Type => GetType().Name;
    public virtual int Count
        => children.Sum(child => child.Count);
    public virtual IEnumerable Children
        => new ReadOnlyCollection<IComponent>(children);
    public virtual void Add(IComponent bookComponent)
    {
        children.Add(bookComponent);
    }
    public virtual void Remove(IComponent bookComponent)
    {
        children.Remove(bookComponent);
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *



          Copyright © 2015-2024 | About | Terms of Service | Privacy Policy