diff --git a/non_dsa/hld/NoSQL Contd..pdf b/non_dsa/hld/NoSQL Contd..pdf new file mode 100644 index 0000000..93ef059 Binary files /dev/null and b/non_dsa/hld/NoSQL Contd..pdf differ diff --git a/non_dsa/hld/NoSQL Continued.pdf b/non_dsa/hld/NoSQL Continued.pdf new file mode 100644 index 0000000..93ef059 Binary files /dev/null and b/non_dsa/hld/NoSQL Continued.pdf differ diff --git a/non_dsa/hld/System Design - CAP Theorem, PACELC Theorem & Master-Slave.pdf b/non_dsa/hld/System Design - CAP Theorem, PACELC Theorem & Master-Slave.pdf new file mode 100644 index 0000000..2114a4f Binary files /dev/null and b/non_dsa/hld/System Design - CAP Theorem, PACELC Theorem & Master-Slave.pdf differ diff --git a/non_dsa/hld/System Design - Caching 1.pdf b/non_dsa/hld/System Design - Caching 1.pdf new file mode 100644 index 0000000..b04a3e2 Binary files /dev/null and b/non_dsa/hld/System Design - Caching 1.pdf differ diff --git a/non_dsa/hld/System Design - Caching Contd..docx.pdf b/non_dsa/hld/System Design - Caching Contd..docx.pdf new file mode 100644 index 0000000..c673b8e Binary files /dev/null and b/non_dsa/hld/System Design - Caching Contd..docx.pdf differ diff --git a/non_dsa/hld/System Design - Caching Contd.pdf b/non_dsa/hld/System Design - Caching Contd.pdf new file mode 100644 index 0000000..c673b8e Binary files /dev/null and b/non_dsa/hld/System Design - Caching Contd.pdf differ diff --git a/non_dsa/hld/System Design - Case Study - Design Hotstar .pdf b/non_dsa/hld/System Design - Case Study - Design Hotstar .pdf new file mode 100644 index 0000000..9b9d75e Binary files /dev/null and b/non_dsa/hld/System Design - Case Study - Design Hotstar .pdf differ diff --git a/non_dsa/hld/System Design - Case Study - Design Uber.pdf b/non_dsa/hld/System Design - Case Study - Design Uber.pdf new file mode 100644 index 0000000..309fd1b Binary files /dev/null and b/non_dsa/hld/System Design - Case Study - Design Uber.pdf differ diff --git a/non_dsa/hld/System Design - Case Study - Elastic Search.pdf b/non_dsa/hld/System Design - Case Study - Elastic Search.pdf new file mode 100644 index 0000000..d690aae Binary files /dev/null and b/non_dsa/hld/System Design - Case Study - Elastic Search.pdf differ diff --git a/non_dsa/hld/System Design - Case Study - Messaging.pdf b/non_dsa/hld/System Design - Case Study - Messaging.pdf new file mode 100644 index 0000000..3d2f68f Binary files /dev/null and b/non_dsa/hld/System Design - Case Study - Messaging.pdf differ diff --git a/non_dsa/hld/System Design - Case Study -Typeahead.pdf b/non_dsa/hld/System Design - Case Study -Typeahead.pdf new file mode 100644 index 0000000..cca0210 Binary files /dev/null and b/non_dsa/hld/System Design - Case Study -Typeahead.pdf differ diff --git a/non_dsa/hld/System Design - Microservices 1.pdf b/non_dsa/hld/System Design - Microservices 1.pdf new file mode 100644 index 0000000..4c50241 Binary files /dev/null and b/non_dsa/hld/System Design - Microservices 1.pdf differ diff --git a/non_dsa/hld/System Design - Microservices 2.pdf b/non_dsa/hld/System Design - Microservices 2.pdf new file mode 100644 index 0000000..461991f Binary files /dev/null and b/non_dsa/hld/System Design - Microservices 2.pdf differ diff --git a/non_dsa/hld/System Design - NoSQL Internals - LSM Trees.pdf b/non_dsa/hld/System Design - NoSQL Internals - LSM Trees.pdf new file mode 100644 index 0000000..1985966 Binary files /dev/null and b/non_dsa/hld/System Design - NoSQL Internals - LSM Trees.pdf differ diff --git a/non_dsa/hld/System Design - Popular Interview Questions.pdf b/non_dsa/hld/System Design - Popular Interview Questions.pdf new file mode 100644 index 0000000..b3dedfd Binary files /dev/null and b/non_dsa/hld/System Design - Popular Interview Questions.pdf differ diff --git a/non_dsa/hld/System Design - S3 + Quad trees (nearest neighbors).pdf b/non_dsa/hld/System Design - S3 + Quad trees (nearest neighbors).pdf new file mode 100644 index 0000000..8d390c6 Binary files /dev/null and b/non_dsa/hld/System Design - S3 + Quad trees (nearest neighbors).pdf differ diff --git a/non_dsa/hld/System Design - SQL vs NoSQL.pdf b/non_dsa/hld/System Design - SQL vs NoSQL.pdf new file mode 100644 index 0000000..b6e306c Binary files /dev/null and b/non_dsa/hld/System Design - SQL vs NoSQL.pdf differ diff --git a/non_dsa/hld/System Design - Zookeeper + Kafka.pdf b/non_dsa/hld/System Design - Zookeeper + Kafka.pdf new file mode 100644 index 0000000..d1b3e8a Binary files /dev/null and b/non_dsa/hld/System Design - Zookeeper + Kafka.pdf differ diff --git a/non_dsa/hld/System Design 101 & Consistent Hashing.pdf b/non_dsa/hld/System Design 101 & Consistent Hashing.pdf new file mode 100644 index 0000000..d844904 Binary files /dev/null and b/non_dsa/hld/System Design 101 & Consistent Hashing.pdf differ diff --git a/non_dsa/lld/lld_2/java/abstract-factory.md b/non_dsa/lld/lld_2/java/abstract-factory.md new file mode 100644 index 0000000..ef11a8e --- /dev/null +++ b/non_dsa/lld/lld_2/java/abstract-factory.md @@ -0,0 +1,147 @@ +# Abstract Factory design pattern + +- [Abstract Factory and adapter design patterns](#abstract-factory-pattern) + - [Abstract Factory](#abstract-factory) + - [Advantages of Abstract Factory](#advantages-of-abstract-factory) + - [Implementation](#implementation) + - [Recap](#recap) + - [Design patterns in different languages](#design-patterns-in-different-languages) + - [Abstract Factory](#abstract-factory-1) + - [Python](#python) + - [JavaScript](#javascript) + +## Abstract Factory + +> The abstract factory pattern is a creational pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. + +Let us take the example of a classroom. We have already created a `User` abstract class. Now we will create the concrete classes `Student` and `Teacher`. To restrict the usage of subclasses, we can create factories for each of the concrete classes. The `StudentFactory` will be used to create `Student` objects and the `TeacherFactory` will be used to create `Teacher` objects. + + +```java +class StudentFactory { + public User createStudent(String firstName, String lastName) { + return new Student(firstName, lastName); + } +} + +class TeacherFactory { + public User createTeacher(String firstName, String lastName) { + return new Teacher(firstName, lastName); + } +} +``` + +So now in order to create a classroom, we can use the respective factories to create the objects. + +```java +StudentFactory studentFactory = new StudentFactory(); +Student student = studentFactory.createStudent("John", "Doe"); + +TeacherFactory teacherFactory = new TeacherFactory(); +Teacher teacher = teacherFactory.createTeacher("John", "Doe"); +``` + +But now we have a problem, we can use the factories to create any type of student and teacher. Should a teacher teaching Physics be able to teach a student of Biology class? This is where the concept of related or a family of objects comes into play. The `Student` and `Teacher` objects are related to each other. A teacher should only be able to teach a student of the same class. So we can create a factory that can create a family of related objects. The `ClassroomFactory` will be used to create `Student` and `Teacher` objects of the same class. + +```java +abstract class ClassroomFactory { + public abstract Student createStudent(String firstName, String lastName); + public abstract Teacher createTeacher(String firstName, String lastName); +} +``` + +Now we can create concrete factories for each family of related objects that we want to create. + +```java +class BiologyClassroomFactory extends ClassroomFactory { + @Override + public Student createStudent(String firstName, String lastName) { + return new BiologyStudent(firstName, lastName); + } + + @Override + public Teacher createTeacher(String firstName, String lastName) { + return new BiologyTeacher(firstName, lastName); + } +} +``` +The class `ClassroomFactory` is an abstract class that contains the factory methods for creating the objects. The child classes can override the factory methods to create objects of their own type. The client code can request an object from the factory class without having to know the class of the object that will be returned. + +```java +ClassroomFactory factory = new BiologyClassroomFactory(); +Student student = factory.createStudent("John", "Doe"); +Teacher teacher = factory.createTeacher("John", "Doe"); +``` + +The class `ClassroomFactory` becomes our abstract factory that essentially is a factory of factories. + +### Advantages of Abstract Factory +* `Isolate concrete classes` - The client code is not coupled to the concrete classes of the objects that it creates. +* `Easy to exchange product families` - The client code can request an object from the factory class without having to know the class of the object that will be returned. This makes it easy to exchange product families. +* `Promotes consistency among products` - The client code can request an object from the factory class without having to know the class of the object that will be returned. This makes it easy to maintain consistency among products. + +### Implementation +1. `Abstract product interface` - Create an interface for the products that will be created by the factory. +```java +interface Button { + void render(); + void onClick(); +} +``` +2. `Concrete products` - Create concrete classes that implement the product interface. +```java +class RoundedButton implements Button { + @Override + public void render() { + System.out.println("Rendered rounded button"); + } + + @Override + public void onClick() { + System.out.println("Clicked rounded button"); + } +} +``` + +3. `Abstract factory interface` - Create an interface for the abstract factory that will be used to create the products. +```java +interface FormFactory { + Button createButton(); +} +``` + +4. `Concrete factories` - Create concrete classes that implement the abstract factory interface. +```java +class RoundedFormFactory implements FormFactory { + @Override + public Button createButton() { + return new RoundedButton(); + } +} +``` +5. `Client code` - Request an object from the factory class without having to know the class of the object that will be returned. +```java +FormFactory factory = new RoundedFormFactory(); +Button button = factory.createButton(); +``` + +## Recap +* The factory pattern is a creational design pattern that can be used to create objects without having to specify the exact class of the object that will be created. +* It reduces the coupling between the client code and the class of the object that it is creating. +* Simple factory - The factory class contains a static method for creating objects. This technique is easy to implement, but it is not extensible and reusable. It violates the open-closed principle and the single responsibility principle. +* Factory method - The responsibility of creating the object is shifted to the child classes. The factory method is implemented in the base class and the child classes can override the factory method to create objects of their own type. This technique is extensible and reusable. It follows the open-closed principle and the single responsibility principle. + + +## Design patterns in different languages + +### Abstract Factory +#### Python +* [Abstract Factory - I](https://refactoring.guru/design-patterns/abstract-factory/python/example) +* [Abstract Factory - II](https://stackabuse.com/abstract-factory-design-pattern-in-python/) +* [Abstract Factory - III](https://python-patterns.guide/gang-of-four/abstract-factory/) +* [Abstract Factory - IV](https://python.plainenglish.io/abstract-factory-design-pattern-in-python-9a3de77d01eb) + +#### JavaScript +* [Abstract Factory - I - Typescript](https://refactoring.guru/design-patterns/abstract-factory/typescript/example#example-0) +* [Abstract Factory - II](https://dev.to/carlillo/understanding-design-patterns-abstract-factory-23e7) +* [Abstract Factory - III](https://gist.github.com/OriginUnknown/d2fc38c8412b52ece8de) diff --git a/non_dsa/lld/lld_2/java/adapter-facade.md b/non_dsa/lld/lld_2/java/adapter-facade.md new file mode 100644 index 0000000..deb4bf8 --- /dev/null +++ b/non_dsa/lld/lld_2/java/adapter-facade.md @@ -0,0 +1,305 @@ +# Structural design patterns - Adapter and Facade + +- [Structural design patterns - Adapter and Facade](#structural-design-patterns---adapter-and-facade) + - [Key terms](#key-terms) + - [Structural Patterns](#structural-patterns) + - [Adapter](#adapter) + - [Facade](#facade) + - [Adapter](#adapter-1) + - [Problem](#problem) + - [Implementation](#implementation) + - [Advantages](#advantages) + - [Facade Pattern](#facade-1) + - [Implementation](#implementation-1) + - [Design patterns in different languages](#design-patterns-in-different-languages) + - [Adapter](#adapter-2) + - [Python](#python) + - [JavaScript](#javascript) + +## Key terms +### Structural Patterns +> Structural patterns are design patterns that ease the design by identifying a simple way to realize relationships between entities. + +> Structural patterns are concerned with how classes and objects are composed to form larger structures. + +### Adapter +> The adapter pattern is a software design pattern (also known as wrapper, an alternative naming shared with the decorator pattern) that allows the interface of an existing class to be used from another interface. It is often used to make existing classes work with others without modifying their source code. + +### Facade +- A structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes. + +## Adapter + +> The adapter pattern is a structural pattern that allows objects with incompatible interfaces to collaborate. + +We come across adapters in our day to day life. For example, we have a 3 pin plug and we want to use it in a 2 pin socket. We can use an adapter to convert the 3 pin plug to a 2 pin plug. + +So, we use an adapter to allow two incompatible interfaces to work together. +Similarly, in software development, we have two incompatible interfaces and we want to use them together. We can use an adapter to convert one interface to another. For instance, we have an API that returns a list of users. Now the request to this API requires a JSON object. Some clients instead of sending a JSON object, want to send an XML object. + +Should we change the API to accept an XML object? Should we create a new API that accepts an XML object? No, that would be redundant. This is where the adapter pattern comes into play. We can create an adapter that converts the XML object to a JSON object and then use the existing API. + +```mermaid +graph LR +A[Client 1] --> B[XML Object] +D[Client 2] --> E[JSON Object] +B --> C[XML Adapter] +C --> E[JSON Object] +E --> F[API Code] +``` + +You can create an adapter. This is a special object that converts the interface of one object so that another object can understand it. + +An adapter wraps one of the objects to hide the complexity of conversion happening behind the scenes. The wrapped object isn’t even aware of the adapter. For example, you can wrap an object that operates in meters and kilometers with an adapter that converts all of the data to imperial units such as feet and miles. + +Adapters can not only convert data into various formats but can also help objects with different interfaces collaborate. Here’s how it works: + +* The adapter gets an interface, compatible with one of the existing objects. +* Using this interface, the existing object can safely call the adapter’s methods. +* Upon receiving a call, the adapter passes the request to the second object, but in a format and order that the second object expects. + +### Problem + +Let us take the example of payment processing. +As a part of our application we want to integrate with different payment gateways. +We first use the Stripe payment gateway. The stripe team provides us with a library that we can use to integrate with their payment gateway. + +```java +public class StripeApi { + public void createPayment() { + // Create payment + } + + public PaymentStatus checkStatus(String paymentId) { + // Check payment status + } +} +``` + +We use the Stripe API to create a payment and check the status of the payment. + +```java +public void processPayment() { + StripeApi stripeApi = new StripeApi(); + Payment object = stripeApi.createPayment(); + PaymentStatus status = stripeApi.checkStatus(object.getId()); +} +``` + +Now we want to integrate with another payment gateway. We use the PayPal payment gateway. The PayPal team provides us with a library that we can use to integrate with their payment gateway. + +```java +public class PayPalApi { + public void makePayment() { + // Create payment + } + + public PaymentStatus getStatus(String paymentId) { + // Check payment status + } +} +``` + +As you can see, the Stripe API and the PayPal API have different method names. The Stripe API uses `createPayment` and `checkStatus` while the PayPal API uses `makePayment` and `getStatus`. +Should we change where we use the Stripe API to use the PayPal API? No, that would be redundant. That would require us to change the code in multiple places. Apart from the additional work, it would also increase the chances of introducing bugs. Also, when we want to switch back to the Stripe API, we would have to change the code again. Hence, our code is also violating SRP and OCP. We are also using concrete classes instead of interfaces. This makes our code tightly coupled. + +### Implementation + +1. `Incompatible classes` - You should have two classes that have incompatible interfaces. For example, the Stripe API and the PayPal API. +```java +public class StripeApi { + public void createPayment() { + // Create payment + } + + public PaymentStatus checkStatus(String paymentId) { + // Check payment status + } +} + +public class PayPalApi { + public void makePayment() { + // Create payment + } + + public PaymentStatus getStatus(String paymentId) { + // Check payment status + } +} +``` + +2. `Adapter interface` - Create an interface for the adapter that will be used to convert the incompatible interfaces. +```java +public interface PaymentProvider { + void makePayment(); + PaymentStatus getStatus(String paymentId); +} +``` + +3. `Concrete adapter classes` - Create a class that implements the target interface. This is the class that the client code expects to work with. The adapter will convert the interface of the existing class to this interface. +```java +public class StripeAdapter implements PaymentProvider { + + @Override + public void makePayment() { + ... + } + + @Override + public PaymentStatus getStatus(String paymentId) { + ... + } +} + +public class PayPalAdapter implements PaymentProvider { + + @Override + public void makePayment() { + ... + } + + @Override + public PaymentStatus getStatus(String paymentId) { + ... + } +} +``` + +4. `Transform request and delegate to original class` - In the adapter class, transform the request to the format that the original class expects. Then, call the original class to perform the operation. +```java +public class StripePaymentProvider implements PaymentProvider { + + private StripeApi stripeApi = new StripeApi(); + + @Override + public void makePayment() { + stripeApi.createPayment(); + } + + @Override + public PaymentStatus getStatus(String paymentId) { + StripeStatus status = stripeApi.checkStatus(paymentId); + return convertStatus(status); + } +} +``` + +5. `Client code` - The client code expects to work with the target interface. The client code doesn’t know that the adapter is converting the interface of the original class. +```java +public class PaymentProcessor { + private PaymentProvider paymentProvider; + + public PaymentProcessor(PaymentProvider paymentProvider) { + this.paymentProvider = paymentProvider; + } + + public void processPayment() { + paymentProvider.makePayment(); + PaymentStatus status = paymentProvider.getStatus("paymentId"); + } +} +``` + +### Advantages +* You can use adapters to reuse existing classes with incompatible interfaces. +* You can even modify the request and response of the original classes. +* Single Responsibility Principle. You can separate the interface or data conversion code from the primary business logic of the program. +* Open/Closed Principle. You can introduce new types of adapters into the program without breaking the existing client code, as long as they work with the adapters through the target interface. + +--- + +## Facade + +> Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes. + +Facade means "face" in French. It is a front-facing building that is the main entrance to a building. The facade is the first thing that a visitor sees when they enter a building. The facade hides the complexity of the building from the visitor. The facade provides a simple interface to the building. The facade is a single point of entry to the building. + +### Problem + +Let us take the example of an e-commerce application. The application has a lot of functionality. It has a product catalog, a shopping cart, a payment system, a shipping system, etc. The application has a lot of classes and a lot of dependencies between them. The application is complex and it is hard to understand how all the classes work together. When you make an order, you have to do the following: +* Call payment gateway to charge the credit card. +* Update the inventory. +* Email the customer. +* Add the order to the shipping queue. +* Update analytics. + +The above steps are not trivial. The application has a lot of classes and a lot of dependencies between them. The application is complex and it is hard to understand how all the classes work together. The application is also hard to maintain. If you want to change the way the application sends emails, you will have to change the code in multiple places. If you want to add a new feature, you will have to change the code in multiple places. Imagine how the class looks: + +```java +public class Order { + private PaymentGateway paymentGateway; + private Inventory inventory; + private EmailService emailService; + private ShippingService shippingService; + private AnalyticsService analyticsService; + + public void checkout() { + paymentGateway.charge(); + inventory.update(); + emailService.send(); + shippingService.add(); + analyticsService.update(); + } +} +``` + +Here we have a lot of dependencies, some of which might be external vendors. +The business logic of your classes would become tightly coupled to the implementation details of 3rd-party classes, making it hard to comprehend and maintain. The Order class is hard to test. You will have to mock all the dependencies. The Order class is also hard to reuse. If you want to reuse the Order class in another application, you will have to change the code. Every time one of the logic changes, you will have to change the code in multiple places and hence violating SOLID principles. + +A facade is a class that provides a simple interface to a complex subsystem which contains lots of moving parts. A facade might provide limited functionality in comparison to working with the subsystem directly. However, it includes only those features that clients really care about. + +Having a facade is handy when you need to integrate your app with a sophisticated library that has dozens of features, but you just need a tiny bit of its functionality. + +### Implementation + +The Facade pattern suggests that you wrap a complex subsystem with a simpler interface. The Facade pattern provides a higher-level interface that makes the subsystem easier to use. The Facade pattern is implemented by simply creating a new class that encapsulates the complex logic of the existing classes. For our example above, we will move the complex logic to a new class called OrderProcessor. + +```java +public class OrderProcessor { + private PaymentGateway paymentGateway; + private Inventory inventory; + private EmailService emailService; + private ShippingService shippingService; + private AnalyticsService analyticsService; + + public void process() { + paymentGateway.charge(); + inventory.update(); + emailService.send(); + shippingService.add(); + analyticsService.update(); + } +} +``` + +Now we can use the OrderProcessor class in our Order class and delegate the complex logic to the OrderProcessor class. + +```java +public class Order { + private OrderProcessor orderProcessor; + + public void checkout() { + orderProcessor.process(); + } +} +``` +The Order class is now much simpler. It has a single responsibility of creating an order. The Order class is also easier to test. You can mock the OrderProcessor class. The Order class is also easier to reuse. You can reuse the Order class in another application without changing the code. + + + +## Design patterns in different languages + +### Adapter +#### Python +* [Adapter - I](https://refactoring.guru/design-patterns/adapter/python/example) +* [Adapter - II](https://sbcode.net/python/adapter/) +* [Adapter - III](https://github.com/faif/python-patterns/blob/master/patterns/structural/adapter.py) +* [Adapter - IV](https://gist.github.com/pazdera/1145859) + +#### JavaScript +* [Adapter - I - Typescript](https://refactoring.guru/design-patterns/adapter/typescript/example#example-0) +* [Adapter - II](https://dev.to/wecarrasco/adapter-pattern-with-javascript-4lgi) +* [Adapter - III](https://jsmanifest.com/adapter-pattern-in-javascript/) +* [Adapter - IV](https://betterprogramming.pub/the-adapter-pattern-in-javascript-69c3f48ee164) + + diff --git a/non_dsa/lld/lld_2/java/builder.md b/non_dsa/lld/lld_2/java/builder.md new file mode 100644 index 0000000..1810e41 --- /dev/null +++ b/non_dsa/lld/lld_2/java/builder.md @@ -0,0 +1,132 @@ +# Creational Design Patterns - Builder + + +- [Builder](#builder-1) + - [Problems](#problems) + - [Constructor with a hash map](#constructor-with-a-hash-map) + - [Inner class](#inner-class) + - [Summary](#summary-1) +- [Reading list](#reading-list) + +## Builder + +### Problems +* `Complex object creation` - There are multiple ways to create an object, but constructors are the primary technique used for creating instances of a class. However, constructors become unmanageable when there is a need to create an object with many parameters. This is known as the telescoping constructor anti-pattern. The telescoping constructor anti-pattern is a code smell that indicates that the class has too many constructors. This is a code smell because it is difficult to maintain and extend the class. +* `Validation and failing object creation` - There are cases when you want to validate the parameters before creating an object. For example, you might want to validate the parameters before creating a database connection. If the parameters are invalid, you might want to throw an exception. However, if we use the default constructor, we cannot fail object creation. +* `Immutability` - Mutable objects are objects whose state can be changed after they are created. Immutable objects are objects whose state cannot be changed after they are created. Immutable objects are easier to maintain and extend whereas mutable objects can lead to bugs. However, if we use the default constructor, we cannot create immutable objects. + +### Constructor with a hash map + +The above problems can be solved using a constructor with a hash map. The constructor will take a hash map as a parameter. The hash map will contain the parameters and their values. The constructor will validate the parameters and create the object. + +```java +public class Database { + private String host; + private int port; + private String username; + private String password; + + public Database(Map config) { + if (config.containsKey("host")) { + this.host = config.get("host"); + } + if (config.containsKey("port")) { + this.port = Integer.parseInt(config.get("port")); + } + if (config.containsKey("username")) { + this.username = config.get("username"); + } + if (config.containsKey("password")) { + this.password = config.get("password"); + } + } +} +``` + +Some problems with the above code are: +* `Type safety` - A hash map cannot have values with different types. If we want to use different types, we need to use a hash map with a string key and an object value. However, this will result in a runtime error if we try to cast the object to the wrong type. +* `Defined parameters` - With the above approach, identifying the parameters is difficult. We need to read the code to identify the parameters. This is not a good approach because it is difficult to maintain and extend the code. + +### Inner class + +Instead of using a hash map, we can use a class to accept parameters for object creation. The parameter class is type safe, and it is easy to identify the parameters. + +```java +public class Database { + private String host; + private int port; + private String username; + private String password; + + public Database(DatabaseParameters parameter) { + this.host = parameter.host; + this.port = parameter.port; + this.username = parameter.username; + this.password = parameter.password; + } +} + +class DatabaseParameters { + public String host; + public int port; + public String username; + public String password; +} +``` + +The above code is type safe. However, it is not easy to use. We need to create an instance of the DatabaseParameters class and then pass it to the Database class. This is not a good approach because it is difficult to maintain and extend the code. Similarly, if we even want to change a single parameter name, we have to open the database class for modification. Instead, we should move the destructuring of the parameter class and validation logic to the Parameter class. This will require creating a Database constructor with all the fields. Again, why would developers not just want to use the constructor instead? +So we need a way to allow the parameter class to create the Database object while not exposing a constructor. This can be done using an inner class. This inner class is known as the builder class. + +```java +public class Database { + private String host; + private int port; + private String username; + private String password; + + private Database() { + } + + public static class DatabaseBuilder { + private String host; + private int port; + private String username; + private String password; + + public Database build() { + Database database = new Database(); + database.host = this.host; + database.port = this.port; + database.username = this.username; + database.password = this.password; + return database; + } + } +} +``` + +The above code now allows us to create a Database object using the DatabaseBuilder class. We can fail object creation by adding a validation hook to the build method. The objects created are immutable because the Database class does not have any setters. And the developer can create objects with any permutation of parameters. + +```java +Database database = new Database.DatabaseBuilder() + .host("localhost") + .port(3306) + .username("root") + .password("password") + .build(); +``` + +### Summary +* The builder pattern is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code. +* Use cases of builder pattern + * Complex object creation - Telescoping constructor anti-pattern + * Validation and failing object creation + * Immutability +* Add a static inner class to the class that you want to create. This inner class is known as the builder class. +* Add a private constructor to the class that you want to create. This constructor will be used by the builder class to create the object. +* Implement the `build()` method in the builder class. This method will return the object created by the private constructor. +* Add a method for each parameter in the builder class. This method will set the parameter value and return the builder class instance. + +## Reading list +* [Telescoping constructor anti-pattern](https://www.vojtechruzicka.com/avoid-telescoping-constructor-pattern/) +* [Why objects should be immutable?](https://octoperf.com/blog/2016/04/07/why-objects-must-be-immutable) \ No newline at end of file diff --git a/non_dsa/lld/lld_2/java/decorator-flyweight.md b/non_dsa/lld/lld_2/java/decorator-flyweight.md new file mode 100644 index 0000000..698b220 --- /dev/null +++ b/non_dsa/lld/lld_2/java/decorator-flyweight.md @@ -0,0 +1,312 @@ +# Decorator and Flyweight +- [Structural design patterns - Decorator and Flyweight](#structural-design-patterns---decorator-and-flyweight) + - [Key terms](#key-terms) + - [Structural Patterns](#structural-patterns) + - [Decorator](#decorator) + - [Flyweight](#flyweight) + - [Decorator](#decorator-1) + - [Problem](#problem) + - [Solution](#solution) + - [Advantages](#advantages) + - [Flyweight Pattern](#flyweight-pattern) + - [Implementation](#implementation-1) + - [Recap](#recap) + - [Design patterns in different languages](#design-patterns-in-different-languages) + - [Flyweight](#flyweight-1) + - [Python](#python-1) + - [JavaScript](#javascript-1) + +## Key terms +### Decorator +- A structural design pattern that allows adding new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors. + +### Flyweight +> Flyweight is a structural design pattern that lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object. + + +## Decorator + +The Decorator attaches additional responsibilities to an object dynamically. The ornaments that are added to pine or fir trees are examples of Decorators. Lights, garland, candy canes, glass ornaments, etc., can be added to a tree to give it a festive look. The ornaments do not change the tree itself which is recognizable as a Christmas tree regardless of particular ornaments used. As an example of additional functionality, the addition of lights allows one to "light up" a Christmas tree. + +Another example: assault gun is a deadly weapon on it's own. But you can apply certain "decorations" to make it more accurate, silent and devastating. + + +### Problem +Let us say we want to build a class that sends our users emails with a greeting. We can start with a simple class: + +```java +public class EmailService { + public void sendEmail(String email, String message) { + ... + } +} +``` + +As the application grows we may want to add some additional functionality to our email service. For example, we may want to send phone notifications to our users or send them slack messages. We can obviously add new methods to our EmailService class, but this will violate the Single Responsibility Principle. So we do as we always do and abstract the functionality into a separate class: + +```java +public interface Communicator { + void send(String target, String message); +} +``` + +Now we have the following hierarchy: + +```mermaid +classDiagram + Communicator <|-- EmailService + Communicator <|-- PhoneService + Communicator <|-- SlackService + class Communicator { + +send(String target, String message) + } + + class EmailService { + +send(String email, String message) + } + + class PhoneService { + +send(String phone, String message) + } + + class SlackService { + +send(String slackId, String message) + } +``` + +The above hierarchy is fine, but what if we want to send emails to our users and also send them a phone notification? We can use the separate classes, but this will violate the Open-Closed Principle. We can't extend the functionality of our EmailService class, because it is final. So we need to create a new class that will encapsulate the functionality of the EmailService and the PhoneService classes: + +```mermaid +classDiagram + Communicator <|-- EmailService + Communicator <|-- PhoneService + Communicator <|-- SlackService + Communicator <|-- EmailPhoneService + class Communicator { + +send(String target, String message) + } + + class EmailService { + +send(String email, String message) + } + + class PhoneService { + +send(String phone, String message) + } + + class SlackService { + +send(String slackId, String message) + } + + class EmailPhoneService { + +send(String target, String message) + } +``` + +The above approach is extremely brittle. If we want to add a new functionality, we will have to create a new class for each combination of existing functionalities. It also leads to class explosion. The number of classes grows exponentially with the number of possible combinations of functionalities. + +### Solution + +The problem with the above approach is that inheritance is static. We can't add new functionality to an existing class at runtime. Apart from that multiple inheritance is not supported in a lot of languages and hence to create a new class that encapsulates the functionality of multiple existing classes we have to duplicate the code of the existing classes. + +Another option is to use composition. We can create a new class that will contain references to the existing classes and delegate the calls to the existing classes. This where the Decorator pattern comes in. The Decorator pattern allows us to add new functionality to an existing object without altering its structure. The Decorator pattern is also known as Wrapper. + +A wrapper is an object that can be linked with some target object. The wrapper contains the same set of methods as the target and delegates to it all requests it receives. However, the wrapper may alter the result by doing something either before or after it passes the request to the target. The wrapper implements the same interface as the wrapped object. + +The Decorator pattern is implemented with the following steps: +1. `Common product interface` - Declare the common interface for both wrappers and wrapped objects. +```java +public interface Communicator { + void send(String target, String message); +} +``` + +2. `Concrete product` - Create a concrete product class that implements the common interface and represents the basic behavior of the wrapped object. +```java +public class EmailService implements Communicator { + @Override + public void send(String email, String message) { + ... + } +} +``` + +3. `Base decorator` - Create a base decorator class that implements the common interface and contains a field for storing a reference to a wrapped object. + +```java +public abstract class CommunicatorDecorator implements Communicator { + protected Communicator communicator; + + public CommunicatorDecorator(Communicator communicator) { + this.communicator = communicator; + } +} +``` + +4. `Concrete decorators` - Create concrete decorator classes that extend the base decorator class and add additional behavior to the wrapped object. + +```java +public class PhoneService extends CommunicatorDecorator { + public PhoneService(Communicator communicator) { + super(communicator); + } + + @Override + public void send(String phone, String message) { + communicator.send(phone, message); + sendPhoneNotification(phone, message); + } + + private void sendPhoneNotification(String phone, String message) { + ... + } +} +``` + +5. `Client` - The client code works with all objects using the common interface. This way it can stay independent of the concrete classes of objects it works with. + +```java +public class Client { + public static void main(String[] args) { + Communicator communicator = new EmailService(); + Communicator phoneService = new PhoneService(communicator); + Communicator slackService = new SlackService(phoneService); + slackService.send("user", "Hello"); + } +} +``` + +### Advantages +* Object behavior can be extended at runtime by wrapping an object with one or several decorators without creating a new subclass. +* Runtime configuration of an object is possible. +* New behavior can be added to an object without changing its code. +* SRP is respected by encapsulating the behavior in a separate class. + + +## Flyweight Pattern + +> The flyweight pattern is used to reduce the memory footprint of a program by sharing as much data as possible with similar objects. + +Today, we again assume the role of game developer and are looking to create a role-playing game like PUBG, counter strike etc. We modeled our game in various classes such as Map, User, Gun and Bullet. We are able to create a functional end to end game. The game works smoothly when you and your friend play it. +So you decide to host a game party to show off your new game. When a lot of players start playing the game, you notice that the game is lagging. You check the memory usage of the game and notice that the memory usage is very high. Each bullet was represented by a separate object containing plenty of data. At some point, when the carnage on a player’s screen reached its climax, newly created particles no longer fit into the remaining RAM, so the program crashed. + +Let us take a closer look at the Bullet class. +```mermaid +classDiagram + class Bullet { + + double x + + double y + + double z + + double radius + + double direction + + double speed + + int status + + int type + + string image + } +``` +The memory used by a single bullet instance would be: +* `Double` - 8 bytes * 6 = 48 bytes +* `Integer` - 4 bytes * 2 = 8 bytes +* `Image` - 1KB + +Let us say each person has around 400 bullets and there are 200 people playing the game. The total memory used by the bullets would be 1KB * 400 * 200 = 80MB. This is a lot of memory for just 200 people playing the game. Imagine if the number of bullets increase or the number of players increase. The memory usage would be even higher. For 2000 bullets for 200 players the memory usage would be 800MB. + +The major problem here is for each object, the image field consumes a lot of memory. The image is also the same for all the bullets. + +Other parts of a particle’s state, such as coordinates, movement vector and speed, are unique to each particle. After all, the values of these fields change over time. This data represents the always changing context in which the particle exists, while the color and sprite remain constant for each particle. + +This constant data of an object is usually called the intrinsic state. It lives within the object; other objects can only read it, not change it. The rest of the object’s state, often altered “from the outside” by other objects, is called the extrinsic state. + +The Flyweight pattern suggests that you stop storing the extrinsic state inside the object. Instead, you should pass this state to specific methods which rely on it. Only the intrinsic state stays within the object, letting you reuse it in different contexts. As a result, you’d need fewer of these objects since they only differ in the intrinsic state, which has much fewer variations than the extrinsic. + +So our Bullet class will have to be divided into two classes. One class will contain the intrinsic state and the other class will contain the extrinsic state. The extrinsic state will be passed to the methods that need it. + +```mermaid +classDiagram + class FlyingBullet { + + double x + + double y + + double z + + double radius + + double direction + + double speed + + int status + + int type + + Bullet bullet + } + class Bullet { + + string image + } + + FlyingBullet *-- Bullet +``` + +Now, every bullet will have a reference to the Bullet object. The Bullet object will contain the image field. The FlyingBullet class will contain the extrinsic state. Each bullet does not need to have its own image field. The image field is shared between all the bullets. This way, the memory usage is reduced. + +### Implementation +* `Intrinsic state` - The intrinsic state is stored in the flyweight object. It is independent of the flyweight’s context and remains the same for all flyweight objects. + +```java +public class Bullet { + private String image; +} +``` +* `Extrinsic state` - The extrinsic state is stored or computed by client objects. It depends on the flyweight’s context and changes with it. + +```java +public class FlyingBullet { + private double x; + private double y; + private double z; + private double radius; + private double direction; + private double speed; + private int status; + private int type; + private Bullet bullet; +} +``` + +* `Flyweight factory` - The flyweight factory is responsible for creating and managing flyweight objects. It ensures that flyweights are shared properly. When a client requests a flyweight, the flyweight factory either returns an existing instance or creates a new one, if it doesn’t exist yet. + +```java +public class BulletFactory { + private static final Map bullets = new HashMap<>(); + + public Bullet getBullet(BulletType type) { + ... + } + + public void addBullet(BulletType type, Bullet bullet) { + ... + } +} +``` + +* `Client code` - The client code usually creates a bunch of pre-populated flyweights in the initialization stage of the application. + + +### Recap +* The flyweight pattern is used to reduce the memory footprint of a program by sharing as much data as possible with similar objects. +* First we need to identify the intrinsic and extrinsic state of the object. +* The intrinsic state is stored in the flyweight object. It is independent of the flyweight’s context and remains the same for all flyweight objects. +* The extrinsic state is stored or computed by client objects. It depends on the flyweight’s context and changes with it. +* The extrinsic object contains a reference to the flyweight object or is composed of the flyweight object. +* A flyweight factory is responsible for creating and managing flyweight objects. It ensures that flyweights are shared properly. When a client requests a flyweight, the flyweight factory either returns an existing instance or creates a new one, if it doesn’t exist yet. + + +### Flyweight +#### Python +* [Flyweight - I](https://refactoring.guru/design-patterns/flyweight/python/example#:~:text=Flyweight%20is%20a%20structural%20design,object%20state%20between%20multiple%20objects.) +* [Flyweight - II](https://towardsdev.com/design-patterns-in-python-flyweight-pattern-ec3d321a86af) +* [Flyweight - III](https://sbcode.net/python/flyweight/) +* [Flyweight - IV](https://www.codespeedy.com/flyweight-design-pattern-in-python/) +* [Flyweight - V](https://github.com/gennad/Design-Patterns-in-Python/blob/master/flyweight.py) + +#### JavaScript +* [Flyweight - I](https://refactoring.guru/design-patterns/flyweight/typescript/example) +* [Flyweight - II](https://www.oreilly.com/library/view/learning-javascript-design/9781449334840/ch09s18.html#:~:text=The%20Flyweight%20pattern%20is%20a,see%20Figure%209%2D12) +* [Flyweight - III](https://jsmanifest.com/power-of-flyweight-design-pattern-in-javascript/) +* [Flyweight - IV](https://www.patterns.dev/posts/flyweight-pattern/) +* [Flyweight - V](https://www.dofactory.com/javascript/design-patterns/flyweight) diff --git a/non_dsa/lld/lld_2/java/facade-observer.md b/non_dsa/lld/lld_2/java/facade-observer.md new file mode 100644 index 0000000..69e3dca --- /dev/null +++ b/non_dsa/lld/lld_2/java/facade-observer.md @@ -0,0 +1,258 @@ +# Facade and Behavioural Design Patterns + +## Key terms +### Facade +> A facade is an object that provides a simplified interface to a larger body of code, such as a class library. + +### Behavioural Design Patterns +> Behavioural design patterns are design patterns that identify common communication patterns between objects and realize these patterns. By doing so, these patterns increase flexibility in carrying out this communication. + +### Observer +> The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. + +### Strategy +> The strategy pattern (also known as the policy pattern) is a software design pattern that enables an algorithm's behavior to be selected at runtime. The strategy pattern defines a family of algorithms, encapsulates each algorithm, and makes the algorithms interchangeable within that family. + + +## Facade + +> Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes. + +Facade means "face" in French. It is a front-facing building that is the main entrance to a building. The facade is the first thing that a visitor sees when they enter a building. The facade hides the complexity of the building from the visitor. The facade provides a simple interface to the building. The facade is a single point of entry to the building. + +### Problem + +Let us take the example of an e-commerce application. The application has a lot of functionality. It has a product catalog, a shopping cart, a payment system, a shipping system, etc. The application has a lot of classes and a lot of dependencies between them. The application is complex and it is hard to understand how all the classes work together. When you make an order, you have to do the following: +* Call payment gateway to charge the credit card. +* Update the inventory. +* Email the customer. +* Add the order to the shipping queue. +* Update analytics. + +The above steps are not trivial. The application has a lot of classes and a lot of dependencies between them. The application is complex and it is hard to understand how all the classes work together. The application is also hard to maintain. If you want to change the way the application sends emails, you will have to change the code in multiple places. If you want to add a new feature, you will have to change the code in multiple places. Imagine how the class looks: + +```java +public class Order { + private PaymentGateway paymentGateway; + private Inventory inventory; + private EmailService emailService; + private ShippingService shippingService; + private AnalyticsService analyticsService; + + public void checkout() { + paymentGateway.charge(); + inventory.update(); + emailService.send(); + shippingService.add(); + analyticsService.update(); + } +} +``` + +Here we have a lot of dependencies, some of which might be external vendors. +The business logic of your classes would become tightly coupled to the implementation details of 3rd-party classes, making it hard to comprehend and maintain. The Order class is hard to test. You will have to mock all the dependencies. The Order class is also hard to reuse. If you want to reuse the Order class in another application, you will have to change the code. Every time one of the logic changes, you will have to change the code in multiple places and hence violating SOLID principles. + +A facade is a class that provides a simple interface to a complex subsystem which contains lots of moving parts. A facade might provide limited functionality in comparison to working with the subsystem directly. However, it includes only those features that clients really care about. + +Having a facade is handy when you need to integrate your app with a sophisticated library that has dozens of features, but you just need a tiny bit of its functionality. + +### Solution + +The Facade pattern suggests that you wrap a complex subsystem with a simpler interface. The Facade pattern provides a higher-level interface that makes the subsystem easier to use. The Facade pattern is implemented by simply creating a new class that encapsulates the complex logic of the existing classes. For our example above, we will move the complex logic to a new class called OrderProcessor. + +```java +public class OrderProcessor { + private PaymentGateway paymentGateway; + private Inventory inventory; + private EmailService emailService; + private ShippingService shippingService; + private AnalyticsService analyticsService; + + public void process() { + paymentGateway.charge(); + inventory.update(); + emailService.send(); + shippingService.add(); + analyticsService.update(); + } +} +``` + +Now we can use the OrderProcessor class in our Order class and delegate the complex logic to the OrderProcessor class. + +```java +public class Order { + private OrderProcessor orderProcessor; + + public void checkout() { + orderProcessor.process(); + } +} +``` +The Order class is now much simpler. It has a single responsibility of creating an order. The Order class is also easier to test. You can mock the OrderProcessor class. The Order class is also easier to reuse. You can reuse the Order class in another application without changing the code. + +--- +## Observer Pattern + +Imagine it is iPhone season again, where Apple releases a new version of the iPhone and millions of individuals can't wait to get there hands on it. The stock at the local store has not yet been updated, but you want to be the first to know when the new iPhone is available. You can go to the store every day to check if the stock has been updated, but that is wasteful. Another option is that the store sends everyone an email when the new phones come in. Again, it is wasteful since not everyone is interested in the new iPhone. The best option is that you register or subscribe to the store's mailing list. When the new iPhone comes in, the store sends an email to everyone on the mailing list. This is the motivation behind the Observer pattern. + +We now want to build a Bitcoin tracking application that sends out emails or tweets when the price of Bitcoin changes. We have a data model for Bitcoin that contains the current price of Bitcoin. Apart from this we have a `BitcoinTracker` class that is responsible for setting the price of the Bitcoin. + +```java +public class BitcoinTracker { + private Bitcoin bitcoin; + + public void setPrice(double price) { + bitcoin.setPrice(price); + } +} +``` + +Now we want to send an email when the price of Bitcoin changes. We can do this by calling the `sendEmail` method in the setter method of the `BitcoinTracker` class. + +```java +public class BitcoinTracker { + private Bitcoin bitcoin; + + public void setPrice(double price) { + bitcoin.setPrice(price); + sendEmail(); + } +} +``` + +The above implementation works but it is not ideal. The `BitcoinTracker` class has two responsibilities. It is responsible for setting the price of Bitcoin and it is responsible for sending an email. The `BitcoinTracker` class violates the Single Responsibility Principle. Similarly, we can also send a tweet when the price of Bitcoin changes. We can do this by calling the `sendTweet` method in the setter method of the `BitcoinTracker` class. This now violates the Open-Closed Principle. We will have to change the code in multiple places if we want to add a new feature. + +```java +public class BitcoinTracker { + private Bitcoin bitcoin; + + public void setPrice(double price) { + bitcoin.setPrice(price); + sendEmail(); + sendTweet(); + } +} +``` + +Another option is to have a secondary class fetch the price of Bitcoin and send an email when the price changes. This is known as polling. The problem with any polling approach is that it is wasteful. The secondary class will have to poll the BitcoinTracker class every few seconds to check if the price has changed. + +```java +public class BitcoinPoller { + private BitcoinTracker bitcoinTracker; + private Bitcoin previousBitcoin; + + public void poll() { + Bitcoin currentBitcoin = bitcoinTracker.getBitcoin(); + if (currentBitcoin.getPrice() != previousBitcoin.getPrice()) { + sendEmail(); + } + this.previousBitcoin = currentBitcoin; + } +} +``` + +The two approaches we have discussed so far are not ideal. The first approach violates the Single Responsibility Principle. The second approach is wasteful. The Observer pattern suggests that you define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. + +### Implementation +1. `Observable interface` - This interface defines the methods that the subject class must implement. The subject class is responsible for notifying the observers when the state of the subject changes. + +```java +public interface class Observable { + void addObserver(Observer observer); + void removeObserver(Observer observer); + void notifyObservers(); +} +``` + +2. `Observer interface` - This interface defines the methods that the observer class must implement. The observer class is responsible for updating itself when the state of the subject changes. + +```java +public interface class Observer { + void notify(); +} +``` + +3. `Concrete observables` - These are the classes that implement the `Observable` interface. The `BitcoinTracker` class is a concrete observable. The `BitcoinTracker` class is responsible for notifying the observers when the state of the subject changes. + +```java +public class BitcoinTracker implements Observable { + private Bitcoin bitcoin; + + public void setPrice(double price) { + bitcoin.setPrice(price); + notifyObservers(); + } +} +``` + +To simplify the code and provide better interfaces, we can borrow from the registry pattern and register observers and even add utility methods to the `Observable` interface. + +```java +public abstract class Observable { + + List observers = new ArrayList<>(); + + public void register(Observer observer) { + observers.add(observer); + } + + public void deregister(Observer observer) { + observers.remove(observer); + } + + public void notifyChange() { + for (Observer observer : observers) { + observer.notifyChange(); + } + } +} +``` + +4. `Concrete observers` - These are the classes that implement the `Observer` interface. The `EmailSender` class is a concrete observer. The `EmailSender` class is responsible for updating itself when the state of the subject changes. + +```java +public class EmailSender implements Observer { + private Bitcoin bitcoin; + + public void notifyChange() { + sendEmail(); + } +} +``` + +5. `Client` - The client is responsible for creating the subject and the observers. The client is also responsible for registering the observers with the subject. + +```java +public class Client { + public static void main(String[] args) { + BitcoinTracker bitcoinTracker = new BitcoinTracker(); + EmailSender emailSender = new EmailSender(); + + bitcoinTracker.register(emailSender); + } +} +``` + +### Recap +* There are often times when you want to notify other objects when the state of an object changes. The Observer pattern suggests that you define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. +* In-place updates and polling are not ideal. In-place updates violate the Single Responsibility Principle whereas polling is wasteful. +* To implement the Observer pattern, define an `Observable` interface that defines the methods that the subject class must implement. +* Define an `Observer` interface that defines the methods that the observer class must implement. The subject class is responsible for notifying the observers when the state of the subject changes. The observer class is responsible for updating itself when the state of the subject changes. + +## Design Patterns in different languages +### Observer Pattern +#### Python +* [Observer Pattern - I](https://refactoring.guru/design-patterns/observer/python/example) +* [Observer Pattern - II](https://stackabuse.com/observer-design-pattern-in-python/) +* [Observer Pattern - III](https://www.protechtraining.com/blog/post/tutorial-the-observer-pattern-in-python-879) +* [Observer Pattern - IV](https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Observer.html) + +#### JavaScipt + +* [Observer Pattern - I](https://refactoring.guru/design-patterns/observer/javascript/example) +* [Observer Pattern - II](https://www.patterns.dev/posts/observer-pattern/) +* [Observer Pattern - III](https://www.oreilly.com/library/view/learning-javascript-design/9781449334840/ch09s05.html) +* [Observer Pattern - IV](https://www.dottedsquirrel.com/observer-pattern-javascript/) +* [Observer Pattern - V](https://blog.bitsrc.io/the-observer-pattern-in-javascript-the-key-to-a-reactive-behavior-f28236e50e10) +* [Observer Pattern - VI](https://www.digitalocean.com/community/conceptual_articles/observer-design-pattern-in-javascript) \ No newline at end of file diff --git a/non_dsa/lld/lld_2/java/factory.md b/non_dsa/lld/lld_2/java/factory.md new file mode 100644 index 0000000..20d8153 --- /dev/null +++ b/non_dsa/lld/lld_2/java/factory.md @@ -0,0 +1,234 @@ +# Factory design pattern + +## Key terms +### Simple factory +> A simple factory is a static method that returns an instance of a class. It is a static method because it does not need to be instantiated. It is a factory because it creates an instance of a class. + +### Factory method +> Rather than having a single static method that returns an instance of a class, the factory method pattern uses a class that has a method that returns an instance of a class. This method is not static because it needs to be instantiated. + +### Abstract factory +> The abstract factory pattern is a factory of factories. It is a factory that creates other factories. It is a factory that creates other factories that create instances of classes. + +## Factory + +> The factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory method—either specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes—rather than by calling a constructor. + +In our previous session, we learnt how to use the prototype to create a clone of the object. One of the motivations for using the prototype is to create a new object without having to know the exact class of the object that will be created. For an instance, there is an external library that we want to use in our application. We don't know the exact class of the object that will be created. We just know that the object will have a method called `doSomething()`. We can use the prototype to create a new object without having to know the exact class of the object that will be created. The library will provide us with a prototype object that we can use to create a new object. But if the library does not expose the prototype object, we will create a prototype object ourselves and use it to create a new object. + +```java +User prototype = new User("John", "Doe"); +User user = prototype.clone(); +``` + +In the above example, the client code is still not completely independent of the class of the object that it is creating. The client code still has to call the `new` keyword to create the prototype of thh object. The client code also has to know the class of the object that it is creating. This is not ideal as the client code should not have to know the class of the object that it is creating. The client code should only know the interface of the object that it is creating. This is where the factory pattern comes into play. + + +If we want to just change the name of the class in our next version, the client code will have to be changed making our code backward incompatible. To avoid this, we can use the factory pattern. The factory pattern allows us to create objects without specifying the exact class of the object that will be created. The client code can request an object from a factory object without having to know the class of the object that will be returned. The factory object can create the object and return it to the client code. + +### Simple Factory + +> The simple factory pattern is a creational pattern that provides a static method for creating objects. The method can be used to create objects without having to specify the exact class of the object that will be created. This is done by creating a factory class that contains a static method for creating objects. + +Let us create a simple factory class that can be used to create different types of users. The factory class will have a static method that can be used to create different types of users. + +```java +class UserFactory { + public static User createUser(UserRole role) { + switch (role) { + case STUDENT: + return new Student("John", "Doe"); + case TEACHER: + return new Teacher("John", "Doe"); + case ADMIN: + return new Admin("John", "Doe"); + } + } +} +``` + +The client code can request a user object from the factory class without having to know the class of the object that will be returned. + +```java +User user = UserFactory.createUser(UserRole.STUDENT); +``` + +The complete steps to implement the simple factory pattern are: +1. `Factory class` - Create a factory class that contains a static method for creating objects. +2. `Conditional` - Use a conditional statement to create the object based on the input. +3. `Request` - Request an object from the factory class without having to know the class of the object that will be returned. + + +### Factory Method + +The simple factory method is easy to implement, but it has a few drawbacks. The factory class is not extensible. If we want to add a new type of user, we will have to modify the factory class. Also, the factory class is not reusable. If we want to create a factory for creating different types of objects, we will have to create a new factory class. To overcome these drawbacks, we can use the factory method pattern. + +In the factory method the responsibility of creating the object is shifted to the child classes. The factory method is implemented in the base class and the child classes can override the factory method to create objects of their own type. The factory method is also known as the virtual constructor. + +```java +@AllArgsContructor +abstract class UserFactory { + public abstract User createUser(String firstName, String lastName); +} + +class StudentFactory extends UserFactory { + @Override + public User createUser(String firstName, String lastName) { + return new Student(firstName, lastName); + } +} +``` + +The client code can request a user object from the base class without having to know the class of the object that will be returned. + +```java +UserFactory factory = new StudentFactory(); +User user = factory.createUser("John", "Doe"); +``` + +The complete steps to implement the factory method pattern are: +1. `Base factory interface` - Create a factory class that contains a method for creating objects. +2. `Child factory class` - Create a child class that extends the base factory class and overrides the factory method to create objects of its own type. +3. `Request` - Request an object from the factory class without having to know the class of the object that will be returned. + +### Abstract Factory + +> The abstract factory pattern is a creational pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. + +Let us take the example of a classroom. We have already created a `User` abstract class. Now we will create the concrete classes `Student` and `Teacher`. To restrict the usage of subclasses, we can create factories for each of the concrete classes. The `StudentFactory` will be used to create `Student` objects and the `TeacherFactory` will be used to create `Teacher` objects. + + +```java +class StudentFactory { + public User createStudent(String firstName, String lastName) { + return new Student(firstName, lastName); + } +} + +class TeacherFactory { + public User createTeacher(String firstName, String lastName) { + return new Teacher(firstName, lastName); + } +} +``` + +So now in order to create a classroom, we can use the respective factories to create the objects. + +```java +StudentFactory studentFactory = new StudentFactory(); +Student student = studentFactory.createStudent("John", "Doe"); + +TeacherFactory teacherFactory = new TeacherFactory(); +Teacher teacher = teacherFactory.createTeacher("John", "Doe"); +``` + +But now we have a problem, we can use the factories to create any type of student and teacher. Should a teacher teaching Physics be able to teach a student of Biology class? This is where the concept of related or a family of objects comes into play. The `Student` and `Teacher` objects are related to each other. A teacher should only be able to teach a student of the same class. So we can create a factory that can create a family of related objects. The `ClassroomFactory` will be used to create `Student` and `Teacher` objects of the same class. + +```java +abstract class ClassroomFactory { + public abstract Student createStudent(String firstName, String lastName); + public abstract Teacher createTeacher(String firstName, String lastName); +} +``` + +Now we can create concrete factories for each family of related objects that we want to create. + +```java +class BiologyClassroomFactory extends ClassroomFactory { + @Override + public Student createStudent(String firstName, String lastName) { + return new BiologyStudent(firstName, lastName); + } + + @Override + public Teacher createTeacher(String firstName, String lastName) { + return new BiologyTeacher(firstName, lastName); + } +} +``` +The class `ClassroomFactory` is an abstract class that contains the factory methods for creating the objects. The child classes can override the factory methods to create objects of their own type. The client code can request an object from the factory class without having to know the class of the object that will be returned. + +```java +ClassroomFactory factory = new BiologyClassroomFactory(); +Student student = factory.createStudent("John", "Doe"); +Teacher teacher = factory.createTeacher("John", "Doe"); +``` + +The class `ClassroomFactory` becomes our abstract factory that essentially is a factory of factories. + +#### Advantages of Abstract Factory +* `Isolate concrete classes` - The client code is not coupled to the concrete classes of the objects that it creates. +* `Easy to exchange product families` - The client code can request an object from the factory class without having to know the class of the object that will be returned. This makes it easy to exchange product families. +* `Promotes consistency among products` - The client code can request an object from the factory class without having to know the class of the object that will be returned. This makes it easy to maintain consistency among products. + +#### Implementation +1. `Abstract product interface` - Create an interface for the products that will be created by the factory. +```java +interface Button { + void render(); + void onClick(); +} +``` +2. `Concrete products` - Create concrete classes that implement the product interface. +```java +class RoundedButton implements Button { + @Override + public void render() { + System.out.println("Rendered rounded button"); + } + + @Override + public void onClick() { + System.out.println("Clicked rounded button"); + } +} +``` + +3. `Abstract factory interface` - Create an interface for the abstract factory that will be used to create the products. +```java +interface FormFactory { + Button createButton(); +} +``` + +4. `Concrete factories` - Create concrete classes that implement the abstract factory interface. +```java +class RoundedFormFactory implements FormFactory { + @Override + public Button createButton() { + return new RoundedButton(); + } +} +``` +5. `Client code` - Request an object from the factory class without having to know the class of the object that will be returned. +```java +FormFactory factory = new RoundedFormFactory(); +Button button = factory.createButton(); +``` + +### Recap +* The factory pattern is a creational design pattern that can be used to create objects without having to specify the exact class of the object that will be created. +* It reduces the coupling between the client code and the class of the object that it is creating. +* Simple factory - The factory class contains a static method for creating objects. This technique is easy to implement, but it is not extensible and reusable. It violates the open-closed principle and the single responsibility principle. +* Factory method - The responsibility of creating the object is shifted to the child classes. The factory method is implemented in the base class and the child classes can override the factory method to create objects of their own type. This technique is extensible and reusable. It follows the open-closed principle and the single responsibility principle. + +## Design patterns in different languages + +### Factory + +#### Python +* [Factory - I](https://realpython.com/factory-method-python/) +* [Factory - II](https://refactoring.guru/design-patterns/factory-method/python/example) +* [Factory - III](https://stackabuse.com/the-factory-method-design-pattern-in-python/) +* [Factory - IV](https://python-patterns.guide/gang-of-four/factory-method/) +* [Factory - V](https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Factory.html) + + +#### Javascript + +* [Factory - I](https://blog.sessionstack.com/how-javascript-works-the-factory-design-pattern-4-use-cases-7b9f0d22151d) +* [Factory - II](https://www.oreilly.com/library/view/learning-javascript-design/9781449334840/ch09s10.html) +* [Factory - III](https://www.theodinproject.com/lessons/node-path-javascript-factory-functions-and-the-module-pattern) +* [Factory - IV](https://javascript.plainenglish.io/design-patterns-with-typescript-factory-method-cb190d7ce275) +* [Factory - V](https://www.digitalocean.com/community/tutorials/js-factory-pattern) + diff --git a/non_dsa/lld/lld_2/java/observer-strategy.md b/non_dsa/lld/lld_2/java/observer-strategy.md new file mode 100644 index 0000000..bae7c1e --- /dev/null +++ b/non_dsa/lld/lld_2/java/observer-strategy.md @@ -0,0 +1,282 @@ +# Behavioural design patterns + +- [Behavioural design patterns](#behavioural-design-patterns) + - [Behavioural Design Patterns](#behavioural-design-patterns-1) + - [Observer](#observer) + - [Strategy](#strategy) + - [Observer Pattern](#observer-pattern) + - [Implementation](#implementation) + - [Recap](#recap) + - [Observer Pattern](#observer-pattern-1) + - [Python](#python) + - [JavaScipt](#javascipt) + - [Strategy design pattern](#strategy-design-pattern) + - [Implementation](#implementation-1) + - [Recap](#recap-1) + +### Behavioural Design Patterns +> Behavioural design patterns are design patterns that identify common communication patterns between objects and realize these patterns. By doing so, these patterns increase flexibility in carrying out this communication. + +### Observer +> The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. + +### Strategy +> The strategy pattern (also known as the policy pattern) is a software design pattern that enables an algorithm's behavior to be selected at runtime. The strategy pattern defines a family of algorithms, encapsulates each algorithm, and makes the algorithms interchangeable within that family. + +--- +## Observer Pattern + +Imagine it is iPhone season again, where Apple releases a new version of the iPhone and millions of individuals can't wait to get there hands on it. The stock at the local store has not yet been updated, but you want to be the first to know when the new iPhone is available. You can go to the store every day to check if the stock has been updated, but that is wasteful. Another option is that the store sends everyone an email when the new phones come in. Again, it is wasteful since not everyone is interested in the new iPhone. The best option is that you register or subscribe to the store's mailing list. When the new iPhone comes in, the store sends an email to everyone on the mailing list. This is the motivation behind the Observer pattern. + +We now want to build a Bitcoin tracking application that sends out emails or tweets when the price of Bitcoin changes. We have a data model for Bitcoin that contains the current price of Bitcoin. Apart from this we have a `BitcoinTracker` class that is responsible for setting the price of the Bitcoin. + +```java +public class BitcoinTracker { + private Bitcoin bitcoin; + + public void setPrice(double price) { + bitcoin.setPrice(price); + } +} +``` + +Now we want to send an email when the price of Bitcoin changes. We can do this by calling the `sendEmail` method in the setter method of the `BitcoinTracker` class. + +```java +public class BitcoinTracker { + private Bitcoin bitcoin; + + public void setPrice(double price) { + bitcoin.setPrice(price); + sendEmail(); + } +} +``` + +The above implementation works but it is not ideal. The `BitcoinTracker` class has two responsibilities. It is responsible for setting the price of Bitcoin and it is responsible for sending an email. The `BitcoinTracker` class violates the Single Responsibility Principle. Similarly, we can also send a tweet when the price of Bitcoin changes. We can do this by calling the `sendTweet` method in the setter method of the `BitcoinTracker` class. This now violates the Open-Closed Principle. We will have to change the code in multiple places if we want to add a new feature. + +```java +public class BitcoinTracker { + private Bitcoin bitcoin; + + public void setPrice(double price) { + bitcoin.setPrice(price); + sendEmail(); + sendTweet(); + } +} +``` + +Another option is to have a secondary class fetch the price of Bitcoin and send an email when the price changes. This is known as polling. The problem with any polling approach is that it is wasteful. The secondary class will have to poll the BitcoinTracker class every few seconds to check if the price has changed. + +```java +public class BitcoinPoller { + private BitcoinTracker bitcoinTracker; + private Bitcoin previousBitcoin; + + public void poll() { + Bitcoin currentBitcoin = bitcoinTracker.getBitcoin(); + if (currentBitcoin.getPrice() != previousBitcoin.getPrice()) { + sendEmail(); + } + this.previousBitcoin = currentBitcoin; + } +} +``` + +The two approaches we have discussed so far are not ideal. The first approach violates the Single Responsibility Principle. The second approach is wasteful. The Observer pattern suggests that you define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. + +### Implementation +1. `Observable interface` - This interface defines the methods that the subject class must implement. The subject class is responsible for notifying the observers when the state of the subject changes. + +```java +public interface class Observable { + void addObserver(Observer observer); + void removeObserver(Observer observer); + void notifyObservers(); +} +``` + +2. `Observer interface` - This interface defines the methods that the observer class must implement. The observer class is responsible for updating itself when the state of the subject changes. + +```java +public interface class Observer { + void notify(); +} +``` + +3. `Concrete observables` - These are the classes that implement the `Observable` interface. The `BitcoinTracker` class is a concrete observable. The `BitcoinTracker` class is responsible for notifying the observers when the state of the subject changes. + +```java +public class BitcoinTracker implements Observable { + private Bitcoin bitcoin; + + public void setPrice(double price) { + bitcoin.setPrice(price); + notifyObservers(); + } +} +``` + +To simplify the code and provide better interfaces, we can borrow from the registry pattern and register observers and even add utility methods to the `Observable` interface. + +```java +public abstract class Observable { + + List observers = new ArrayList<>(); + + public void register(Observer observer) { + observers.add(observer); + } + + public void deregister(Observer observer) { + observers.remove(observer); + } + + public void notifyChange() { + for (Observer observer : observers) { + observer.notifyChange(); + } + } +} +``` + +4. `Concrete observers` - These are the classes that implement the `Observer` interface. The `EmailSender` class is a concrete observer. The `EmailSender` class is responsible for updating itself when the state of the subject changes. + +```java +public class EmailSender implements Observer { + private Bitcoin bitcoin; + + public void notifyChange() { + sendEmail(); + } +} +``` + +5. `Client` - The client is responsible for creating the subject and the observers. The client is also responsible for registering the observers with the subject. + +```java +public class Client { + public static void main(String[] args) { + BitcoinTracker bitcoinTracker = new BitcoinTracker(); + EmailSender emailSender = new EmailSender(); + + bitcoinTracker.register(emailSender); + } +} +``` + +### Recap +* There are often times when you want to notify other objects when the state of an object changes. The Observer pattern suggests that you define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. +* In-place updates and polling are not ideal. In-place updates violate the Single Responsibility Principle whereas polling is wasteful. +* To implement the Observer pattern, define an `Observable` interface that defines the methods that the subject class must implement. +* Define an `Observer` interface that defines the methods that the observer class must implement. The subject class is responsible for notifying the observers when the state of the subject changes. The observer class is responsible for updating itself when the state of the subject changes. + +### Observer Pattern +#### Python +* [Observer Pattern - I](https://refactoring.guru/design-patterns/observer/python/example) +* [Observer Pattern - II](https://stackabuse.com/observer-design-pattern-in-python/) +* [Observer Pattern - III](https://www.protechtraining.com/blog/post/tutorial-the-observer-pattern-in-python-879) +* [Observer Pattern - IV](https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Observer.html) + +#### JavaScipt + +* [Observer Pattern - I](https://refactoring.guru/design-patterns/observer/javascript/example) +* [Observer Pattern - II](https://www.patterns.dev/posts/observer-pattern/) +* [Observer Pattern - III](https://www.oreilly.com/library/view/learning-javascript-design/9781449334840/ch09s05.html) +* [Observer Pattern - IV](https://www.dottedsquirrel.com/observer-pattern-javascript/) +* [Observer Pattern - V](https://blog.bitsrc.io/the-observer-pattern-in-javascript-the-key-to-a-reactive-behavior-f28236e50e10) +* [Observer Pattern - VI](https://www.digitalocean.com/community/conceptual_articles/observer-design-pattern-in-javascript) + +## Strategy design pattern + +> The strategy pattern (also known as the policy pattern) is a software design pattern that enables an algorithm's behavior to be selected at runtime. The strategy pattern encapsulates alternative algorithms (or strategies) for a specific task and supports their interchangeable use. The strategy pattern lets the algorithm vary independently from clients that use it. + +Today we are building a navigation system for a car. The navigation system should be able to calculate the shortest route between two points and tell the driver how to get there. +We start with a simple implementation that only supports driving on roads and hence we design a simple `RoadNavigation` class that calculates the shortest route between two points on a road. + +```java +public class Navigator { + public void navigate(Point from, Point to) { + ... + } +} +``` + +Our application is a hit and we get a lot of requests to support other types of navigation such as using a bike or walking. In order to support various types of navigation we introduce a new `ModeType` enum that defines the different types of navigation. + +```java +public class Navigator { + public void navigate(Point from, Point to, ModeType mode){ + if (mode == ModeType.ROAD) { + ... + } else if (mode == ModeType.BIKE) { + ... + } else if (mode == ModeType.WALK) { + ... + } + } +} +``` + +This works but it is not very flexible. If we want to add a new type of navigation we have to modify the `navigate` method. This is an example of a violation of the open-closed principle. Similarly, the method has multiple reasons to change, and we have to test it for all the different types of navigation. + +We could create an interface and have a separate class for each type of navigation. This would solve the problem of having to modify the `navigate` method, but inheritance is static and we cannot change the behavior of the `navigate` method at runtime. Also code duplication is a problem. +This is where the strategy pattern comes in. The strategy pattern allows us to encapsulate the different types of navigation in separate classes and select the appropriate one at runtime. The Strategy pattern suggests that you take a class that does something specific in a lot of different ways and extract all of these algorithms into separate classes called strategies. The original class, called context, must have a field for storing a reference to one of the strategies. The context delegates the work to a linked strategy object instead of executing it on its own. + +### Implementation + +1. `Strategy` interface - defines an algorithm interface common to all supported versions. + +```java +public interface NavigationStrategy { + void navigate(Point from, Point to); +} +``` + +2. `Concrete Strategy` classes - implement the algorithms using the Strategy interface. + +```java +public class RoadNavigation implements NavigationStrategy { + @Override + public void navigate(Point from, Point to) { + ... + } +} +``` + +3. `Context` class - maintains a reference to a Strategy object and defines an interface that lets the strategy access its data. + +```java +public class Navigator { + private NavigationStrategy strategy; + + public Navigator(NavigationStrategy strategy) { + this.strategy = strategy; + } + + public void navigate(Point from, Point to) { + strategy.navigate(from, to); + } +} +``` + +4. `Client` - creates and configures the context and the strategy objects. + +```java +public class Main { + public static void main(String[] args) { + Navigator navigator = new Navigator(new RoadNavigation()); + navigator.navigate(new Point(0, 0), new Point(10, 10)); + } +} +``` + +To create a strategy object we can also use a factory method. + +### Recap +* The strategy pattern encapsulates alternative algorithms (or strategies) for a specific task and supports their interchangeable use. +* Implementing it in place violates the open-closed principle and makes the code harder to maintain. +* Inheritance is static, and we cannot change the behavior of the system at runtime. +* To use the strategy pattern we create a strategy interface and a set of classes that implement it. +* The context class maintains a reference to a strategy object and delegates the work to it. \ No newline at end of file diff --git a/non_dsa/lld/lld_2/java/prototype-factory.md b/non_dsa/lld/lld_2/java/prototype-factory.md new file mode 100644 index 0000000..1e8d3b1 --- /dev/null +++ b/non_dsa/lld/lld_2/java/prototype-factory.md @@ -0,0 +1,230 @@ +# Prototype and Factory design patterns + +- [Prototype and Factory design patterns](#prototype-and-factory-design-patterns) + - [Key terms](#key-terms) + - [Prototype](#prototype) + - [Factory](#factory) + - [Prototype](#prototype-1) + - [Prototype Registry](#prototype-registry) + - [Recap](#recap) + - [Factory](#factory-1) + - [Simple Factory](#simple-factory) + - [Factory Method](#factory-method) + - [Recap](#recap-1) + - [Design patterns in different languages](#design-patterns-in-different-languages) + - [Prototype](#prototype-2) + - [Python](#python) + - [Javascript](#javascript) + - [Factory](#factory-2) + - [Python](#python-1) + - [Javascript](#javascript-1) + +## Key terms +### Prototype +> The prototype pattern is a creational design pattern that can be used to create objects that are similar to each other. The pattern is used to avoid the cost of creating new objects by cloning an existing object and avoiding dependencies on the class of the object that needs to be cloned. + +### Factory +> The factory pattern is a creational design pattern that can be used to create objects without specifying the exact class of the object that will be created. The pattern is used to avoid dependencies on the class of the object that needs to be created. + +## Prototype + +> Prototype allows us to hide the complexity of making new instances from the client. The concept is to copy an existing object rather than creating a new instance from scratch, something that may include costly operations. The existing object acts as a prototype and contains the state of the object. The newly copied object may change same properties only if required. This approach saves costly resources and time, especially when object creation is a heavy process. + +Let us say we have to created a new `User` API and we want to test it. To test it, we need to create a new user. We can create a new user by using the `new` keyword. + +```java +User user = new User("John", "Doe", "john@doe.in", "1234567890"); +``` + +We might be calling a separate API to get these random values for the user. So each time we want to create a new user we have to call the API. Instead, we can create a new user by cloning an existing user and modifying the fields that are necessary. This way we can avoid calling the API each time we want to create a new user. To clone an existing user, we have to implement a common interface for all the user objects `clone()` + +```java +public abstract class User { + public abstract User clone(); +} +``` + +Then we can create an initial user object which is known as the prototype and then clone it using the `clone()` method. + +```java +User user = new User("John", "Doe", "john@doe.in", "1234567890"); +User user2 = user.clone(); +user2.setId(2); +``` + +Apart from reducing the cost of creating new objects, the prototype pattern also helps in reducing the complexity of creating new objects. The client code does not have to deal with the complexity of creating new objects. It can simply clone the existing object and modify it as per its needs. The client code does not have a dependency on the class of the object that it is cloning. + +### Prototype Registry + +The prototype pattern can be extended to use a registry of pre-defined prototypes. The registry can be used to store a set of pre-defined prototypes. The client code can then request a clone of a prototype from the registry instead of creating a new object from scratch. The registry can be implemented as a key-value store where the key is the name of the prototype and the value is the prototype object. + +For example, we might want to create different types of users. A user with a `Student` role, a user with a `Teacher` role, and a user with an `Admin` role. Each such different type of user might have some fields that are specific to the type so the fields to be copied might be different. We can create a registry of pre-defined prototypes for each of these roles. + +```java +interface UserRegistry { + User getPrototype(UserRole role); + void addPrototype(UserRole role, User user); +} +``` + +Now we can implement the `UserRegistry` interface and store the pre-defined prototypes in a map. + +```java +class UserRegistryImpl implements UserRegistry { + private Map registry = new HashMap<>(); + + @Override + public User getPrototype(UserRole role) { + return registry.get(role).clone(); + } + + @Override + public void addPrototype(UserRole role, User user) { + registry.put(role, user); + } +} +``` + +The client code can request a prototype from the registry, clone it, and modify it as per its needs. + +```java +UserRegistry registry = new UserRegistryImpl(); +registry.addPrototype(UserRole.STUDENT, new Student("John", "Doe", "john@doe.in", "1234567890", UserRole.STUDENT, "CS")); + +User user = registry.getPrototype(UserRole.STUDENT); +user.setId(1); +``` + +### Recap + +- The prototype pattern is a creational design pattern that can be used to create objects that are similar to each other. +- Recreating an object from scratch can be costly as we might have to call an API to get the values for the fields or to perform some other costly operations. The prototype pattern can be used to avoid this cost by cloning an existing object and modifying the fields that are necessary. +- Also, the client code does not have to deal with the complexity of creating new objects. It can simply clone the existing object and modify it as per its needs. +- To implement the prototype pattern, we follow these steps: + 1. `Clonable interface` - Create a common interface for all the objects that can be cloned. + 2. `Object class` - Create a concrete class that implements the common interface and overrides the `clone()` method. + 3. `Registry` - Create a registry of pre-defined prototypes with `register` and `get` methods. + 4. `Prototype` - Create a prototype object and store in the registry. + 5. `Clone` - Request a clone of the prototype from the registry and modify it as per its needs. + + +## Factory + +> The factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory method—either specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes—rather than by calling a constructor. + +In the above example, the client code is still not completely independent of the class of the object that it is creating. The client code still has to call the `new` keyword to create the prototype of thh object. The client code also has to know the class of the object that it is creating. This is not ideal as the client code should not have to know the class of the object that it is creating. The client code should only know the interface of the object that it is creating. This is where the factory pattern comes into play. + +```java +User prototype = new User("John", "Doe"); +User user = prototype.clone(); +``` + +If we want to just change the name of the class in our next version, the client code will have to be changed making our code backward incompatible. To avoid this, we can use the factory pattern. The factory pattern allows us to create objects without specifying the exact class of the object that will be created. The client code can request an object from a factory object without having to know the class of the object that will be returned. The factory object can create the object and return it to the client code. + +### Simple Factory + +> The simple factory pattern is a creational pattern that provides a static method for creating objects. The method can be used to create objects without having to specify the exact class of the object that will be created. This is done by creating a factory class that contains a static method for creating objects. + +Let us create a simple factory class that can be used to create different types of users. The factory class will have a static method that can be used to create different types of users. + +```java +class UserFactory { + public static User createUser(UserRole role) { + switch (role) { + case STUDENT: + return new Student("John", "Doe"); + case TEACHER: + return new Teacher("John", "Doe"); + case ADMIN: + return new Admin("John", "Doe"); + } + } +} +``` + +The client code can request a user object from the factory class without having to know the class of the object that will be returned. + +```java +User user = UserFactory.createUser(UserRole.STUDENT); +``` + +The complete steps to implement the simple factory pattern are: +1. `Factory class` - Create a factory class that contains a static method for creating objects. +2. `Conditional` - Use a conditional statement to create the object based on the input. +3. `Request` - Request an object from the factory class without having to know the class of the object that will be returned. + + +### Factory Method + +The simple factory method is easy to implement, but it has a few drawbacks. The factory class is not extensible. If we want to add a new type of user, we will have to modify the factory class. Also, the factory class is not reusable. If we want to create a factory for creating different types of objects, we will have to create a new factory class. To overcome these drawbacks, we can use the factory method pattern. + +In the factory method the responsibility of creating the object is shifted to the child classes. The factory method is implemented in the base class and the child classes can override the factory method to create objects of their own type. The factory method is also known as the virtual constructor. + +```java +@AllArgsContructor +abstract class UserFactory { + public abstract User createUser(String firstName, String lastName); +} + +class StudentFactory extends UserFactory { + @Override + public User createUser(String firstName, String lastName) { + return new Student(firstName, lastName); + } +} +``` + +The client code can request a user object from the base class without having to know the class of the object that will be returned. + +```java +UserFactory factory = new StudentFactory(); +User user = factory.createUser("John", "Doe"); +``` + +The complete steps to implement the factory method pattern are: +1. `Base factory interface` - Create a factory class that contains a method for creating objects. +2. `Child factory class` - Create a child class that extends the base factory class and overrides the factory method to create objects of its own type. +3. `Request` - Request an object from the factory class without having to know the class of the object that will be returned. + + +### Recap +* The factory pattern is a creational design pattern that can be used to create objects without having to specify the exact class of the object that will be created. +* It reduces the coupling between the client code and the class of the object that it is creating. +* Simple factory - The factory class contains a static method for creating objects. This technique is easy to implement, but it is not extensible and reusable. It violates the open-closed principle and the single responsibility principle. +* Factory method - The responsibility of creating the object is shifted to the child classes. The factory method is implemented in the base class and the child classes can override the factory method to create objects of their own type. This technique is extensible and reusable. It follows the open-closed principle and the single responsibility principle. + +## Design patterns in different languages + +### Prototype + +#### Python + +* [Prototype - I](https://refactoring.guru/design-patterns/prototype/python/example) +* [Prototype - II](https://stackabuse.com/the-prototype-design-pattern-in-python/) +* [Prototype - III](https://python-patterns.guide/gang-of-four/prototype/) +* [Prototype - Code](https://gist.github.com/pazdera/1122366) + +#### Javascript + +* [Prototype - I](https://www.digitalocean.com/community/conceptual_articles/prototype-design-pattern-in-javascript) +* [Prototype - II](https://www.oreilly.com/library/view/learning-javascript-design/9781449334840/ch09s07.html) +* [Prototype - III](https://www.patterns.dev/posts/prototype-pattern/) +* [Prototype - IV](https://vegibit.com/javascript-prototype-pattern/) + +### Factory + +#### Python +* [Factory - I](https://realpython.com/factory-method-python/) +* [Factory - II](https://refactoring.guru/design-patterns/factory-method/python/example) +* [Factory - III](https://stackabuse.com/the-factory-method-design-pattern-in-python/) +* [Factory - IV](https://python-patterns.guide/gang-of-four/factory-method/) +* [Factory - V](https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Factory.html) + + +#### Javascript + +* [Factory - I](https://blog.sessionstack.com/how-javascript-works-the-factory-design-pattern-4-use-cases-7b9f0d22151d) +* [Factory - II](https://www.oreilly.com/library/view/learning-javascript-design/9781449334840/ch09s10.html) +* [Factory - III](https://www.theodinproject.com/lessons/node-path-javascript-factory-functions-and-the-module-pattern) +* [Factory - IV](https://javascript.plainenglish.io/design-patterns-with-typescript-factory-method-cb190d7ce275) +* [Factory - V](https://www.digitalocean.com/community/tutorials/js-factory-pattern) diff --git a/non_dsa/lld/lld_2/java/singleton.md b/non_dsa/lld/lld_2/java/singleton.md new file mode 100644 index 0000000..337ef68 --- /dev/null +++ b/non_dsa/lld/lld_2/java/singleton.md @@ -0,0 +1,140 @@ +# Creational Design Patterns - Singleton + + + - [Key terms](#key-terms) + - [Design patterns](#design-patterns) + - [Creational design patterns](#creational-design-patterns) + - [Singleton](#singleton) + - [Builder](#builder) + - [Singleton](#singleton-1) + - [Problem](#problem) + - [Solution](#solution) + - [Simple singleton](#simple-singleton) + - [Thread-safe singleton](#thread-safe-singleton) + - [Double-checked locking](#double-checked-locking) + - [Summary](#summary) + + +## Key terms +### Design patterns +> A design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system. + +### Creational design patterns +> Creational design patterns provide various object creation mechanisms, which increase flexibility and reuse of existing code. + +### Singleton +> The singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. + +### Builder +> Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code. + +## Singleton + +### Problem + +* `Shared resource` - Imagine you have a class that is responsible for managing the database connection. You want to make sure that only one instance of this class exists in your application. If you create multiple instances of this class, you will end up with multiple database connections, which is not what you want. Similarly, there can be a class that is responsible for managing the logging mechanism. You want to make sure that only one instance of this class exists in your application. If you create multiple instances of this class, you will end up with multiple log files, which is not what you want. +* `Single access point` - Applications often require configuration. For example, you might want to configure the database connection parameters. You want to make sure that only one instance of this class exists in your application. A configuration class should have a single access point to the configuration parameters. If you create multiple instances of this class, you will end up with multiple configuration files. + +### Solution + +Singleton pattern is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance. To implement the Singleton patter, the following steps are required: +* `Constructor hiding` - The constructor of the singleton class should be private or protected. This will prevent other classes from instantiating the singleton class. +* `Global access point` - The singleton class should provide a global access point to get the instance of the singleton class. This global access point should be static and should return the same instance of the singleton class every time it is called. If the instance does not exist, it should create the instance and then return it. + +### Simple singleton + +The first step is to hide the constructor by making it private. This will prevent other classes from instantiating the singleton class. + +```java +public class Database { + private Database() { + } +} +``` + +The above code restricts the instantiation of the Database class. Now, we need to provide a global access point to get the instance of the Database class. We can do this by creating a static method that returns the instance of the Database class. If the instance does not exist, it should create the instance and then return it. + +```java +public class Database { + private static Database instance = new Database(); + + private Database() { + } + + public static Database getInstance() { + return instance; + } +} +``` + +To implement the getInstance() method, we need to create a static variable of the Database class. This variable will hold the instance of the Database class. We will initialize this variable to null. The getInstance() method will check if the instance variable is null. If it is null, it will create a new instance of the Database class and assign it to the instance variable. Finally, it will return the instance variable. This is known as lazy initialization. + +```java +public class Database { + private static Database instance = null; + + private Database() { + } + + public static Database getInstance() { + if (instance == null) { + instance = new Database(); + } + return instance; + } +} +``` + +### Thread-safe singleton +The above code is not thread-safe. If two threads call the getInstance() method at the same time, both threads will check if the instance variable is null. Both threads will find that the instance variable is null. Both threads will create a new instance of the Database class. This will result in two instances of the Database class. To make the above code thread-safe, we can make the getInstance() method synchronized. + +```java +public class Database { + private static Database instance = null; + + private Database() { + } + + public static synchronized Database getInstance() { + if (instance == null) { + instance = new Database(); + } + return instance; + } +} +``` + +### Double-checked locking + +The above code is thread-safe. However, it is not efficient. If two threads call the getInstance() method at the same time, both threads will check if the instance variable is null. Both threads will find that the instance variable is null. Both threads will wait for the lock to be released. Once the lock is released, one thread will create a new instance of the Database class. The other thread will wait for the lock to be released. Once the lock is released, it will create a new instance of the Database class. This will result in two instances of the Database class. To make the above code efficient, we can use double-checked locking. + +```java +public class Database { + private static Database instance = null; + + private Database() { + } + + public static Database getInstance() { + if (instance == null) { + synchronized (Database.class) { + if (instance == null) { + instance = new Database(); + } + } + } + return instance; + } +} +``` + +### Summary +* The singleton pattern is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance. +* Use cases of singleton pattern + * Shared resource like database connection, logging mechanism + * Object that should be instantiated only once like configuration object +* Hide the constructor of the singleton class by making it private so that other classes cannot instantiate the singleton class. +* Add a static method that returns the instance of the singleton class. If the instance does not exist, it should create the instance and then return it. +* Thread safety + * Make the `getInstance()` method synchronized. + * Use double-checked locking. \ No newline at end of file diff --git a/non_dsa/lld/lld_2/java/solid_principles_1.md b/non_dsa/lld/lld_2/java/solid_principles_1.md new file mode 100644 index 0000000..fc592a7 --- /dev/null +++ b/non_dsa/lld/lld_2/java/solid_principles_1.md @@ -0,0 +1,255 @@ +# SOLID principles 1: SRP and OCP +- [SOLID principles](#solid-principles) + - [Key terms](#key-terms) + - [SOLID principles](#solid-principles-1) + - [Single responsibility principle](#single-responsibility-principle) + - [Open/closed principle](#openclosed-principle) + - [Single responsibility principle](#single-responsibility-principle-1) + - [Case study - Design a bird](#case-study---design-a-bird) + - [Reasons to follow SRP](#reasons-to-follow-srp) + - [How/Where to spot violations of SRP?](#howwhere-to-spot-violations-of-srp) + - [Side-assignment alert](#side-assignment-alert) + - [Open/closed principle](#openclosed-principle-1) + - [Abstract classes and interfaces](#abstract-classes-and-interfaces) + - [Interface](#interface) + - [When to use abstract classes and interfaces?](#when-to-use-abstract-classes-and-interfaces) + - [Fixing OCP violation in the `Bird` class](#fixing-ocp-violation-in-the-bird-class) + - [Reading List](#reading-list) + +## Key terms +### SOLID principles +> SOLID is a mnemonic acronym for five design principles intended to make object-oriented designs more understandable, flexible, and maintainable. + +### Single responsibility principle +> There should never be more than one reason for a class/code unit to change. Every class should have only one responsibility. + +### Open/closed principle +> Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification. + +## Single responsibility principle + +> When designing our classes, we should aim to put related features together, so whenever they tend to change they change for the same reason. And we should try to separate features if they will change for different reasons. + +

+ Single responsibility principle +

+ +The Single Responsibility Principle states that a class should do one thing, and therefore it should have only a single reason to change. Only one potential change (database logic, logging logic, and so on.) in the software’s specification should be able to affect the specification of the class. + +This means that if a class is a data container, like a Book class or a Student class, and it has some fields regarding that entity, it should change only when we change the data model. + +### Case study - Design a bird + +To understand the SOLID principles, let us take the help of a bird. A bird is a living creature that can fly, eat, and make a sound. How can we design a bird? + +The simplest solution would be to create a `Bird` class with different attributes and methods. A bird could have the following attributes: +* Weight +* Colour +* Type +* Size +* BeakType + +A bird would also exhibit the following behaviours: +* Fly +* Eat +* Make a sound + +```mermaid +classDiagram + class Bird{ + +weight: int + +colour: string + +type: string + +size: string + +beakType: string + +fly() + +eat() + +makeSound() + } +``` + +The `Bird` class would look as follows: + +```java +public class Bird { + private int weight; + private String colour; + private String type; + private String size; + private String beakType; + + public void fly() { + ... + } + + public void eat() { + ... + } + + public void makeSound() { + ... + } +} +``` + +In order to understand the design further, let us try to implement the `fly` method. +Since each bird has a different method of flying, we would have to implement conditional statements to check the type of the bird and then call the appropriate method. + +```java +public void fly() { + if (type.equals("eagle")) { + flyLikeEagle(); + } else if (type.equals("penguin")) { + flyLikePenguin(); + } else if (type.equals("parrot")) { + flyLikeParrot(); + } +} +``` + +The above code exhibits the following problems: +* `Readability` - The code is not readable. It is difficult to understand what the code is doing. +* `Testing` - It is difficult to test the code. We would have to test each type of bird separately. +* `Reusability` - The code is not reusable. If we want to re-use the code of specific type of bird, we would have to change the above code. +* `Parallel development` - The code is not parallel development friendly. If multiple developers are working on the same code, they could face merge conflicts. +* `Multiple reasons to change` - The code has multiple reasons to change. If we want to change the way a type of bird flies, we would have to change the code in the `fly` method. + + +### Reasons to follow SRP +Apart from overcoming the problems mentioned above, there are other reasons to follow the SRP: +* Maintainability - Smaller, well-organized classes are easier to search than monolithic ones. +* Ease of testing – A class with one responsibility will have far fewer test cases. +* Lower coupling – Less functionality in a single class will have fewer dependencies. + +### How/Where to spot violations of SRP? +* A method with multiple `if-else` statements. An example would be the `fly` method of the `Bird` class. This is not a silver bullet, but it is a good indicator. There can be other reasons for multiple `if-else` statements such as business logic e.g. calculating the tax, checking access rights, etc. +* `Monster methods` or `God classes` - Methods that are too long and doing much more than the name suggests. This is a good indicator of a violation of SRP. + +```java +public saveToDatabase() { + // Connect to database + // Create a query + // Execute the query + // Create a user defined object + // Close the connection +} +``` +The above method is doing much more than the name suggests. It is connecting to the database, creating a query, executing the query, creating a user defined object, and closing the connection. This method violates the SRP. It should be split into multiple methods such as `connectToDatabase`, `createQuery`, `executeQuery`, `createUserDefinedObject`, and `closeConnection`. +* `Utility classes` - Utility classes are classes that contain only static methods which are used to perform some utility functions. Have a look at the utility package of Java [here](https://docs.oracle.com/javase/8/docs/api/java/util/package-summary.html). There is just way too many responsibilities of this package. + +### Side-assignment alert +* Identify the violations of SRP in [this](../code/src/../oop/src/main/java/com/scaler/lld/questions/Invoice.java) class. +* Refactor the code to follow SRP. + +--- + +## Open/closed principle +We identified a bunch of problems with the `Bird` class. Let us see the fly method again to spot another problem. + +```java +public void fly() { + if (type.equals("eagle")) { + flyLikeEagle(); + } else if (type.equals("penguin")) { + flyLikePenguin(); + } else if (type.equals("parrot")) { + flyLikeParrot(); + } +} +``` + +In the above code, we are checking the type of the bird and then calling the appropriate method. If we want to add a new type of bird, we would have to change the code in the `fly` method. This is a violation of the Open/Closed Principle. + +

+ Open/closed principle +

+ +**The Open/Closed Principle states that a class should be open for extension but closed for modification. This means that we should be able to add new functionality to the class without changing the existing code.** To add a new feature, we should ideally create a new class or method and have very little or no changes in the existing code. +In doing so, we stop ourselves from modifying existing code and causing potential new bugs in an otherwise happy application. We should be able to add new functionality without touching the existing code for the class. This is because whenever we modify the existing code, we are taking the risk of creating potential bugs. So we should avoid touching the tested and reliable (mostly) production code if possible. + +* A module will be said to be open if it is still available for extension. For example, it should be possible to add fields to the data structures it contains, or new elements to the set of functions it performs. +* A module will be said to be closed if [it] is available for use by other modules. This assumes that the module has been given a well-defined, stable description (the interface in the sense of information hiding). + +### Abstract classes and interfaces + +An abstract class is nothing but a class that is declared using the abstract keyword. It also allows us to declare method signatures using the abstract keyword (abstract method) and forces its subclasses to implement all the declared methods. Suppose if a class has a method that is abstract, then the class itself must be abstract. + +Abstract classes have no restrictions on field and method modifiers, while in an interface, all are public by default. We can have instance and static initialization blocks in an abstract class, whereas we can never have them in the interface. Abstract classes may also have constructors which will get executed during the child object's instantiation. + +Abstract classes can be defined using the `abstract` keyword. An abstract class can have abstract methods and non-abstract methods. An abstract method is a method that is declared without an implementation. It is a method that is declared using the `abstract` keyword and does not have a body. An abstract class can have a constructor and it gets executed when an object of the child class is created. An abstract class can have instance variables, static variables, instance methods, static methods, and abstract methods. + +```java +public abstract class Animal { + private String name; + private int age; + + public Animal(String name, int age) { + this.name = name; + this.age = age; + } + + public abstract void makeSound(); + + public void eat() { + System.out.println("Eating..."); + } +} +``` + +#### Interface + +An Interface in Java programming language is defined as an abstract type used to specify the behavior of a class. An interface in Java is a blueprint of a class. A Java interface contains static constants and abstract methods. The interface in Java is a mechanism to achieve abstraction. + +You can think of an interface as a completely abstract class that can only contain abstract methods. An interface is similar to a class, in that it contains methods and variables, but the methods declared in an interface are by default abstract (only method signature, no body). Interfaces cannot be used to create objects (in the example above, it is not possible to create an "Animal" object). An interface is not inherited by a class; it must be implemented by a class. + +```java +public interface Animal { + public void makeSound(); +} +``` + +### When to use abstract classes and interfaces? +* If multiple classes have common functionalities, we would like to use inheritance to avoid code duplication and also have fixed contracts so that the subclasses are forced to implement the common functionalities. +* If the common classes have common attributes, consider using abstract classes since they can have instance variables. +* If the common classes have common methods, consider using interfaces since they can have only abstract methods. However, the implementation of the methods can be different in the subclasses. Interfaces are also useful when we want to have multiple inheritance. + +### Fixing OCP violation in the `Bird` class + +Now that we have learnt about abstract classes and interfaces, let us fix the SRP and OCP violation in the `Bird` class. In order to fix the SRP violations, we would consider having a parent class `Bird` and child classes `Eagle`, `Penguin`, and `Parrot`. Since, different birds have the same attributes and behaviours, we would want to use classes. An instance of the `Bird` class does not make sense, hence we would use an abstract class. We can't use an interface since we would want to have instance variables. We would also want to have a fixed contract for the subclasses to implement the common functionalities. Hence, we would use an abstract class. +Now, our `Bird` class would look like this. + +```mermaid +classDiagram + Bird <|-- Eagle + Bird <|-- Penguin + Bird <|-- Parrot + class Bird{ + +weight: int + +colour: string + +type: string + +size: string + +beakType: string + +fly() + } + class Eagle{ + +fly() + } + class Penguin{ + +fly() + } + class Parrot{ + +fly() + } +``` + +## Reading List +* [SOLID vs CUPID vs GRASP](https://www.boldare.com/blog/solid-cupid-grasp-principles-object-oriented-design/#what-is-solid-and-why-is-it-more-than-just-an-acronym?-solid-vs.-cupid---is-the-new-always-better?) +* [Java and SRP](https://medium.com/swlh/java-packages-and-the-single-responsibility-principle-a23e151719d1) \ No newline at end of file diff --git a/non_dsa/lld/lld_2/java/solid_principles_2.md b/non_dsa/lld/lld_2/java/solid_principles_2.md new file mode 100644 index 0000000..7997912 --- /dev/null +++ b/non_dsa/lld/lld_2/java/solid_principles_2.md @@ -0,0 +1,308 @@ +# SOLID Principles 2: Liskov, Interface Segregation, and Dependency Inversion + +## Key Terms +### Liskov Substitution Principle +> Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. + +### Interface Segregation Principle +> Many client-specific interfaces are better than one general-purpose interface. + +### Dependency Inversion Principle +> Depend upon abstractions. Do not depend upon concrete classes. + +## Liskov Substitution Principle + +Let us take a look at our final version of the `Bird` class from [the last session](https://github.com/kanmaytacker/fundamentals/blob/master/oop/notes/04-solid-01.md#fixing-ocp-violation-in-the-bird-class). We started with a `Bird` class which had SRP and OCP violations. We now have a `Bird` abstract class which can be extended by the `Eagle`, `Penguin` and `Parrot` subclasses. + +```mermaid +classDiagram + Bird <|-- Eagle + Bird <|-- Penguin + Bird <|-- Parrot + class Bird{ + +weight: int + +colour: string + +type: string + +size: string + +beakType: string + +fly() + } + class Eagle{ + +fly() + } + class Penguin{ + +fly() + } + class Parrot{ + +fly() + } +``` + +We have also added a `fly()` method to the `Bird` class. All the subclasses of `Bird` have to implement this method. A penguin cannot fly, yet we have added a `fly()` method to the `Penguin` class. How can we handle this? +* `Dummy method` - We can add a dummy method to the `Penguin` class which does nothing. +* `Return null` +* `Throw an exception` + +In the above methods, we are trying to force a contract on a class which does not follow it. If we try to use a `Penguin` object in a place where we expect a `Bird` object, we could have unexpected outcomes. For example, if we call the `fly()` method on a `Penguin` object, we would get an exception. This is not what we want. We want to be able to use a `Penguin` object in a place where we expect a `Bird` object. We want to be able to call the `fly()` method on a `Penguin` object and get the same result as if we had called it on a `Sparrow` object. This is where the Liskov Substitution Principle comes into play. + +```java +List birds = List.of(new Eagle(), new Penguin(), new Parrot()); +for (Bird bird : birds) { + bird.fly(); +} +``` +This is a violation of the Liskov Substitution Principle. The Liskov Substitution Principle states that objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. In other words, if we have a `Bird` object, we should be able to replace it with an instance of its subclasses without altering the correctness of the program. In our case, we cannot replace a `Bird` object with a `Penguin` object because the `Penguin` object requires special handling. + +![Liskov Substitution Principle](https://www.globalnerdy.com/wordpress/wp-content/uploads/2009/07/liskov_substitution_principle.jpg) + +### Creating new abstract classes + +A way to solve the issue with the `Penguin` class is to create a new set of abstract classes, `FlyableBird` and `NonFlyableBird`. The `FlyableBird` class will have the `fly()` method and the `NonFlyableBird` class will not have the `fly()` method. The `Penguin` class will extend the `NonFlyableBird` class and the `Eagle` and `Parrot` classes will extend the `FlyableBird` class. This way, we can ensure that the `Penguin` class does not have to implement the `fly()` method. + +```mermaid +classDiagram + class Bird{ + +weight: int + +colour: string + +type: string + +size: string + +beakType: string + } + class FlyableBird{ + +fly() + } + Bird <|-- FlyableBird + FlyableBird <|-- Eagle + FlyableBird <|-- Parrot + + class NonFlyableBird{ + +eat() + } + Bird <|-- NonFlyableBird + NonFlyableBird <|-- Penguin + + class Eagle{ + +fly() + } + class Penguin{ + +eat() + } + class Parrot{ + +fly() + } +``` + +This is an example of multi-level inheritance. The issue with the above approach is that we are tying behaviour to the class hierarchy. If we want to add a new type of behaviour, we will have to add a new abstract class. For instance if we can have birds that can swim and birds that cannot swim, we will have to create a new abstract class `SwimableBird` and `NonSwimableBird` and add them to the class hierarchy. But now how do you extends from two abstract classes? You can't. Then we would have to create classes with composite behaviours such as `SwimableFlyableBird` and `SwimableNonFlyableBird`. + +```mermaid +classDiagram + class Bird{ + +weight: int + +colour: string + +type: string + +size: string + +beakType: string + } + class SwimableFlyableBird{ + +fly() + +swim() + } + Bird <|-- SwimableFlyableBird + SwimableFlyableBird <|-- Swan + + class NonSwimableFlyableBird{ + +fly() + } + Bird <|-- NonSwimableFlyableBird + NonSwimableFlyableBird <|-- Eagle + + class SwimableNonFlyableBird{ + +swim() + } + + Bird <|-- SwimableNonFlyableBird + SwimableNonFlyableBird <|-- Penguin + + class NonSwimableNonFlyableBird{ + +eat() + } + + Bird <|-- NonSwimableNonFlyableBird + NonSwimableNonFlyableBird <|-- Toy Bird + + class Swan{ + +fly() + +swim() + } + + class Eagle{ + +fly() + } + + class Penguin{ + +eat() + } + + class Toy Bird{ + +makeSound() + } +``` + +If we want to add a new type of behaviour, we will have to add a new abstract class. This is why we should not tie behaviour to the class hierarchy. + +### Creating new interfaces + +We can solve the issue with the `Penguin` class by creating new interfaces. We can create an `Flyable` interface and an `Swimmable` interface. The `Penguin` class will implement the `Swimmable` interface and the `Eagle` and `Parrot` classes will implement the `Flyable` interface. This way, we can ensure that the `Penguin` class does not have to implement the `fly()` method. + +```mermaid +classDiagram + class Bird{ + <> + +weight: int + +colour: string + +type: string + +size: string + +beakType: string + + +makeSound() + } + + Bird <|-- Eagle + Bird <|-- Parrot + Bird <|-- Penguin + class Flyable{ + <> + +fly()* + } + Flyable <|-- Eagle + Flyable <|-- Parrot + + class Swimmable{ + <> + +swim()* + } + Swimmable <|-- Penguin + + class Eagle{ + +fly() + } + class Penguin{ + +swim() + } + class Parrot{ + +fly() + } +``` + +Since we are not tying behaviour to the class hierarchy, we can add new types of behaviour without having to add new abstract classes. For instance, if we want to add a new type of behaviour, we can create a new interface `CanSing` and add it to the class hierarchy. + +### Summary +* The Liskov Substitution Principle states that objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. +* To identify violations, we can check if we can replace a class with its subclasses having to handle special cases and expect the same behaviour. +* Prefer using interfaces over abstract classes to implement behaviour since abstract classes tend to tie behaviour to the class hierarchy. + +## Interface Segregation Principle + +Segregation means keeping things separated, and the Interface Segregation Principle is about separating the interfaces. + +The principle states that many client-specific interfaces are better than one general-purpose interface. Clients should not be forced to implement a function they do no need. Declaring methods in an interface that the client doesn’t need pollutes the interface and leads to a “bulky” or “fat” interface + +![Interface Segregation Principle](https://www.globalnerdy.com/wordpress/wp-content/uploads/2009/07/interface_segregation_principle_thumb.jpg) + +A client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use. In other words, we should not create fat interfaces. A fat interface is an interface that has too many methods. If we have a fat interface, we will have to implement all the methods in the interface even if we don’t use them. This is known as the interface segregation principle. + +Let us take the example of our `Bird` class. To not tie the behaviour to the class hierarchy, we created an interface `Flyable` and implemented it in the `Eagle` and `Parrot` classes. + +```java +public interface Flyable { + void fly(); + void makeSound(); +} +``` +Along with the `fly()` method, we also have the `makeSound()` method in the `Flyable` interface. This is because the `Eagle` and `Parrot` classes both make sounds when they fly. But what if we have a class that implements the `Flyable` interface? The class does not make a sound when it flies. This is a violation of the interface segregation principle. We should not have the `makeSound()` method in the `Flyable` interface. + +Larger interfaces should be split into smaller ones. By doing so, we can ensure that implementing classes only need to be concerned about the methods that are of interest to them. If a class exposes so many members that those members can be broken down into groups that serve different clients that don’t use members from the other groups, you should think about exposing those member groups as separate interfaces. + +Precise application design and correct abstraction is the key behind the Interface Segregation Principle. Though it'll take more time and effort in the design phase of an application and might increase the code complexity, in the end, we get a flexible code. + +## Dependency Inversion Principle +The principle of dependency inversion refers to the decoupling of software modules. This way, instead of high-level modules depending on low-level modules, both will depend on abstractions. If the OCP states the goal of OO architecture, the DIP states the primary mechanism for achieving that goal. + +The general idea of this principle is as simple as it is important: High-level modules, which provide complex logic, should be easily reusable and unaffected by changes in low-level modules, which provide utility features. To achieve that, you need to introduce an abstraction that decouples the high-level and low-level modules from each other. Dependency inversion principle consists of two parts: +* High-level modules should not depend on low-level modules. Both should depend on abstractions. +* Abstractions should not depend on details. Details should depend on abstractions. + +![Dependency Inversion Principle](https://www.globalnerdy.com/wordpress/wp-content/uploads/2009/07/dependency_inversion_principle_thumb.jpg) + +Our bird class looks pretty neat now. We have separated the behaviour into different lean interfaces which are implemented by the classes that need them. When we add new sub-classes we identify an issue. For birds that have the same behaviour, we have to implement the same behaviour multiple times. + +```java +public class Eagle implements Flyable { + @Override + public void fly() { + System.out.println("Eagle is gliding"); + } +} + +public class Sparrow implements Flyable { + @Override + public void fly() { + System.out.println("Sparrow is gliding"); + } +} +``` + +The above can be solved by adding a default method to the `Flyable` interface. This way, we can avoid code duplication. +But which method should be the default implementation? What if in future we add more birds that have the same behaviour? We will have to change the default implementation or either duplicate the code. + +Instead of default implementations, let us abstract the common behaviours to a separate helper classes. We will create a `GlidingBehaviour` class and a `FlappingBehaviour` class. The `Eagle` and `Sparrow` classes will implement the `Flyable` interface and use the `GlidingBehaviour` class. The `Parrot` class will implement the `Flyable` interface and use the `FlappingBehaviour` class. + +```java +public class Eagle implements Flyable { + private GlidingBehaviour glidingBehaviour; + + public Eagle() { + this.glidingBehaviour = new GlidingBehaviour(); + } + + @Override + public void fly() { + glidingBehaviour.fly(); + } +} +``` + +Now we have a problem. The `Eagle` class is tightly coupled to the `GlidingBehaviour` class. If we want to change the behaviour of the `Eagle` class, we will have to open the Eagle class to change the behaviour. This is a violation of the dependency inversion principle. We should not depend on concrete classes. We should depend on abstractions. + +Naturally, we rely on interfaces as the abstraction. We create a new interface `FlyingBehaviour` and implement it in the `GlidingBehaviour` and `FlappingBehaviour` classes. The `Eagle` class will now depend on the `FlyingBehaviour` interface. + + +```java + interface FlyingBehaviour{ + void fly() + } + class GlidingBehaviour implements FlyingBehaviour{ + @Override + public void fly() { + System.out.println("Eagle is gliding"); + } + } + ... + + class Eagle implements Flyable { + private FlyingBehaviour flyingBehaviour; + + public Eagle() { + this.flyingBehaviour = new GlidingBehaviour(); + } + + @Override + public void fly() { + flyingBehaviour.fly(); + } + } +``` + +## Reading list +* [LSP](http://web.archive.org/web/20151128004108/http://www.objectmentor.com/resources/articles/lsp.pdf) +* [SOLID - Recap](https://www.cs.odu.edu/~zeil/cs330/latest/Public/solid/) \ No newline at end of file diff --git a/non_dsa/lld/lld_2/java/uml-diagrams.md b/non_dsa/lld/lld_2/java/uml-diagrams.md new file mode 100644 index 0000000..15f9799 --- /dev/null +++ b/non_dsa/lld/lld_2/java/uml-diagrams.md @@ -0,0 +1,369 @@ +# UML - Use case and class diagrams +- [UML - Use case and class diagrams](#uml---use-case-and-class-diagrams) + - [UML diagrams](#uml-diagrams) + - [Structural diagrams](#structural-diagrams) + - [Behavioral diagrams](#behavioral-diagrams) + - [Use case diagram](#use-case-diagram) + - [Assignment](#assignment) + - [Solution](#solution) + - [Class diagram](#class-diagram) + - [Entities and attributes](#entities-and-attributes) + - [Attributes](#attributes) + - [Methods](#methods) + - [Interfaces and abstract classes](#interfaces-and-abstract-classes) + - [Relationships](#relationships) + - [Cardinality](#cardinality) + +## UML diagrams + +> UML is a general-purpose, developmental, modeling language in the field of software engineering that is intended to provide a standard way to visualize the design of a system. + +Maintainability is a key factor in software development. +A codebase is worked on by many people over a long period of time. For a new developer to be able to understand the codebase, it is important to have a good documentation. UML diagrams are a good way to document the design of a system. +Apart from documentation, UML diagrams are also used to communicate the design of a system to other developers. +It is very important to document our design decisions so that we can refer to them later and even get feedback from other developers. + +UML diagrams are a great way to visualize the design of a system. They are a great way to communicate with other developers and stakeholders. They are also a great way to document the design of a system. There are two main types of UML diagrams - +* `Structural diagrams` - These diagrams are used to describe the structure of a system. They are used to describe the classes and objects in a system. They are also used to describe the relationships between classes and objects. +* `Behavioral diagrams` - These diagrams are used to describe the behavior of a system. They are used to describe the interactions between classes and objects. They are also used to describe the sequence of events in a system. + +### Structural diagrams + +> Structural diagrams are used to describe the structure of a system. They are used to describe the classes and objects in a system. They are also used to describe the relationships between classes and objects. + +There are four main types of structural diagrams - +* `Class diagram` - This diagram is used to describe the classes and objects in a system. It is also used to describe the relationships between classes and objects. +* `Object diagram` - This diagram is used to describe the objects in a system. It is also used to describe the relationships between objects. +* `Component diagram` - This diagram is used to describe the components in a system. It is also used to describe the relationships between components. +* `Package diagram` - This diagram is used to describe the packages in a system. It is also used to describe the relationships between packages. + +### Behavioral diagrams + +> Behavioral diagrams are used to describe the behavior of a system. They are used to describe the interactions between classes and objects. They are also used to describe the sequence of events in a system. + +There are four main types of behavioral diagrams - +* `Use case diagram` - This diagram is used to describe the use cases in a system and the actors that interact with the system. +* `Sequence diagram` - This diagram is used to describe the sequence of events in a system. +* `Activity diagram` - This diagram is used to describe the flow of control in a system. +* `State diagram` - This diagram is used to describe the state of an object in a system. + +Learn more about different types of UML diagrams [here](https://creately.com/blog/diagrams/uml-diagram-types-examples/). + +#### Use case diagram + +> A use case diagram is a behavioral diagram that is used to describe the use cases in a system and the actors that interact with the system. + +Use case diagrams give a graphic overview of the actors involved in a system, different functions needed by those actors and how these different functions interact. It’s a great starting point for any project discussion because you can easily identify the main actors involved and the main processes of the system. + +Some main components of a use case diagram are - +* `System boundary` - This is the boundary of the system. It is the boundary of the system that we are designing. It contains all the use cases that are part of the system. All the internal elements of the system are part of the system boundary, but the external elements are not part of the system boundary. +* `Use case` - This represents a functionality or a feature that is supported by a system. A use case is represented by an ellipse/oval. The name of the use case is written inside the ellipse/oval. +* `Actor` - This represents a person or a system that interacts with the system. An actor is represented by a stick figure. +* `Relationships` - Not all use cases will be available for all actors. For instance, a user can login but cannot change their email. An admin can login and change emails. We draw a line between actors and their supported use cases to represent the relationships between them. +* `Include` - This is a relationship between two use cases. It represents that one use case is a subset of another use case. For instance, a use case to create an order has a dependency on the payment use case. We draw an arrow from the create order use case to the payment use case to represent this relationship. +* `Extend` - This is a relationship between two use cases. It represents that one use case can be extended by another use case. For instance, there can be multiple ways to login such as login with email, login with phone number, login with social media. We draw an arrow from the different login use cases to the login use case to represent this relationship. + +## Assignment + +* Draw a use case diagram for Scaler Academy. +* Your use case diagram should have at least 5 use cases. +* Your use case diagram should have at least 2 actors. +* Introduce a use case that should depend on another use case. (Use include relationship) +* Introduce a use case that should extend another use case. (Use extend relationship) + +### Solution +
+Actors +
    +
  • Student
  • +
  • Instructor
  • +
+
+ +
+ +Use cases +
    +
  • Join class
  • +
  • Schedule mentor session
  • +
  • Raise TA request
  • +
  • Upload class notes
  • +
  • Submit solution
  • +
  • Check mentor availability
  • +
  • Check plagiarism
  • +
  • Join as Host
  • +
  • Join as Audience
  • +
+
+ +
+ +Include relationship + +
    +
  • Schedule mentor session depends on Check mentor availability
  • +
  • Submit solution depends on Check plagiarism
  • +
+
+ +
+ +Extend relationship + +
    +
  • Join as Host extends Join class
  • +
  • Join as Audience extends Join class
  • +
+
+ +
+ +Use case diagram + + +```plantuml +@startuml +left to right direction + +actor Student +actor Instructor + +rectangle Scaler { + usecase "Join class" as Join + usecase "Schedule mentor session" as Schedule + usecase "Raise TA request" as Raise + usecase "Upload class notes" as Upload + usecase "Submit solution" as Submit + + usecase "Join as Host" as Host + usecase "Join as Audience" as Audience + + (Host) .> (Join) : extends + (Audience) .> (Join) : extends + + usecase "Check mentor availability" as Check + (Schedule) .> (Check) : includes +} + +Student --> Join +Student --> Schedule +Student --> Raise +Student --> Submit + +Instructor --> Join +Instructor --> Submit +Instructor --> Upload +@enduml +``` +Learn more about PlantUML [here](https://plantuml.com/). +
+ + +## Class diagram + +A class diagram is a graphical representation of a system's low level implementation and describes the realtionships between the various components of the system. A class diagram represents the following components of a system: +* Classes +* Interfaces +* Abstract classes + +The following data points about the relationships between the components are also represented: +* Which classes implement which interfaces? +* Which class is a subclass? +* Which class is a superclass? +* Which class is an attribute of another class? + +### Entities and attributes + +A class is represented by a rectangle with the name of the class in the middle. The attributes and methods of the class are presented in the rectangle. The attributes are presented in the top half of the rectangle and the methods are presented in the bottom half of the rectangle. + +```mermaid +classDiagram + class Person { + -String name + -String address + -String phone + -String email + +isStudent() bool + +validate(Person)$ bool + } +``` + +#### Attributes + +Attributes are the properties of a class. An attribute is represented in the form of +> [access modifier] [attribute name]: [attribute type] +The following are parts of an attribute: +* `Access modifier` - Visibility of the attribute. It can be one of the following: + * `+` public + * `-` private + * `#` protected + * `~` package +* `Attribute name` - Name of the attribute +* `Attribute type` - Type of the attribute. It can be a primitive type or a class type. + +#### Methods + +Methods are the operations that can be performed on a class. A method is represented in the form of +> [access modifier] [method name]([parameter list]): [return type] + +The following are parts of a method: +* `Access modifier` - Visibility of the method. Same as the access modifier of an attribute. +* `Method name` - Name of the method +* `Parameter list` - List of parameters that the method takes but instead of the parameter name, the parameter type is used. The parameters are separated by commas. +* `Return type` - Type of the value returned by the method. It can be a primitive type or a class type. + +Static methods and attributes are underlined. + + +#### Interfaces and abstract classes + +An interface is a contract that a class must implement. An interface is represented by a rectangle with the name of the interface in the middle surrounded by an angled bracket. The methods of the interface are presented in the rectangle. + +```mermaid +classDiagram + class Walkable { + <> + +walk() + } +``` + +Similarly, abstract classes and methods are italicized. + +```mermaid +classDiagram + class Walkable { + <> + #int legs* + +walk() + } +``` + +### Relationships + +There are 2 major types of relationships between classes: +* `Inheritance` - A relationship between a superclass and a subclass. The subclass inherits the attributes and methods of the superclass. +* `Association` - A relationship between 2 classes. The classes are associated with each other. The association can be one of the following: + * `Aggregation` - A relationship between 2 classes where the lifetime of the child class is dependent on the lifetime of the parent class. The child class can exist without the parent class. + * `Composition` - A relationship between 2 classes where the lifetime of the child class is dependent on the lifetime of the parent class. The child class cannot exist without the parent class. + +Inheritance is represented by a line with an arrow pointing to the superclass. Sometimes the arrow is replaced by a triangle. + +```mermaid +classDiagram + class Walkable { + <> + +walk() + } + + class Person { + -String name + -String address + -String phone + -String email + +isStudent() bool + +walk() + +validate(Person)$ bool + } + + Person --|> Walkable +``` + +Aggregation is represented by a line with a diamond at the end of the line. + +```mermaid +classDiagram + class Person { + -String name + -String address + -String phone + -String email + +isStudent() bool + +validate(Person)$ bool + } + class Student { + -Person person + } + Student --o Person +``` + +Composition is represented by a line with a filled diamond at the end of the line. + +```mermaid +classDiagram + class Person { + -String name + -String address + -String phone + -String email + +isStudent() bool + +validate(Person)$ bool + } + class Student { + -Person person + } + Student --* Person +``` + +#### Cardinality + +> Cardinality is the maximum times an entity can relate to an instance with another entity or entity set. + +> the number of interactions entities have with each other. + +**One to One (1:1)** +> A "one-to-one" relationship is seen when one instance of entity 1 is related to only one instance of entity 2 and vice-versa + +A student can only have one email address and one email address can be associated with only one student. + +```mermaid +classDiagram + class Student { + -String name + -String email + } + class Email { + -String email + } + Student --o Email +``` + +An attribute shared by both entities can be added to either of the entities. + +**One to Many or Many to one (1:m or m:1)** +> When one instance of entity 1 is related to more than one instance of entity 2, the relationship is referred to as "one-to-many. + +A student can only be associated with one batch, but a batch can have many students. + +```mermaid +classDiagram + class Student { + -String name + -Batch batch + } + class Batch { + -String name + -Student[] students + } + Student --o Batch +``` + +An attribute shared by both entities can only be added to the entity which has multiple instances i.e. the M side. + +**Many to Many (m:n)** + +> When multiple instances of entity 1 are linked to multiple instances of entity 2, we have a "many-to-many" relationship. Imagine a scenario where an employee is assigned more than one project. + +A student can attend multiple classes and a class can have multiple students. + +```mermaid +classDiagram + class Student { + -String name + -Class[] classes + } + class Class { + -String name + -Student[] students + } + Student --o Class +``` diff --git a/non_dsa/lld/lld_2/python/SOLID principles - Liskov, Interface Segregation, and Dependency Inversion.pdf b/non_dsa/lld/lld_2/python/SOLID principles - Liskov, Interface Segregation, and Dependency Inversion.pdf new file mode 100644 index 0000000..dd131fe Binary files /dev/null and b/non_dsa/lld/lld_2/python/SOLID principles - Liskov, Interface Segregation, and Dependency Inversion.pdf differ diff --git a/non_dsa/lld/lld_2/python/SOLID principles - SRP and OCP.pdf b/non_dsa/lld/lld_2/python/SOLID principles - SRP and OCP.pdf new file mode 100644 index 0000000..eb5e94d Binary files /dev/null and b/non_dsa/lld/lld_2/python/SOLID principles - SRP and OCP.pdf differ diff --git a/non_dsa/lld/lld_3/java/design_book_my_show.md b/non_dsa/lld/lld_3/java/design_book_my_show.md new file mode 100644 index 0000000..02b35e6 --- /dev/null +++ b/non_dsa/lld/lld_3/java/design_book_my_show.md @@ -0,0 +1,266 @@ +# Design BookMyShow + +## Context + +BookMyShow is India's biggest online movie and events ticketing brand. The website caters to ticket sales for movies, plays, concerts and sporting events via the online platform. + +BookMyShow is an online ticketing facility like Movietickets.com, Explara and Ticketmaster.com. BookMyShow took the primary services provided by these two websites and consolidated it into one website for movies, plays, events and sports tickets. Apart from being an online ticketing portal, BookMyShow offers information about upcoming movies and events, show timings, venue details and artist bios. + +![BookMyShow](https://ideausher.com/wp-content/uploads/2021/09/Frame-14.jpg) + +## Requirements gathering + +What are some questions you would ask to gather requirements? + +1. Can a cinema hall have multiple screens/halls? +2. Can a movie be shown in multiple halls? +3. Can a movie be of multiple languages? +4. Can a user book multiple tickets for a single movie? +5. Are there multiple types of tickets? +6. How does user find a movie? +7. How does a user book a ticket? +8. How does a user pay for a ticket? +9. How does a new movie get added to the system? +10. Are there any discounts available? + +## Requirements + +Build an online movie ticket booking system that can support the following requirements: + +* Support for multiple cities +* Each city will have multiple cinemas +* Each cinema can have multiple halls +* Each hall will play one movie at a time +* A cinema will play multiple movies +* Each hall can have multiple types of seats + * GOLD + * DIAMOND + * PLATINUM +* Allow the user to search a movie by name +* Allow the user to filter movies by the following fields + * Location + * Cinema + * Language + * Rating + * Category +* Each movie can have multiple slots +* Users can book tickets and pay via multiple payment methods + * UPI + * Credit Card + * Netbanking +* A user can apply a coupon or a promo code at checkout +* A user can see the availability of seats in a hall +* The price of a ticket will be decided by multiple parameters + * Seat Type + * Day of the week + * Time of the Day + * Movie + * Cinema hall +* A user can also cancel or update a booking +* A user cannot book or cancel after the cutoff time which is 1 hour before the movie starts + + +## Use case diagrams + +Are the requirements clear enough to define use cases? +If not, try to think of the actors and their interactions with the system. + +### Actors + +What would be the actors in this system? + +1. User +2. Admin + +### Use cases + +What would be the use cases i.e. the interactions between the actors and the system? + +#### Actor 1 + +Name of actor - `Admin` + +Use cases: +* Add a new movie +* Add a new cinema hall +* Add a new show + +#### Actor 2 + +Name of actor - `User` + +Use cases: +* Search for a movie +* Book a ticket +* Pay for a ticket +* Cancel a ticket + + +--- + +Draw the use case diagram. + +```plantuml +@startuml +left to right direction +actor User +actor Admin + +rectangle BookYourShow { + User --> (Search for a movie) + User --> (Book a ticket) + User --> (Pay for a ticket) + User --> (Cancel a ticket) + + Admin --> (Add a new movie) + Admin --> (Add a new cinema hall) + Admin --> (Add a new show) +} + +@enduml +``` + +## Class diagrams + +What will be the major classes and their attributes? + +* City + * name + * Theaters +* Theater + * name + * address + * Halls + * Shows +* Hall + * name + * Seats + * Shows +* Seat + * Number + * Type - `GOLD, DIAMOND, PLATINUM` +* Show + * Movie + * start time + * duration + * Language + * ShowSeats +* Movie + * name + * rating + * category + * languages + * Shows +* Ticket + * amount + * Seats + * Show + * User + * Payment + * Status +* Payment + * amount + * Mode - `UPI, CREDIT_CARD, NETBANKING` + * Status - `SUCCESS, FAILED` + * Ticket + +Draw the class diagram. +```mermaid +classDiagram + class City { + -String name + -Theater[] theaters + } + + class Theater { + -String name + -String address + -Hall[] halls + -Show[] shows + } + + class Hall { + -String name + -Seat[] seats + -Show[] shows + } + + class Seat { + -String number + -String type + } + + class Show { + -Movie movie + -Date startTime + -int duration + -String language + -ShowSeat[] showSeats + } + + class Movie { + -String name + -int rating + -String category + -String[] languages + -Show[] shows + } + + class ShowSeat { + -Seat seat + -Show show + -SeatStatus status + } + + class SeatStatus { + <> + AVAILABLE + BOOKED + } + + class Ticket { + -double amount + -Seat[] seats + -Show show + -User user + -Payment payment + -TicketStatus status + } + + class Payment { + -double amount + -PaymentMode mode + -PaymentStatus status + -Ticket ticket + } + + class PaymentMode { + <> + UPI + CREDIT_CARD + NETBANKING + } + + class PaymentStatus { + <> + SUCCESS + FAILED + } + + class TicketStatus { + <> + BOOKED + CANCELLED + } +``` + +## API design + +What will be some APIs that you would design for this system? + +Look at the use cases and try to design APIs for each of them. + +You can simply write the APIs in the following format: +`API name` - `HTTP method` - `URL` - `?Request body` - `?Response body` + +You could also use a tool like [Swagger](https://swagger.io/) to design the APIs or follow [this](https://github.com/jamescooke/restapidocs) repository for a simple way to use Markdown to structure your API documentation. diff --git a/non_dsa/lld/lld_3/java/design_parking_lot.md b/non_dsa/lld/lld_3/java/design_parking_lot.md new file mode 100644 index 0000000..c61b930 --- /dev/null +++ b/non_dsa/lld/lld_3/java/design_parking_lot.md @@ -0,0 +1,473 @@ +# Design a parking lot +- [Design a parking lot](#design-a-parking-lot) + - [Requirements gathering](#requirements-gathering) + - [Requirements](#requirements) + - [Use case diagrams](#use-case-diagrams) + - [Actors](#actors) + - [Use cases](#use-cases) + - [Actor 1](#actor-1) + - [Actor 2](#actor-2) + - [Actor 3](#actor-3) + - [API design](#api-design) + - [Admin APIs](#admin-apis) + - [Parking lot APIs](#parking-lot-apis) + - [Parking spot APIs](#parking-spot-apis) + - [Parking attendant APIs](#parking-attendant-apis) + - [Check empty slots](#check-empty-slots) + - [Issue a ticket](#issue-a-ticket) + - [Collect payment](#collect-payment) + - [Checkout](#checkout) + - [Class diagram](#class-diagram) + +> A parking lot or car park is a dedicated cleared area that is intended for parking vehicles. In most countries where cars are a major mode of transportation, parking lots are a feature of every city and suburban area. Shopping malls, sports stadiums, megachurches, and similar venues often feature parking lots over large areas +[Reference](https://github.com/tssovi/grokking-the-object-oriented-design-interview/blob/master/object-oriented-design-case-studies/design-a-parking-lot.md) + +![Parking Lot]( + https://github.com/tssovi/grokking-the-object-oriented-design-interview/raw/master/media-files/parking-lot.png) + +> Parking lot is an open area designated for parking cars. We will design a parking lot where a certain number of cars can be parked for a certain amount of time. The parking lot can have multiple floors where each floor carries multiple slots. Each slot can have a single vehicle parked in it. +[Reference](https://medium.com/double-pointer/system-design-interview-parking-lot-system-ff2c58167651) + + +![Parking Lot](https://miro.medium.com/max/640/1*-6QRtfh6OrHJBb7nJvsCVA.jpeg) + +## Requirements gathering + +What are some questions you would ask to gather requirements? +``` +1. Can a parking lot have multiple floors? +2. Can a parking lot have multiple entrances? +3. Can a parking lot have multiple exits? +4. Can a parking lot have multiple types of vehicles? +5. Can we park any type of vehicle in any slot? +6. How do we get a ticket? +7. How do we know if a slot is empty? +8. How are we allocated a slot? +9. How do we pay for parking? +10. What are the multiple ways to pay for parking? +``` + +## Requirements +What will be 10 requirements of the system, according to you? +Do not worry about the correctness of the requirements, just write down whatever comes to your mind. +Your job is not to generate the requirements, but get better at understanding problem statements and anticipating the functionalities your application might need. + +Build an online parking lot management system that can support the following requirements: +* Should have multiple floors. +* Multiple entries and exit points. +* A person has to collect a ticket at entry and pay at or before exit. +* Pay at: + * Exit counter (Cash to the parking attendant) + * Dedicated automated booth on each floor + * Online +* Pay via: + * Cash + * Credit Card + * UPI +* Allow entry for a vehicle if a slot is available for it. Show on the display at entry if a slot is not available. +* Parking Spots of 3 types: + * Large + * Medium + * Small +* A car can only be parked at its slot. Not on any other (even larger). +* A display on each floor with the status of that floor. +* Fees calculated based on per hour price: e.g. 50 rs for the first hour, then 80 rs per extra hour. + * Small - 50, 80 + * Medium - 80, 100 + * Large - 100, 120 + +## Use case diagrams + +Are the requirements clear enough to define use cases? +If not, try to think of the actors and their interactions with the system. + +### Actors +What would be the actors in this system? +1. Customer +2. Parking Attendant, Operator +3. Admin + +### Use cases + +What would be the use cases i.e. the interactions between the actors and the system? + +#### Actor 1 + +Name of the actor - `Admin` + +Use cases: `CRUD` +1. `Create a parking lot` +2. `Create a parking floor` +3. `Add new parking spots` +4. `Update status of a parking spot` + +#### Actor 2 + +Name of the actor - `Parking attendant` +Use cases: +1. `Check empty slots` +2. `Issue a ticket` - `Allocating a slot` +3. `Collect payment` +4. `Checkout` - `Has the user paid?` + +#### Actor 3 + +Name of the actor - `Customer` +Use cases: +1. `Pay` - `Pay online`, `Pay at exit gate` +2. `Check status` + +Add more actors and their use cases as needed. + +```plantuml +@startuml +left to right direction +actor ParkingAttendant +actor Customer +actor Admin + +rectangle FastAndCalm { + Admin --> (Add a parking lot) + Admin --> (Add a parking floor) + Admin --> (Add a parking spot) + Admin --> (Update status of parking spot) + + usecase "Pay" as Pay + usecase "Pay Online" as PayOnline + usecase "Pay Cash" as PayCash + + Customer --> (Pay) + Customer --> (Check spot's status) + + PayOnline .> (Pay) : extends + PayCash .> (Pay) : extends + + + ParkingAttendant --> (Check empty slots) + ParkingAttendant --> (Issue a ticket) + ParkingAttendant --> (Collect payment) + ParkingAttendant --> (Checkout) + + (Issue a ticket) .> (Allocate a slot) : includes + Checkout .> (CheckPaymentStatus) : includes +} +@enduml +``` + +## API design + +What will be some APIs that you would design for this system? + +Look at the use cases and try to design APIs for each of them. + +You can simply write the APIs in the following format: +`API name` - `HTTP method` - `URL` - `?Request body` - `?Response body` + +You could also use a tool like [Swagger](https://swagger.io/) to design the APIs or follow [this](https://github.com/jamescooke/restapidocs) repository for a simple way to use Markdown to structure your API documentation. + +### Admin APIs + +All the various use cases are simple CRUD operations. We can design the following APIs for the admin: + +#### Parking lot APIs +* `createParkingLot` - `POST /parking-lot` - Request body: `ParkingLot` +* `getParkingLot` - `GET /parking-lot/{id}` - Response body: `ParkingLot` +* `getAllParkingLots` - `GET /parking-lot` - Response body: `List` +* `updateParkingLot` - `PUT /parking-lot/{id}` - Request body: `ParkingLot` +* `deleteParkingLot` - `DELETE /parking-lot/{id}` + +Similarly, we can design APIs for `ParkingFloor`, `ParkingSpot`. + +#### Parking spot APIs +* `createParkingSpot` - `POST /parking-spot` - Request body: `ParkingSpot` +* `getParkingSpot` - `GET /parking-spot/{id}` - Response body: `ParkingSpot` +* `getAllParkingSpots` - `GET /parking-spot` - Response body: `List` +* `updateParkingSpot` - `PUT /parking-spot/{id}` - Request body: `ParkingSpot` +* `deleteParkingSpot` - `DELETE /parking-spot/{id}` + +You might also want an API to `Update status of a parking spot`. This can be done by using the existing `updateParkingSpot` API or by creating a new API that only updates the status of the parking spot. + +* `updateParkingSpotStatus` - `PUT /parking-spot/{id}/status` - Request body: `ParkingSpotStatus` +* `getParkingSpotStatus` - `GET /parking-spot/{id}/status` - Response body: `ParkingSpotStatus` + +### Parking attendant APIs + +Use cases: +1. `Check empty slots` +2. `Issue a ticket` - `Allocating a slot` +3. `Collect payment` +4. `Checkout` - `Has the user paid?` + +#### Check empty slots + +Let us look at the various requirements for a parking spot: +* CRUD on parking spots +* Get all parking spots +* Get all available parking spots + +We can augment our current `getAllParkingSpots` API by adding a query parameter to filter the parking spots based on their status. This will allow us to get all the available parking spots as well. + +**Get all parking spots** +* `getAllParkingSpots` - `GET /parking-spot` - Response body: `List` + +**Get all available parking spots** +* `getAllParkingSpots` - `GET /parking-spot?status=AVAILABLE` - Response body: `List` + +**Get all occupied parking spots** +* `getAllParkingSpots` - `GET /parking-spot?status=OCCUPIED` - Response body: `List` + +#### Issue a ticket + +* `issueTicket` - `POST /ticket` - Request body: `TicketRequest` - Response body: `Ticket` + +We might not want to use the current `Ticket` class for the request body since it contains a lot of information that is either not required or is not available at the time of ticket generation. We can create a new class `TicketRequest` that contains only the required information. + +```mermaid +classDiagram + class TicketRequest { + +String licensePlate + +VehicleType vehicleType + } +``` + +### Collect payment + +* `collectPayment` - `POST /payment` - Request body: `PaymentRequest` - Response body: `Payment` + +PaymentRequest: +```mermaid +classDiagram + class PaymentRequest { + +String ticketId + +PaymentType paymentType + } +``` + +### Checkout + +* `checkout` - `POST /checkout` - Request body: `CheckoutRequest` - Response body: `CheckoutResponse` + +CheckoutRequest: +```mermaid +classDiagram + class CheckoutRequest { + +String ticketId + +Date checkoutTime + +String exitGateId + } +``` + +## Class diagram + +What will be the major classes and their attributes? + +* ParkingLot + * Name + * Address + * ParkingFloors + * Entry Gates + * Exit Gates + +* ParkingFloor + * Floor Number + * ParkingSpots + +* ParkingSpot + * Spot Number + * Spot Type - `Large, Medium, Small` + * Status - `Occupied, Free, Out of order` + +* ParkingTicket + * Ticket ID + * ParkingSpot + * Entry Time + * Vehicle + * Entry Gate + * Entry Operator + +* Invoice + * Invoice ID + * Exit Time + * ParkingTicket + * Amount + * Payment + * Payment Status + +* Payment + * Amount + * Ticket + * Type - `Cash, Credit Card, UPI` + * Status - `Done, Pending` + * Time + +* Vehicle + * License Plate + * Vehicle Type - `Car, Truck, Bus, Bike, Scooter` + +* ParkingAttendant + * Name + * Email + +List down the cardinalities of the relationships between the classes. + +* `ParkingLot` - `ParkingFloor` - **One to many** +* `ParkingLot` - `ParkingGate` - `entryGates` - **One to many** +* `ParkingLot` - `ParkingGate` - `exitGates` - **One to many** +* `ParkingFloor` - `ParkingSpot` - **One to many** +* `ParkingGate` - `ParkingAttendant` - `currentGate` - **One to one** +* `ParkingSpot` - `ParkingTicket` - **One to many** +* `ParkingTicket` - `Invoice` - **One to one** +* `ParkingTicket` - `Vehicle` - **Many to one** +* `ParkingTicket` - `ParkingSpot` - **Many to one** +* `Payment` - `ParkingTicket` - **One to one** + +Draw the class diagram. +```mermaid +classDiagram + class ParkingLot { + +String name + +String address + +ParkingFloor[] parkingFloors + +ParkingGate[] entryGates + +ParkingGate[] exitGates + } + + class ParkingFloor { + +int floorNumber + +ParkingSpot[] parkingSpots + +PaymentCounter paymentCounter + } + + class PaymentCounter { + +int counterNumber + } + + class ParkingSpot { + +int spotNumber + +ParkingSpotType spotType + +ParkingSpotStatus status + } + + class ParkingTicket { + +String ticketId + +ParkingSpot parkingSpot + +Date entryTime + +Vehicle vehicle + +ParkingGate entryGate + +ParkingAttendant entryOperator + } + + class Invoice { + +String invoiceId + +Date exitTime + +ParkingTicket parkingTicket + +double amount + +Payment payment + } + + class Payment { + +double amount + +ParkingTicket ticket + +PaymentType type + +PaymentStatus status + +Date time + } + + class Vehicle { + +String licensePlate + +VehicleType vehicleType + } + + class ParkingAttendant { + +String name + +String email + } + + class ParkingGate { + +String gateId + +ParkingGateType gateType + +ParkingAttendant attendant + } + + class ParkingSpotType { + <> + Small, + Medium, + Large, + } + + class ParkingSpotStatus { + <> + Occupied, + Free, + OutOfOrder, + } + + class PaymentType { + <> + Cash, + CreditCard, + UPI, + } + + class PaymentStatus { + <> + Done, + Pending, + } + + class VehicleType { + <> + Car, + Truck, + Bus, + Bike, + Scooter, + } + + class ParkingGateType { + <> + Entry, + Exit, + } + + ParkingLot "1" --* "*" ParkingFloor + ParkingLot "1" --* "*" ParkingGate : entryGates + ParkingLot "1" --* "*" ParkingGate : exitGates + + ParkingFloor "1" --* "*" ParkingSpot + ParkingFloor "1" -- "1" PaymentCounter + + ParkingTicket "*" --o "1" ParkingSpot : generated for + ParkingTicket "*" --o "1" Vehicle : generated for + ParkingTicket "*" --o "1" ParkingGate : generated at + ParkingTicket "*" --o "1" ParkingAttendant : generated by + + Invoice "1" --o "1" ParkingTicket : generated for + Invoice "1" --o "1" Payment : paid by + + Payment "*" --o "1" PaymentType + Payment "*" --o "1" PaymentStatus + Payment "1" --o "1" ParkingTicket : paid for + + Vehicle "*" --o "1" VehicleType + + ParkingGate "*" --o "1" ParkingAttendant + ParkingGate "*" --o "1" ParkingGateType + + ParkingSpot "*" --o "1" ParkingSpotType + + ParkingSpot "*" --o "1" ParkingSpotStatus +``` + +Look for differences between your class diagram and the one in the solution. List them down below. + +``` +1. +2. +3. +4. +5. +``` + + diff --git a/non_dsa/lld/lld_3/java/design_split_wise.md b/non_dsa/lld/lld_3/java/design_split_wise.md new file mode 100644 index 0000000..7572807 --- /dev/null +++ b/non_dsa/lld/lld_3/java/design_split_wise.md @@ -0,0 +1,242 @@ +# Design Splitwise + +## Context + +> Splitwise makes it easy to split bills with friends and family. We organize all your shared expenses and IOUs in one place, so that everyone can see who they owe. Whether you are sharing a ski vacation, splitting rent with roommates, or paying someone back for lunch, Splitwise makes life easier. We store your data in the cloud so that you can access it anywhere: on iPhone, Android, or on your computer. + +![Splitwise](https://miro.medium.com/max/606/1*bwUxF9nFQ7xllloBdXGFXQ.png) + +## Requirements + +- Users can register and update their profiles. +- A user's profile should contain at least their name, phone number and password +- Users can participate in expenses with other users +- Users can participate in groups. +- To add an expense, a user must specify either the group, or the other users involved in the expense, along with who paid + what and who owes what. They must also specify a description for the expense. +- A user can see their total owed amount +- A user can see a history of the expenses they're involved in +- A user can see a history of the expenses made in a group that they're participating in +- Users shouldn't be able to query about groups they are not a member of +- Only the user who has created a group can add/remove members to the group +- Users can request a settle-up. The application should show a list of transactions, which when executed will ensure that + the user no longer owes or recieves money from any other user. Note that this need not settle-up any other users. +- Users can request a settle-up for any group they're participating in. The application should show a list of transactions, + which if executed, will ensure that everyone participating in the group is settled up (owes a net of 0 Rs). Note that will + only deal with the expenses made inside that group. Expenses outside the group need not be settled. + Good to Have Requirements +- When settling a group, we should try to minimize the number of transactions that the group members should make to + settle up. + +Note: All tests will be performed in one go. The application doesn't need to persist data between runs. + +### Input Format + +`Register vinsmokesanji 003 namisswwaann` + +> u1 is registering with the username "vinsmokesanji", phone "003" and password as "namisswwaann" +-- -- +`u1 UpdateProfile robinchwan` + +> u1 is updating their profile password to "robinchwan" +-- -- +`u1 AddGroup Roommates` + +> u1 is creating a group titled "Roommates" +-- -- +`u1 AddMember g1 u2` + +> u1 is adding u2 as a member of the group "Roommates" (which has the id g1) +-- -- +`u1 MyTotal` +> u1 is asking to see the total amount they owe/recieve after everything is settled. +-- -- +`u1 History` +> u1 is asking to see their history of transactions (whether added by themselves or someone +else) +-- -- +`u1 Groups` +> u1 is asking to see the groups that they're a member of +-- -- +`u1 SettleUp` +> u1 is asking to see the list of transactions they should perform to settle up +-- -- +`u1 SettleUp g1` +> u1 is asking to see the list of transactions that need to be performed by members of g1 to +completely settle up the group. +-- -- +`u1 Expense g1 iPay 1000 Equal Desc Wifi Bill` +> u1 is adding an expense in the group g1. +> u1 paid 1000 Rs +> each user of g1 owes an equal amount (the exact amount will depend on the number of users in group g1. Say g1 has 5 +users, then the amount owed by each will be 200Rs). +-- -- +`u1 Expense u2 u3 u4 iPay 1000 Equal Desc Last night dinner` +> u1 is adding an expense with users u2, u3 and u4. +> u1 paid 1000 Rs +> each user owes an equal amount - 250Rs. +-- -- +`u1 Expense u2 u3 iPay 1000 Percent 20 30 50 Desc House rent` +> u1 is adding an expense with users u2 and u3 +> u1 paid 1000 Rs +> u1 owes 20% (200Rs), u2 owes 30% (300Rs) and u3 owes 50% (500Rs). +-- -- +`u1 Expense u2 u3 u4 iPay 1000 Ratio 1 2 3 4 Desc Goa trip` +> u1 is adding an expense with users u2, u3 and u4. +> u1 paid 1000 Rs +> u1 owes 100Rs (1 part), u2 owes 200Rs (2 parts), u3 owes 300Rs (3 parts) and u4 owes 400Rs (4 +parts). +-- -- +`u1 Expense u2 u3 iPay 1000 Exact 100 300 600 Desc Groceries` +> u1 is adding an expense with users u2 and u3. +> u1 paid 1000 Rs +> u1 owes 100Rs, u2 owes 300Rs and u3 owes 600Rs. +-- -- +`u1 Expense u2 u3 MultiPay 100 300 200 Equal Desc Lunch at office` +> u1 is adding an expense with users u2 and u3. +> u1 paid 100 Rs, u2 paid 300Rs and u3 paid 200Rs. +> Each user owes an equal amount - 200Rs. +-- -- +`u1 Expense u2 u3 MultiPay 500 300 200 Percent 20 30 50 Desc Netflix subscription` +> u1 is adding an expense with users u2 and u3. +> u1 paid 500 Rs, u2 paid 300Rs and u3 paid 200Rs. +> u1 owes 20% (200Rs), u2 owes 30% (300Rs) and u3 owes 50% (500Rs). + +## Class diagrams + +What will be the major classes and their attributes? + +* User + * name + * username + * email + * password +* Expense + * description + * amount + * currency + * createdAt + * Participants + * paidBy + * paidFor + * Group +* Group + * name + * Members + * Admins + * CreatedBy + +Draw the class diagram. +```mermaid +classDiagram + class User { + -String name + -String username + -String email + -String hashedPassword + } + + class Expense { + -String description + -Double amount + -String currency + -Date createdAt + -List participants + -Map paidBy + -Map paidFor + -Group group + } + + class Group { + -String name + -List members + -List admins + -User createdBy + } +``` + +!!! Note + The class diagram can be optimised further. Think about it. + +## Schema design + +What will be the tables and their columns? + +* User - `USERS` + * id + * name + * username + * email + * password +* Expense - `EXPENSES` + * id - PK + * description - VARCHAR(255) + * amount - DOUBLE + * currency - VARCHAR(3) + * createdAt - DATETIME + * groupId - FK +* Group - `GROUPS` + * id - PK + * name - VARCHAR(255) + * createdBy - FK + * createdAt - DATETIME +* `Mapping` - `GROUP_MEMBERS` + * groupId - FK + * userId - FK +* `Mapping` - `GROUP_ADMINS` + * groupId - FK + * userId - FK +* `Mapping` - `EXPENSE_PARTICIPANTS` + * expenseId - FK + * userId - FK + +```mermaid +erDiagram + USERS { + int id + string name + string username + string email + string password + } + + EXPENSES { + int id + string description + double amount + string currency + datetime createdAt + int groupId + } + + GROUPS { + int id + string name + int createdBy + datetime createdAt + } + + GROUP_MEMBERS { + int groupId + int userId + } + + GROUP_ADMINS { + int groupId + int userId + } + + EXPENSE_PARTICIPANTS { + int expenseId + int userId + } + + + GROUP_MEMBERS ||--o{ GROUPS : contains + GROUP_ADMINS ||--o{ GROUPS : contains + EXPENSE_PARTICIPANTS ||--o{ EXPENSES : contains + USERS ||--o{ EXPENSE_PARTICIPANTS : contains + USERS ||--o{ GROUP_MEMBERS : contains + USERS ||--o{ GROUP_ADMINS : contains + GROUPS ||--o{ EXPENSES : contains +``` diff --git a/non_dsa/lld/lld_3/java/design_tic_tac_toe.md b/non_dsa/lld/lld_3/java/design_tic_tac_toe.md new file mode 100644 index 0000000..3bdbb69 --- /dev/null +++ b/non_dsa/lld/lld_3/java/design_tic_tac_toe.md @@ -0,0 +1,307 @@ +# Design Tic-Tac-Toe + +## What is Tic-Tac-Toe? + +TicTacToe is a 2 player game played on a 3 x 3 board. Each player is allotted a symbol (one X and one O). Initially, the board is empty. Alternatively, each player takes a turn and puts their symbol at any empty slot. The first player to get their symbol over a complete row OR a complete column OR a diagonal wins. + +You can play the game within Google Search by just searching for “tictactoe”! + +![TicTacToe](https://www.tuitec.com/wp-content/uploads/2016/08/morpion-640x411.jpg) + + +## Questions to Ask +* Will the game be played amongst only 2 players or can there be any number of players in future? +* Is the board size restricted to 3x3 or can it be any NxN? +* Can there be different ways to win? +* Can one of the players be a bot? +* Feature Suggestions: + * Do we want to time a move? Skip/ Declare the other person as winner if the move doesn’t happen within x seconds. + * Do we want to support undo operation? + * Can there be some players who are just watching? Not playing. + * Do we want to store analytics? Basically previous games, who played what move etc. + * Support for tournaments? Basically a set of matches, each match between 2 players of the tournament. + +## Expectations +* The code should be working and functionally correct +* Good software design practices should be followed: +* Code should be modular, readable, extensible +* Separation of concern should be addressed +* Project structured well across multiple files/ packages +* Write unit tests +* No need of GUI + + +## Problem Requirements +* Board can be of any NxN size. +* There can be two players. +* Each player will be allotted a symbol. +* The symbol can be one of O and X. +* The players can be either humans or bots. +* Each human player will have a name, email and profile image. +* Each bot player will have a difficulty level. +* Any random player can start the game. +* Then the players will take turns alternatively. +* The player with any consecutive N symbols in a row, column or diagonal wins. +* If the board is full and no player has won, the game is a draw. + +## Entities and their attributes +* Game + * Board + * Players +* Board + * Cells +* Cell + * Row + * Column + * Symbol +* Human Player + * Name + * Email + * Profile Image +* Bot Player + * Difficulty Level + +## Design + +### Use Case Diagram +```plantuml +@startuml +left to right direction +actor HumanPlayer +actor Bot +rectangle Game { + HumanPlayer -- (Start Game) + HumanPlayer -- (Make Move) + Bot -- (Make Move) + HumanPlayer -- (Register) + + (Make Move) .> (Check Winner) : includes +} + +@enduml +``` + +### Initial Design + +```mermaid +classDiagram + class Game { + -Board board + -HumanPlayer humanPlayer + -BotPlayer botPlayer + +register(HumanPlayer) HumanPlayer + +startGame(HumanPlayer, BotPlayer, int row, int column) Board + +makeMove(PlayerId, int, int) Board + +checkWinner(Board, HumanPlayer, BotPlayer) int + } + + class Board { + -Cell[][] cells + +Board(int, int) : Board + } + + class Cell { + -int x + -int y + -Symbol symbol + } + + class HumanPlayer { + -int id + -String name + -String email + -Byte[] photo + -Symbol symbol + +play(Board) Cell + } + + class BotPlayer { + -int id + -Level level + -Symbol symbol + +play(Board) Cell + } + + Game "1" --* "1" Board + Board "1" --* "*" Cell + + Game "1" --* "1" BotPlayer + Game "1" --* "1" HumanPlayer +``` + +* There is no common contract for players. Parent class to represent all different types of players. +* There is tight coupling between Game and different types of players. It is not extensible to support multiple players +* OCP and SRP violation in play method. +* Huge memory consumption - multiple instances of the player will be created for multiple games. Each instance has a new photo. + +## Common contract - Player abstract class + +- Common behaviour - `play` +- Common attributes - `Symbol` + +```mermaid +classDiagram + + class Game { + -Board board + -Player[] players + } + + class Player { + <> + -Symbol symbol + +play(Board)* Cell + } + + class HumanPlayer { + -String name + -String email + -Byte[] photo + +play(Board) Cell + } + + class BotPlayer { + -Level level + +play(Board) Cell + } + + Player <|-- HumanPlayer + Player <|-- BotPlayer + + Game "1" --* "*" Player +``` + +* ~~There is no common contract for players. Parent class to represent all different types of players.~~ +* ~~There is tight coupling between Game and different types of players. It is not extensible to support multiple players~~ +* OCP and SRP violation in play method. +* Huge memory consumption - multiple instances of the player will be created for multiple games. Each instance has a new photo. + +## Tight coupling +-HumanPlayer +-BotPlayer +-Player[] players +``mermaid +`` + + +## OCP and SRP violation in play method - Strategy + +## Huge memory consumption - Flyweight + +- Paul Morphy +- Instance 1 - + - name - Paul Morphy + - email - paul@blind.in + - photo - 5MB + - symbol - O +- Instance 2 - + - name - Paul Morphy + - email - paul@blind.in + - photo - 5MB + - symbol - X + +- Store fields that do not change in a class - Intrinsic state +- Store field that change in a class - Extrinsic state + + +```mermaid +classDiagram + class Game { + -Board board + -Player[] players + } + + class Player { + <> + -Symbol symbol + +play(Board)* Cell + } + + class User { + -String name + -String email + -Byte[] photo + } + + class HumanPlayer { + -User user + +play(Board) Cell + } + + Player <|-- HumanPlayer + Game "1" --* "*" Player + + HumanPlayer "*" --o "1" User + +``` + +* Problems so far +* OCP and SRP violation in play method. + + +### Implement different levels in a bot + +```java + +class BotPlayer { + + private Level level; + + private Cell play(Board board) { + switch (level) { + case EASY: + // Really easy move + case MEDIUM: + // Medium level moves + } + } +} +``` + +```mermaid +classDiagram + class BotPlayer { + -int id + -Level level + -Symbol symbol + -PlayingStrategy strategy + +play(Board) Cell + } + + class PlayingStrategy { + <> + +play(Board) Cell + } + + class RandomPlayingStrategy { + +play(Board) Cell + } + + class MinMaxPlayingStrategy { + +play(Board) Cell + } + + class AlphaBetaPlayingStrategy { + +play(Board) Cell + } + + PlayingStrategy <|-- RandomPlayingStrategy + + PlayingStrategy <|-- MinMaxPlayingStrategy + + PlayingStrategy <|-- AlphaBetaPlayingStrategy + + BotPlayer "*" --o "1" PlayingStrategy + +``` + +* Inject different behaviours +* Such that they can be reused + * Strategy Design pattern + + +* There is no common contract for players. Parent class to represent all different types of players. - Abstract classes +* There is tight coupling between Game and different types of players. It is not extensible to support multiple players - `List` +* OCP and SRP violation in play method. + - Strategy pattern +* Huge memory consumption - multiple instances of the player will be created for multiple games. Each instance has a new photo. - Flyweight \ No newline at end of file diff --git a/non_dsa/product_management/README.md b/non_dsa/product_management/README.md new file mode 100644 index 0000000..47a0bb2 --- /dev/null +++ b/non_dsa/product_management/README.md @@ -0,0 +1 @@ +# Repo for Product Management Typed Notes \ No newline at end of file