From 3297eb42a0b8f3ab64803995e64a0219644e944b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Sun, 19 May 2024 13:33:42 +0300 Subject: [PATCH] docs: update role object (#2961) --- retry/README.md | 23 --- role-object/README.md | 154 ++++++++++++++++-- .../com/iluwatar/roleobject/BorrowerRole.java | 13 +- .../com/iluwatar/roleobject/InvestorRole.java | 21 +-- 4 files changed, 148 insertions(+), 63 deletions(-) diff --git a/retry/README.md b/retry/README.md index 976d4144b..1c0dfaf90 100644 --- a/retry/README.md +++ b/retry/README.md @@ -20,29 +20,6 @@ Transparently retry certain operations that involve communication with external ## Explanation -Retry pattern consists retrying operations on remote resources over the network a set number of -times. It closely depends on both business and technical requirements: How much time will the -business allow the end user to wait while the operation finishes? What are the performance -characteristics of the remote resource during peak loads as well as our application as more threads -are waiting for the remote resource's availability? Among the errors returned by the remote service, -which can be safely ignored in order to retry? Is the operation -[idempotent](https://en.wikipedia.org/wiki/Idempotence)? - -Another concern is the impact on the calling code by implementing the retry mechanism. The retry -mechanics should ideally be completely transparent to the calling code (service interface remains -unaltered). There are two general approaches to this problem: From an enterprise architecture -standpoint (strategic), and a shared library standpoint (tactical). - -From a strategic point of view, this would be solved by having requests redirected to a separate -intermediary system, traditionally an [ESB](https://en.wikipedia.org/wiki/Enterprise_service_bus), -but more recently a [Service Mesh](https://medium.com/microservices-in-practice/service-mesh-for-microservices-2953109a3c9a). - -From a tactical point of view, this would be solved by reusing shared libraries like -[Hystrix](https://github.com/Netflix/Hystrix) (please note that Hystrix is a complete implementation -of the [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/) pattern, of -which the Retry pattern can be considered a subset of). This is the type of solution showcased in -the simple example that accompanies this `README.md`. - Real world example > Imagine you're a delivery driver attempting to deliver a package to a customer's house. You ring the doorbell, but no one answers. Instead of leaving immediately, you wait for a few minutes and try again, repeating this process a few times. This is similar to the Retry pattern in software, where a system retries a failed operation (e.g., making a network request) a certain number of times before finally giving up, in hopes that the issue (e.g., transient network glitch) will be resolved and the operation will succeed. diff --git a/role-object/README.md b/role-object/README.md index cb5881556..0e50ea6d1 100644 --- a/role-object/README.md +++ b/role-object/README.md @@ -3,30 +3,152 @@ title: Role Object category: Structural language: en tag: - - Extensibility + - Abstraction + - Decoupling + - Extensibility + - Interface + - Object composition + - Polymorphism + - Runtime --- -## Also known as -Post pattern, Extension Object pattern - ## Intent -Adapt an object to different client’s needs through transparently attached role objects, each one representing a role -the object has to play in that client’s context. The object manages its role set dynamically. By representing roles as -individual objects, different contexts are kept separate and system configuration is simplified. + +To dynamically assign roles to objects, enabling them to change behavior and responsibilities at runtime. + +## Explanation + +Real world example + +> Imagine a restaurant where staff members can take on different roles based on the needs of the moment. For example, an employee could be a server, a cashier, or a kitchen helper depending on the situation. When the restaurant is busy, a server might also take on the role of a cashier to help process payments quickly. Later, the same employee might assist in the kitchen during a rush. This flexibility allows the restaurant to dynamically allocate responsibilities to meet real-time demands, enhancing efficiency and customer satisfaction. The Role Object pattern in software mimics this by allowing objects to assume different roles and behaviors at runtime, providing similar flexibility and adaptability. + +In plain words + +> The Role Object pattern suggests to model context-specific views of an object as separate role objects which are dynamically attached to and removed from the core object. + +wiki.c2.com says + +> Adapt an object to different client’s needs through transparently attached role objects, each one representing a role the object has to play in that client’s context. The object manages its role set dynamically. By representing roles as individual objects, different contexts are kept separate and system configuration is simplified. + +**Programmatic Example** + +The Role Object design pattern is a pattern that suggests modeling context-specific views of an object as separate role objects. These role objects are dynamically attached to and removed from the core object. The resulting composite object structure, consisting of the core and its role objects, is called a subject. A subject often plays several roles and the same role is likely to be played by different subjects. + +In the provided code, we have a `Customer` object that can play different roles such as `Borrower` and `Investor`. These roles are represented by `BorrowerRole` and `InvestorRole` classes respectively, which extend the `CustomerRole` class. + +Here is a simplified version of the `BorrowerRole` class: + +```java +@Getter +@Setter +public class BorrowerRole extends CustomerRole { + + private String name; + + public String borrow() { + return String.format("Borrower %s wants to get some money.", name); + } +} +``` + +In this class, the `borrow` method represents an operation specific to the `Borrower` role. + +Similarly, the `InvestorRole` class represents the `Investor` role: + +```java +@Getter +@Setter +public class InvestorRole extends CustomerRole { + + private String name; + + private long amountToInvest; + + public String invest() { + return String.format("Investor %s has invested %d dollars", name, amountToInvest); + } +} +``` + +In the `InvestorRole` class, the `invest` method represents an operation specific to the `Investor` role. + +The `Customer` object can play either of these roles or both. This is demonstrated in the `ApplicationRoleObject` class: + +```java +@Slf4j +public class ApplicationRoleObject { + + public static void main(String[] args) { + var customer = Customer.newCustomer(BORROWER, INVESTOR); + + LOGGER.info(" the new customer created : {}", customer); + + var hasBorrowerRole = customer.hasRole(BORROWER); + LOGGER.info(" customer has a borrowed role - {}", hasBorrowerRole); + var hasInvestorRole = customer.hasRole(INVESTOR); + LOGGER.info(" customer has an investor role - {}", hasInvestorRole); + + customer.getRole(INVESTOR, InvestorRole.class) + .ifPresent(inv -> { + inv.setAmountToInvest(1000); + inv.setName("Billy"); + }); + customer.getRole(BORROWER, BorrowerRole.class) + .ifPresent(inv -> inv.setName("Johny")); + + customer.getRole(INVESTOR, InvestorRole.class) + .map(InvestorRole::invest) + .ifPresent(LOGGER::info); + + customer.getRole(BORROWER, BorrowerRole.class) + .map(BorrowerRole::borrow) + .ifPresent(LOGGER::info); + } +} +``` + +In this class, a `Customer` object is created with both `Borrower` and `Investor` roles. The `hasRole` method is used to check if the `Customer` object has a specific role. The `getRole` method is used to get a reference to the role object, which is then used to perform role-specific operations. ## Class diagram -![alt text](./etc/role-object.urm.png "Role Object pattern class diagram") + +![Role Object](./etc/role-object.urm.png "Role Object pattern class diagram") ## Applicability -Use the Role Object pattern, if: -- You want to handle a key abstraction in different contexts and you do not want to put the resulting context specific interfaces into the same class interface. -- You want to handle the available roles dynamically so that they can be attached and removed on demand, that is at runtime, rather than fixing them statically at compile-time. -- You want to treat the extensions transparently and need to preserve the logical object identity of the resultingobject conglomerate. -- You want to keep role/client pairs independent from each other so that changes to a role do not affect clients that are not interested in that role. +* When an object needs to change its behavior dynamically based on its role. +* When multiple objects share common behaviors but should exhibit those behaviors differently based on their roles. +* In scenarios where roles can be added, removed, or changed at runtime. + +## Known Uses + +* User role management in applications where users can have different permissions and responsibilities. +* Game character roles where characters can take on different roles (e.g., healer, warrior, mage) dynamically. +* Workflow systems where tasks can be assigned different roles depending on the context. + +## Consequences + +Benefits: + +* Promotes flexibility by allowing objects to change roles dynamically. +* Enhances code maintainability by decoupling role-specific behaviors from core object logic. +* Facilitates the addition of new roles without modifying existing code. + +Trade-offs: + +* Increases complexity due to the need for managing multiple role objects. +* Potential performance overhead due to the dynamic nature of role assignment and behavior switching. + +## Related Patterns + +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Similar in dynamically changing an object's behavior, but Role Object focuses on roles that can be combined. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Both can add behaviors to objects, but Role Object allows for dynamic role switching rather than static enhancement. +* [State](https://java-design-patterns.com/patterns/state/): Manages state transitions similar to role changes, but Role Object deals more with behavioral roles rather than states. ## Credits -- [Hillside - Role object pattern](https://hillside.net/plop/plop97/Proceedings/riehle.pdf) -- [Role object](http://wiki.c2.com/?RoleObject) -- [Fowler - Dealing with roles](https://martinfowler.com/apsupp/roles.pdf) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Pattern-Oriented Software Architecture Volume 1: A System of Patterns](https://amzn.to/3xZ1ELU) +* [Role-Based Access Control](https://amzn.to/3UJzL2l) +* [Role object pattern - Hillside](https://hillside.net/plop/plop97/Proceedings/riehle.pdf) +* [Role object - wiki.c2.com](http://wiki.c2.com/?RoleObject) +* [Dealing with roles - Martin Fowler](https://martinfowler.com/apsupp/roles.pdf) diff --git a/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java b/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java index e082438a1..5d0b8111c 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java @@ -24,21 +24,18 @@ */ package com.iluwatar.roleobject; +import lombok.Getter; +import lombok.Setter; + /** * Borrower role. */ +@Getter +@Setter public class BorrowerRole extends CustomerRole { private String name; - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - public String borrow() { return String.format("Borrower %s wants to get some money.", name); } diff --git a/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java b/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java index 0606c9b54..1390d72f6 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java @@ -24,31 +24,20 @@ */ package com.iluwatar.roleobject; +import lombok.Getter; +import lombok.Setter; + /** * Investor role. */ +@Getter +@Setter public class InvestorRole extends CustomerRole { private String name; private long amountToInvest; - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public long getAmountToInvest() { - return amountToInvest; - } - - public void setAmountToInvest(long amountToInvest) { - this.amountToInvest = amountToInvest; - } - public String invest() { return String.format("Investor %s has invested %d dollars", name, amountToInvest); }