Week 8 Now that you have started developing or writing code for your system, it is once again important to take the big picture view in order to make sure that everything is going along as intended. This is software architecture. Software architecture is a way of organizing your code. How should different classes or modules be organized? It is actually similar to building the architecture of any product, so there is an efficient way in which an entire building or a house is constructed. There is efficiency of space, efficiency of functionality, and so on. Software architecture is quite similar. It defines the software elements or modules, the relations among them and the properties of both the modules and the relations. Now this might all sound very abstract. Let us take an example of two software systems: Google search engine and another, which is a compiler. If we consider these as software systems, what are some key differences between these two systems? So what is the difference between a Google Search and engine type of software system and a compiler Software System right? So let's look at Google search, so Google search is essentially a web-based system. So when I, as a user, type a search query, a request goes to one of the servers of Google and Google returns a response which returns Pages related to my query, and these types of systems are commonly known as client server systems where data transactions happen. In response to requests - and you might know that this is put on predominantly how the internet functions today right, so this client server system is a type of software architecture where there are systems which behave as clients and other systems which behave as servers. Now, let's look at a compiler right, so what does a compiler do? Compiler takes source code as input and then transforms it into what is known as assembly code and then again transforms this into an executable file which can be run by the underlying operating system. This type of software system is known as a pipe and filter type of system where data is passed from one component to the other component and it is transformed and filtered along the way. So if you have noticed this client server type of system, which is like a Google search engine, is different from a compiler system which is a pipe and filter type of system. So the interactions which the different components have with each other are different. So in a client-server model the client and server communicate with each other through requests and response, whereas in a pipe and filter type of system, data is passed from one component to the other. So these types of patterns of information between different components - they are known as software architecture styles. So let's look at some more examples to highlight this concept. Another type of software architecture is known as the model view controller or the MVC type of architecture. So in this style, the views of the data are separated from the manipulations of the data. So let's look at this MVC architecture. So m stands for model, which is usually the component Which models the data required for the service. So, for example, in the case of the Amazon Seller portal, the model is nothing but the classes or the objects which model the tables in the database. Now the view component is nothing but the GUI objects, the presentation, layer or the visual representation of the model. For example, how information is seen on a particular website, so the view component, it retrieves data from the model and displays it to the user, and it also passes requests back and forth between the user and the model. Now, the controller component coordinates multiple views on the screen and helps users manipulate the model. So, for example, you can have a filter for catalog items which is based on name in the case of the Amazon Seller portal right. So, in this case, the controller sends this message to the model which receives the user input and sends the appropriate messages to its underlying views. Another type of software architecture is known as peer-to-peer or P2P, so P2P architecture is nothing but a distributed application with different systems. They form nodes and they share resources with each other. So, unlike a client server model or the MVC type of architecture, there does not exist a centralized system which monitors all the transactions of the system so nodes in the network. They make a portion of their resources, such as processing power, the disk storage directly available to all the other nodes in the network, without the need for Central coordination by servers or stable hosts. Now let us examine the common aspects in the different architectural styles that we saw earlier. What is similar, one important aspect of these tiles is the notion of a component A software is divided into components. Component is nothing but some well defined functionality or behavior, separate from other functionality and behavior, for example, in a client server system, the client software is a component and the server software is another component. Your browser, which sends a request to a search engine, is a component and the server which responds to the request is a component. Now that we have seen what components are, another important unit of the architecture is connectors, components cannot function in isolation, they have to be able to talk to each other. These are the connectors, so characters are code that transmit information between the components they are responsible for. Regulating the interaction among the components, now there needs to be a standard way in which components can interact with each other. These are called protocols. A protocol is nothing but a set of predefined rules which describe how the component should interact with each other. For example, it may be that, let us say there are, there is the client component and the server component. Now both the client component and the server component need to understand and agree upon the way in which the message will be transmitted between them right. It cannot be that the client sends the message you know in one format, while the server is attempting to process the same message in a different format, so the protocol or connector is the understanding between these two components. The most common types of connectors are function calls and rest API calls. You have specific rules to create endpoints which help you communicate with other components. So how do we come up with the components of a software system in the past, experienced designers, consciously reused, solutions that have worked in the past? So there would be libraries of various modules and many of these Solutions later became standardized and are commonly known as design patterns. So there are several good books on design patterns. For example, there is one element of reusable object oriented software. Then there are design patterns and so on. The key idea is a design pattern is a description of communicating objects and classes that are customized to solve a general design problem, so we will be looking at some design patterns. Even if we have knowledge of good design patterns, often even with good intentions, one might not use a pattern correctly or new code might get added along the way. This is actually known as an anti-pattern, so this can actually lead to a lot of problems in the code. So now what are warning signs that you may be headed towards an anti-pattern? Interestingly enough, this is called a design smell, so a key way to avoid such design smells is to follow effective guidelines of software architecture. This is known as the solid guidelines okay, which we will discuss. Solid, is basically an acronym. Ok, s stands for single responsibility, o stands for open closed and so on. So we will see all these details as we go along. So when you see that there are design smells in your architecture, moving back towards a good design process is known as refactoring refactoring, a design involves moving code between classes, creating new classes and modules or removing classes that are not required. Solid principles are object oriented principles for Effective software design, so these principles were conceptualized by Robert Martin, popularly known as Uncle Bob, and he is a very prominent figure in software engineering. He is one of the founders of the agile Manifesto of the Agile development process and using these principles we can make our code easy to understand and modular, and these principles support the concepts of good cohesion and loose coupling as well. So, let's look at these principles in detail now solid is an acronym. S stands for the single responsibility principle. O stands for open, close principle. L stands for liskov substitution principle. I stands for interface, segregation and D for dependency inversion. Now, let's look at each of these principles in detail now. The first principle is the single responsibility principle. Now, as the name suggests, every class should have a single responsibility or purpose right, so every class in your Software System should have only one responsibility in the software system. So let's take an example from the seller portal itself. Let's have a look at this class diagram, which contains only a single class, so the seller class contains information about the seller, the seller ID the name, the address and about the product, and it has functionalities related to adding a seller, deleting a seller, adding products, as well as deleting some items from the inventory, adding items to the inventory and viewing an inventory. So, as you would have noticed this design, it violates the single responsibility principle right. The reason being you have functions and attributes related to the seller related to the product and related to the inventory right. So, let's see how we can refactor this code, so refactoring means how can we change this code so that it follows the single responsibility principle, so what we can do is split that big seller class into three different classes right. The seller class contains details only about the seller, and appropriate functionalities related only to the seller like add seller and delete seller. Then we have a product class which has details regarding the product and the inventory class takes the seller ID as an attribute and different products get added to the seller inventory. So in this way each class has a single responsibility or purpose. Now that we have seen the single responsibility principle, let's look at the next principle, which is the open, close principle. So, according to the open, close principles, software entities should be open for extension, but it should be closed for modification. So what does this mean? This means that whenever we write code, we should make sure that we are only extending a class's behavior, for example, by creating subclasses Etc without modifying the existing glasses. So, let's take an example, so let's say we have a product class right which has product attributes like the ID name type and cause. Now, let's say I want to add a new product type, for example: clothing. So a clothing product, like a shirt, will have additional parameters like the size, the color, Etc. Right now, one way to add a new product type like clothing is to make changes in the existing product class right. So I can add size and color attributes and I can add a new member function called add product which takes the size and color as well. Now this design actually violates the open, close principle. So in this case, we are modifying the product class by adding attributes to an existing class. So now, let's see how we can implement the open, close principle for this design right. So what we can do is create another class called clothing which inherits properties of the product plus, so clothing is a subclass of product, and attributes specific to the clothing class can be defined in the clothing class, so the clothing class inherits these attributes Like product ID name, type cost as well as it has specialized attributes like size and color. So in this way we see that our product class is untouched, but we are extending the class by adding a new class called clothing. In this way, our design satisfies the open - closed principle. Now that we have seen what the open close principle is, let's look at the next principle, which is the Liskov substitution principle. So the liskov substitution principle generally works and is applied to inheritance hierarchies where you have a base class and you have derived classes which extend the functionality of the base class. Now the liskov substitution principle states that the derived classes should be substitutable by their Base Class. So what this means is that, let's say we have a derived class D and D is a subtype of a base class, let's say B, and what liskov substitution principle says is that we should be able to replace methods in the base class with the methods In the derived class, without interrupting the behavior of the program, so this might seem a little abstract. So, let's take an example, so let's say we want to implement a feature where we want to notify sellers about feedback regarding their product and we want to notify sellers through email. So here is one possible design which implements this feature, so we have a product class and we have an abstract class known as feedback notification, which has an abstract method called notify right and the email notification class, extends the feedback notification class and the notify method here actually implements the feature of notifying sellers about the feedback through email. Now, let's say I want to extend this feature, and now I want to notify sellers through email as well as SMS. So what we can do is create another class known as SMS notification, which extends feedback notification right and we can implement the logic of how sellers will be notified through SMS in the notify method. However, there is a slight issue here right, so if you've noticed the third parameter is email and here instead of email, we need to have a mobile number field right, since we are going to send an SMS to a particular mobile number and not an email. So here this design is actually violating the liskov substitution principle, because the method in the base class cannot be substituted by this method in the derived class right. What changes should we do in this design so that we can follow the liskov substitution principle, so what we can do, as we notice that this email field was causing issues right, so we can remove that field from the abstract class and we can create derived Classes and add the necessary parameters there. So, for example, we'll have an email notification class which has an additional parameter: email, we'll have an SMS notification class which will have an additional parameter: Mobile number. Both of these classes extend the feedback notification class and, if you look at the method, both can actually implement the abstract method from the base class. So in this case, you can see that the methods in the derived class can be substituted by the methods in the base class. In this case, and so this design follows the liskov substitution principle. We are going to look at I, which is the interface segregation principle. So first, what is an interface right? So an interface is nothing but a group of related methods right with empty bodies. So we do not write the definition of what these methods do. We just specify what the interface should do, what the methods are, but not how we are going to implement these methods. Other classes can implement this interface by implementing all the methods in that interface. So what is the interface segregation principle state? It states that we should not force any client to implement an interface which is irrelevant to them. So what does this mean? This means that if we have an interface, we should make sure that all the methods in that interface can be implemented by a client. So if there are some methods which are not relevant, then they should either be removed or they should be placed in another interface. It should be segregated such that an interface performs their actions specific to that interface. Let's take an example, so let's say we have a feature where we want to process the delivery of an order. So what does this mean? So let's say we need to process the order. We need to notify the seller to pack the order and send it for delivery. So this is a class diagram right which implements this feature, so we have a seller class and a product class and the delivery processing here is an interface right which has methods like place order. You notify the seller, you package the order and you deliver it. Now Clients can implement this delivery processing interface and write definitions for each of these methods right to implement this feature of processing the delivery. However, there is one issue with this interface right: let's say we have online orders or we have orders in which you have a digital product, for example an e-book right such orders. You need not actually pack them right and you need not deliver them to a specific physical address. So if there is a client who wants to implement a delivery of an e-product right, these two methods are irrelevant to them right. Well, they can, of course, implement the delivery processing interface, but not have any meaningful implementations for package order and delivery. So, as this principle suggests, it is necessary to segregate or split this interface, the delivery processing interface right. So what we can do is create two interfaces. One is for a physical delivery and the other is for an online delivery right and the physical delivery Processing interface Remains the Same as the previous one right, where you have placed an order by notifying the seller. You package the order and you deliver it to a specific address, whereas an online delivery processing interface can have, for example, the place order and the notify seller can be the same right, but you do not require a package order or a deliver order method. So now, if a client wants to implement an online delivery order, they just have to implement the online delivery processing interface and if some other client wants to implement physical delivery, then just need to implement the physical delivery processing interface. So the key idea to note here is that it is necessary that we segregate or split our interfaces such that each interface performs a particular function right. So we should not force a client to implement any of our methods which are irrelevant to them in an interface. Now, let's look at the final principle, which is the dependency inversion principle, so this principle states that a client or when we write code we should always prefer abstractions or interfaces over specific implementations. We should always prefer abstractions over implementations. So what does this mean right? So, let's take an example, so let's say we want to implement a feature where a seller should receive payment of their products sold through the seller portal right so to implement this feature, I of course need a seller class right with seller attributes and some Seller methods for adding and deleting a seller and bank account is one of the attributes of the seller right and a bank account can have Account Details like the number, the branch, the balance and all of that right. So now let's say I want to extend this feature and now let's say I want to receive payment via other mediums like paytm, Google, pay or UPI Etc right, so I will Implement classes for each of these payment methods and now I want to add the payment method for this seller as let's say, UPI right, but now the issue is so then I will have to create - or I might have to add another attribute for UPI here right because currently seller is Tightly coupled with the bank account class now. Similarly, if I want to have the seller associate with other payment methods, like Google pay, paytm UPI, I need to couple the seller with these classes or these objects as well right and this is an issue right - and this is making my code unnecessarily complex. This can be fixed by using an interface. So what are design patterns ? Design patterns are nothing but descriptions of how objects and classes should communicate with each other to solve a general design problem in a particular context. Right so, as we saw earlier, design patterns have evolved over many years and several experienced Developers realize that these patterns appear again and again in code and hence these patterns have been generalized and these descriptions are known as design patterns. So what are elements of a design pattern? So first we need to identify the problem for which a particular design pattern can be applied to. So we need to know when to apply the design pattern and in what context right. So that is the first thing we need to know. Then we apply the design pattern that forms part of our solution and this solution describes the elements that make up the design. So what are the relationships between classes or between objects? How do they collaborate with each other, so these are different aspects of the solution and this solution serves as a template right which can be applied in different situations. And, finally, when we apply a design pattern, it has certain consequences right. We need to understand what the trade-offs are, what the costs are, what the benefits are and whether we actually need to apply the design pattern or not in a particular context, now that we know what the elements of a design pattern are let's look at certain types of design patterns, so there are three types of design patterns. One is known as a creational design pattern. It is used during the process of object creation. So whenever we want to create objects in certain situations, certain creational design patterns can be used, and this will give us more flexibility as well as help us reuse existing code. We will look at examples of creational design patterns. Another type of design pattern is known as the structural design pattern, so structural design patterns, it explains how we can assemble objects and classes into larger structures again with the aim of making our code more flexible and efficient, and the third type of design patterns are known as behavioral design patterns, these patterns ensure that there is effective communication between objects and classes, as well as it ensures that the responsibility between objects are distributed properly right. We will be looking at examples of each of these types of design patterns. we'll be looking at two types of creational design patterns, the factory design pattern and the Builder design pattern. So, let's look at the factory design pattern first, so let's take an example of the seller portal itself and let's say I want to implement a functionality where I need to deliver a package using a particular Courier Service. So I need to deliver a package using a particular Courier Service, so looking at this functionality, you realize that you will need two objects or two classes. One is the delivery which delivers the package right using a particular Courier Service. So Courier forms another class and we see that there's an association between delivery and Courier, so the delivery class has a method called deliver which takes Courier as an argument. So what changes will you make to include the functionality of adding, let's say, International couriers? We will create separate classes which extend The Courier class right so we'll create a class called National Courier, we'll create another class called International Courier right, which corresponds to sending something nationally and internationally. And in addition, we will also create classes which extend the delivery class, one which corresponds to National delivery, another which corresponds to international delivery, and in this case we have an additional method called create Courier, which returns a courier object right. So the delivery class has a Creator method which creates or which returns a courier object, and so the national delivery class will return a national Courier. The create courier method will return a national Courier object and similarly, the international delivery class has a method called create Courier, which returns an international Courier object right. So the responsibility of creating objects has now been dedicated to these classes, the national delivery and the international delivery, and to a specific method known as the factory method. So in this case, the create Courier method is known as a factory method which actually creates The Courier objects. So what is the advantage of using this Factory design method? Let's say I want to extend my Courier Services to let's say local couriers right, so all that I have to do is create the appropriate classes and call the factory method, and it will create The Courier Service for me. so in this way it makes the code more understandable and more extendable. Now that we have seen what the factory design pattern is, let's look at some of the pros and cons. So what are the pros? What are the advantages of using this design pattern? One is that this helps us follow the single responsibility principle so recall that single responsibility means that each class has a single functionality or responsibility to perform. And second, it follows the open, close principle which states that classes should be open to extension but closed for modification. Now, what are some cons of using this design pattern, so you might have observed that the code can become complicated right. There can be a lot of new subclasses which are added, and this can make the code a little more difficult to understand. So, hence we need to decide or make a trade-off as to whether we need to apply this Factory design pattern or not. Let's look at another creational design pattern known as the Builder design pattern. So let's take an example. So let's say I have a book class and I need to create a book object with some mandatory parameters and some other optional parameters. So in this case let's say that ISBN title and cost are mandatory, whereas author published and description are optional fields right. So, in order to create such book objects, I need to call different types of Constructors right. So let's see how we can use the Builder design pattern to solve this problem. So the Builder design pattern tells us to extract the object construction code out of its own class right. So we separate out the object construction code and we move it to a separate object, known as a builder object right. So if we take the example of the book class, we create another class called Builder which has the same Fields as the book class right and in addition to that, it has methods like author. So this method is the method which initializes the author field. The published method is nothing but the method which initializes the published field and the same for description. So for each of the optional parameters. We have additional methods right which initialize those fields and we have a build method which finally builds the object and returns a book. So what are the pros and cons of the Builder design pattern? So the pros are one: it helps us construct objects step by step right, so we saw that we can construct this book object by initializing each parameter right so as opposed to calling lengthy Constructors right, and the other Advantage is that it follows the single responsibility principle Of separating the task of object, Creation with other responsibilities right, so the Builder class takes care of building the object, and the actual object now can focus on other aspects required of that class. Now what are some drawbacks or disadvantages of this design pattern? Well, the key disadvantage is that the code may become more complicated right. So, as we saw, we need to create another Builder class with the same parameters right and additional functions as well. And if you feel that in your code you do not have many mandatory and optional parameters, then it might be okay to not use this design pattern. The first structural design pattern we are going to look at is the facade design pattern. So let's say you have to create several objects and perform several steps. So let's say you are to initialize many objects and for a particular task. You need to execute methods in the correct order. Give the right parameters Etc. Right or you need to have used an existing complicated Library function right and the clients or others who are going to reuse your code will face an issue all right. They will have to know details about which all are the different objects to be created. What are the necessary Library, functions, Etc, right, and this makes it difficult for the client to use your code. So, let's take an example from the seller portal itself: let's say after a buyer places an order. The system should do the following task: the system should create an order order. The system should notify the appropriate seller, prepare the packaging and send the item out for delivery. Right so here we see that there are several steps which need to be performed. So this problem can be solved using the facade design pattern, so facade design pattern. It provides a simple interface to a library or a complex set of classes. So, for example, let's say there are several functions and classes which need to be called right for a particular operation. So in the facade design pattern you create a class which has a method which calls all the respective or required functions and places them in a single method. So in this case, do something calls all the appropriate methods in each of the individual classes and now clients do not have to remember each of the individual function calls, but we'll just have to call do something with the necessary parameters. So what are the pros and cons of using the facade design pattern? So one advantage is, as we saw, that it helps us isolate the complexity of the code or the different libraries that we have and expose clients to just a single method or function. The disadvantage of using the facade design pattern is that the facade is tightly coupled to other objects and because of this maintenance becomes more difficult. So, for example, in this case, we see that the delivery facade is coupled to the order, the seller, the package and the delivery classes right and if any change happens in any of these objects or these functions, then we need to make appropriate changes in the delivery Facade class as well. Now, let's look at the next design pattern, which is the adapter design pattern. So, let's look at the problem: let's take the example of the Amazon Seller portal itself and let's say some products have cost in dollars or euros and hence conversion to rupees is needed. Let's say we have a product class and we have a library or a class called a conversion calculator. So the conversion calculator is an existing library for which we do not have a controller right. So this product class cannot directly access the conversion calculator class or it has an incompatible interface to that of the product class. Right so, let's say a conversion calculator is an interface which is not compatible with products. So product cannot directly access the conversion calculator due to various reasons. So in such cases, the adapter design pattern comes in handy right. So what is the adapter design pattern so between so let's say, the client wants to call method B right and the client cannot directly call method B. So what we can do is create an adapter class right and what the adapter class does is It provides a wrapper or it provides a link between the client and the adapter. So what the client will do is call the adapters method and the adapter in turn will call the adaptees method right and these methods might be incompatible to each other. But that issue of incompatibility is taken care of by the adapter right, so the adapter wraps one of the objects to hide the complexity of what is happening behind the scenes right and the client need not be aware of. You know what are the complexities involved? The adapter takes care of that now. So let's look at some of the pros and cons of using the adapter design pattern. So one advantage is that we are following the single responsibility principle right. So, in our case, we were, we separated the code to convert from dollars to rupees or Euros to rupees from the actual business logic of the product class, which is to add products edit delete products right in that way, we are following the single responsibility principle, the Other Advantage is that if we want to add new types of adapters - let's say for other currencies, then this can be done easily, and this follows the open, close principle where we say that a class should be opened for extension. So we can extend the functionality of these classes by adding new types of adapters. The disadvantage is that the complexity of code can increase. So let's say we are adding an adapter, so we have to add a new class and add appropriate methods, and if we were able to make changes in the product class or maybe get access to the conversion calculator, then we need not create a separate class. So, let's look at one behavioral design pattern known as the iterator design pattern. So what is the problem which this design pattern is trying to solve? So we use different types of collections right in our algorithms. When we store data, it can be lists, it can be queues, stacks and trees, and a common operation which we have to do is to access as well as iterate, through various elements in this collection. So it can, for example, it can be a list of products right which can be stored, maybe as a list it can be a list of orders. It can be a list of deliveries right. So all of these are Collections and a common set of operations are accessing them as well as iterating through these various elements. Now, as a client, I am concerned only with accessing these elements right, I am not really interested in how these elements are stored, or what is the underlying data structure which stores these collections, so the iterator design pattern helps us to separate the behavior of how The elements are accessed into a separate object called an iterator, so you all must be familiar with the iterator interface in Java right and the iterator interface in Java has two key functions. One is has next, which returns true If the iteration has more elements and next, which Returns the next element in the iteration Now, let's look at some pros and cons of using this design pattern. So what are some advantages? One is that it follows the single responsibility principle, where we separate the axis of elements from other functionalities. So even in our example, we saw that the accessing the elements has been delegated to a separate class known as the iterator right. Another Advantage is that it helps us implement the open, close principle. So let's say I want to implement a new type of iterator right, so I can create or extend or implement the iterator interface in Java or create my own iterator right, and this will help me. I trade through different or new types of collections. What is the disadvantage? So one disadvantage is that let's say we have simple collections like maybe an array right. It might be an overkill for you to create a separate iterator class for simple applications and which have simple collections. Another type of behavioral design pattern is the Observer design pattern. So let's say you want to implement a feature where you want to notify a particular set of buyers when a new product is launched right and most of you are familiar with such notifications, so you might be getting such notifications in several of the apps, which you Use right so, let's say you have subscribed to a channel on YouTube and the channel posts, a new video right, and if you have subscribed to Notifications, you would get a notification that this particular channel has uploaded a new video right. So as a subscriber you get a notification when there are updates right, and I do not want to send this notification to all users of my system, but only to those who have subscribed to get notifications right. So how do I solve this problem? So this problem can be solved using the Observer design pattern. So what happens? Is that the subject object? It maintains a list of Observers and as and when there are updates it notifies The subject object & that notifies All The Observers for any updates or changes right. So let's look at the class diagram for The Observer design pattern, so we have a subject class right, which maintains a collection of Observers. The subject class can register Observers and we see that the Observer is an interface right which has a function called update and there can be other classes which implement this interface right and implement the update method. And now the subject has a method called notify. Observers which simply iterates over the collection and calls the appropriate update function. Now, let's look at another design pattern known as the strategy design pattern. So let's say you want to implement a feature where you want to process shopping orders based on different strategies. For example, one strategy is first in first out where the orders which come first, they are processed first. Another type of strategy can be based on priority right or based on type of user. So, let's say Prime users or super users receive a higher priority and hence their orders are processed first, whereas normal users orders are processed later right. So to solve this problem, you can use the strategy design pattern. So in the strategy design pattern you extract different strategies or algorithms into separate classes. So, for example, you can have a priority class right and you can have another class, which is the first and first out class right and the original class, which is known as the context it delegates this work of implementing this algorithm to the strategy object right. So in this case, you can have two strategies like strategy A and strategy B which implement the interface strategy and the algorithm is present in the execute method right so the original class delegates the work of implementing the strategy or the algorithm to the strategy. Interface. Now, let's look at some of the pros and cons of this strategy. So what are some pros? So, as we saw the strategy design pattern, it isolates the implementation details of the algorithm right. So each class will have a logic for a specific strategy and we also saw that it helps implement the open, close principle right. We can introduce new strategies without changing the existing classes or methods, and what is the disadvantage when, let's say our application does not require many algorithms or strategies we just have one or two strategies right then we need not actually implement the strategy design pattern.