Problem Statement: Creating different types of enemies in a game
The problem with the current approach is that it violates the Open/Closed Principle. Whenever a new enemy type needs to be added, the createEnemy method in the EnemyFactory class needs to be modified, which is a violation of the Open/Closed Principle.
The Factory Method design pattern can solve this problem by providing an interface for creating objects (enemies), but allowing subclasses to decide which class to instantiate.
UML Diagram:
Problem Statement: Text formatting in a text editor application
The issue with the current implementation is that it doesn’t support dynamic composition of formatting options. If we need to create a new formatting option that combines existing options, we would need to create a new class for that specific combination.
The Decorator design pattern can solve this problem by allowing dynamic composition of formatting options at runtime.
UML Diagram:
Problem Statement: Complex interactions between subsystems
The problem with the current approach is that the client code is tightly coupled with the implementation details of the subsystems and their interactions. This makes the code difficult to understand, maintain, and extend.
The Facade design pattern can solve this problem by providing a simplified interface that hides the complexity of the subsystem interactions from the client code.
UML Diagram:
Problem Statement: Accessing remote objects or resources
The potential issues with the current approach are:
Performance: Accessing remote objects or resources directly can lead to performance issues, especially in distributed systems or over slow networks.
Availability: If the remote object or resource is not available, the client code will fail.
The Proxy design pattern can solve this problem by providing a local representation or placeholder for the remote object or resource.
UML Diagram:
Problem Statement: Creating families of related objects
The potential issue with the current approach is that it violates the Open/Closed Principle. Whenever a new product type needs to be added, multiple classes need to be created or modified (e.g., ProductC, ProductCComponent, ProductCAccessory, ProductCDocumentation).
The Abstract Factory design pattern can solve this problem by providing an interface for creating families of related objects without specifying their concrete classes.
UML Diagram:
Problem Statement: Implementing different duck behaviors
The potential issue with the current approach is that it violates the Single Responsibility Principle. The Duck class is responsible for both the quacking and flying behaviors, which can lead to inflexible and rigid code when new behaviors need to be added or modified.
The Strategy design pattern can solve this problem by separating the behavior implementations from the core object (Duck) and allowing them to be easily interchangeable.
UML Diagram:
Problem Statement: Managing weather data sources
The potential issue with the current approach is that it violates the Open/Closed Principle. If we need to add new features like encryption or compression to the data sources, we would need to modify the existing FileDataSource and DatabaseDataSource classes, which can lead to code duplication and increased complexity.
The Decorator design pattern can solve this problem by allowing the addition of new features to the data sources dynamically without modifying their existing code.
UML Diagram:
Problem Statement: Managing user authentication and authorization
The potential issue with the current approach is that it violates the Open/Closed Principle. If we need to add additional security checks or logging mechanisms, we would need to modify the existing BasicAuthenticationService and BasicAuthorizationService classes, which can lead to code duplication and increased complexity.
The Decorator design pattern can solve this problem by allowing the addition of new features to the authentication and authorization services dynamically without modifying their existing code.
UML Diagram:
Problem Statement: Managing different types of notifications
The potential issue with the current approach is that it violates the Open/Closed Principle. If we need to add a new notification channel, we would need to create a new class implementing the NotificationService interface, which can lead to code duplication and increased complexity.
The Strategy design pattern can solve this problem by separating the different notification strategies from the core NotificationService and allowing them to be easily interchangeable.
UML Diagram:
Source Code
---title: "Design Patterns"format: pdf: toc: truefilters: - diagram---```{r setup, include=FALSE}```# Tutorial Sheet Week 5 Solutions1. **Problem Statement: Creating different types of enemies in a game**a) The problem with the current approach is that it violates the Open/Closed Principle. Whenever a new enemy type needs to be added, the `createEnemy` method in the `EnemyFactory` class needs to be modified, which is a violation of the Open/Closed Principle.b) The Factory Method design pattern can solve this problem by providing an interface for creating objects (enemies), but allowing subclasses to decide which class to instantiate.c) UML Diagram:```plantuml@startumlinterface Enemy { +attackPlayer() +move()}interface EnemyFactory { +createEnemy() : Enemy}class Alien implements Enemy { +attackPlayer() +move()}class Robot implements Enemy { +attackPlayer() +move()}class AlienEnemyFactory implements EnemyFactory { +createEnemy() : Enemy}class RobotEnemyFactory implements EnemyFactory { +createEnemy() : Enemy}EnemyFactory <|.. AlienEnemyFactoryEnemyFactory <|.. RobotEnemyFactoryEnemyFactory ..> EnemyEnemy <|.. AlienEnemy <|.. Robot@enduml```2. **Problem Statement: Text formatting in a text editor application**a) The issue with the current implementation is that it doesn't support dynamic composition of formatting options. If we need to create a new formatting option that combines existing options, we would need to create a new class for that specific combination.b) The Decorator design pattern can solve this problem by allowing dynamic composition of formatting options at runtime.c) UML Diagram:```plantuml@startumlinterface TextComponent { +render()}class PlainText implements TextComponent {-text: String +render()}abstract class TextDecorator implements TextComponent {-wrappedComponent: TextComponent +render()}class BoldDecorator extends TextDecorator { +render()}class ItalicDecorator extends TextDecorator { +render()}class UnderlineDecorator extends TextDecorator { +render()}TextComponent <|.. PlainTextTextComponent <|-- TextDecoratorTextDecorator o-- TextComponentTextDecorator <|-- BoldDecoratorTextDecorator <|-- ItalicDecoratorTextDecorator <|-- UnderlineDecorator@enduml```3. **Problem Statement: Complex interactions between subsystems**a) The problem with the current approach is that the client code is tightly coupled with the implementation details of the subsystems and their interactions. This makes the code difficult to understand, maintain, and extend.b) The Facade design pattern can solve this problem by providing a simplified interface that hides the complexity of the subsystem interactions from the client code.c) UML Diagram:```plantuml@startumlinterface Facade { +doSomething()}class SubsystemFacade implements Facade {-subsystemA: SubsystemA-subsystemB: SubsystemB-subsystemC: SubsystemC +doSomething()}class SubsystemA { +operation1() +getResult()}class SubsystemB { +operation2(data) +getResult()}class SubsystemC { +operation3(data1, data2)}Facade <|.. SubsystemFacadeSubsystemFacade o-- SubsystemASubsystemFacade o-- SubsystemBSubsystemFacade o-- SubsystemC@enduml```4. **Problem Statement: Accessing remote objects or resources**a) The potential issues with the current approach are:- Performance: Accessing remote objects or resources directly can lead to performance issues, especially in distributed systems or over slow networks.- Availability: If the remote object or resource is not available, the client code will fail.b) The Proxy design pattern can solve this problem by providing a local representation or placeholder for the remote object or resource.c) UML Diagram:```plantuml@startumlinterface RemoteObject { +performOperation()}class RealRemoteObject implements RemoteObject { +performOperation()}class RemoteObjectProxy implements RemoteObject {-realRemoteObject: RealRemoteObject +performOperation()}RemoteObject <|.. RealRemoteObjectRemoteObject <|.. RemoteObjectProxyRemoteObjectProxy o-- RealRemoteObject@enduml```5. **Problem Statement: Creating families of related objects**a) The potential issue with the current approach is that it violates the Open/Closed Principle. Whenever a new product type needs to be added, multiple classes need to be created or modified (e.g., `ProductC`, `ProductCComponent`, `ProductCAccessory`, `ProductCDocumentation`).b) The Abstract Factory design pattern can solve this problem by providing an interface for creating families of related objects without specifying their concrete classes.c) UML Diagram:```plantuml@startumlinterface Product { +operation()}interface Component { +operation()}interface Accessory { +operation()}interface Documentation { +operation()}interface AbstractFactory { +createProduct() : Product +createComponent() : Component +createAccessory() : Accessory +createDocumentation() : Documentation}class ProductA implements Product { +operation()}class ProductAComponent implements Component { +operation()}class ProductAAccessory implements Accessory { +operation()}class ProductADocumentation implements Documentation { +operation()}class ProductAFactory implements AbstractFactory { +createProduct() : Product +createComponent() : Component +createAccessory() : Accessory +createDocumentation() : Documentation}AbstractFactory ..> ProductAbstractFactory ..> ComponentAbstractFactory ..> AccessoryAbstractFactory ..> DocumentationAbstractFactory <|.. ProductAFactoryProduct <|.. ProductAComponent <|.. ProductAComponentAccessory <|.. ProductAAccessoryDocumentation <|.. ProductADocumentation@enduml```6. **Problem Statement: Implementing different duck behaviors**a) The potential issue with the current approach is that it violates the Single Responsibility Principle. The `Duck` class is responsible for both the quacking and flying behaviors, which can lead to inflexible and rigid code when new behaviors need to be added or modified.b) The Strategy design pattern can solve this problem by separating the behavior implementations from the core object (Duck) and allowing them to be easily interchangeable.c) UML Diagram:```plantuml@startumlinterface FlyBehavior { +fly()}interface QuackBehavior { +quack()}class Duck {-flyBehavior: FlyBehavior -quackBehavior: QuackBehavior +setFlyBehavior(FlyBehavior) +setQuackBehavior(QuackBehavior) +performFly() +performQuack()}class FlyWithWings implements FlyBehavior { +fly()}class FlyNoWay implements FlyBehavior { +fly()}class Quack implements QuackBehavior { +quack()}class Squeak implements QuackBehavior { +quack()}Duck o-- FlyBehaviorDuck o-- QuackBehaviorFlyBehavior <|.. FlyWithWingsFlyBehavior <|.. FlyNoWayQuackBehavior <|.. QuackQuackBehavior <|.. Squeak@enduml```7. **Problem Statement: Managing weather data sources**a) The potential issue with the current approach is that it violates the Open/Closed Principle. If we need to add new features like encryption or compression to the data sources, we would need to modify the existing `FileDataSource` and `DatabaseDataSource` classes, which can lead to code duplication and increased complexity.b) The Decorator design pattern can solve this problem by allowing the addition of new features to the data sources dynamically without modifying their existing code.c) UML Diagram:```plantuml@startumlinterface DataSource { +writeData(data: String) +readData() : String}class FileDataSource implements DataSource { +writeData(data: String) +readData() : String}class DatabaseDataSource implements DataSource { +writeData(data: String) +readData() : String}abstract class DataSourceDecorator implements DataSource {-wrappedSource: DataSource +writeData(data: String) +readData() : String}class EncryptionDecorator extends DataSourceDecorator { +writeData(data: String) +readData() : String}class CompressionDecorator extends DataSourceDecorator { +writeData(data: String) +readData() : String}DataSource <|.. FileDataSourceDataSource <|.. DatabaseDataSourceDataSource <|-- DataSourceDecoratorDataSourceDecorator o-- DataSourceDataSourceDecorator <|-- EncryptionDecoratorDataSourceDecorator <|-- CompressionDecorator@enduml```8. **Problem Statement: Managing user authentication and authorization**a) The potential issue with the current approach is that it violates the Open/Closed Principle. If we need to add additional security checks or logging mechanisms, we would need to modify the existing `BasicAuthenticationService` and `BasicAuthorizationService` classes, which can lead to code duplication and increased complexity.b) The Decorator design pattern can solve this problem by allowing the addition of new features to the authentication and authorization services dynamically without modifying their existing code.c) UML Diagram:```plantuml@startumlinterface AuthenticationService { +authenticate(username: String, password: String) : boolean}interface AuthorizationService { +isAuthorized(username: String, resource: String) : boolean}class BasicAuthenticationService implements AuthenticationService { +authenticate(username: String, password: String) : boolean}class BasicAuthorizationService implements AuthorizationService { +isAuthorized(username: String, resource: String) : boolean}abstract class AuthenticationDecorator implements AuthenticationService {-wrappedService: AuthenticationService +authenticate(username: String, password: String) : boolean}abstract class AuthorizationDecorator implements AuthorizationService {-wrappedService: AuthorizationService +isAuthorized(username: String, resource: String) : boolean}class LoggingAuthenticationDecorator extends AuthenticationDecorator { +authenticate(username: String, password: String) : boolean}class SecureAuthenticationDecorator extends AuthenticationDecorator { +authenticate(username: String, password: String) : boolean}class LoggingAuthorizationDecorator extends AuthorizationDecorator { +isAuthorized(username: String, resource: String) : boolean}class SecureAuthorizationDecorator extends AuthorizationDecorator { +isAuthorized(username: String, resource: String) : boolean}AuthenticationService <|.. BasicAuthenticationServiceAuthorizationService <|.. BasicAuthorizationServiceAuthenticationService <|-- AuthenticationDecoratorAuthorizationService <|-- AuthorizationDecoratorAuthenticationDecorator o-- AuthenticationServiceAuthorizationDecorator o-- AuthorizationServiceAuthenticationDecorator <|-- LoggingAuthenticationDecoratorAuthenticationDecorator <|-- SecureAuthenticationDecoratorAuthorizationDecorator <|-- LoggingAuthorizationDecoratorAuthorizationDecorator <|-- SecureAuthorizationDecorator@enduml```9. **Problem Statement: Managing different types of notifications**a) The potential issue with the current approach is that it violates the Open/Closed Principle. If we need to add a new notification channel, we would need to create a new class implementing the `NotificationService` interface, which can lead to code duplication and increased complexity.b) The Strategy design pattern can solve this problem by separating the different notification strategies from the core `NotificationService` and allowing them to be easily interchangeable.c) UML Diagram:```plantuml@startumlinterface NotificationStrategy { +sendNotification(message: String)}class EmailNotificationStrategy implements NotificationStrategy { +sendNotification(message: String)}class SMSNotificationStrategy implements NotificationStrategy { +sendNotification(message: String)}class PushNotificationStrategy implements NotificationStrategy { +sendNotification(message: String)}class NotificationService { -notificationStrategy: NotificationStrategy +setNotificationStrategy(strategy: NotificationStrategy) +sendNotification(message: String)}NotificationService o-- NotificationStrategyNotificationStrategy <|.. EmailNotificationStrategyNotificationStrategy <|.. SMSNotificationStrategyNotificationStrategy <|.. PushNotificationStrategy@enduml```