Dive Into Design Pat... by Alexander Shvets.pdf
Document Details
Uploaded by SelfSufficientRadon
NHL Stenden University of Applied Sciences
2019
Tags
Full Transcript
Divee Int n to DE DESSIGN PAT TERN TERNSS A Few Words on Copyright Hi! My name is Alexander Shvets. I’m the author of the book Dive Into Design Patterns and the online course Dive Into Refactoring. This book is for your personal use only. Please don’t share it with any third part...
Divee Int n to DE DESSIGN PAT TERN TERNSS A Few Words on Copyright Hi! My name is Alexander Shvets. I’m the author of the book Dive Into Design Patterns and the online course Dive Into Refactoring. This book is for your personal use only. Please don’t share it with any third parties except your family members. If you’d like to share the book with a friend or colleague, buy and send them a new copy. All profit from the sale of my books and courses is spent on the development of Refactoring.Guru. Each copy sold helps the project immensely and brings the moment of a new book release a little bit closer. Alexander Shvets, Refactoring.Guru, 2019 [email protected] Illustrations: Dmitry Zhart Editing: Andrew Wetmore, Rhyan Solomon I dedicate this book to my wife, Maria. If it hadn’t been for her, I’d probably have finished the book some 30 years later. 4 Table of Contents Table of Contents Table of Contents....................................................................................... 4 How to Read This Book.............................................................................. 6 INTRODUCTION TO OOP............................................................................. 7 Basics of OOP................................................................................... 8 Pillars of OOP.................................................................................13 Relations Between Objects........................................................20 INTRODUCTION TO DESIGN PATTERNS.................................................23 What’s a Design Pattern?............................................................24 Why Should I Learn Patterns?...................................................28 SOFTWARE DESIGN PRINCIPLES...........................................................29 Features of Good Design............................................................30 Design Principles.............................................................................34 § Encapsulate What Varies...............................................35 § Program to an Interface, not an Implementation.39 § Favor Composition Over Inheritance........................44 SOLID Principles..............................................................................48 § Single Responsibility Principle....................................49 § Open/Closed Principle....................................................51 § Liskov Substitution Principle.......................................54 § Interface Segregation Principle...................................61 § Dependency Inversion Principle.................................64 5 Table of Contents CATALOG OF DESIGN PATTERNS............................................................68 Creational Design Patterns...........................................................69 § Factory Method.................................................................71 § Abstract Factory................................................................87 § Builder............................................................................... 103 § Prototype.......................................................................... 122 § Singleton.......................................................................... 136 Structural Design Patterns........................................................ 146 § Adapter............................................................................. 149 § Bridge................................................................................ 162 § Composite........................................................................ 177 § Decorator.......................................................................... 191 § Facade............................................................................... 209 § Flyweight......................................................................... 219 § Proxy.................................................................................. 233 Behavioral Design Patterns....................................................... 246 § Chain of Responsibility............................................... 250 § Command......................................................................... 268 § Iterator.............................................................................. 289 § Mediator........................................................................... 304 § Memento.......................................................................... 320 § Observer........................................................................... 336 § State................................................................................... 352 § Strategy............................................................................ 368 § Template Method.......................................................... 381 § Visitor................................................................................ 393 Conclusion.............................................................................................. 409 6 How to read this book How to Read This Book This book contains the descriptions of 22 classic design pat- terns formulated by the “Gang of Four” (or simply GoF) in 1994. Each chapter explores a particular pattern. Therefore, you can read from cover to cover or by picking the patterns you’re inter- ested in. Many patterns are related, so you can easily jump from topic to topic using numerous anchors. The end of each chapter has a list of links between the current pattern and others. If you see the name of a pattern that you haven’t seen yet, just keep reading—this item will appear in one of the next chapters. Design patterns are universal. Therefore, all code samples in this book are written in pseudocode that doesn’t constrain the material to a particular programming language. Prior to studying patterns, you can refresh your memory by going over the key terms of object-oriented programming. That chapter also explains the basics of UML diagrams, which is useful because the book has tons of them. Of course, if you already know all of that, you can proceed to learning patterns right away. INTRODUCTION TO OOP 8 Introduction to OOP / Basics of OOP Basics of OOP Object-oriented programming is a paradigm based on the con- cept of wrapping pieces of data, and behavior related to that data, into special bundles called objects, which are construct- ed from a set of “blueprints”, defined by a programmer, called classes. Objects, classes Do you like cats? I hope you do because I’ll try to explain the OOP concepts using various cat examples. This is a UML class diagram. You’ll see a lot of such diagrams in the book. 9 Introduction to OOP / Basics of OOP Say you have a cat named Oscar. Oscar is an object, an instance of the Cat class. Every cat has a lot of standard attributes: name, sex, age, weight, color, favorite food, etc. These are the class’s fields. All cats also behave similarly: they breathe, eat, run, sleep and meow. These are the class’s methods. Collectively, fields and methods can be referenced as the members of their class. Data stored inside the object’s fields is often referenced as state, and all the object’s methods define its behavior. Objects are instances of classes. 10 Introduction to OOP / Basics of OOP Luna, your friend’s cat, is also an instance of the Cat class. It has the same set of attributes as Oscar. The difference is in values of these attributes: her sex is female, she has a differ- ent color, and weighs less. So a class is like a blueprint that defines the structure for objects, which are concrete instances of that class. Class hierarchies Everything fine and dandy when we talk about one class. Nat- urally, a real program contains more than a single class. Some of these classes might be organized into class hierarchies. Let’s find out what that means. Say your neighbor has a dog called Fido. It turns out, dogs and cats have a lot in common: name, sex, age, and color are attributes of both dogs and cats. Dogs can breathe, sleep and run the same way cats do. So it seems that we can define the base Animal class that would list the common attributes and behaviors. A parent class, like the one we’ve just defined, is called a superclass. Its children are subclasses. Subclasses inherit state and behavior from their parent, defining only attributes or behaviors that differ. Thus, the Cat class would have the meow method, and the Dog class the bark method. 11 Introduction to OOP / Basics of OOP UML diagram of a class hierarchy. All classes in this diagram are part of the Animal class hierarchy. Assuming that we have a related business requirement, we can go even further and extract a more general class for all liv- ing Organisms which will become a superclass for Animals and Plants. Such a pyramid of classes is a hierarchy. In such a hierarchy, the Cat class inherits everything from both the Animal and Organism classes. 12 Introduction to OOP / Basics of OOP Classes in a UML diagram can be simplified if it’s more important to show their relations than their contents. Subclasses can override the behavior of methods that they inherit from parent classes. A subclass can either complete- ly replace the default behavior or just enhance it with some extra stuff. 13 Introduction to OOP / Pillars of OOP Pillars of OOP Object-oriented programming is based on four pillars, con- cepts that differentiate it from other programming paradigms. Abstraction Most of the time when you’re creating a program with OOP, you shape objects of the program based on real-world objects. However, objects of the program don’t represent the origi- nals with 100% accuracy (and it’s rarely required that they do). Instead, your objects only model attributes and behaviors of real objects in a specific context, ignoring the rest. For example, an Airplane class could probably exist in both a flight simulator and a flight booking application. But in the former case, it would hold details related to the actual flight, whereas in the latter class you would care only about the seat map and which seats are available. 14 Introduction to OOP / Pillars of OOP Different models of the same real-world object. Abstraction is a model of a real-world object or phenomenon, limited to a specific context, which represents all details rele- vant to this context with high accuracy and omits all the rest. Encapsulation To start a car engine, you only need to turn a key or press a button. You don’t need to connect wires under the hood, rotate the crankshaft and cylinders, and initiate the power cycle of the engine. These details are hidden under the hood of the car. You have only a simple interface: a start switch, a steering wheel and some pedals. This illustrates how each object has an interface—a public part of an object, open to interactions with other objects. 16 Introduction to OOP / Pillars of OOP that any object passed to an airport object, whether it’s an Airplane , a Helicopter or a freaking DomesticatedGryphon would be able to arrive or depart from this type of airport. UML diagram of several classes implementing an interface. You could change the implementation of the fly method in these classes in any way you want. As long as the signature of the method remains the same as declared in the interface, all instances of the Airport class can work with your flying objects just fine. 17 Introduction to OOP / Pillars of OOP Inheritance Inheritance is the ability to build new classes on top of exist- ing ones. The main benefit of inheritance is code reuse. If you want to create a class that’s slightly different from an existing one, there’s no need to duplicate code. Instead, you extend the existing class and put the extra functionality into a resulting subclass, which inherits fields and methods of the superclass. The consequence of using inheritance is that subclasses have the same interface as their parent class. You can’t hide a method in a subclass if it was declared in the superclass. You must also implement all abstract methods, even if they don’t make sense for your subclass. UML diagram of extending a single class versus implementing multiple interfaces at the same time. In most programming languages a subclass can extend only one superclass. On the other hand, any class can implement several interfaces at the same time. But, as I mentioned before, 18 Introduction to OOP / Pillars of OOP if a superclass implements an interface, all of its subclasses must also implement it. Polymorphism Let’s look at some animal examples. Most Animals can make sounds. We can anticipate that all subclasses will need to over- ride the base makeSound method so each subclass can emit the correct sound; therefore we can declare it abstract right away. This lets us omit any default implementation of the method in the superclass, but force all subclasses to come up with their own. Imagine that we’ve put several cats and dogs into a large bag. Then, with closed eyes, we take the animals one-by-one out of 19 Introduction to OOP / Pillars of OOP the bag. After taking an animal from the bag, we don’t know for sure what it is. However, if we cuddle it hard enough, the animal will emit a specific sound of joy, depending on its con- crete class. 1 bag = [new Cat(), new Dog()]; 2 3 foreach (Animal a : bag) 4 a.makeSound() 5 6 // Meow! 7 // Woof! The program doesn’t know the concrete type of the object con- tained inside the a variable; but, thanks to the special mech- anism called polymorphism, the program can trace down the subclass of the object whose method is being executed and run the appropriate behavior. Polymorphism is the ability of a program to detect the real class of an object and call its implementation even when its real type is unknown in the current context. You can also think of polymorphism as the ability of an object to “pretend” to be something else, usually a class it extends or an interface it implements. In our example, the dogs and cats in the bag were pretending to be generic animals. 20 Introduction to OOP / Relations Between Objects Relations Between Objects In addition to inheritance and implementation that we’ve already seen, there are other types of relations between objects that we haven’t talked about yet. UML Association. Professor communicates with students. Association is a type of relationship in which one object uses or interacts with another. In UML diagrams the association rela- tionship is shown by a simple arrow drawn from an object and pointing to the object it uses. By the way, having a bi-direc- tional association is a completely normal thing. In this case, the arrow has a point at each end. In general, you use an association to represent something like a field in a class. The link is always there, in that you can always ask an order for its customer. It need not actually be a field, if you are modeling from a more interface perspective, it can just indicate the presence of a method that will return the order’s customer. 21 Introduction to OOP / Relations Between Objects UML Dependency. Professor depends on salary. Dependency is a weaker variant of association that usually implies that there’s no permanent link between objects. Dependency typically (but not always) implies that an object accepts another object as a method parameter, instantiates, or uses another object. Here’s how you can spot a dependency between classes: a dependency exists between two classes if changes to the definition of one class result in modifications in another class. UML Composition. University consists of departments. Composition is a “whole-part” relationship between two objects, one of which is composed of one or more instances of the other. The distinction between this relation and others is that the component can only exist as a part of the container. In UML the composition relationship is shown by a line with a filled diamond at the container end and an arrow at the end pointing toward the component. 22 Introduction to OOP / Relations Between Objects While we talk about relations between objects, keep in mind that UML represents relations between classes. It means that a university object might consist of multiple departments even though you see just one “block” for each entity in the diagram. UML notation can represent quantities on both sides of relationships, but it’s okay to omit them if the quantities are clear from the context. UML Aggregation. Department contains professors. Aggregation is a less strict variant of composition, where one object merely contains a reference to another. The contain- er doesn’t control the life cycle of the component. The com- ponent can exist without the container and can be linked to several containers at the same time. In UML the aggregation relationship is drawn the same as for composition, but with an empty diamond at the arrow’s base. INTRODUCTION TO PATTERNS 24 Introduction to Design Patterns / What’s a Design Pattern? What’s a Design Pattern? Design patterns are typical solutions to commonly occurring problems in software design. They are like pre-made blue- prints that you can customize to solve a recurring design prob- lem in your code. You can’t just find a pattern and copy it into your program, the way you can with off-the-shelf functions or libraries. The pattern is not a specific piece of code, but a general concept for solving a particular problem. You can follow the pattern details and implement a solution that suits the realities of your own program. Patterns are often confused with algorithms, because both concepts describe typical solutions to some known problems. While an algorithm always defines a clear set of actions that can achieve some goal, a pattern is a more high-level descrip- tion of a solution. The code of the same pattern applied to two different programs may be different. An analogy to an algorithm is a cooking recipe: both have clear steps to achieve a goal. On the other hand, a pattern is more like a blueprint: you can see what the result and its features are, but the exact order of implementation is up to you. 25 Introduction to Design Patterns / What’s a Design Pattern? What does the pattern consist of? Most patterns are described very formally so people can repro- duce them in many contexts. Here are the sections that are usually present in a pattern description: Intent of the pattern briefly describes both the problem and the solution. Motivation further explains the problem and the solution the pattern makes possible. Structure of classes shows each part of the pattern and how they are related. Code example in one of the popular programming languages makes it easier to grasp the idea behind the pattern. Some pattern catalogs list other useful details, such as applic- ability of the pattern, implementation steps and relations with other patterns. Classification of patterns Design patterns differ by their complexity, level of detail and scale of applicability to the entire system being designed. I like the analogy to road construction: you can make an intersec- tion safer by either installing some traffic lights or building an entire multi-level interchange with underground passages for pedestrians. 26 Introduction to Design Patterns / What’s a Design Pattern? The most basic and low-level patterns are often called idioms. They usually apply only to a single programming language. The most universal and high-level patterns are architectural patterns. Developers can implement these patterns in virtual- ly any language. Unlike other patterns, they can be used to design the architecture of an entire application. In addition, all patterns can be categorized by their intent, or purpose. This book covers three main groups of patterns: Creational patterns provide object creation mechanisms that increase flexibility and reuse of existing code. Structural patterns explain how to assemble objects and class- es into larger structures, while keeping the structures flexible and efficient. Behavioral patterns take care of effective communication and the assignment of responsibilities between objects. Who invented patterns? That’s a good, but not a very accurate, question. Design pat- terns aren’t obscure, sophisticated concepts—quite the oppo- site. Patterns are typical solutions to common problems in object-oriented design. When a solution gets repeated over and over in various projects, someone eventually puts a name 27 Introduction to Design Patterns / What’s a Design Pattern? to it and describes the solution in detail. That’s basically how a pattern gets discovered. The concept of patterns was first described by Christopher Alexander in A Pattern Language: Towns, Buildings, Construc- tion 1. The book describes a “language” for designing the urban environment. The units of this language are patterns. They may describe how high windows should be, how many levels a building should have, how large green areas in a neighbor- hood are supposed to be, and so on. The idea was picked up by four authors: Erich Gamma, John Vlissides, Ralph Johnson, and Richard Helm. In 1995, they pub- lished Design Patterns: Elements of Reusable Object-Oriented Software 2, in which they applied the concept of design pat- terns to programming. The book featured 23 patterns solving various problems of object-oriented design and became a best- seller very quickly. Due to its lengthy name, people started to call it “the book by the gang of four” which was soon short- ened to simply “the GOF book”. Since then, dozens of other object-oriented patterns have been discovered. The “pattern approach” became very popular in other programming fields, so lots of other patterns now exist outside of object-oriented design as well. 1. A Pattern Language: Towns, Buildings, Construction: https://refactoring.guru/pattern-language-book 2. Design Patterns: Elements of Reusable Object-Oriented Software: https://refactoring.guru/gof-book 28 Introduction to Design Patterns / Why Should I Learn Patterns? Why Should I Learn Patterns? The truth is that you might manage to work as a programmer for many years without knowing about a single pattern. A lot of people do just that. Even in that case, though, you might be implementing some patterns without even knowing it. So why would you spend time learning them? Design patterns are a toolkit of tried and tested solutions to common problems in software design. Even if you never encounter these problems, knowing patterns is still useful because it teaches you how to solve all sorts of problems using principles of object-oriented design. Design patterns define a common language that you and your teammates can use to communicate more efficiently. You can say, “Oh, just use a Singleton for that,” and everyone will understand the idea behind your suggestion. No need to explain what a singleton is if you know the pattern and its name. SOFTWARE DESIGN PRINCIPLES 30 Features of Good Design Features of Good Design Before we proceed to the actual patterns, let’s discuss the process of designing software architecture: things to aim for and things you’d better avoid. Code reuse Cost and time are two of the most valuable metrics when developing any software product. Less time in development means entering the market earlier than competitors. Lower development costs mean more money is left for marketing and a broader reach to potential customers. Code reuse is one of the most common ways to reduce devel- opment costs. The intent is pretty obvious: instead of develop- ing something over and over from scratch, why don’t we reuse existing code in new projects? The idea looks great on paper, but it turns out that making existing code work in a new context usually takes extra effort. Tight coupling between components, dependencies on con- crete classes instead of interfaces, hardcoded operations—all of this reduces flexibility of the code and makes it harder to reuse it. Using design patterns is one way to increase flexibility of soft- ware components and make them easier to reuse. However, 32 Features of Good Design / Extensibility There also is a middle level. This is where I see patterns. Design patterns are both smaller and more abstract than frameworks. They’re really a description about how a couple of classes can relate to and interact with each other. The level of reuse increases when you move from classes to patterns and finally frameworks. What is nice about this middle layer is that patterns offer reuse in a way that is less risky than frameworks. Building a framework is high-risk and a significant investment. Patterns let you reuse design ideas and concepts independently of con- crete code. „ Extensibility Change is the only constant thing in a programmer’s life. You released a video game for Windows, but now people ask for a macOS version. You created a GUI framework with square buttons, but several months later round buttons become a trend. You designed a brilliant e-commerce website architecture, but just a month later customers ask for a feature that would let them accept phone orders. Each software developer has dozens of similar stories. There are several reasons why this happens. 33 Features of Good Design / Extensibility First, we understand the problem better once we start to solve it. Often by the time you finish the first version of an app, you’re ready to rewrite it from scratch because now you under- stand many aspects of the problem much better. You have also grown professionally, and your own code now looks like crap. Something beyond your control has changed. This is why so many dev teams pivot from their original ideas into something new. Everyone who relied on Flash in an online application has been reworking or migrating their code as browser after browser drops support for Flash. The third reason is that the goalposts move. Your client was delighted with the current version of the application, but now sees eleven “little” changes he’d like so it can do other things he never mentioned in the original planning sessions. These aren’t frivolous changes: your excellent first version has shown him that even more is possible. There’s a bright side: if someone asks you to change something in your app, that means someone still cares about it. That’s why all seasoned developers try to provide for possible future changes when designing an application’s architecture. 34 Design Principles Design Principles What is good software design? How would you measure it? What practices would you need to follow to achieve it? How can you make your architecture flexible, stable and easy to understand? These are the great questions; but, unfortunately, the answers are different depending on the type of application you’re build- ing. Nevertheless, there are several universal principles of soft- ware design that might help you answer these questions for your own project. Most of the design patterns listed in this book are based on these principles. 35 Design Principles / Encapsulate What Varies Encapsulate What Varies Identify the aspects of your application that vary and separate them from what stays the same. The main goal of this principle is to minimize the effect caused by changes. Imagine that your program is a ship, and changes are hideous mines that linger under water. Struck by the mine, the ship sinks. Knowing this, you can divide the ship’s hull into independent compartments that can be safely sealed to limit damage to a single compartment. Now, if the ship hits a mine, the ship as a whole remains afloat. In the same way, you can isolate the parts of the program that vary in independent modules, protecting the rest of the code from adverse effects. As a result, you spend less time getting the program back into working shape, implementing and test- ing the changes. The less time you spend making changes, the more time you have for implementing features. 36 Design Principles / Encapsulate What Varies Encapsulation on a method level Say you’re making an e-commerce website. Somewhere in your code, there’s a getOrderTotal method that calculates a grand total for the order, including taxes. We can anticipate that tax-related code might need to change in the future. The tax rate depends on the country, state or even city where the customer resides, and the actual formu- la may change over time due to new laws or regulations. As a result, you’ll need to change the getOrderTotal method quite often. But even the method’s name suggests that it doesn’t care about how the tax is calculated. 1 method getOrderTotal(order) is 2 total = 0 3 foreach item in order.lineItems 4 total += item.price * item.quantity 5 6 if (order.country == "US") 7 total += total * 0.07 // US sales tax 8 else if (order.country == "EU"): 9 total += total * 0.20 // European VAT 10 11 return total BEFORE: tax calculation code is mixed with the rest of the method’s code. You can extract the tax calculation logic into a separate method, hiding it from the original method. 37 Design Principles / Encapsulate What Varies 1 method getOrderTotal(order) is 2 total = 0 3 foreach item in order.lineItems 4 total += item.price * item.quantity 5 6 total += total * getTaxRate(order.country) 7 8 return total 9 10 method getTaxRate(country) is 11 if (country == "US") 12 return 0.07 // US sales tax 13 else if (country == "EU") 14 return 0.20 // European VAT 15 else 16 return 0 AFTER: you can get the tax rate by calling a designated method. Tax-related changes become isolated inside a single method. Moreover, if the tax calculation logic becomes too complicat- ed, it’s now easier to move it to a separate class. Encapsulation on a class level Over time you might add more and more responsibilities to a method which used to do a simple thing. These added behav- iors often come with their own helper fields and methods that eventually blur the primary responsibility of the containing class. Extracting everything to a new class might make things much more clear and simple. 38 Design Principles / Encapsulate What Varies BEFORE: calculating tax in Order class. Objects of the Order class delegate all tax-related work to a special object that does just that. AFTER: tax calculation is hidden from the order class. 39 Design Principles / Program to an Interface, not an Implementation Program to an Interface, not an Implementation Program to an interface, not an implementation. Depend on abstractions, not on concrete classes. You can tell that the design is flexible enough if you can easily extend it without breaking any existing code. Let’s make sure that this statement is correct by looking at another cat exam- ple. A Cat that can eat any food is more flexible than one that can eat just sausages. You can still feed the first cat with sausages because they are a subset of “any food”; however, you can extend that cat’s menu with any other food. When you want to make two classes collaborate, you can start by making one of them dependent on the other. Hell, I often start by doing that myself. However, there’s another, more flex- ible way to set up collaboration between objects: 1. Determine what exactly one object needs from the other: which methods does it execute? 2. Describe these methods in a new interface or abstract class. 3. Make the class that is a dependency implement this interface. 4. Now make the second class dependent on this interface rather than on the concrete class. You still can make it work with 40 Design Principles / Program to an Interface, not an Implementation objects of the original class, but the connection is now much more flexible. Before and after extracting the interface. The code on the right is more flexible than the code on the left, but it’s also more complicated. After making this change, you won’t probably feel any immedi- ate benefit. On the contrary, the code has become more com- plicated than it was before. However, if you feel that this might be a good extension point for some extra functionality, or that some other people who use your code might want to extend it here, then go for it. 41 Design Principles / Program to an Interface, not an Implementation Example Let’s look at another example which illustrates that working with objects through interfaces might be more beneficial than depending on their concrete classes. Imagine that you’re creat- ing a software development company simulator. You have dif- ferent classes that represent various employee types. BEFORE: all classes are tightly coupled. In the beginning, the Company class is tightly coupled to con- crete classes of employees. However, despite the difference in their implementations, we can generalize various work-related 42 Design Principles / Program to an Interface, not an Implementation methods and then extract a common interface for all employ- ee classes. After doing that, we can apply polymorphism inside the Company class, treating various employee objects via the Employee interface. BETTER: polymorphism helped us simplify the code, but the rest of the Company class still depends on the concrete employee classes. The Company class remains coupled to the employee classes. This is bad because if we introduce new types of companies that work with other types of employees, we’ll need to over- ride most of the Company class instead of reusing that code. To solve this problem, we could declare the method for get- ting employees as abstract. Each concrete company will imple- 43 Design Principles / Program to an Interface, not an Implementation ment this method differently, creating only those employees that it needs. AFTER: the primary method of the Company class is independent from concrete employee classes. Employee objects are created in concrete company subclasses. After this change, the Company class has become independent from various employee classes. Now you can extend this class and introduce new types of companies and employees while still reusing a portion of the base company class. Extending the base company class doesn’t break any existing code that already relies on it. By the way, you’ve just seen applying a design pattern in action! That was an example of the Factory Method pattern. Don’t worry: we’ll discuss it later in detail. 44 Design Principles / Favor Composition Over Inheritance Favor Composition Over Inheritance Inheritance is probably the most obvious and easy way of reusing code between classes. You have two classes with the same code. Create a common base class for these two and move the similar code into it. Piece of cake! Unfortunately, inheritance comes with caveats that often become apparent only after your program already has tons of classes and changing anything is pretty hard. Here’s a list of those problems. A subclass can’t reduce the interface of the superclass. You have to implement all abstract methods of the parent class even if you won’t be using them. When overriding methods you need to make sure that the new behavior is compatible with the base one. It’s important because objects of the subclass may be passed to any code that expects objects of the superclass and you don’t want that code to break. Inheritance breaks encapsulation of the superclass because the internal details of the parent class become available to the subclass. There might be an opposite situation where a pro- grammer makes a superclass aware of some details of sub- classes for the sake of making further extension easier. 45 Design Principles / Favor Composition Over Inheritance Subclasses are tightly coupled to superclasses. Any change in a superclass may break the functionality of subclasses. Trying to reuse code through inheritance can lead to creat- ing parallel inheritance hierarchies. Inheritance usually takes place in a single dimension. But whenever there are two or more dimensions, you have to create lots of class combina- tions, bloating the class hierarchy to a ridiculous size. There’s an alternative to inheritance called composition. Whereas inheritance represents the “is a” relationship between classes (a car is a transport), composition represents the “has a” relationship (a car has an engine). I should mention that this principle also applies to aggrega- tion—a more relaxed variant of composition where one object may have a reference to the other one but doesn’t manage its lifecycle. Here’s an example: a car has a driver, but he or she may use another car or just walk without the car. Example Imagine that you need to create a catalog app for a car manu- facturer. The company makes both cars and trucks; they can be either electric or gas; all models have either manual controls or an autopilot. 46 Design Principles / Favor Composition Over Inheritance INHERITANCE: extending a class in several dimensions (cargo type × engine type × navigation type) may lead to a combinatorial explosion of subclasses. As you see, each additional parameter results in multiply- ing the number of subclasses. There’s a lot of duplicate code between subclasses because a subclass can’t extend two class- es at the same time. You can solve this problem with composition. Instead of car objects implementing a behavior on their own, they can dele- gate it to other objects. The added benefit is that you can replace a behavior at run- time. For instance, you can replace an engine object linked to a car object just by assigning a different engine object to the car. 47 Design Principles / Favor Composition Over Inheritance COMPOSITION: different “dimensions” of functionality extracted to their own class hierarchies. This structure of classes resembles the Strategy pattern, which we’ll go over later in this book. 48 SOLID Principles SOLID Principles Now that you know the basic design principles, let’s take a look at five that are commonly known as the SOLID princi- ples. Robert Martin introduced them in the book Agile Software Development, Principles, Patterns, and Practices 1. SOLID is a mnemonic for five design principles intended to make software designs more understandable, flexible and maintainable. As with everything in life, using these principles mindlessly can cause more harm than good. The cost of applying these principles into a program’s architecture might be making it more complicated than it should be. I doubt that there’s a suc- cessful software product in which all of these principles are applied at the same time. Striving for these principles is good, but always try to be pragmatic and don’t take everything writ- ten here as dogma. 1. Agile Software Development, Principles, Patterns, and Practices: https://refactoring.guru/principles-book 49 SOLID Principles / Single Responsibility Principle S ingle Responsibility Principle A class should have just one reason to change. Try to make every class responsible for a single part of the functionality provided by the software, and make that respon- sibility entirely encapsulated by (you can also say hidden with- in) the class. The main goal of this principle is reducing complexity. You don’t need to invent a sophisticated design for a program that only has about 200 lines of code. Make a dozen methods pret- ty, and you’ll be fine. The real problems emerge when your program constantly grows and changes. At some point classes become so big that you can no longer remember their details. Code navigation slows down to a crawl, and you have to scan through whole classes or even an entire program to find specific things. The number of entities in program overflows your brain stack, and you feel that you’re losing control over the code. There’s more: if a class does too many things, you have to change it every time one of these things changes. While doing that, you’re risking breaking other parts of the class which you didn’t even intend to change. 50 SOLID Principles / Single Responsibility Principle If you feel that it’s becoming hard to focus on specific aspects of the program one at a time, remember the single responsibil- ity principle and check whether it’s time to divide some classes into parts. Example The Employee class has several reasons to change. The first reason might be related to the main job of the class: managing employee data. However, there’s another reason: the format of the timesheet report may change over time, requiring you to change the code within the class. BEFORE: the class contains several different behaviors. Solve the problem by moving the behavior related to printing timesheet reports into a separate class. This change lets you move other report-related stuff to the new class. AFTER: the extra behavior is in its own class. 51 SOLID Principles / Open/Closed Principle O pen/Closed Principle Classes should be open for extension but closed for modification. The main idea of this principle is to keep existing code from breaking when you implement new features. A class is open if you can extend it, produce a subclass and do whatever you want with it—add new methods or fields, override base behavior, etc. Some programming languages let you restrict further extension of a class with special keywords, such as final. After this, the class is no longer open. At the same time, the class is closed (you can also say complete) if it’s 100% ready to be used by other classes—its interface is clearly defined and won’t be changed in the future. When I first learned about this principle, I was confused because the words open & closed sound mutually exclusive. But in terms of this principle, a class can be both open (for extension) and closed (for modification) at the same time. If a class is already developed, tested, reviewed, and includ- ed in some framework or otherwise used in an app, trying to mess with its code is risky. Instead of changing the code of the class directly, you can create a subclass and override parts of 52 SOLID Principles / Open/Closed Principle the original class that you want to behave differently. You’ll achieve your goal but also won’t break any existing clients of the original class. This principle isn’t meant to be applied for all changes to a class. If you know that there’s a bug in the class, just go on and fix it; don’t create a subclass for it. A child class shouldn’t be responsible for the parent’s issues. Example You have an e-commerce application with an Order class that calculates shipping costs and all shipping methods are hard- coded inside the class. If you need to add a new shipping method, you have to change the code of the Order class and risk breaking it. BEFORE: you have to change the Order class whenever you add a new shipping method to the app. 53 SOLID Principles / Open/Closed Principle You can solve the problem by applying the Strategy pattern. Start by extracting shipping methods into separate classes with a com- mon interface. AFTER: adding a new shipping method doesn’t require changing existing classes. Now when you need to implement a new shipping method, you can derive a new class from the Shipping interface without touching any of the Order class’ code. The client code of the Order class will link orders with a shipping object of the new class whenever the user selects this shipping methods in the UI. As a bonus, this solution let you move the delivery time calcula- tion to more relevant classes, according to the single responsibility principle. 54 SOLID Principles / Liskov Substitution Principle L iskov Substitution Principle 1 When extending a class, remember that you should be able to pass objects of the subclass in place of objects of the parent class without breaking the client code. This means that the subclass should remain compatible with the behavior of the superclass. When overriding a method, extend the base behavior rather than replacing it with some- thing else entirely. The substitution principle is a set of checks that help pre- dict whether a subclass remains compatible with the code that was able to work with objects of the superclass. This concept is critical when developing libraries and frameworks because your classes are going to be used by other people whose code you can’t directly access and change. Unlike other design principles which are wide open for inter- pretation, the substitution principle has a set of formal requirements for subclasses, and specifically for their methods. Let’s go over this checklist in detail. 1. This principle is named by Barbara Liskov, who defined it in 1987 in her work Data abstraction and hierarchy: https://refactoring.guru/liskov/dah 55 SOLID Principles / Liskov Substitution Principle Parameter types in a method of a subclass should match or be more abstract than parameter types in the method of the super- class. Sounds confusing? Let’s have an example. ◦ Say there’s a class with a method that’s supposed to feed cats: feed(Cat c). Client code always passes cat objects into this method. ◦ Good: Say you created a subclass that overrode the method so that it can feed any animal (a superclass of cats): feed(Animal c). Now if you pass an object of this subclass instead of an object of the superclass to the client code, everything would still work fine. The method can feed all animals, so it can still feed any cat passed by the client. ◦ Bad: You created another subclass and restricted the feed- ing method to only accept Bengal cats (a subclass of cats): feed(BengalCat c). What will happen to the client code if you link it with an object like this instead of with the origi- nal class? Since the method can only feed a specific breed of cats, it won’t serve generic cats passed by the client, break- ing all related functionality. The return type in a method of a subclass should match or be a subtype of the return type in the method of the superclass. As you can see, requirements for a return type are inverse to requirements for parameter types. 56 SOLID Principles / Liskov Substitution Principle ◦ Say you have a class with a method buyCat(): Cat. The client code expects to receive any cat as a result of execut- ing this method. ◦ Good: A subclass overrides the method as follows: buyCat(): BengalCat. The client gets a Bengal cat, which is still a cat, so everything is okay. ◦ Bad: A subclass overrides the method as follows: buyCat(): Animal. Now the client code breaks since it receives an unknown generic animal (an alligator? a bear?) that doesn’t fit a structure designed for a cat. Another anti-example comes from the world of programming languages with dynamic typing: the base method returns a string, but the overridden method returns a number. A method in a subclass shouldn’t throw types of exceptions which the base method isn’t expected to throw. In other words, types of exceptions should match or be subtypes of the ones that the base method is already able to throw. This rule comes from the fact that try-catch blocks in the client code target specific types of exceptions which the base method is likely to throw. Therefore, an unexpected exception might slip through the defensive lines of the client code and crash the entire application. 57 SOLID Principles / Liskov Substitution Principle In most modern programming languages, especially sta- tically typed ones (Java, C#, and others), these rules are built into the language. You won’t be able to compile a program that violates these rules. A subclass shouldn’t strengthen pre-conditions. For example, the base method has a parameter with type int. If a sub- class overrides this method and requires that the value of an argument passed to the method should be positive (by throw- ing an exception if the value is negative), this strengthens the pre-conditions. The client code, which used to work fine when passing negative numbers into the method, now breaks if it starts working with an object of this subclass. A subclass shouldn’t weaken post-conditions. Say you have a class with a method that works with a database. A method of the class is supposed to always close all opened database con- nections upon returning a value. You created a subclass and changed it so that database con- nections remain open so you can reuse them. But the client might not know anything about your intentions. Because it expects the methods to close all the connections, it may sim- ply terminate the program right after calling the method, pol- luting a system with ghost database connections. Invariants of a superclass must be preserved. This is probably the least formal rule of all. Invariants are conditions in which 58 SOLID Principles / Liskov Substitution Principle an object makes sense. For example, invariants of a cat are having four legs, a tail, ability to meow, etc. The confusing part about invariants is that while they can be defined explicitly in the form of interface contracts or a set of assertions within methods, they could also be implied by certain unit tests and expectations of the client code. The rule on invariants is the easiest to violate because you might misunderstand or not realize all of the invariants of a complex class. Therefore, the safest way to extend a class is to introduce new fields and methods, and not mess with any existing members of the superclass. Of course, that’s not always doable in real life. A subclass shouldn’t change values of private fields of the superclass. What? How’s that even possible? It turns out some programming languages let you access private members of a class via reflection mechanisms. Other languages (Python, JavaScript) don’t have any protection for the private members at all. Example Let’s look at an example of a hierarchy of document classes that violates the substitution principle. 59 SOLID Principles / Liskov Substitution Principle BEFORE: saving doesn’t make sense in a read-only document, so the subclass tries to solve it by resetting the base behavior in the overridden method. The save method in the ReadOnlyDocuments subclass throws an exception if someone tries to call it. The base method doesn’t have this restriction. This means that the client code will break if we don’t check the document type before sav- ing it. The resulting code also violates the open/closed principle, since the client code becomes dependent on concrete class- es of documents. If you introduce a new document subclass, you’ll need to change the client code to support it. 60 SOLID Principles / Liskov Substitution Principle AFTER: the problem is solved after making the read-only document class the base class of the hierarchy. You can solve the problem by redesigning the class hierar- chy: a subclass should extend the behavior of a superclass, therefore the read-only document becomes the base class of the hierarchy. The writable document is now a subclass which extends the base class and adds the saving behavior. 61 SOLID Principles / Interface Segregation Principle I nterface Segregation Principle Clients shouldn’t be forced to depend on methods they do not use. Try to make your interfaces narrow enough that client classes don’t have to implement behaviors they don’t need. According to the interface segregation principle, you should break down “fat” interfaces into more granular and specific ones. Clients should implement only those methods that they really need. Otherwise, a change to a “fat” interface would break even clients that don’t use the changed methods. Class inheritance lets a class have just one superclass, but it doesn’t limit the number of interfaces that the class can imple- ment at the same time. Hence, there’s no need to cram tons of unrelated methods to a single interface. Break it down into several more refined interfaces—you can implement them all in a single class if needed. However, some classes may be fine with implementing just one of them. Example Imagine that you created a library that makes it easy to inte- grate apps with various cloud computing providers. While in 62 SOLID Principles / Interface Segregation Principle the initial version it only supported Amazon Cloud, it covered the full set of cloud services and features. At the time you assumed that all cloud providers have the same broad spectrum of features as Amazon. But when it came to implementing support for another provider, it turned out that most of the interfaces of the library are too wide. Some methods describe features that other cloud providers just don’t have. BEFORE: not all clients can satisfy the requirements of the bloated interface. While you can still implement these methods and put some stubs there, it wouldn’t be a pretty solution. The better 63 SOLID Principles / Interface Segregation Principle approach is to break down the interface into parts. Classes that are able to implement the original interface can now just implement several refined interfaces. Other classes can imple- ment only those interfaces which have methods that make sense for them. AFTER: one bloated interface is broken down into a set of more granular interfaces. As with the other principles, you can go too far with this one. Don’t further divide an interface which is already quite spe- cific. Remember that the more interfaces you create, the more complex your code becomes. Keep the balance. 64 SOLID Principles / Dependency Inversion Principle D ependency Inversion Principle High-level classes shouldn’t depend on low-level class- es. Both should depend on abstractions. Abstractions shouldn’t depend on details. Details should depend on abstractions. Usually when designing software, you can make a distinction between two levels of classes. Low-level classes implement basic operations such as working with a disk, transferring data over a network, connecting to a database, etc. High-level classes contain complex business logic that directs low-level classes to do something. Sometimes people design low-level classes first and only then start working on high-level ones. This is very common when you start developing a prototype on a new system, and you’re not even sure what’s possible at the higher level because low-level stuff isn’t yet implemented or clear. With such an approach business logic classes tend to become dependent on primitive low-level classes. The dependency inversion principle suggests changing the direction of this dependency. 65 SOLID Principles / Dependency Inversion Principle 1. For starters, you need to describe interfaces for low-level oper- ations that high-level classes rely on, preferably in business terms. For instance, business logic should call a method openReport(file) rather than a series of methods openFile(x) , readBytes(n) , closeFile(x). These interfaces count as high-level ones. 2. Now you can make high-level classes dependent on those interfaces, instead of on concrete low-level classes. This dependency will be much softer than the original one. 3. Once low-level classes implement these interfaces, they become dependent on the business logic level, reversing the direction of the original dependency. The dependency inversion principle often goes along with the open/closed principle: you can extend low-level classes to use with different business logic classes without breaking existing classes. Example In this example, the high-level budget reporting class uses a low-level database class for reading and persisting its data. This means that any change in the low-level class, such as when a new version of the database server gets released, may affect the high-level class, which isn’t supposed to care about the data storage details. 66 SOLID Principles / Dependency Inversion Principle BEFORE: a high-level class depends on a low-level class. You can fix this problem by creating a high-level interface that describes read/write operations and making the report- ing class use that interface instead of the low-level class. Then you can change or extend the original low-level class to implement the new read/write interface declared by the busi- ness logic. AFTER: low-level classes depend on a high-level abstraction. 67 SOLID Principles / Dependency Inversion Principle As a result, the direction of the original dependency has been inverted: low-level classes are now dependent on high-level abstractions. CATALOG OF DESIGN PATTERNS 71 Creational Design Patterns / Factory Method FACTORY METHOD Also known as: Virtual Constructor Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. 72 Creational Design Patterns / Factory Method Problem Imagine that you’re creating a logistics management appli- cation. The first version of your app can only handle trans- portation by trucks, so the bulk of your code lives inside the Truck class. After a while, your app becomes pretty popular. Each day you receive dozens of requests from sea transportation companies to incorporate sea logistics into the app. Adding a new class to the program isn’t that simple if the rest of the code is already coupled to existing classes. Great news, right? But how about the code? At present, most of your code is coupled to the Truck class. Adding Ships into the app would require making changes to the entire codebase. Moreover, if later you decide to add another type of transporta- tion to the app, you will probably need to make all of these changes again. 73 Creational Design Patterns / Factory Method As a result, you will end up with pretty nasty code, riddled with conditionals that switch the app’s behavior depending on the class of transportation objects. Solution The Factory Method pattern suggests that you replace direct object construction calls (using the new operator) with calls to a special factory method. Don’t worry: the objects are still created via the new operator, but it’s being called from within the factory method. Objects returned by a factory method are often referred to as “products.” Subclasses can alter the class of objects being returned by the factory method. At first glance, this change may look pointless: we just moved the constructor call from one part of the program to anoth- er. However, consider this: now you can override the factory method in a subclass and change the class of products being created by the method. 74 Creational Design Patterns / Factory Method There’s a slight limitation though: subclasses may return dif- ferent types of products only if these products have a common base class or interface. Also, the factory method in the base class should have its return type declared as this interface. All products must follow the same interface. For example, both Truck and Ship classes should imple- ment the Transport interface, which declares a method called deliver. Each class implements this method different- ly: trucks deliver cargo by land, ships deliver cargo by sea. The factory method in the RoadLogistics class returns truck objects, whereas the factory method in the SeaLogistics class returns ships. The code that uses the factory method (often called the client code) doesn’t see a difference between the actual products returned by various subclasses. The client treats all the prod- ucts as abstract Transport. 75 Creational Design Patterns / Factory Method As long as all product classes implement a common interface, you can pass their objects to the client code without breaking it. The client knows that all transport objects are supposed to have the deliver method, but exactly how it works isn’t important to the client. Structure 76 Creational Design Patterns / Factory Method 1. The Product declares the interface, which is common to all objects that can be produced by the creator and its subclasses. 2. Concrete Products are different implementations of the prod- uct interface. 3. The Creator class declares the factory method that returns new product objects. It’s important that the return type of this method matches the product interface. You can declare the factory method as abstract to force all sub- classes to implement their own versions of the method. As an alternative, the base factory method can return some default product type. Note, despite its name, product creation is not the primary responsibility of the creator. Usually, the creator class already has some core business logic related to products. The factory method helps to decouple this logic from the concrete prod- uct classes. Here is an analogy: a large software development company can have a training department for programmers. However, the primary function of the company as a whole is still writing code, not producing programmers. 4. Concrete Creators override the base factory method so it returns a different type of product. 77 Creational Design Patterns / Factory Method Note that the factory method doesn’t have to create new instances all the time. It can also return existing objects from a cache, an object pool, or another source. Pseudocode This example illustrates how the Factory Method can be used for creating cross-platform UI elements without coupling the client code to concrete UI classes. The base dialog class uses different UI elements to render its window. Under various operating systems, these elements may look a little bit different, but they should still behave consis- tently. A button in Windows is still a button in Linux. The cross-platform dialog example. 78 Creational Design Patterns / Factory Method When the factory method comes into play, you don’t need to rewrite the logic of the dialog for each operating system. If we declare a factory method that produces buttons inside the base dialog class, we can later create a dialog subclass that returns Windows-styled buttons from the factory method. The subclass then inherits most of the dialog’s code from the base class, but, thanks to the factory method, can render Windows- looking buttons on the screen. For this pattern to work, the base dialog class must work with abstract buttons: a base class or an interface that all concrete buttons follow. This way the dialog’s code remains functional, whichever type of buttons it works with. Of course, you can apply this approach to other UI elements as well. However, with each new factory method you add to the dialog, you get closer to the Abstract Factory pattern. Fear not, we’ll talk about this pattern later. 1 // The creator class declares the factory method that must 2 // return an object of a product class. The creator's subclasses 3 // usually provide the implementation of this method. 4 class Dialog is 5 // The creator may also provide some default implementation 6 // of the factory method. 7 abstract method createButton() 8 9 // Note that, despite its name, the creator's primary 10 // responsibility isn't creating products. It usually 79 Creational Design Patterns / Factory Method 11 // contains some core business logic that relies on product 12 // objects returned by the factory method. Subclasses can 13 // indirectly change that business logic by overriding the 14 // factory method and returning a different type of product 15 // from it. 16 method render() is 17 // Call the factory method to create a product object. 18 Button okButton = createButton() 19 // Now use the product. 20 okButton.onClick(closeDialog) 21 okButton.render() 22 23 24 // Concrete creators override the factory method to change the 25 // resulting product's type. 26 class WindowsDialog extends Dialog is 27 method createButton() is 28 return new WindowsButton() 29 30 class WebDialog extends Dialog is 31 method createButton() is 32 return new HTMLButton() 33 34 35 // The product interface declares the operations that all 36 // concrete products must implement. 37 interface Button is 38 method render() 39 method onClick(f) 40 41 // Concrete products provide various implementations of the 42 // product interface. 80 Creational Design Patterns / Factory Method 43 class WindowsButton implements Button is 44 method render(a, b) is 45 // Render a button in Windows style. 46 method onClick(f) is 47 // Bind a native OS click event. 48 49 class HTMLButton implements Button is 50 method render(a, b) is 51 // Return an HTML representation of a button. 52 method onClick(f) is 53 // Bind a web browser click event. 54 55 56 class Application is 57 field dialog: Dialog 58 59 // The application picks a creator's type depending on the 60 // current configuration or environment settings. 61 method initialize() is 62 config = readApplicationConfigFile() 63 64 if (config.OS == "Windows") then 65 dialog = new WindowsDialog() 66 else if (config.OS == "Web") then 67 dialog = new WebDialog() 68 else 69 throw new Exception("Error! Unknown operating system.") 70 71 // The client code works with an instance of a concrete 72 // creator, albeit through its base interface. As long as 73 // the client keeps working with the creator via the base 74 // interface, you can pass it any creator's subclass. 81 Creational Design Patterns / Factory Method 75 method main() is 76 this.initialize() 77 dialog.render() Applicability Use the Factory Method when you don’t know beforehand the exact types and dependencies of the objects your code should work with. The Factory Method separates product construction code from the code that actually uses the product. Therefore it’s easier to extend the product construction code independently from the rest of the code. For example, to add a new product type to the app, you’ll only need to create a new creator subclass and override the factory method in it. Use the Factory Method when you want to provide users of your library or framework with a way to extend its internal components. Inheritance is probably the easiest way to extend the default behavior of a library or framework. But how would the frame- work recognize that your subclass should be used instead of a standard component? 82 Creational Design Patterns / Factory Method The solution is to reduce the code that constructs components across the framework into a single factory method and let any- one override this method in addition to extending the compo- nent itself. Let’s see how that would work. Imagine that you write an app using an open source UI framework. Your app should have round buttons, but the framework only provides square ones. You extend the standard Button class with a glorious RoundButton subclass. But now you need to tell the main UIFramework class to use the new button subclass instead of a default one. To achieve this, you create a subclass UIWithRoundButtons from a base framework class and override its createButton method. While this method returns Button objects in the base class, you make your subclass return RoundButton objects. Now use the UIWithRoundButtons class instead of UIFramework. And that’s about it! Use the Factory Method when you want to save system resources by reusing existing objects instead of rebuilding them each time. You often experience this need when dealing with large, resource-intensive objects such as database connections, file systems, and network resources. 83 Creational Design Patterns / Factory Method Let’s think about what has to be done to reuse an existing object: 1. First, you need to create some storage to keep track of all of the created objects. 2. When someone requests an object, the program should look for a free object inside that pool. 3. … and then return it to the client code. 4. If there are no free objects, the program should create a new one (and add it to the pool). That’s a lot of code! And it must all be put into a single place so that you don’t pollute the program with duplicate code. Probably the most obvious and convenient place where this code could be placed is the constructor of the class whose objects we’re trying to reuse. However, a constructor must always return new objects by definition. It can’t return existing instances. Therefore, you need to have a regular method capable of creating new objects as well as reusing existing ones. That sounds very much like a factory method. How to Implement 1. Make all products follow the same interface. This interface should declare methods that make sense in every product. 84 Creational Design Patterns / Factory Method 2. Add an empty factory method inside the creator class. The return type of the method should match the common product interface. 3. In the creator’s code find all references to product constructors. One by one, replace them with calls to the factory method, while extracting the product creation code into the factory method. You might need to add a temporary parameter to the factory method to control the type of returned product. At this point, the code of the factory method may look pret- ty ugly. It may have a large switch operator that picks which product class to instantiate. But don’t worry, we’ll fix it soon enough. 4. Now, create a set of creator subclasses for each type of prod- uct listed in the factory method. Override the factory method in the subclasses and extract the appropriate bits of construc- tion code from the base method. 5. If there are too many product types and it doesn’t make sense to create subclasses for all of them, you can reuse the control parameter from the base class in subclasses. For instance, imagine that you have the following hierarchy of classes: the base Mail class with a couple of subclasses: AirMail and GroundMail ; the Transport classes are Plane , 85 Creational Design Patterns / Factory Method Truck and Train. While the AirMail class only uses Plane objects, GroundMail may work with both Truck and Train objects. You can create a new subclass (say TrainMail ) to handle both cases, but there’s another option. The client code can pass an argument to the factory method of the GroundMail class to control which product it wants to receive. 6. If, after all of the extractions, the base factory method has become empty, you can make it abstract. If there’s something left, you can make it a default behavior of the method. Pros and Cons You avoid tight coupling between the creator and the concrete products. Single Responsibility Principle. You can move the product cre- ation code into one place in the program, making the code eas- ier to support. Open/Closed Principle. You can introduce new types of products into the program without breaking existing client code. The code may become more complicated since you need to introduce a lot of new subclasses to implement the pattern. The best case scenario is when you’re introducing the pattern into an existing hierarchy of creator classes. 86 Creational Design Patterns / Factory Method Relations with Other Patterns Many designs start by using Factory Method (less complicat- ed and more customizable via subclasses) and evolve toward Abstract Factory, Prototype, or Builder (more flexible, but more complicated). Abstract Factory classes are often based on a set of Facto- ry Methods, but you can also use Prototype to compose the methods on these classes. You can use Factory Method along with Iterator to let collec- tion subclasses return different types of iterators that are com- patible with the collections. Prototype isn’t based on inheritance, so it doesn’t have its drawbacks. On the other hand, Prototype requires a complicat- ed initialization of the cloned object. Factory Method is based on inheritance but doesn’t require an initialization step. Factory Method is a specialization of Template Method. At the same time, a Factory Method may serve as a step in a large Tem- plate Method. 87 Creational Design Patterns / Abstract Factory ABSTRACT FACTORY Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes. 88 Creational Design Patterns / Abstract Factory Problem Imagine that you’re creating a furniture shop simulator. Your code consists of classes that represent: 1. A family of related products, say: Chair + Sofa + CoffeeTable. 2. Several variants of this family. For example, products Chair + Sofa + CoffeeTable are available in these variants: Modern , Victorian , ArtDeco. Product families and their variants. 89 Creational Design Patterns / Abstract Factory You need a way to create individual furniture objects so that they match other objects of the same family. Customers get quite mad when they receive non-matching furniture. A Modern-style sofa doesn’t match Victorian-style chairs. Also, you don’t want to change existing code when adding new products or families of products to the program. Furniture ven- dors update their catalogs very often, and you wouldn’t want to change the core code each time it happens. Solution The first thing the Abstract Factory pattern suggests is to explicitly declare interfaces for each distinct product of the product family (e.g., chair, sofa or coffee table). Then you can make all variants of products follow those interfaces. For example, all chair variants can implement the Chair inter- face; all coffee table variants can implement the CoffeeTable interface, and so on. 90 Creational Design Patterns / Abstract Factory All variants of the same object must be moved to a single class hierarchy. The next move is to declare the Abstract Factory—an interface with a list of creation methods for all products that are part of the product family (for example, createChair , createSofa and createCoffeeTable ). These methods must return abstract product types represented by the interfaces we extracted pre- viously: Chair , Sofa , CoffeeTable and so on. Now, how about the product variants? For each variant of a product family, we create a separate factory class based on the AbstractFactory interface. A factory is a class that returns products of a particular kind. For example, the ModernFactory can only create ModernChair , ModernSofa and ModernCoffeeTable objects. 91 Creational Design Patterns / Abstract Factory Each concrete factory corresponds to a specific product variant. The client code has to work with both factories and products via their respective abstract interfaces. This lets you change the type of a factory that you pass to the client code, as well as the product variant that the client code receives, without breaking the actual client code. The client shouldn’t care about the concrete class of the factory it works with. 92 Creational Design Patterns / Abstract Factory Say the client wants a factory to produce a chair. The client doesn’t have to be aware of the factory’s class, nor does it matter what kind of chair it gets. Whether it’s a Modern model or a Victorian-style chair, the client must treat all chairs in the same manner, using the abstract Chair interface. With this approach, the only thing that the client knows about the chair is that it implements the sitOn method in some way. Also, whichever variant of the chair is returned, it’ll always match the type of sofa or coffee table pro- duced by the same factory object. One more thing left to clarify: if the client is only exposed to the abstract interfaces, what creates the actual factory objects? Usual- ly, the application creates a concrete factory object at the initial- ization stage. Just before that, the app must select the factory type depending on the configuration or the environment settings. Structure 93 Creational Design Patterns / Abstract Factory 1. Abstract Products declare interfaces for a set of distinct but related products which make up a product family. 2. Concrete Products are various implementations of abstract products, grouped by variants. Each abstract product (chair/ sofa) must be implemented in all given variants (Victorian/ Modern). 3. The Abstract Factory interface declares a set of methods for creating each of the abstract products. 4. Concrete Factories implement creation methods of the abstract factory. Each concrete factory corresponds to a specif- ic variant of products and creates only those product variants. 5. Although concrete factories instantiate concrete products, sig- natures of their creation methods must return corresponding abstract products. This way the client code that uses a facto- ry doesn’t get coupled to the specific variant of the product it gets from a factory. The Client can work with any concrete factory/product variant, as long as it communicates with their objects via abstract interfaces. Pseudocode This example illustrates how the Abstract Factory pattern can be used for creating cross-platform UI elements without cou- pling the client code to concrete UI classes, while keeping all created elements consistent with a selected operating system. 94 Creational Design Patterns / Abstract Factory The cross-platform UI classes example. The same UI elements in a cross-platform application are expected to behave similarly, but look a little bit different under different operating systems. Moreover, it’s your job to make sure that the UI elements match the style of the current operating system. You wouldn’t want your program to render macOS controls when it’s executed in Windows. The Abstract Factory interface declares a set of creation meth- ods that the client code can use to produce different types of UI elements. Concrete factories correspond to specific operat- ing systems and create the UI elements that match that partic- ular OS. It works like this: when an application launches, it checks the type of the current operating system. The app uses this infor- 95 Creational Design Patterns / Abstract Factory mation to create a factory object from a class that matches the operating system. The rest of the code uses this factory to cre- ate UI elements. This prevents the wrong elements from being created. With this approach, the client code doesn’t depend on con- crete classes of factories and UI elements as long as it works with these objects via their abstract interfaces. This also lets the client code support other factories or UI elements that you might add in the future. As a result, you don’t need to modify the client code each time you add a new variation of UI elements to your app. You just have to create a new factory class that produces these ele- ments and slightly modify the app’s initialization code so it selects that class when appropriate. 96 Creational Design Patterns / Abstract Factory 1 // The abstract factory interface declares a set of methods that 2 // return different abstract products. These products are called 3 // a family and are related by a high-level theme or concept. 4 // Products of one family are usually able to collaborate among 5 // themselves. A family of products may have several variants, 6 // but the products of one variant are incompatible with the 7 // products of another variant. 8 interface GUIFactory is 9 method createButton():Button 10 method createCheckbox():Checkbox 11 12 13 // Concrete factories produce a family of products that belong 14 // to a single variant. The factory guarantees that the 15 // resulting products are compatible. Signatures of the concrete 16 // factory's methods return an abstract product, while inside 17 // the method a concrete product is instantiated. 18 class WinFactory implements GUIFactory is 19 method createButton():Button is 20 return new WinButton() 21 method createCheckbox():Checkbox is 22 return new WinCheckbox() 23 24 // Each concrete factory has a corresponding product variant. 25 class MacFactory implements GUIFactory is 26 method createButton():Button is 27 return new MacButton() 28 method createCheckbox():Checkbox is 29 return new MacCheckbox() 30 31 32 97 Creational Design Patterns / Abstract Factory 33 // Each distinct product of a product family should have a base 34 // interface. All variants of the product must implement this 35 // interface. 36 interface Button is 37 method paint() 38 39 // Concrete products are created by corresponding concrete 40 // factories. 41 class WinButton implements Button is 42 method paint() is 43 // Render a button in Windows style. 44 45 class MacButton implements Button is 46 method paint() is 47 // Render a button in macOS style. 48 49 // Here's the base interface of another product. All products 50 // can interact with each other, but proper interaction is 51 // possible only between products of the same concrete variant. 52 interface Checkbox is 53 method paint() 54 55 class WinCheckbox implements Checkbox is 56 method paint() is 57 // Render a checkbox in Windows style. 58 59 class MacCheckbox implements Checkbox is 60 method paint() is 61 // Render a checkbox in macOS style. 62 63 64 98 Creational Design Patterns / Abstract Factory 65 // The client code works with factories and products only 66 // through abstract types: GUIFactory, Button and Checkbox. This 67 // lets you pass any factory or product subclass to the client 68 // code without breaking it. 69 class Application is 70 private field button: Button 71 constructor Application(factory: GUIFactory) is 72 this.factory = factory 73 method createUI() is 74 this.button = factory.createButton() 75 method paint() is 76 button.paint() 77 78 79 // The application picks the factory type depending on the 80 // current configuration or environment settings and creates it 81 // at runtime (usually at the initialization stage). 82 class ApplicationConfigurator is 83 method main() is 84 config = readApplicationConfigFile() 85 86 if (config.OS == "Windows") then 87 factory = new WinFactory() 88 else if (config.OS == "Mac") then 89 factory = new MacFactory() 90 else 91 throw new Exception("Error! Unknown operating system.") 92 93 Application app = new Application(factory) 99 Creational Design Patterns / Abstract Factory Applicability Use the Abstract Factory when your code needs to work with various families of related products, but you don’t want it to depend on the concrete classes of those products—they might be unknown beforehand or you simply want to allow for future extensibility. The Abstract Factory provides you with an interface for cre- ating objects from each class of the product family. As long as your code creates objects via this interface, you don’t have to worry about creating the wrong variant of a product which doesn’t match the products already created by your app. Consider implementing the Abstract Factory when you have a class with a set of Factory Methods that blur its primary responsibility. In a well-designed program each class is responsible only for one thing. When a class deals with multiple product types, it may be worth extracting its factory methods into a stand- alone factory class or a full-blown Abstract Factory implemen- tation. How to Implement 1. Map out a matrix of distinct product types versus variants of these products. 100 Creational Design Patterns / Abstract Factory 2. Declare abstract product interfaces for all product types. Then make all concrete product classes implement these interfaces. 3. Declare the abstract factory interface with a set of creation methods for all abstract products. 4. Implement a set of concrete factory classes, one for each prod- uct variant. 5. Create factory initialization code somewhere in the app. It should instantiate one of the concrete factory classes, depend- ing on the application configuration or the current environ- ment. Pass this factory object to all classes that construct products. 6. Scan through the code and find all direct calls to product con- structors. Replace them with calls to the appropriate creation method on the factory object. Pros and Cons You can be sure that the products you’re getting from a factory are compatible with each other. You avoid tight coupling between concrete products and client code. Single Responsibility Principle. You can extract the product cre- ation code into one place, making the code easier to support. 101 Creational Design Patterns / Abstract Factory Open/Closed Principle. You can introduce new variants of prod- ucts without breaking existing client code. The code may become more complicated than it should be, since a lot of new interfaces and classes are introduced along with the pattern. Relations with Other Patterns Many designs start by using Factory Method (less complicat- ed and more customizable via subclasses) and evolve toward Abstract Factory, Prototype, or Builder (more flexible, but more complicated). Builder focuses on constructing complex objects step by step. Abstract Factory specializes in creating families of related objects. Abstract Factory returns the product immediately, whereas Builder lets you run some additional construction steps before fetching the product. Abstract Factory classes are often based on a set of Facto- ry Methods, but you can also use Prototype to compose the methods on these classes. Abstract Factory can serve as an alternative to Facade when you only want to hide the way the subsystem objects are cre- ated from the client code. 102 Creational Design Patterns / Abstract Factory You can use Abstract Factory along with Bridge. This pairing is useful when some abstractions defined by Bridge can only work with specific implementations. In this case, Abstract Fac- tory can encapsulate these relations and hide the complexity from the client code. Abstract Factories, Builders and Prototypes can all be imple- mented as Singletons. 103 Creational Design Patterns / Builder BUILDER Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code. 104 Creational Design Patterns / Builder Problem Imagine a complex object that requires laborious, step-by-step initialization of many fields and nested objects. Such initial- ization code is usually buried inside a monstrous constructor with lots of parameters. Or even worse: scattered all over the client code. You might make the program too complex by creating a subclass for every possible configuration of an object. For example, let’s think about how to create a House object. To build a simple house, you need to construct four walls and a floor, install a door, fit a pair of windows, and build a roof. But what if you want a bigger, brighter house, with a backyard and other goodies (like a heating system, plumbing, and electrical wiring)? 105 Creational Design Patterns / Builder The simplest solution is to extend the base House class and create a set of subclasses to cover all combinations of the parameters. But eventually you’ll end up with a considerable number of subclasses. Any new parameter, such as the porch style, will require growing this hierarchy even more. There’s another approach that doesn’t involve breeding sub- classes. You can create a giant constructor right in the base House class with all possible parameters that control the house object. While this approach indeed eliminates the need for subclasses, it creates another problem. The constructor with lots of parameters has its downside: not all the parameters are needed at all times. In most cases most of the parameters will be unused, making the constructor calls pretty ugly. For instance, only a fraction of houses have swimming pools, so the parameters related to swimming pools will be useless nine times out of ten. 106 Creational Design Patterns / Builder Solution The Builder pattern suggests that you extract the object con- struction code out of its own class and move it to separate objects called builders. The Builder pattern lets you construct complex objects step by step. The Builder doesn’t allow other objects to access the product while it’s being built. The pattern organizes object construction into a set of steps ( buildWalls , buildDoor , etc.). To create an object, you exe- cute a series of these steps on a builder object. The important part is that you don’t need to call all of the steps. You can call only those steps that are necessary for producing a particular configuration of an object. 107 Creational Design Patterns / Builder Some of the construction steps might require different imple- mentation when you need to build various representations of the product. For example, walls of a cabin may be built of wood, but the castle walls must be built with stone. In this case, you can create several different builder classes that implement the same set of building steps, but in a differ- ent manner. Then you can use these builders in the construc- tion process (i.e., an ordered set of calls to the building steps) to produce different kinds of objects. Different builders execute the same task in various ways. For example, imagine a builder that builds everything from wood and glass, a second one that builds everything with stone and iron and a third one that uses gold and diamonds. By calling the same set of steps, you get a regular house from the first builder, a small castle from the second and a palace from the third. However, this would only work if the client code that 108 Creational Design Patterns / Builder calls the