Featured resource
2026 Tech Forecast
2026 Tech Forecast

1,500+ tech insiders, business leaders, and Pluralsight Authors share their predictions on what’s shifting fastest and how to stay ahead.

Download the forecast
  • Lab
    • Libraries: If you want this lab, consider one of these libraries.
    • Core Tech
Labs

Creational Design Pattern in C++

BrewForge runs the software behind a self-service coffee kiosk. The original prototype hard-wires a single espresso machine implementation, scatters brewing, grinding and logging concerns into one class, and rebuilds full drink recipes from scratch for every order. Your job across this lab is to turn that prototype into a maintainable creational-pattern showcase: components depend on abstractions instead of concrete classes, a single guarded configuration object controls shop-wide settings, drinks are assembled with a fluent builder and cloned from ready-made signature recipes, whole families of products (regular versus decaf) are swapped through an abstract factory, and the checkout flow depends on an injected payment interface you can substitute with a stub in tests.

Lab platform
Lab Info
Level
Intermediate
Last updated
Nov 04, 2026
Duration
45m

Contact sales

By clicking submit, you agree to our Privacy Policy and Terms of Use, and consent to receive marketing emails from Pluralsight.
Table of Contents
  1. Challenge

    Step 1: Meet the BrewForge kiosk codebase

    BrewForge: one kiosk, one tangled class

    BrewForge runs the software behind a self-service coffee kiosk. The prototype you inherited works, sort of: a single CoffeeMachine class heats water, grinds beans, tracks shop settings and writes log lines, all in one place. Drinks are built by hand-filling a struct field by field, every order rebuilds a recipe from nothing, and the checkout code calls a concrete CreditCardProcessor directly. None of this is wrong, exactly, but none of it can be swapped, tested in isolation, or reused without dragging the rest along with it.

    Over the next four steps you will not add features. You will change how the same behavior is produced, one class at a time, using the five creational patterns of the Gang of Four. ## Where each pattern lands

    • Step 2 -- SOLID split. CoffeeMachine stops doing its own heating and grinding. Those become separate, substitutable collaborators it depends on through interfaces.
    • Step 3 -- Singleton. Shop-wide settings (target temperature, default dose) move into one guarded MachineConfig object that every machine reads from, instead of each machine keeping its own copy.
    • Step 4 -- Builder and Prototype. Custom drinks get assembled through a fluent CoffeeDrinkBuilder chain; signature drinks get cloned from a prototype registry instead of rebuilt field by field.
    • Step 5 -- Factory Method, Abstract Factory, and injected services. A factory method picks the right drink for an order type, an abstract factory swaps whole regular-versus-decaf ingredient families, and checkout depends on an injected PaymentProcessor interface rather than a concrete class.

    Each step edits existing files under src/; nothing you build in an earlier step gets thrown away later -- it gets depended on.

    info> If you get stuck, you can refer to the provided solution code for each task, available in the solution folder.

  2. Challenge

    Step 2: Rebuild the coffee machine around SOLID abstractions

    Splitting the machine along its seams

    CoffeeMachine currently owns three unrelated jobs: reaching a temperature, grinding a dose, and writing a log line. The SOLID fix is not to rename methods -- it is to give each job its own interface (HeatingElement, Grinder, Logger) and a concrete implementation, then have CoffeeMachine hold pointers to those interfaces instead of doing the work itself. That is what makes each piece substitutable: a test can hand CoffeeMachine a fake heater without ever touching real hardware.

    Start with the heating element, since it has no dependency on anything else in the file. ## The second collaborator: grinding

    With the heater reporting real numbers instead of echoing requests, BladeGrinder needs the same treatment: a real grinder does not turn every requested gram into an identical gram of output, because different grind sizes lose more or less material. Model that loss explicitly so grind() returns something a caller can actually trust. ## Wiring the collaborators together

    HeatingElement and Grinder are now independent and testable. The last piece is CoffeeMachine itself: it should stop containing any brewing logic of its own and instead orchestrate the two interfaces it holds, plus a third injected Logger interface that replaces the print statements the old monolith had scattered through it.

  3. Challenge

    Step 3: Guard shared machine settings with a singleton

    One settings object, guarded

    Right now nothing stops two parts of the kiosk from holding two different copies of "the" shop temperature. A singleton fixes that by making the class itself refuse to be copied and exposing exactly one way to reach an instance: a static accessor. MachineConfig's constructor is private and its copy operations are already deleted in the starter -- your job is the accessor that hands back the one shared object. ## Making the machine read the singleton

    A singleton is only useful once something consults it. CoffeeMachine::brew() already calls a describeSettings() method as part of assembling its output; right now that method is a stub. Complete it so the description comes from MachineConfig::instance(), and every machine in the kiosk will report the same settings the moment you change them in one place.

  4. Challenge

    Step 4: Assemble drinks with a fluent builder and clone prototypes

    Assembling drinks by chaining, not filling a struct

    Hand-filling a drink struct field by field scales badly once a drink can have any number of syrups. A fluent builder fixes this by making every setter return the builder itself, so calls read as one chain: builder.withSyrup("vanilla").withSyrup("caramel").build(). Start with the setter that has to accumulate a list rather than just overwrite a field. ## Closing the chain

    Every setter so far just records state. build() is where that accumulated state finally becomes a real CoffeeDrink, which is also where a chain with zero syrup calls has to still produce a sensible result rather than a string with a dangling separator. ## Cloning instead of rebuilding

    Signature drinks -- the kiosk's named specials -- do not need to be assembled fresh through the builder every time; they need to be copied from a known-good prototype. That is the Prototype pattern: a clone() method on the prototype itself, returning a new object rather than a pointer to the one the registry keeps. Getting this wrong by returning the original pointer would let one order share, and potentially corrupt, the registry's signature recipe.

  5. Challenge

    Step 5: Centralize creation with factories and inject services

    Choosing a drink from an order type

    Somewhere, an order type has to turn into an actual drink -- today that logic is duplicated wherever it is needed. A factory method centralizes it: one function that takes the order type and returns the finished drink, so every caller asks the factory instead of repeating the branching itself. ## Swapping whole families of ingredients

    Regular and decaf orders do not just differ in one bean field -- they differ in a whole family of ingredients that all have to agree with each other. An abstract factory captures that: one interface, IngredientFactory, with two implementations that each produce a self-consistent family. Implement the regular family first. ## The second family

    The checkout code that consumes an IngredientFactory should never need to know which concrete family it is holding. Implement the decaf side of the same interface so that swapping RegularIngredientFactory for DecafIngredientFactory is the only change needed anywhere in the pipeline. ## Checkout depends on an interface, not a class

    The last concrete dependency left in the pipeline is payment: OrderService should hold a std::shared_ptr<PaymentProcessor> handed to it at construction, not a specific card processor it constructs itself. That single change is what lets a test substitute a stub processor later without touching OrderService at all.

  6. Challenge

    Step 6: Run the application

    Every task is done, and each one was graded the moment you completed it. What you have not done yet is watch the whole application run at once -- which is the only place the pieces you wrote separately become a single working program.

    Start the application

    Open the Terminal tab and run:

    make app && ./app
    

    The program runs in the terminal and prints its output there. Every behaviour it shows is code you wrote in the previous steps, so read the output against what each task asked for -- that is the whole lab, working as one program. ### Run the checks yourself

    The lab ran each task's test for you as you went, but they are ordinary tests and you can run them whenever you like. The whole suite:

    bash runTest.sh
    

    Or one task at a time:

    bash runTest.sh "task 2.1"
    

    Each failure names the task it belongs to, so the output tells you which step to go back to rather than which line to stare at. Two things worth trying before you finish: break something on purpose and watch the matching check fail, then put it back. Knowing what a failure looks like is worth as much as knowing what a pass looks like, and it costs you nothing here.

    When you are done, everything you wrote is still in the editor tabs -- the lab is yours to keep reading.

About the author

Pluralsight’s AI authoring technology is designed to accelerate the creation of hands-on, technical learning experiences. Serving as a first-pass content generator, it produces structured lab drafts aligned to learning objectives defined by Pluralsight’s Curriculum team. Each lab is then enhanced by our Content team, who configure the environments, refine instructions, and conduct rigorous technical and quality reviews. The result is a collaboration between artificial intelligence and human expertise, where AI supports scale and efficiency, and Pluralsight experts ensure accuracy, relevance, and instructional quality, helping learners build practical skills with confidence.

Real skill practice before real-world application

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Learn by doing

Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.

Follow your guide

All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.

Turn time into mastery

On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.

Get started with Pluralsight