User Identity and Access Management – What’s the Deal with IDP?
What user identity is? Why managing access is essential for businesses? How an IDP (Identity Provider) works? You will find the answer to these questions in the article.
Hello! Today, as Innokrea, we’ll talk to you about what design patterns are and why every developer should have them in their virtual toolbox of development tools. If you’re interested, we invite you to read further, and if you haven’t yet checked out our previous articles on SOLID principles, we highly encourage you to do so, as they are closely related to the topic of design patterns.
Design patterns are tried-and-tested solutions to frequently occurring problems in programming. They mainly deal with what classes a developer should create to solve a particular problem (in other words, how to distribute responsibility between components) and how to connect them. By using design patterns, we ensure that our code adheres to the SOLID principles and is easy to extend with additional functionalities. Design patterns encourage developers to write code that follows SOLID principles. Without them, writing large systems becomes more difficult due to the increasing complexity of dependencies between components. In practice, this means that a single change in the code can affect components that it shouldn’t. Maintaining such code becomes very challenging, time-consuming, and costly, increasing the risk of introducing errors and creating technical debt.
Design patterns not only simplify software development but also improve collaboration between developers. If every team member knows patterns that can be applied to solve common problems, communication within the team and work on the project become much more efficient.
There are dozens of design patterns addressing various programming problems, but we generally distinguish three main categories:
There are also other types of design patterns, such as those related to architecture or multithreading programming.
Let’s try implementing one of the behavioral patterns – the Strategy pattern. It allows you to define a family of algorithms/strategies, encapsulate them, and make them interchangeable during the program’s execution. This pattern provides flexibility, enabling the client to choose an algorithm without changing the code. The example we’ll use in this article involves payment mechanisms for an online store. Imagine that our program is an application for an online shop that processes user payments, among other things. We must allow for the use of various payment mechanisms, and our solution should be extensible to include new payment providers without significant modifications to the code. This is where the Strategy design pattern comes to the rescue.
Figure 1 – Class Diagram for an Online Store Application
Our application has several classes responsible for simulating the operation of the store. Of course, this is not a complete and real system, but it is meant to show the context of applying the strategy pattern. We can see the Store class, which holds an IPaymentStrategy object, an interface with the pay method. Thanks to using an interface, the PaypalPayment and StripePayment classes can be used in place of IPaymentStrategy. A customer wanting to make a payment chooses a payment method, and the code in the Store class, through the setPaymentStrategy and executePayment methods, is able to make a payment using any implemented method. This allows us to easily change the payment method through abstraction. The Store class does not depend directly on any specific implementation, only on the interface.
Figure 2 – Sequence Diagram for the Implemented Use Case of the Strategy Pattern
The Java code implementing the above use case is presented below.
public interface IPaymentStrategy {
void pay(Double amount, String toAccountId);
}
public class PaypalPayment implements IPaymentStrategy{
@Override
public void pay(Double amount, String toAccountId) {
// Payment logic implementation
System.out.println("Paypal payment: "+amount+" paid to "+toAccountId);
}
}
public class StripePayment implements IPaymentStrategy{
@Override
public void pay(Double amount, String toAccountId) {
// Payment logic implementation
System.out.println("Stripe payment: "+amount+" paid to "+toAccountId);
}
}
public class Store {
// Online store class
Double cartValueAmount = 30.0;
String shopAccountId = "123321";
IPaymentStrategy paymentStrategy;
public Store(IPaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void setPaymentStrategy(IPaymentStrategy paymentStrategy){
this.paymentStrategy = paymentStrategy;
}
public void executePayment() {
System.out.println("Executing payment strategy with class: " + paymentStrategy.getClass().getName()); this.paymentStrategy.pay(this.cartValueAmount,this.shopAccountId);
System.out.println("Payment successful!\n");
}
}
public class Client {
public static void main(String[] args) {
System.out.print("Welcome to the shop! \n \n");
IPaymentStrategy paymentStrategy1 = new PaypalPayment();
IPaymentStrategy paymentStrategy2 = new StripePayment();
Store onlineStore = new Store(paymentStrategy1);
onlineStore.executePayment();
onlineStore.setPaymentStrategy(paymentStrategy2);
onlineStore.executePayment();
}
}
The second behavioral pattern we will implement is the Template Method pattern, used to define the skeleton of an algorithm in a base class while allowing subclasses to override specific steps without modifying the algorithm’s structure. Let’s continue with the example of the online store—this time in the context of order processing, where the process might look like this: select a product, check if it is a gift, wrap it if needed, and deliver.
Figure 3 – Template Method pattern in the context of the store
Imagine a scenario where the store needs to process orders in a certain way, which may look like this: select a product, check if it is marked as a gift, wrap it in gift paper if it is, and deliver it. It’s important to note that this process is quite abstract, and its specific implementation may vary depending on the context. For this purpose, we will use the Template Method pattern, which allows us to define an abstract algorithm and then apply its very specific implementation. We will do this using the abstract class OrderProcessTemplate and its two implementations, OnlineOrderProcess and StoreOrderProcess, which will define the methods for processing online orders and in-store pickup orders.
Figure 4 – Sequence diagram for the implementation of the Template Method pattern in the above example
We can see how calling the processOrder method triggers specific implementations of methods in the inheriting subclasses, namely OnlineOrderProcess and StoreOrderProcess. This way, we can create additional “processors” for other cases without modifying other classes. Let’s take a look at the Java code that implements these concepts.
public class StoreOrderProcess extends OrderProcessTemplate {
public StoreOrderProcess() {
super();
}
@Override
protected void selectProduct() {
System.out.println("Selecting product from the inventory.");
}
@Override
protected void deliver() {
System.out.println("Customer will pick up the product from the store.");
}
}
public abstract class OrderProcessTemplate {
public OrderProcessTemplate() {
}
public final void processOrder() {
selectProduct();
if (isGift()) {
wrapGift();
}
deliver();
}
protected abstract void selectProduct();
protected abstract void deliver();
protected boolean isGift() {
return false;
}
public class OnlineOrderProcess extends OrderProcessTemplate {
public OnlineOrderProcess() {
super();
}
@Override
protected void selectProduct() {
System.out.println("Selecting product from online catalog.");
}
@Override
protected void deliver() {
System.out.println("Delivering product to the customer's address.");
}
@Override
protected boolean isGift() {
return true;
}
}
private void wrapGift() {
System.out.println("Gift wrapping completed.");
}
}
public class StoreOrderProcess extends OrderProcessTemplate {
public StoreOrderProcess() {
super();
}
@Override
protected void selectProduct() {
System.out.println("Selecting product from the inventory.");
}
@Override
protected void deliver() {
System.out.println("Customer will pick up the product from the store.");
}
}
Let’s take note of the role of the abstract class, which provides implementations for the processOrder and isGift methods, while on the other hand, it defines two abstract methods, selectProduct and deliver, which must be overridden in the subclass.
If we combine the two previous examples to demonstrate the application of both patterns simultaneously, it might look like the diagram below.
Figure 5 – Class diagram for the combined Strategy and Template Method patterns
Figure 6 – Sequence diagram for the combined use case
If you’re curious about the code for the final solution, we’ve included the entire repository as an attachment.
Today, we’ve explained what design patterns are, their role, and why it’s worth using them. We’ve also presented a few implementations that will help you explore this topic further on your own. If you’re interested, feel free to check out the code or follow the links in the sources for more information. See you in the next post!
All the examples provided are primarily educational and do not fully represent real-world applications of these patterns, but they do help in understanding how they work and how they should be applied.
User Identity and Access Management – What’s the Deal with IDP?
What user identity is? Why managing access is essential for businesses? How an IDP (Identity Provider) works? You will find the answer to these questions in the article.
Security
Hey, hey... Programmer, this is another article for you! The second part of the article on design patterns. Get to know Adapter and Memento.
Programming
Blockchain – Payments in the World of Cryptocurrencies
Blockchain - discover the world of transactions, cryptocurrencies, and electronic payments.
FinancialSecurity