{"id":18358512,"url":"https://github.com/ccozad/composite-js-example","last_synced_at":"2025-04-10T02:39:14.328Z","repository":{"id":250183307,"uuid":"833502514","full_name":"ccozad/composite-js-example","owner":"ccozad","description":"The composite pattern gives a uniform interface to collections and individual objects, allowing both to be processed by the same logic.","archived":false,"fork":false,"pushed_at":"2024-07-26T07:58:54.000Z","size":30,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-15T18:36:24.605Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ccozad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-25T07:27:08.000Z","updated_at":"2024-07-26T07:58:57.000Z","dependencies_parsed_at":"2024-12-23T22:25:47.736Z","dependency_job_id":"fd71f2e9-babe-4b81-8936-1831523dbe05","html_url":"https://github.com/ccozad/composite-js-example","commit_stats":null,"previous_names":["ccozad/composite-js-example"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccozad%2Fcomposite-js-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccozad%2Fcomposite-js-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccozad%2Fcomposite-js-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccozad%2Fcomposite-js-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ccozad","download_url":"https://codeload.github.com/ccozad/composite-js-example/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248144766,"owners_count":21054982,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-05T22:18:15.031Z","updated_at":"2025-04-10T02:39:14.296Z","avatar_url":"https://github.com/ccozad.png","language":"JavaScript","readme":"# Introduction\n\nThe composite pattern gives a uniform interface to collections and individual objects, allowing both to be processed by the same logic. In this example we'll use a restaurant bill to show how the composite pattern can be used to model a real world problem.\n\nUsing the composite pattern to calculate the total for an order\n```js\nconst Order = require('./orders/order');\nconst Food = require('./items/food');\nconst Drink = require('./items/drink');\nconst Combo = require('./items/combo');\nconst ConstantOffTotal = require('./discounts/constantOffTotal');\nconst SalesTax = require('./fees/salesTax');\n\n// Create a complex order and calculate the bill\nconst order = new Order();\n        \nconst tax = new SalesTax(10, \"CA\", 300, 'TAX');\norder.addEntry(tax);\n        \nconst burger= new Food('Burger', 7, '1');\nconst fries = new Food('Fries', 3, '2');\nconst drink = new Drink('Large', 5, 'Pepsi', 100, '3');\nconst combo = new Combo('Combo', 100, 'a');\ncombo.addItem(burger);\ncombo.addItem(fries);\ncombo.addItem(drink);\norder.addEntry(combo);\n\nconst discount = new ConstantOffTotal(5, 200, 'SAVE5');\norder.addEntry(discount);\n\nconst bill = order.calculateBill();\nconsole.log(bill);\n```\n\nThe results of the calculation\n```js\n{\n  total: 11,\n  items: [\n    {\n      type: 'combo',\n      name: 'Combo: Burger, Fries, Large - Pepsi',\n      price: 15,\n      billingPriority: 100,\n      billingCode: 'a'\n    },\n    {\n      type: 'constantOffTotal',\n      name: 'Up to $5 off total',\n      price: -5,\n      billingPriority: 200,\n      billingCode: 'SAVE5'\n    },\n    {\n      type: 'salesTax',\n      name: '10% CA sales tax',\n      price: 1,\n      billingPriority: 300,\n      billingCode: 'TAX'\n    }\n  ]\n}\n```\n\n# The domain\n\nThe bill we will model will be for a fast food type establishment that sells multiple items as a combo and individual items. The goal will be to calculate the total price the customer needs to pay. Besides the food itself, we'll include the idea of non-food fees such as tax and card fees. Discounts such as coupons, sales and manager overrides will also be included.\n\n## Items\n\n - Food (Hamburger, fries, etc.)\n - Drink (Soda, milk, etc.)\n - Combo (Some combination of food and drinks)\n\n## Fees\n\n - Tax (Based on locality, calculated on the total)\n\n## Discounts\n\n - Constant value off (constant amount off not to exceed the total)\n - Percentage value off\n\n ## Bringing it all together\n\n  - A customer paying for an order wants to know the final amount and the itemized list of fees and deductions. We'll need some type of shared state to operate on.\n\n```js\nconst bill = {\n    total: 0.0,\n    items:[],\n}\n```\n\nEach entry on the bill needs to be able to:\n - Identify a billing code\n - Indicate priority to influence order of operations\n - Calculate the impact to the total\n - Render itself on the final bill\n\n```js\n// The billable entry interface\n\nbillingCode() // An identier that discounts can use to identify a type of purchase\nbillingPriority() // The priority against other calculations, lower numbers are processed first\ncalculate(bill) // Entry specific processing for the total\nrenderToString() // A way to represent the entry as a string\nrenderToObject() // A way to represent the entry as an object\n\n```\n\nProcessing a bill should do all the following:\n\n 1. Sort all bill entries in ascending priority\n 2. Iterate through each entry\n    1. Call calculate with the current bill state\n    2. Update the items on the bill\n 3. Return the final bill\n\n Because every entry, whether it be simple or complex has a uniform interface, we can process any combination of orders using the same logic.\n\n ```js\n calculateBill() {\n    const bill = {\n        total: 0.0,\n        items: []\n    };\n    this.entries = this.entries.sort((a, b) =\u003e a.billingPriority - b.billingPriority);\n    this.entries.forEach(entry =\u003e {\n        bill.total = entry.calculate(bill);\n        bill.items.push(entry.renderToObject());\n    });\n\n    return bill;\n}\n```\n\n# Running the code\n\nAfter you pull the code you will need to run `npm install` followed by `npm start`\n\n# Testing the code\n\nYou can run the Mocha based unit tests by running `npm install` followed by `npm run test`\n\n# Resources\n - https://en.wikipedia.org/wiki/Composite_pattern\n - https://refactoring.guru/design-patterns/composite\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fccozad%2Fcomposite-js-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fccozad%2Fcomposite-js-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fccozad%2Fcomposite-js-example/lists"}