May 1, 2023
Design patterns and SOLID principles: A comprehensive overview
In the world of software development, writing code is not just about solving a problem at hand; it’s also about structuring the code in a way that makes it maintainable, scalable, and easy to work with. This is where design patterns and SOLID principles come into play.
Design Patterns
Design patterns are reusable solutions to common problems encountered in software design. They provide a template for solving certain issues and can speed up the development process by providing tested, proven development paradigms. Let’s dive into some of the most prominent design patterns and provide examples in C and Python.
Singleton Pattern
The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance. This is useful when exactly one object is needed to coordinate actions across the system. Here’s an example in C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| #include <stdio.h>
typedef struct {
int data;
} Singleton;
Singleton* singleton_instance = NULL;
Singleton* get_singleton_instance() {
if (singleton_instance == NULL) {
singleton_instance = malloc(sizeof(Singleton));
singleton_instance->data = 10;
}
return singleton_instance;
}
int main() {
Singleton* s1 = get_singleton_instance();
printf("%d\n", s1->data); // Output: 10
Singleton* s2 = get_singleton_instance();
s2->data = 20;
printf("%d\n", s1->data); // Output: 20
return 0;
}
|
And here’s an example in Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.data = 10
return cls._instance
s1 = Singleton()
print(s1.data) # Output: 10
s2 = Singleton()
s2.data = 20
print(s1.data) # Output: 20
|
Factory Pattern
The Factory pattern is used to create an object based on certain conditions. This allows the creation of objects without specifying the exact class of object that will be created. Here’s an example in C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| #include <stdio.h>
typedef struct {
int data;
} Product;
typedef struct {
Product* (*create_product)();
} Factory;
Product* create_product() {
Product* product = malloc(sizeof(Product));
product->data = 100;
return product;
}
int main() {
Factory factory;
factory.create_product = create_product;
Product* product = factory.create_product();
printf("%d\n", product->data); // Output: 100
return 0;
}
|
And here’s an example in Python:
1
2
3
4
5
6
7
8
9
10
11
| class Product:
def __init__(self):
self.data = 100
class Factory:
def create_product(self):
return Product()
factory = Factory()
product = factory.create_product()
print(product.data) # Output: 100
|
These are just a couple of examples of design patterns, and there are many more out there, each serving a different purpose.
SOLID Principles
The SOLID principles are a set of five design principles to make software designs more understandable, flexible, and maintainable. Let’s go through each principle and provide examples in C and Python.
Single Responsibility Principle (SRP)
The Single Responsibility Principle states that a class should have only one reason to change. It advocates for separating a class into distinct parts where each part addresses a separate concern. Here’s an example in C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| #include <stdio.h>
typedef struct {
int data;
} DataStorage;
void save_data(DataStorage* storage, int value) {
storage->data = value;
}
void print_data(DataStorage* storage) {
printf("%d\n", storage->data);
}
int main() {
DataStorage storage;
save_data(&storage, 100);
print_data(&storage); // Output: 100
return 0;
}
|
And here’s an example in Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
| class DataStorage:
def __init__(self):
self.data = None
def save_data(self, value):
self.data = value
def print_data(self):
print(self.data)
storage = DataStorage()
storage.save_data(100)
storage.print_data() # Output: 100
|
Open/Closed Principle (OCP)
The Open/Closed Principle states that a class should be open for extension but closed for modification. It encourages the use of abstraction and polymorphism to achieve this. Here’s an example in C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| #include <stdio.h>
typedef struct {
int data;
} Shape;
void draw(Shape* shape) {
// Drawing logic specific to each shape
}
int main() {
// Drawing logic using the draw function
return 0;
}
|
And here’s an example in Python:
1
2
3
4
5
| class Shape:
def draw(self):
pass # Drawing logic specific to each shape
# Drawing logic using the draw method
|
Liskov Substitution Principle (LSP)
The Liskov Substitution Principle states that objects of a superclass shall be replaceable with objects of its subclass without affecting the functionality of the program. Here’s an example in C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| #include <stdio.h>
typedef struct {
int width;
int height;
} Rectangle;
int calculate_area(Rectangle* rect) {
return rect->width * rect->height;
}
int main() {
Rectangle rect;
rect.width = 5;
rect.height = 10;
printf("%d\n", calculate_area(&rect)); // Output: 50
return 0;
}
|
And here’s an example in Python:
1
2
3
4
5
6
7
8
9
10
| class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
rect = Rectangle(5, 10)
print(rect.calculate_area()) # Output: 50
|
Interface Segregation Principle (ISP)
The Interface Segregation Principle states that a client should not be forced to implement interfaces they don’t use. It encourages the use of smaller, specific interfaces rather than one large, general interface. Here’s an example in C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| #include <stdio.h>
typedef struct {
int data;
} Printer;
void print(Document* doc) {
// Printing logic
}
int main() {
// Document creation and printing logic
return 0;
}
|
And here’s an example in Python:
1
2
3
4
5
| class Printer:
def print(self, doc):
pass # Printing logic
# Document creation and printing logic
|
By understanding and applying these design patterns and SOLID principles, developers can write better, more maintainable code and improve the overall quality of their software.
I hope this comprehensive overview has given you a good understanding of these concepts and how they can be implemented in both C and Python. Let me know in the comments if you have any questions or want to see more examples!
Go back to Posts.