1/11
Design Patterns studied from Refactoring guru
Name | Mastery | Learn | Test | Matching | Spaced |
---|
No study sessions yet.
what’s design pattern? how it’s different from algorithms?
Design patterns are typical solutions to commonly occurring problems in software design. They are like pre-made blueprints that you can customize to solve a recurring design problem 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 description 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.
what’s History of patterns?
Who invented patterns? That’s a good, but not a very accurate, question. Design patterns aren’t obscure, sophisticated concepts—quite the opposite. 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 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, Construction. 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 neighborhood 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 1994, they published Design Patterns: Elements of Reusable Object-Oriented Software, in which they applied the concept of design patterns 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 shortened 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.
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.
what is Classification of patterns?
what’s difference between idioms and architectural 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 intersection safer by either installing some traffic lights or building an entire multi-level interchange with underground passages for pedestrians.
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 virtually 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 classes into larger structures, while keeping these structures flexible and efficient.
Behavioral patterns take care of effective communication and the assignment of responsibilities between objects.
what do you know about abstract factory?
what problem does it solve?
Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.
Problem
Imagine that you’re creating a furniture shop simulator. Your code consists of classes that represent:
A family of related products, say: Chair
+ Sofa
+ CoffeeTable
.
Several variants of this family. For example, products Chair
+ Sofa
+ CoffeeTable
are available in these variants: Modern
, Victorian
, ArtDeco
.
Product families and their variants.
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 vendors 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
interface; all coffee table variants can implement the CoffeeTable
interface, and so on.
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 previously: Chair
, Sofa
, CoffeeTable
and so on.
Each concrete factory corresponds to a specific product variant.
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 ModernFurnitureFactory
can only create ModernChair
, ModernSofa
and ModernCoffeeTable
objects.
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.
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 produced by the same factory object.
There’s one more thing left to clarify: if the client is only exposed to the abstract interfaces, what creates the actual factory objects? Usually, the application creates a concrete factory object at the initialization stage. Just before that, the app must select the factory type depending on the configuration or the environment settings.
what’s structure of Abstract Factory Pattern?
what situations in which we might need it?
what’s its pros and cons?
how it’s different from builder?
Structure
Abstract Products declare interfaces for a set of distinct but related products which make up a product family.
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).
The Abstract Factory interface declares a set of methods for creating each of the abstract products.
Concrete Factories implement creation methods of the abstract factory. Each concrete factory corresponds to a specific variant of products and creates only those product variants.
Although concrete factories instantiate concrete products, signatures of their creation methods must return corresponding abstract products. This way the client code that uses a factory 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
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 creating 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 implementation.
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 creation code into one place, making the code easier to support.
Open/Closed Principle. You can introduce new variants of products without breaking existing client code.
Con :: The code may become more complicated than it should be, since a lot of new interfaces and classes are introduced along with the pattern.
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.
what’s Factory Pattern? what problem does it solve? how it solves it?
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.
Imagine that you’re creating a logistics management application. The first version of your app can only handle transportation 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 transportation to the app, you will probably need to make all of these changes again.
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.
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 another. However, consider this: now you can override the factory method in a subclass and change the class of products being created by the method.
There’s a slight limitation though: subclasses may return different 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 implement the Transport
interface, which declares a method called deliver
. Each class implements this method differently: 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.
As long as all product classes implement a common interface, you can pass their objects to the client code without breaking it.
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 products as abstract Transport
. 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.
what applications in which we might need factory?
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 framework recognize that your subclass should be used instead of a standard component?
The solution is to reduce the code that constructs components across the framework into a single factory method and let anyone override this method in addition to extending the component 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.
Let’s think about what has to be done to reuse an existing object:
First, you need to create some storage to keep track of all of the created objects.
When someone requests an object, the program should look for a free object inside that pool.
… and then return it to the client code.
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.
what do you know about builder DP? what’s its structure? what’s usage of Director class?
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.
Structure
The Builder interface declares product construction steps that are common to all types of builders.
Concrete Builders provide different implementations of the construction steps. Concrete builders may produce products that don’t follow the common interface.
Products are resulting objects. Products constructed by different builders don’t have to belong to the same class hierarchy or interface.
The Director class defines the order in which to call construction steps, so you can create and reuse specific configurations of products.
The Client must associate one of the builder objects with the director. Usually, it’s done just once, via parameters of the director’s constructor. Then the director uses that builder object for all further construction. However, there’s an alternative approach for when the client passes the builder object to the production method of the director. In this case, you can use a different builder each time you produce something with the director.
This example of the Builder pattern illustrates how you can reuse the same object construction code when building different types of products, such as cars, and create the corresponding manuals for them.
The example of step-by-step construction of cars and the user guides that fit those car models.
A car is a complex object that can be constructed in a hundred different ways. Instead of bloating the Car
class with a huge constructor, we extracted the car assembly code into a separate car builder class. This class has a set of methods for configuring various parts of a car.
If the client code needs to assemble a special, fine-tuned model of a car, it can work with the builder directly. On the other hand, the client can delegate the assembly to the director class, which knows how to use a builder to construct several of the most popular models of cars.
You might be shocked, but every car needs a manual (seriously, who reads them?). The manual describes every feature of the car, so the details in the manuals vary across the different models. That’s why it makes sense to reuse an existing construction process for both real cars and their respective manuals. Of course, building a manual isn’t the same as building a car, and that’s why we must provide another builder class that specializes in composing manuals. This class implements the same building methods as its car-building sibling, but instead of crafting car parts, it describes them. By passing these builders to the same director object, we can construct either a car or a manual.
The final part is fetching the resulting object. A metal car and a paper manual, although related, are still very different things. We can’t place a method for fetching results in the director without coupling the director to concrete product classes. Hence, we obtain the result of the construction from the builder which performed the job.
what applications we may need Builder DP in? what’s difference between it and abstract factory?
Use the Builder pattern to get rid of a “telescoping constructor”.
Say you have a constructor with ten optional parameters. Calling such a beast is very inconvenient; therefore, you overload the constructor and create several shorter versions with fewer parameters. These constructors still refer to the main one, passing some default values into any omitted parameters.
class Pizza {
Pizza(int size) { ... }
Pizza(int size, boolean cheese) { ... }
Pizza(int size, boolean cheese, boolean pepperoni) { ... }
// ...
Creating such a monster is only possible in languages that support method overloading, such as C# or Java.
The Builder pattern lets you build objects step by step, using only those steps that you really need. After implementing the pattern, you don’t have to cram dozens of parameters into your constructors anymore.
Use the Builder pattern when you want your code to be able to create different representations of some product (for example, stone and wooden houses).
The Builder pattern can be applied when construction of various representations of the product involves similar steps that differ only in the details.
The base builder interface defines all possible construction steps, and concrete builders implement these steps to construct particular representations of the product. Meanwhile, the director class guides the order of construction.
Use the Builder to construct Composite trees or other complex objects.
The Builder pattern lets you construct products step-by-step. You could defer execution of some steps without breaking the final product. You can even call steps recursively, which comes in handy when you need to build an object tree.
A builder doesn’t expose the unfinished product while running construction steps. This prevents the client code from fetching an incomplete result.
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.
what do you know about State Pattern? what problem does it solve? what’s its structure?
State is a behavioral design pattern that lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.
The State pattern is closely related to the concept of a Finite-State Machine .
Finite-State Machine.
The main idea is that, at any given moment, there’s a finite number of states which a program can be in. Within any unique state, the program behaves differently, and the program can be switched from one state to another instantaneously. However, depending on a current state, the program may or may not switch to certain other states. These switching rules, called transitions, are also finite and predetermined.
Structure
Context stores a reference to one of the concrete state objects and delegates to it all state-specific work. The context communicates with the state object via the state interface. The context exposes a setter for passing it a new state object.
The State interface declares the state-specific methods. These methods should make sense for all concrete states because you don’t want some of your states to have useless methods that will never be called.
Concrete States provide their own implementations for the state-specific methods. To avoid duplication of similar code across multiple states, you may provide intermediate abstract classes that encapsulate some common behavior.
State objects may store a backreference to the context object. Through this reference, the state can fetch any required info from the context object, as well as initiate state transitions.
Both context and concrete states can set the next state of the context and perform the actual state transition by replacing the state object linked to the context.
what’s use cases that make you think about using State Pattern? what’s it’s relationship with Strategy?
Applicability
Use the State pattern when you have an object that behaves differently depending on its current state, the number of states is enormous, and the state-specific code changes frequently.
The pattern suggests that you extract all state-specific code into a set of distinct classes. As a result, you can add new states or change existing ones independently of each other, reducing the maintenance cost.
Use the pattern when you have a class polluted with massive conditionals that alter how the class behaves according to the current values of the class’s fields.
The State pattern lets you extract branches of these conditionals into methods of corresponding state classes. While doing so, you can also clean temporary fields and helper methods involved in state-specific code out of your main class.
Use State when you have a lot of duplicate code across similar states and transitions of a condition-based state machine.
The State pattern lets you compose hierarchies of state classes and reduce duplication by extracting common code into abstract base classes.
State can be considered as an extension of Strategy. Both patterns are based on composition: they change the behavior of the context by delegating some work to helper objects. Strategy makes these objects completely independent and unaware of each other. However, State doesn’t restrict dependencies between concrete states, letting them alter the state of the context at will.