In this blog, I would like to introduce the concepts that I believe are the most powerful in programming languages, and mastering them will give you solid programming thinking. I will explain them one by one with examples in Java and Python. After introducing the concepts of object-oriented, recursion, concurrency, functional programming, and data structure in earlier articles, I will elucidate the concept of software design patterns in this article.
1. Object-oriented
2. Recursion
3. Concurrency
4. Functional Programming and Lambda Expressions
5. Data Structure
6. Software Design Patterns
7. Annotations/Decorators
8. Machine Learning
1. Software Design Patterns
A design pattern is a reusable, universal solution to a common problem that occurs during software design and development. It is like a pre-made blueprint or template that you can accommodate to solve repeating design problems in software code. A simple example of a design pattern is the ‘Undo’ operation in text editors.
Design patterns provide best practices for structuring code and organizing classes. Using design patterns allows developers to work at a high level because they work at the abstract block level and do not have to worry about details that are already clearly defined when the design pattern is created.
When a team member says that we will use MVC (Model-View-Controller) in this project, all team members will understand what it is and the team will save a lot of time to explain it. In short, design patterns contribute to code reuse, modularity, flexibility, and maintainability.
But design patterns are not one-size-fits-all solutions. So don’t force yourself to use design patterns when you don’t need them. You must be sure that your problem and its application context require the use of a specific design pattern.
Classification of Design Patterns
We can classify design patterns based on their degree of complexity, level of abstraction, and scale of applicability of the software under design. If you take the scale of applicability as a categorization factor, you get two types: idioms and architectural patterns.
While idioms are low-level patterns to tackle implementation-specific situations in a particular programming language, architectural patterns are high-level general and independent of any programming language. All design patterns are classified according to their purpose. Three main patterns are distinguished.
Creational Patterns
You can use these patterns to create objects. That makes code easier to reuse and increases flexibility. Examples of these patterns are Singleton, Factory Method, and Builder.
- Singleton: This pattern ensures that a single class instance is created and makes it available to the rest of the application through a global point.
- Factory Method: This pattern aims to encapsulate the object creation logic. This pattern helps hide details and reuse code. New objects can be created using a common interface. However, the creation of instances occurs at the subclass level.
- Builder: This pattern helps you build complex objects in stages. A complex object is an object with many fields that must be initialized. The builder pattern can help you do it step-by-step.
Structural Patterns
You can use these patterns to compose objects into larger structures. As examples of these patterns, we can mention Composite, Facade, and Proxy.
- Composite: This pattern allows you to compose objects into a tree structure and then deal with the structure as if it were a single object. The tree structure is made up of objects descending to the common base type.
- Proxy: This pattern is a replacement of an object. The purpose of a proxy is to control access to the original object, for example for security reasons. You verify the sent request in the proxy before redirecting it to the original object to ensure that it is correct and secure.
- Facade: This pattern helps you create a simpler interface for accessing a complex collection of classes.
Behavioral Patterns
You can use these patterns to manage communication between objects and the flow in a system. An example of this pattern is Observer.
- Observer: This pattern allows objects (called observers) to subscribe to an object they are observing (called an observable) and be notified when an event occurs on that observable or its state changes.
- State: This pattern enables an object to change its behavior when its internal state changes.
- Memento: This pattern allows saving and restoring an object’s previous state, as well as hiding its implementation. The ‘Undo’ operation in a text editor exemplifies this pattern.
Architectural Patterns
Architectural patterns are general, high-level patterns that are independent of programming languages. They concern the design structure of systems.
- Model-View-Controller (MVC): This pattern is used in web-application and aims to separate the code logic into three interconnected elements. The model holds the data. The view represents the user interface and the controller is the connection between the model and the view.
- Model-View-ViewModel (MVVM): This pattern is similar to MVC but defined for GUI (Graphical User Interface). It promotes the separation of the GUI from the model and defines business logic for it.
- Layered Architecture: This pattern breaks down an application into sub-tasks. Each sub-task is implemented in a distinct layer of a specific level of abstraction. The layers interact with each other. The four common layers are the presentation layer, the application layer, the business logic layer, and the data access layer.
Concurrency Patterns
These patterns contribute to solving the common problems in the multi-threaded programming paradigm.
- Producer-Consumer: This pattern coordinates the exchange of data between the threads which produce data (producers) and those which consume them (consumers).
- Mutex: This pattern synchronizes access to a shared resource in a concurrent application. It allows only one thread to use that resource at a time.
- Read-Write Lock: This pattern is similar to mutex, except that it allows multiple readers or a single writer to access a shared resource.
2. Example in Java
Here we provide a simple example of a design pattern in Java, which is the singleton. This pattern ensures that only one instance is created. This is useful in cases where your application only needs a single object to act as a coordinator.
To implement this pattern in Java, we need to declare the constructor as private to prevent new instances from being created. But we need to define a method (‘getInstance’ in this case) to create an instance of the ‘Singleton’ class and return it.
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
System.out.println("Singleton created");
}
return instance;
}
}
We can call this instance anywhere inside the application and use it.
public class MainApp {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
}
}
Let’s check if the ‘Singleton’ class only creates one instance. To do this, we create two instances of this class and then compare them in the following code:
public class MainApp {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1.equals(singleton2));
}
}
The output of the execution of this code is:
Singleton created
true
That confirms that only one instance of the ‘Singleton’ class is created.
3. Example in Python
Python has the built-in syntax to implement different design patterns. We show in this section how Python implements the singleton pattern. For this, we define a class that we want to be singleton and override its ‘__new__’ method.
Remember that the static method ‘__new__’ is part of any class and is called each time to create a new object of the corresponding class. But, before that, we declare a new field which we bane ‘single_instance’ to hold the unique instance of the class.
The ‘__new__’ method has at least the class as parameter. Inside the code of ‘__new__’ method, we verify whether ‘single_instance’ is already defined or not. If it is not defined, we call ‘__new__’ method of the super-class to create an instance and store it in ‘single_instance’. At the end of the code, we return this instance accordingly.
class Singleton:
single_instance = None
def __new__(cls):
if cls.single_instance is None:
print("Creating instance")
cls.single_instance = super().__new__(cls)
return cls.single_instance
To make sure that this code only creates a single instance at the beginning and returns it every time you want to create an object of this class, you can add the following code:
s1 = Singleton()
s2 = Singleton()
print(s1 == s2)
In this code, we create two instances of the ‘Singleton’ class and indicate whether these two instances are equal or not. The result of running this code is:
Creating instance
True
The message ‘Creating instance’ is displayed only one time and the two instances are equal.
No comments:
Post a Comment