Creational design patterns in C# made easy | Fiodar Sazanavets | Skillshare
Drawer
Search

Playback Speed


  • 0.5x
  • 1x (Normal)
  • 1.25x
  • 1.5x
  • 2x

Creational design patterns in C# made easy

teacher avatar Fiodar Sazanavets, Microsoft MVP | senior software engineer

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Lessons in This Class

    • 1.

      The author and his mission

      1:37

    • 2.

      Introduction to design patterns

      3:15

    • 3.

      Problem 1: having to choose object implementation at runtime

      4:29

    • 4.

      Problem 2: cloning objects

      0:49

    • 5.

      Problem 3: maintaining many object instances

      1:52

    • 6.

      Problem 4: keeping a single object instance throughout the code

      0:54

    • 7.

      Factory Method overview

      0:45

    • 8.

      Factory Method example

      2:14

    • 9.

      Factory Method pros and cons

      1:27

    • 10.

      Abstract Factory overview

      0:45

    • 11.

      Abstract Factory example

      2:07

    • 12.

      Abstract Factory pros and cons

      0:55

    • 13.

      Builder overview

      1:05

    • 14.

      Builder example

      2:38

    • 15.

      Builder pros and cons

      1:04

    • 16.

      Prototype overview

      0:36

    • 17.

      Prototype example

      1:40

    • 18.

      Prototype pros and cons

      0:50

    • 19.

      Singleton overview

      0:37

    • 20.

      Singleton example

      1:46

    • 21.

      Singleton pros and cons

      1:21

  • --
  • Beginner level
  • Intermediate level
  • Advanced level
  • All levels

Community Generated

The level is determined by a majority opinion of students who have reviewed this class. The teacher's recommendation is shown until at least 5 student responses are collected.

66

Students

--

Projects

About This Class

Design patters are something that you will need to get familiar with as a programmer who works with object oriented languages. And this is primarily because they represent well-defined solutions to common software development problems. So, instead of thinking through all the details of your solution, you can simply check if any of the existing design patterns can be used. You won’t have to reinvent the wheel.

The main problem with design patterns is that they are not necessarily easy to learn. Many developers, especially the ones who don’t have a lot of software-building experience, struggle with them. But if you do struggle with them, it may prevent you from getting a programming job at a reputable organization. After all, recruiting managers often ask questions about design patterns. Otherwise, not knowing design patterns will make you less effective as a software developer, which will slow down your career progress.

The main reason why design patterns are so hard to learn is because of the way they are normally taught. Usually, if you pick pretty much any Classon design patterns or open pretty much any online article about them, it would provide a collection of design patterns that you would need to go through. You would then have to got through each of them, try your best to understand the principles behind it and only then try to figure out how to apply it in a real-life situation.

It's a tedious process that doesn't always bring about the right results. It's not uncommon for software developers to memorize just a handful of design patterns that they have been using in their own projects. The remaining ones have been forgotten as soon as they've been learned. And it's hard to figure out which design pattern applies in which situation if you only remember a handful of them.

This class, which focuses on creational design patterns, provides a different approach. It uses a methodology that makes it easy to learn design patterns. So, you no longer have to brute-force your way through them. The process of effective learning is not about memorization. It's about associations. You learn new things easily when you can clearly see how new facts related to your existing knowledge. And this is precisely the method that this class is built around.

You won't have to brute-force your way into design patterns. In fact, you won't even start with the design patterns. First, we will go through a list of common problems that software developers are required to solve. Those are the things that every software developer can associate with. Even if you haven't faced a particular type of a problem yet, you will still be able to easily understand its description. For each of these problems, we will go through the design patterns that can solve it. And for each one of them, you will go through its core principle and the description of how it can solve this type of a problem. Only then you will be invited to examine this particular design pattern in detail, so you can understand how to implement it in your own code.

This structure of the class also makes it valuable as a reference source. Even when you don't know or don't remember design patterns, looking them up becomes easy. What you need to find is a description of the type of a problem you are trying to solve. And then you will be able to follow it to find the actual design patterns that you can apply to solve it.

Meet Your Teacher

Teacher Profile Image

Fiodar Sazanavets

Microsoft MVP | senior software engineer

Teacher

I am a Microsoft MVP and lead/senior software engineer with over a decade of professional experience. I primarily specialize in .NET and Microsoft stack. I am enthusiastic about creating well-crafted software that fully meets business needs.

Throughout my career, I have successfully developed software of various types and various levels of complexity in multiple industries. This includes a passenger information management system for a railway, distributed smart clusters of IoT devices, e-commerce systems, financial transaction processing systems, and more. I have also successfully led and mentored teams of software developers.

I enjoy sharing my knowledge with the community. This motivates me to mentor aspiring developers and create educational content, which includes blog... See full profile

Level: Intermediate

Class Ratings

Expectations Met?
    Exceeded!
  • 0%
  • Yes
  • 0%
  • Somewhat
  • 0%
  • Not really
  • 0%

Why Join Skillshare?

Take award-winning Skillshare Original Classes

Each class has short lessons, hands-on projects

Your membership supports Skillshare teachers

Learn From Anywhere

Take classes on the go with the Skillshare app. Stream or download to watch on the plane, the subway, or wherever you learn best.

Transcripts

1. The author and his mission: My name is Susanna. I'm a senior software engineer. I'm also I'm also for multiple technical books, some coding courses, animals according coach. At 1 in my career, I had to learn design patterns and I find them very difficult to learn. So I gave up learning them. And that was until the point when I failed the technical interview for a company that I really wanted to work for because I didn't know some design patterns. So after this failure, I went back to learn them, how to brute force my way through them because there are so difficult to learn. Eventually I went there. I have to be familiar with them. Realize how important they are. We have indeed help me to solve many problems that are faced in my career. Design patterns have been designed afterload. So some common problems to provide standard solutions to them. So you don't have to reinvent the wheel. But the biggest challenge with that is that patterns has to do with the way how they are normally taught. Normally, if you go to any reserves that teaches design patterns, it will show you the structure of each of the design patterns. But that structure will not make much sense unless you know the context. Without the context being provided first. It will look like the code has been just over complicated for no reason. So to make things easier for any new developers who are just starting to learn design patterns. I have written two books on them. One from dotnet developers and another one for those who are using JavaScript. And now I have also published a course. 2. Introduction to design patterns: So why are design patterns so important for software developers to know? And why I've decided to create a course for them. They will make you an effective program because design patterns provide standard solutions to standard problems. They will help you to solve problems much quicker because you don't have to think about the solutions. Once you are familiar with design patterns and the problems that they are trying to solve, you will immediately identify which design pattern can solve a particular problem and just apply that. They will keep your code maintainable. Because design patterns have been structured in such a way that the principles of clean coal that followed from purely pragmatic perspective that will help you to enhance your career. A lot of companies ask about design patterns, doing technical interviews, or they provide you with the problems that you will only be able to solve properly if you apply design patterns. If you don't know design patterns, you won't be able to pass those technical interviews. Based on my own experience. A lot of reputable IT companies do this. But the problem with design patterns is that they are really hard to learn. But there are some reasons for that. One of the major reasons for this is that structure of the design patterns is often provided without sufficient contexts. Essentially, as a new developer who has never used design patterns before, you just have a look at some code which looks too complicated and you don't know what this code does. This is all because design patterns are not really intuitive in their structure. But this course aims to do things differently. It provides relatable contexts first, before it does deeper into design pattern structure. First, you get introduced to problems that you can easily relate to that you will often see as software developers in real life situations. Only then, after briefly describing what design patterns solve each problem, you will show an implementation examples of each of the patterns. And this makes design patterns easier to learn by associations. First of all, you can easily understand relatable problems when they're described to you. You can easily see how each design pattern can solve this particular problem. What is briefly described to you after the problem itself is described. And then when you know the basics, you are ready to expand your knowledge and learn the details of each design pattern. Also in this course, we don't overload you with information. All examples that are provided use vanilla implementation of dotnet. We don't use any external libraries in the examples until it's absolutely necessary to make a point. You aren't showing dozens of examples for each of the pattern. Each of the example is simple enough to understand, but complex enough to serve as an example of a design pattern implementation. For each of the design patterns, we will provide an overview. Then we'll have a look at some code examples of it. Then we'll examine its pros and cons. Broadly speaking, design patterns can be split into three categories. Creational, structural, and behavioral. Creational patterns consists of factory methods, abstract factory, build a prototype, and Singleton. 3. Problem 1: having to choose object implementation at runtime: Imagine that you have a code that uses a particular object type. The code that uses this object only needs to know the signatures of accessible methods and properties of the object. It doesn't care about the implementation details. Therefore, using an interface that represents an object rather than a concrete implementation of the actual object is desirable. If you know what concrete implementation your application will use in ahead of time. You can simply implied dependency inversion principle along with some simple dependency injection. In this case, you will know there's always a particular implementation of the interface that will be used throughout the working code. You will only replace it with mock objects for unit testing. But what if the concrete implementation of the interface needs to be chosen dynamically based on the runtime condition. What is the shape of the object needs to be defined based on input parameters in the actual place where the object is about to be used. In either of these cases, the dependency injection approach would work. Let's look at an example of this. Imagine that you are building an application that can play audio. The application should be able to work on both Windows and Linux. And inside of it, you have an interface called iPlayer that has standard methods. That's an audio player would have, such as play, pause and stop. It is the implementation of this interface that actually interacts with the operating system. The problem is that Windows and Linux have completely different audio architectures. Therefore, you can not just have a single concrete implementation of iPlayer interface that would work on both operating systems. You want to know ahead of time what operating system the application will run on. Here, the design patterns that you can use to solve this problem. Factory methods, abstract factor and build a factory method is a method that returns the type of object that implements a particular interface. This method belongs in a creative object that manages the life cycle of the object that is to be returned. Normally, you would have several variations of creates an object, each returning a specific implementation of the object that you want, you would instantiate a specific variation of the creator object based on a specific condition, will then call its factory method to obtain the implementation of the actual object. In our example above, you will have one version of the creator object that returns a Windows implementation of iPlayer, and another version that returns its Linux implementation. The creator object will also be responsible for initializing all dependencies that your iPlayer implementation needs. Some code blocks. Your application will check which operating system that runs on. And we'll initialize the corresponding version of the creator object. Abstract factory is designed parchment that uses multiple factory methods inside of the creator object. So you can create a whole family of related objects based on a particular condition. Built. A design pattern is similar to factoring method, but instead of just returning a concrete implementation of an object all at once, it builds the object step-by-step. You will choose the factory methods design pattern for the following reasons. Good users single responsibility principle, as the creator object is solely responsible for creating only one specific implementation type of the object and nothing else. The pattern has been prescribed in such a way that it makes it easy to extend the functionality of the output object. I'm not violate open, closed principle. It's easy to write unit tests as creational logic will be separate from the conditional logic. Exactly same principles apply to abstract factory with only one or two additional benefit that it's a family of related objects that you are creating a more just a single object. And here's why you may want to choose builder instead. There's a good use of single responsibility principle as each building method has its own very specific role. And there's a single method on the director class. But each condition, once again, it's easy to write unit tests. This is facilitated by a single responsibility principle. But additional benefit of Builder is that there is no need to have a different version of a class if only some of its implementation details may change in different circumstances. Because builder builds up an object step-by-step. Because builder builds an object step-by-step, you may want to use the same version of the object and just modify some of its properties. 4. Problem 2: cloning objects: Imagine a situation where you need to be able to clone an object. You will need to make an exact copy of an object. And also you will need to clone all the private fields of the object. The design pattern that is capable of doing that is prophesied. Prototype is essentially the design pattern that allows objects to be clonal. While using the prototype. All of your calls to copy the complex objects will be located in one place inside of the object itself. You will also be able to copy private members of the objects. So the copy of the object will be an exact clone. Any code that needs to generate a copy of the object will only need to call the clone method without having to worry about the details of the cloning process. The cloning process will be encapsulated within the object itself. 5. Problem 3: maintaining many object instances: Imagine a situation where you need to create many instances of an object, but keep your code still running smoothly. So the requirement is that you need to create many objects, instances. But you also need to ensure that you don't run out of memory or CPU resources. You need to minimize object construction as much as possible. Because object constructors, especially if they contain complex logic, may give you performance penalty. Especially when you are creating many copies of such an object. You also need to make sure that the objects get disposed of properly. Because the last thing that you want is a memory leak or some objects that remain in your memory but become unusable. The suitable design patterns for it, a fly weight object pole and prototype object pool is not one of the classic design patterns. But I have included in here because it works to solve this problem if you use it with a prototype, which is one of the classic patterns. Here are the reasons why you would want to use object pool. Objects are being re-used so you will not get any performance penalty associated with instantiating new objects. Essentially will use an object pool. You instantiate some objects at the beginning. Then when the object is get out of scope, you don't dispose of them. You just keep them in the pool until they are re-used. Object pool will maintain its size as needed. So you will not end up with way more objects instances that you would ever expect to use. Flyways is another design pattern that can be used to solve this problem. Flywheel is all about sharing some memory between objects. You will be squeezing way more information into the memory, then you would have been able to do otherwise. And the main reason why you would combine prototype with Object Pole is that the objects in the pool are much easier to instantiate as they can now be cloned. 6. Problem 4: keeping a single object instance throughout the code: Imagine a situation where you need to use the same single instance of the object throughout the entire application. This instance needs to be shared between all the components of the application. You should try to avoid tracking the same variable throughout your code, because otherwise, your code will become unmanageable. The suitable design patterns to solve this problem is called singleton. Design pattern is all about instantiated. A private constructor on the class. That class can only be used as a single instance. You can only obtain a particular instance of it via static method. And this will allow you to use the same instance of an object in any part of your application without explicitly passing and instantiated object. Because it only has a private constructor which is accessible via static method. It will prevent you from creating more than one instance of a particular type. 7. Factory Method overview: Factoring method is a design pattern that allows you to conditional to create some objects. It initially consists of two components, Abstract Factory and abstract object. Factory is a class that produces the object. And both of them are structs because then you have concrete implementations of them. So you will have some conditioning the code based on that condition. You will instantiate a specific concrete factory whose job it is just to instantiate a specific type of concrete objects. Then you may have another implementation of it with another concrete factory type that instantiates its own type of concrete object. We will now have a look at the implementation of it in the code. 8. Factory Method example: A cross-platform application which is supposed to play audio regardless of which operating system is deployed on, is a perfect opportunity to show a factory method. So in this example, we have the following abstract class is called player. Its only purpose is to play audio based on some file name that we pass into it. We're going to use the interface as well. It doesn't really matter. I'll have player creator, which could also be called play a factor, creates and factory replaceable wars in from a factory method. So our actual factory method is create player. And all it does is creates an instance of player object. Both of these classes are abstract because we have OS specific implementations for each of them. Because different operating systems have different audio drivers and different ways the audio is played, we have two separate implementations. So here's our Linux implementation. Here's our Windows implementation of the player. Likewise, we have Linux play a creator. The only thing it does is return a concrete Linux instance of player. Have the same one for Windows. I pull that one, which returns a Windows specific instance. All we then do is set a factor and decide which one to implement. We do it by looking up the OS that we are on. If we happen to be on Windows, which was Windows player creator. And if we happen to be on Linux, we choose Linux Player creator. We don't currently support any other operating systems. And what happens afterwards is just whichever creates a reuse. We play audio by using the player that is produced by that particular creator. The class that implements the logic does not have to care about which specific implementation of player it pulls. The factory implementation will take care of that. 9. Factory Method pros and cons: We will now examine the pros and cons of using factoring method. One of his best benefits is that it enforces a single responsibility principle because your conditional logic will be separated from the implementation in the factory. Each implementation in the factory and in the object that it produces will have its own responsibility. This design pattern also allows us to easily maintain the code and write automated tests for it. Because at the end of the day, the conditional logic will be separate from the implementation. So each object will have its own small scope, which will be easy to write tests against. Finally, factoring method allows us to execute a particular condition on the ones. You can essentially have conditional logic. Then select implementation of the factory. Then once then competition has been selected, you can just keep reusing either the factor implementation or the target object itself. This is why factoring method is often used when dependencies have been registered for the application. Now let's have a look at some disadvantages of using factoring methods. The first character you have to remember is that it might make your code more complicated if only a single factory method is used. If you want to create an object conditionally, and it's only one object that you want to create. You might as well just create the object directly rather than using separate factory class. 10. Abstract Factory overview: Abstract factory and factoring method are almost synonymous. In fact, they are very related. Abstract Factory is the abstract class that has a factory method. On the factory method is the method that produces some target object. Only that abstract factory can consist of more than just one factory method. In this case, it produces a family of related objects rather than just a single object. Other than that, everything that applies to factoring method applies to abstract factory. A particular concrete factory implementation may produce a specific kinds of concrete objects. Another implementation will produce a different set of object implementations. Let's now have a look at the code example of it. 11. Abstract Factory example: An abstract factory object is nothing more than an abstract object that contains one or more factoring methods. To demonstrate this, we have expanded our previous solution. We have realized that our audio player requires Stop button as well as play button. So we have added the following abstract objects. One is resentful Play button, the other one is reserved for stop button. Our abstracts factor, play a creator. Now it looks like this. Instead of producing a single-player object, it produces two separate buttons. We have also added some utilities because playing audio and stopping audio or related functions. So we have a utility class though specifically for Linux, and have another utility class that is specific for Windows. We've done this because regardless of whether it plays audio or stops playing audio, it will be a command. The only difference between those commands will be that the exact command will be different depending on what action you want to do. So now we have a Linux implementation of play button. On Linux implementation of Stop button. Likewise, we have Windows implementation of Play button. Window supplementation of Stop button. I will Linux creates it looks like this. So as before, all we do is just return a family of related objects. So in this case, both of these buttons that will return a Linux specific. Likewise for Windows, we are returning the window specific implementations of these buttons. And our program entry point. It looks like this. Now, just like before we are looking at the operating system that we're running our application on. But this time we are extracting specific buttons rather than just using a single player object. 12. Abstract Factory pros and cons: All the benefits of factoring methods applied to abstract factory. It doesn't for single responsibility principle. It makes it easier to write automated tests for your code. And it allows you to execute a particular condition only once after which a concrete copy of the object is created. But on top of just factory method, it also produces a family of related object. So in this design pattern, having a factory class is actually helpful. And perhaps the only caveat of using abstract factory is that it's not very suitable for scenarios where an object is created step-by-step. Abstract factory is particularly suitable for situations where a family of related objects is created in one go. But the next design patent that we will have a look at, builder has been specifically intended for this situations where the creation of an object happens step-by-step. 13. Builder overview: Builder is a design pattern that is intended for building objects step-by-step. Typically, it will have a built-in interface or an abstract class. This interface will have a number of methods for adding different components to the object. At the end, there will be some method that we'll build the actual object. So the output of the builder will be target object. You can have multiple implementations of builder, or you can have a builder object that just builds conditionally objects step-by-step. In its classical implementation or the design pattern has a single build, the interface, and multiple builds implementations. Each implementation is conditionally chosen with a building's design pattern. You may or may not also use a director object. Director object is an object that accepts our particular concrete implementation of a building object. And then just execute all the methods on the builder in a particular order. And at the end, returns to the target object. What we'll now have a look at the building implementation in dotnet. 14. Builder example: To demonstrate, build a design pattern, we will use the same components as we used in our abstract factory. But this time we will build our player by using a builder rather than factory. The difference between the two, between abstract factory on the builder is that builder allows you to build objects step-by-step. While factor is designed to return a family of related objects instantly. Just like before, we have played button abstract class and stop button. The implementations of these buttons I exactly the same as we used in Abstract Factory. For each one of these, we have Linux implementation and Windows implementation. This time we also have this interface. I play a buildup. The interface has the following methods. At play button, stop button, and build player. Essentially, when we use builder, we do various things with it. We add various components to the object that we're trying to build. We build the actual object. The object that we are building is player. So we are using a single player that has two buttons. But which implementations of the buttons we are going to be useful is going to be determined by the exact implementation of each of our builders. We have Linux Player builder, Windows player builder. As you can see, both of these just add Linux and Windows specific buttons accordingly. And each one of this then just returns the player object. We also have this player director object, which will execute all the methods on the builder in the correct order. But it will depend on exactly implementation of the bill that would pass into it. What do you do? The design pattern? A director object is not strictly necessary, but they are helpful. So this is what the entry points to our program looks like. Now, we have a variable which will hold player. We decide which exact instance of ability to pass into build player method of the player director object based on the operating system that we're running our application on. After that, it's all the same as we have before. The exact builds. The implementation will build a specific version of the player with specific implementations of his buttons. And then we will just be able to play audio with it. 15. Builder pros and cons: So here are some major benefits of using design pattern. It allows you to build the object step-by-step. And it also allows you to implement single responsibility principle and make your components but a testable. Because see your entire code base will be split into small components. If direct object is applied. Building this design pattern also allows you to build different representations of the same object. But there are some caveats of using the builder as well. It will make your code more complicated. Therefore, if you are in a situation where your object needs to do it in one goal is much better to use abstract factor rather than builder. We have seen this in our code example. Even though the output of both Abstract Factory and builds the examples that we used were very similar to each other. Build a design pattern, how far more classes in it? And that's the reason why you should only use builder. If you are using a step-by-step building of objects. 16. Prototype overview: Prototype is a design pattern that allows you to create copies of an object easily. Typically, we will start with unclonable interface, which will have a method called either copy or clone or something similar. This interface will be implemented by a concrete object. And then if you call clone method on it, an exact clone of this object will be produced. Typically, a prototype will also copy all the private fields. So the object will be indeed a clone of the original object. And now we'll have a look at example of implements and prototype. 17. Prototype example: Prototype is a design pattern that allows us to make our objects clonal. One of the best ways of doing that is to define a global interface. It doesn't have to be this exactly, but it will have to have a method that returns the datatype of itself, which in our case is Claude. We have this cloner object that implements this interface. It doesn't matter what other interfaces the object implements. All that matters is that it has cloned interface and it returns an instance of itself. But it's gonna be a completely separate object. So all the fields will be passed, including the internal data. But otherwise, it will be a completely different objects reference. In C-Sharp in can be achieved by colon memberwise clone method. In other languages, you will have to do it the field by field manually. Here's our program class. Essentially we are creating a new instance of collectible object given the title. Checking this data. As you can see from here, our internal data is populated by some random number. Then we're cloning this object that we're checking, that this new object has the same values in all of its fields. And we can test whether it worked or not by running this application. As we can see, both of our objects have identical values in the properties. 18. Prototype pros and cons: So the obvious benefits of using prototype are as follows. It makes it easier to adhere to DRY principle because the only place in your entire code base where an object is being Corporate field by field is within the object itself. And also allows you to copy private fields of an object. The object that gets returned by the copy method is an exact clone of the original object. There are some caveats of prototype, but they are very minor. Perhaps the main caveat of using process side, and perhaps the only one is that why are we using it? You need to be aware of the difference between a shallow copy and a deep copy. So for deep copy, you might have to implement prototype in the objects that represent that the fields of your original object. 19. Singleton overview: Singleton is the design patterns that you may want to implement if you want a single instance of an object shared throughout your entire application. Typically it's implemented via a private constructor on the private static method. So essentially when the static method is called, the first time it's called, it triggers a private constructor on an object and it returns an instance of the object, what is called again, it just returns exactly the same instance of the object as it did before. And if you are using a singleton object, you cannot instantiate object in any other way than that. Let's now have a look at an example of singleton. 20. Singleton example: Singleton design pattern guarantees that a particular object will have the same instance whenever you access it throughout the application. In a classical version is achieved by having a private constructor and having a static instance on a class that you might have to turn into a singleton object. So here's our example. We have a private constructor. So you will not be able to instantiate this object from outside. Only from the inside. I'm the only way to instantiate it is via this static getInstance method. Getinstance method works with a private field which contains the datatype of itself, which is known by default. So when we call this matters for the first time. We are calling the constructor of singleton object. But if we already have instance populated, we just return the existing instance. To demonstrate how it works. We have this data property with the private sector, so we won't be able to set its value from outside. This value gets set to a random number. Whenever we create this first object. This is how we test it in our program dot CSS file. We are calling get instance once and we're checking the data property. For the second object we're calling get instance again. And once again, we are checking the data property. We can see if this is too much. Then we are comparing the objects. If it is indeed an instance of the same object, then this method equals should return true. I will launch our application to test whether it is indeed the case. And as we can see, it is the same object. 21. Singleton pros and cons: Let's now have a look at some pros and cons of using singleton. So the first benefit is the object is only instantiated once, the first time we retrieve it. And after that, it's just the same instance that is shared between components of your application. The second benefit is that it forces the use of the same instance of the object everywhere. You cannot instantiate this object in any other way. So nobody will be able to accidentally override this behavior. And the third important benefit is that it's easy to understand and sets up, but there's some kind of ease of use a singleton as well. The first caveat is that it's potentially violates a single responsibility principle. Because suddenly the responsibility of the object is to perform some action, but also to create an instance of itself. This makes the object a little bit less cohesive. The other caveat is that it doesn't work very well in the multi-threaded environment. If you are running multiple threads, you may end up with a single instance of an object per thread rather than per application. But in modern day applications, a singleton behavior can be mimicked by a dependency injection systems. This is why if you need to share a single instance of an object throughout the application, it's always better to use dependency injection rather than implement Singleton design pattern.