process for creating, assembling and initializing objects. It hides this whole process from the client code that just needs to get a new made object. Having a Factory also allows to change the process for creating objects at any point in time without impacting the client code that still relies on the same interface.
factory and the client, Client code is more difficult to unit test, Unable to switch to another factory, Difficulty to rely on dependencies in the factory. Very simple, Very pragmatic, Easy to understand.
function createMedia($path) { $format = strtolower(pathinfo($path, PATHINFO_EXTENSION)); if ('pdf' === $format) { return new PdfFile($path); } if (in_array($format, [ 'jpg', 'png', 'gif' ])) { return new ImageFile($path); } if ('txt' === $format) { return new TextFile($path); } throw new \UnexpectedValueException('Unexpected format '. $format); } }
real factory method object. The factory produces multiple kinds of objects. Simple & pragmatic approach. Loose coupling between factory and client code. Leverages the dependency injection principle. Easy to unit test the client code. Easy to replace the actual factory by another one.
flexible system able to analyze and extract the metadata of any kind of media files like pictures, video clips or music clips. Depending on the file type to analyze, a concrete factory object must be chosen. It will be responsible for creating and initializing a concrete type metadata class. Every concrete metadata object must inherit the attributes of an abstract parent Metadata class.
private $size; private $createdAt; public function initialize(\SplFileInfo $file) { $this->size = $file->getSize(); $this->realPath = $file->getRealPath(); $this->createdAt = $file->getMTime(); } // … plus one getter for each private property }
a size, a creation date but also a set of dimensions (width & height) and an orientation (portrait, landscape or square). The ImageMetadataFactory class is responsible for producing ImageMetadata objects. To analyze a picture, the factory uses an ImageAnalyzer instance.
lots of classes and interfaces, Client still doesn’t know the exact type of products. Managing new file types means creating new factories, Easy to swap a factory with another one, Each factory only produces one concrete type of object, Objects instanciations are centralized, Provides lots of flexibility to the code.
single object and collections share the same unified interface. Composite design pattern enables to combine objects and perform recursive operations very easily.
flexible ecommerce system able to sell single articles (physical or digital) and combos of articles for a cheaper price. Each article must have a unit price and a mass. The mass of a combo is the sum of the masses of all articles of the combo. The unit price of the combo can be a fixed price or the sum of all unit prices of the articles in the combo.
new HardProduct('Digital Camera', new EUR(78900), new Mass(855)), new HardProduct('Camera Bag', new EUR(3900), new Mass(220)), new HardProduct('Memory Card 128 Gb', new EUR(7900), new Mass(42)), ]; $combo = new Combo('Digital Camera & Bag', $products, new EUR(83900)); echo 'Name: ', $combo->getName() ,"\n"; echo 'Mass: ', $combo->getMass()->getValue() ," g\n"; echo 'Price: ', $combo->getPrice()->getConvertedAmount() ," €\n"; Name: Digital Camera & Bag Mass: 1117 g Price: 839.00 €
new HardProduct('Digital Camera', new EUR(78900), new Mass(855)), new HardProduct('Camera Bag', new EUR(3900), new Mass(220)), new HardProduct('Memory Card 128 Gb', new EUR(7900), new Mass(42)), ]; $combo = new Combo('Digital Camera & Bag', $products); echo 'Name: ', $combo->getName() ,"\n"; echo 'Mass: ', $combo->getMass()->getValue() ,"\n"; echo 'Price: ', $combo->getPrice()->getConvertedAmount() ,"\n"; Name: Digital Camera & Bag Mass: 1117 g Price: 907.00 €
Camera', new EUR(78900), new Mass(855)), new HardProduct('Camera Bag', new EUR(3900), new Mass(220)), new HardProduct('Memory Card 128 Gb', new EUR(7900), new Mass(42)), ]; $combo = new Combo('Digital Camera Combo Pack + Tripod', [ new HardProduct('Lightweight Tripod', new EUR(2690), new Mass(570)), new Combo('Digital Camera & Bag', $products, new EUR(83900)), ]); echo 'Name: ', $combo->getName() ,"\n"; echo 'Weight: ', $combo->getWeight()->getValue() ,"\n"; echo 'Price: ', $combo->getPrice()->getConvertedAmount() ,"\n"; Name: Digital Camera Combo Pack + Tripod Mass: 1687 g Price: 865.90 €
flexible coupons system in order to discount the total price of an order upon certain conditions. Each discount coupon can be either a rate discount (-15%) or a discount value (-10€). Each coupon can also have zero or multiple restrictions to constraint their eligibility. The system must allow to create coupons with any complex combination for restrictions.
Minimum total amount required, Minimum quantity of ordered items required, Valid for a specific geographical area, Customer must own the loyalty membership card, Coupon is valid for Premium / VIP customers only, Valid for the customer’s very first order, Some products are not eligible for discounts, Valid only on some specific products areas (luxury, food…), etc.
20 € off if total amount is greater than 300 € $discount = Money::fromString('EUR 20'); $coupon = new MinimumPurchaseAmountCoupon( new ValueCoupon('3s2h7pd65s', $discount), $minAmount ); // Get 25% off if total amount is greater than 300 € $coupon = new MinimumPurchaseAmountCoupon( new RateCoupon('76cqa6qr19', .25), $minAmount );
Money::fromString('EUR 170.00'); $discount = Money::fromString('EUR 20'); // Create the special coupon $coupon = new ValueCoupon('3s2h7pd65s', $discount); $coupon = new LimitedLifetimeCoupon($coupon, 'now', '+60 days'); $coupon = new MinimumPurchaseAmountCoupon($coupon, $minAmount); $coupon = new CustomerFirstOrderCoupon($coupon); // Create the order instance $customer = new Customer('[email protected]'); $order = new Order($customer, $totalAmount); // Apply discount coupon on the order $discountedAmount = $coupon->applyDiscount($order);
remains unchanged, Leverage the SRP and OCP principles, Allow to support any complex decoration combinations, Ability to choose in which order decorations are applied. The variable doesn’t always contain the « real » object, All method calls must be forwarded, Not suitable for interfaces with lots of public methods, Doesn’t work for methods that returns the current instance.
performed on elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
accept(VisitorInterface $visitor) { $visitor->visit($this); } } class ConcreteVisitor implement VisitorInterface { public function visit(ElementInterface $element) { // do something with the element } }
$element1->accept(new ConcreteVisitorB()); $element1->accept(new ConcreteVisitorC()); $element2 = new OtherConcreteElement(); $element2->accept(new ConcreteVisitorD()); $element2->accept(new ConcreteVisitorC()); The accept() method of each concrete element receives a concrete instance of the Visitor type and invokes the its visit() method on it. The concrete element injects itself into the visit() method list of arguments.
a list of ShoppingCartItem instances. Each of these instances holds a reference to a Product reference object and the ordered quantity. During checkout, we want to perform several operations on the shopping cart like computing the total price, sorting all products per categories or even issueing the receipt.
new Tomatoes('Vegetables', new EUR(189))); $cart->addItem(1.38, new Salmon('Seafood', new EUR(2250))); $cart->addItem(2, new ToothpasteTube('Wellness', new EUR(115))); $cart->addItem(0.89, new Apples('Vegetables', new EUR(128))); $cart->addItem(3, new Shampoo('Wellness', new EUR(123))); $cart->addItem(0.47, new Tuna('Seafood', new EUR(1980))); $cart->addItem(0.26, new GoatCheese('Creamery', new EUR(1723))); Each product reference object has a category and a price. The price can be either a unit price (for a shampoo for instance) or a price per kilogram (for vegetables or seafood).
@var ShoppingCartItems[] */ private $items; public function getTotalPrice() { ... } public function sortByCategories() { ... } public function issueReceipt(PDFGenerator $gen) { ... } } Implementing all these operations in the ShoppingCart class will bloat it at some point. Also, adding new operations requires to change the class body and will increase responsabilities and coupling with other dependencies.
function accept(ShoppingCartVisitorInterface $visitor) { $visitor->visit($this); } } The first step is to make the ShoppingCart instance accept a ShoppingCartVisitorInterface implementation. The latter will then visit the shopping cart object that injects itself into the visit() method.
ShoppingCart(); // ... $pricerVisitor = new TotalPriceCartVisitor(); $cart->accept($pricerVisitor); $totalPrice = $pricerVisitor->getTotalPrice(); Computing the total price of the whole shopping cart is as simple as passing the visitor to the shopping cart instance.
// ... $sortVisitor = new SortCartItemsVisitor(); $cart->accept($sortVisitor); print_r($sortVisitor->getSortedCartItems()); Sorting the whole list of shopping cart items by product references categories is as simple as injecting the visitor into the ShoppingCart instance.
and their operations separated from each other, Encourage developers to follow the SRP from SOLID, Encourage developers to follow the OCP from SOLID, Encourage losely coupled classes, Easy to extend the operations list by creating new visitors. Visitors must be mutable, Visitors may have to be updated if visitee changes, Potential edge cases if visitors change the state of the visitee, Cannot use polymorphism in visitors with PHP.