Compare commits
81 Commits
all-contri
...
all-contri
Author | SHA1 | Date | |
---|---|---|---|
|
6c7715ace6 | ||
|
ef43af3b23 | ||
|
95e513b6ec | ||
|
194040543e | ||
|
846a174ade | ||
|
7f60f7be25 | ||
|
cfb58191ea | ||
|
82bf9fdd61 | ||
|
be552d0ece | ||
|
d584404df6 | ||
|
353a2d9fcf | ||
|
8c5740563d | ||
|
89bda692f8 | ||
|
241f93f3cc | ||
|
79a6af703e | ||
|
00d06871f4 | ||
|
7afb065a10 | ||
|
af9f8fe4e1 | ||
|
a1c96ede13 | ||
|
0d2c4abe5a | ||
|
e7e3ace01f | ||
|
41dacc2e2e | ||
|
dc7bf6190d | ||
|
98d2dc0104 | ||
|
870d44b127 | ||
|
834f20911b | ||
|
2b7949ce6a | ||
|
4ffab7da85 | ||
|
1683fbdf9c | ||
|
d091e369ec | ||
|
b91bbc773d | ||
|
3c07a42e9d | ||
|
e99bcf772b | ||
|
2d16e5afbf | ||
|
2c6f1832b0 | ||
|
04a2be0c99 | ||
|
10815b6469 | ||
|
75b10ed657 | ||
|
108532d8dd | ||
|
5527cf4234 | ||
|
4008ae41b5 | ||
|
8166a1835d | ||
|
4c9cad5475 | ||
|
24126edd86 | ||
|
c0edac0046 | ||
|
f5c337981b | ||
|
5441db6582 | ||
|
87f3a4d956 | ||
|
31d753e59d | ||
|
6cef98d41e | ||
|
7450456dae | ||
|
45e416928d | ||
|
94c131f7e9 | ||
|
5eb9b98e78 | ||
|
8305e9365d | ||
|
04e3368a81 | ||
|
d219a104c7 | ||
|
700f5c6d27 | ||
|
f4fa73cd48 | ||
|
e09de2fb36 | ||
|
a59c9bba97 | ||
|
7acc5fbf95 | ||
|
bdf2145b3f | ||
|
1e90d0d645 | ||
|
5bfaeffecf | ||
|
c0acaf073b | ||
|
9f190c59c4 | ||
|
08cec0e254 | ||
|
1f6e6f34ea | ||
|
fa68789cd5 | ||
|
12d392931e | ||
|
4017c37b6f | ||
|
9d191324ff | ||
|
75ab3b5245 | ||
|
e0fb2d014a | ||
|
c4b1b89f1f | ||
|
52e6ea2b68 | ||
|
112973482d | ||
|
58e3fbfc6a | ||
|
bf01f3e68b | ||
|
58b98c54e5 |
@@ -1093,6 +1093,52 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ashishtrivedi16",
|
||||
"name": "Ashish Trivedi",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/23194128?v=4",
|
||||
"profile": "https://www.linkedin.com/in/ashish-trivedi-218379135/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "RayYH",
|
||||
"name": "洪月阳",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/41055099?v=4",
|
||||
"profile": "https://rayyounghong.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "xdvrx1",
|
||||
"name": "xdvrx1",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/47092464?v=4",
|
||||
"profile": "https://xdvrx1.github.io/",
|
||||
"contributions": [
|
||||
"review",
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ohbus",
|
||||
"name": "Subhrodip Mohanta",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/13291222?v=4",
|
||||
"profile": "http://subho.xyz",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nahteb",
|
||||
"name": "Bethan Palmer",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/13121570?v=4",
|
||||
"profile": "https://github.com/nahteb",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 4,
|
||||
|
@@ -24,13 +24,11 @@
|
||||
# This workflow will build a Java project with Maven
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||
|
||||
name: Java CI with Maven
|
||||
name: Java CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -43,6 +41,12 @@ jobs:
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
# Some tests need screen access
|
||||
- name: Install xvfb
|
||||
run: sudo apt-get install xvfb
|
57
.github/workflows/maven-pr-builder.yml
vendored
Normal file
57
.github/workflows/maven-pr-builder.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
#
|
||||
# The MIT License
|
||||
# Copyright © 2014-2019 Ilkka Seppälä
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
|
||||
# This workflow will build a Java project with Maven
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||
|
||||
name: Java PR Builder
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
# Some tests need screen access
|
||||
- name: Install xvfb
|
||||
run: sudo apt-get install xvfb
|
||||
# SonarQube scan does not work for forked repositories
|
||||
# See https://jira.sonarsource.com/browse/MMF-1371
|
||||
- name: Build with Maven
|
||||
if: github.ref != 'refs/heads/master'
|
||||
run: xvfb-run mvn clean verify
|
11
README.md
11
README.md
@@ -9,7 +9,7 @@
|
||||
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
# Introduction
|
||||
@@ -248,6 +248,15 @@ This project is licensed under the terms of the MIT license.
|
||||
<td align="center"><a href="https://github.com/ravening"><img src="https://avatars1.githubusercontent.com/u/10645273?v=4" width="100px;" alt=""/><br /><sub><b>Rakesh</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ravening" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/vINCENT8888801"><img src="https://avatars0.githubusercontent.com/u/8037883?v=4" width="100px;" alt=""/><br /><sub><b>Wei Seng</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=vINCENT8888801" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/ashish-trivedi-218379135/"><img src="https://avatars3.githubusercontent.com/u/23194128?v=4" width="100px;" alt=""/><br /><sub><b>Ashish Trivedi</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ashishtrivedi16" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://rayyounghong.com"><img src="https://avatars1.githubusercontent.com/u/41055099?v=4" width="100px;" alt=""/><br /><sub><b>洪月阳</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=RayYH" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://xdvrx1.github.io/"><img src="https://avatars0.githubusercontent.com/u/47092464?v=4" width="100px;" alt=""/><br /><sub><b>xdvrx1</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Axdvrx1" title="Reviewed Pull Requests">👀</a> <a href="#ideas-xdvrx1" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="http://subho.xyz"><img src="https://avatars0.githubusercontent.com/u/13291222?v=4" width="100px;" alt=""/><br /><sub><b>Subhrodip Mohanta</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ohbus" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/nahteb"><img src="https://avatars3.githubusercontent.com/u/13121570?v=4" width="100px;" alt=""/><br /><sub><b>Bethan Palmer</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=nahteb" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-enable -->
|
||||
|
@@ -6,6 +6,8 @@ permalink: /patterns/aggregator-microservices/
|
||||
categories: Architectural
|
||||
tags:
|
||||
- Cloud distributed
|
||||
- Decoupling
|
||||
- Microservices
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
@@ -12,49 +12,53 @@ tags:
|
||||
|
||||
## Intent
|
||||
|
||||
Aggregate calls to microservices in a single location: the API Gateway. The user makes a single call to the API Gateway,
|
||||
and the API Gateway then calls each relevant microservice.
|
||||
Aggregate calls to microservices in a single location, the API Gateway. The user makes a single call
|
||||
to the API Gateway, and the API Gateway then calls each relevant microservice.
|
||||
|
||||
## Explanation
|
||||
|
||||
With the Microservices pattern, a client may need data from multiple different microservices. If the client called each
|
||||
microservice directly, that could contribute to longer load times, since the client would have to make a network request
|
||||
for each microservice called. Moreover, having the client call each microservice directly ties the client to that
|
||||
microservice - if the internal implementations of the microservices change (for example, if two microservices are
|
||||
combined sometime in the future) or if the location (host and port) of a microservice changes, then every client that
|
||||
With the Microservices pattern, a client may need data from multiple different microservices. If the
|
||||
client called each microservice directly, that could contribute to longer load times, since the
|
||||
client would have to make a network request for each microservice called. Moreover, having the
|
||||
client call each microservice directly ties the client to that microservice - if the internal
|
||||
implementations of the microservices change (for example, if two microservices are combined sometime
|
||||
in the future) or if the location (host and port) of a microservice changes, then every client that
|
||||
makes use of those microservices must be updated.
|
||||
|
||||
The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway pattern, an additional
|
||||
entity (the API Gateway) is placed between the client and the microservices. The job of the API Gateway is to aggregate
|
||||
the calls to the microservices. Rather than the client calling each microservice individually, the client calls the
|
||||
API Gateway a single time. The API Gateway then calls each of the microservices that the client needs.
|
||||
The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway
|
||||
pattern, an additional entity (the API Gateway) is placed between the client and the microservices.
|
||||
The job of the API Gateway is to aggregate the calls to the microservices. Rather than the client
|
||||
calling each microservice individually, the client calls the API Gateway a single time. The API
|
||||
Gateway then calls each of the microservices that the client needs.
|
||||
|
||||
Real world example
|
||||
|
||||
> We are implementing microservices and API Gateway pattern for an e-commerce site. In this system the API Gateway makes
|
||||
calls to the Image and Price microservices.
|
||||
> We are implementing microservices and API Gateway pattern for an e-commerce site. In this system
|
||||
> the API Gateway makes calls to the Image and Price microservices.
|
||||
|
||||
In plain words
|
||||
|
||||
> For a system implemented using microservices architecture, API Gateway is the single entry point that aggregates the
|
||||
calls to the individual microservices.
|
||||
> For a system implemented using microservices architecture, API Gateway is the single entry point
|
||||
> that aggregates the calls to the individual microservices.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling and security
|
||||
policies, passes requests to the back-end service and then passes the response back to the requester. A gateway often
|
||||
includes a transformation engine to orchestrate and modify the requests and responses on the fly. A gateway can also
|
||||
provide functionality such as collecting analytics data and providing caching. The gateway can provide functionality to
|
||||
support authentication, authorization, security, audit and regulatory compliance.
|
||||
> API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling
|
||||
> and security policies, passes requests to the back-end service and then passes the response back
|
||||
> to the requester. A gateway often includes a transformation engine to orchestrate and modify the
|
||||
> requests and responses on the fly. A gateway can also provide functionality such as collecting
|
||||
> analytics data and providing caching. The gateway can provide functionality to support
|
||||
> authentication, authorization, security, audit and regulatory compliance.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
This implementation shows what the API Gateway pattern could look like for an e-commerce site. The `ApiGateway` makes
|
||||
calls to the Image and Price microservices using the `ImageClientImpl` and `PriceClientImpl` respectively. Customers
|
||||
viewing the site on a desktop device can see both price information and an image of a product, so the `ApiGateway` calls
|
||||
both of the microservices and aggregates the data in the `DesktopProduct` model. However, mobile users only see price
|
||||
information; they do not see a product image. For mobile users, the `ApiGateway` only retrieves price information, which
|
||||
it uses to populate the `MobileProduct`.
|
||||
This implementation shows what the API Gateway pattern could look like for an e-commerce site. The
|
||||
`ApiGateway` makes calls to the Image and Price microservices using the `ImageClientImpl` and
|
||||
`PriceClientImpl` respectively. Customers viewing the site on a desktop device can see both price
|
||||
information and an image of a product, so the `ApiGateway` calls both of the microservices and
|
||||
aggregates the data in the `DesktopProduct` model. However, mobile users only see price information;
|
||||
they do not see a product image. For mobile users, the `ApiGateway` only retrieves price
|
||||
information, which it uses to populate the `MobileProduct`.
|
||||
|
||||
Here's the Image microservice implementation.
|
||||
|
||||
@@ -64,7 +68,6 @@ public interface ImageClient {
|
||||
}
|
||||
|
||||
public class ImageClientImpl implements ImageClient {
|
||||
|
||||
@Override
|
||||
public String getImagePath() {
|
||||
var httpClient = HttpClient.newHttpClient();
|
||||
@@ -114,7 +117,7 @@ public class PriceClientImpl implements PriceClient {
|
||||
}
|
||||
```
|
||||
|
||||
And here we can see how API Gateway maps the requests to the microservices.
|
||||
Here we can see how API Gateway maps the requests to the microservices.
|
||||
|
||||
```java
|
||||
public class ApiGateway {
|
||||
|
@@ -23,11 +23,16 @@
|
||||
|
||||
package com.iluwatar.api.gateway;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -35,6 +40,8 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Component
|
||||
public class ImageClientImpl implements ImageClient {
|
||||
private static final Logger LOGGER = getLogger(ImageClientImpl.class);
|
||||
|
||||
/**
|
||||
* Makes a simple HTTP Get request to the Image microservice.
|
||||
*
|
||||
@@ -49,12 +56,26 @@ public class ImageClientImpl implements ImageClient {
|
||||
.build();
|
||||
|
||||
try {
|
||||
LOGGER.info("Sending request to fetch image path");
|
||||
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
|
||||
logResponse(httpResponse);
|
||||
return httpResponse.body();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("Failure occurred while getting image path", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void logResponse(HttpResponse<String> httpResponse) {
|
||||
if (isSuccessResponse(httpResponse.statusCode())) {
|
||||
LOGGER.info("Image path received successfully");
|
||||
} else {
|
||||
LOGGER.warn("Image path request failed");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSuccessResponse(int responseCode) {
|
||||
return responseCode >= 200 && responseCode <= 299;
|
||||
}
|
||||
}
|
||||
|
@@ -23,18 +23,26 @@
|
||||
|
||||
package com.iluwatar.api.gateway;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
/**
|
||||
* An adapter to communicate with the Price microservice.
|
||||
*/
|
||||
@Component
|
||||
public class PriceClientImpl implements PriceClient {
|
||||
private static final Logger LOGGER = getLogger(PriceClientImpl.class);
|
||||
|
||||
/**
|
||||
* Makes a simple HTTP Get request to the Price microservice.
|
||||
*
|
||||
@@ -49,12 +57,26 @@ public class PriceClientImpl implements PriceClient {
|
||||
.build();
|
||||
|
||||
try {
|
||||
LOGGER.info("Sending request to fetch price info");
|
||||
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
|
||||
logResponse(httpResponse);
|
||||
return httpResponse.body();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("Failure occurred while getting price info", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void logResponse(HttpResponse<String> httpResponse) {
|
||||
if (isSuccessResponse(httpResponse.statusCode())) {
|
||||
LOGGER.info("Price info received successfully");
|
||||
} else {
|
||||
LOGGER.warn("Price info request failed");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSuccessResponse(int responseCode) {
|
||||
return responseCode >= 200 && responseCode <= 299;
|
||||
}
|
||||
}
|
||||
|
@@ -23,15 +23,20 @@
|
||||
|
||||
package com.iluwatar.image.microservice;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
||||
/**
|
||||
* Exposes the Image microservice's endpoints.
|
||||
*/
|
||||
@RestController
|
||||
public class ImageController {
|
||||
private static final Logger LOGGER = getLogger(ImageController.class);
|
||||
|
||||
/**
|
||||
* An endpoint for a user to retrieve an image path.
|
||||
@@ -40,6 +45,7 @@ public class ImageController {
|
||||
*/
|
||||
@RequestMapping(value = "/image-path", method = RequestMethod.GET)
|
||||
public String getImagePath() {
|
||||
LOGGER.info("Successfully found image path");
|
||||
return "/product-image.png";
|
||||
}
|
||||
}
|
||||
|
@@ -23,15 +23,20 @@
|
||||
|
||||
package com.iluwatar.price.microservice;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
||||
/**
|
||||
* Exposes the Price microservice's endpoints.
|
||||
*/
|
||||
@RestController
|
||||
public class PriceController {
|
||||
private static final Logger LOGGER = getLogger(PriceController.class);
|
||||
|
||||
/**
|
||||
* An endpoint for a user to retrieve a product's price.
|
||||
@@ -40,6 +45,7 @@ public class PriceController {
|
||||
*/
|
||||
@RequestMapping(value = "/price", method = RequestMethod.GET)
|
||||
public String getPrice() {
|
||||
LOGGER.info("Successfully found price info");
|
||||
return "20";
|
||||
}
|
||||
}
|
||||
|
@@ -46,7 +46,7 @@ public class Orcs implements WeatherObserver {
|
||||
|
||||
@Override
|
||||
public void update(WeatherType currentWeather) {
|
||||
LOGGER.info("The hobbits are facing " + currentWeather.getDescription() + " weather now");
|
||||
LOGGER.info("The orcs are facing " + currentWeather.getDescription() + " weather now");
|
||||
}
|
||||
}
|
||||
|
||||
|
3
pom.xml
3
pom.xml
@@ -53,7 +53,7 @@
|
||||
<jaxb-impl.version>2.3.2</jaxb-impl.version>
|
||||
<annotation-api.version>1.3.2</annotation-api.version>
|
||||
<system-rules.version>1.19.0</system-rules.version>
|
||||
<urm.version>1.4.8</urm.version>
|
||||
<urm.version>2.0.0</urm.version>
|
||||
<!-- SonarCloud -->
|
||||
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
|
||||
<sonar.organization>iluwatar</sonar.organization>
|
||||
@@ -192,6 +192,7 @@
|
||||
<module>leader-followers</module>
|
||||
<module>strangler</module>
|
||||
<module>arrange-act-assert</module>
|
||||
<module>transaction-script</module>
|
||||
</modules>
|
||||
|
||||
<repositories>
|
||||
|
@@ -9,9 +9,119 @@ tags:
|
||||
---
|
||||
|
||||
## Intent
|
||||
Private Class Data design pattern seeks to reduce exposure of
|
||||
attributes by limiting their visibility. It reduces the number of class
|
||||
attributes by encapsulating them in single Data object.
|
||||
Private Class Data design pattern seeks to reduce exposure of attributes by limiting their
|
||||
visibility. It reduces the number of class attributes by encapsulating them in single Data object.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> Imagine you are cooking a stew for your family for dinner. You want to prevent your family members
|
||||
> from consuming the stew by tasting it while you are cooking, otherwise there will be no more stew
|
||||
> for dinner later.
|
||||
|
||||
In plain words
|
||||
|
||||
> Private class data pattern prevents manipulation of data that is meant to be immutable by
|
||||
> separating the data from the methods that use it into a class that maintains the data state.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> Private class data is a design pattern in computer programming used to encapsulate class
|
||||
> attributes and their manipulation.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Taking our stew example from above. First we have a `Stew` class where its data is not protected by
|
||||
private class data, making the stew's ingredient mutable to class methods.
|
||||
|
||||
```java
|
||||
public class Stew {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Stew.class);
|
||||
private int numPotatoes;
|
||||
private int numCarrots;
|
||||
private int numMeat;
|
||||
private int numPeppers;
|
||||
public Stew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) {
|
||||
this.numPotatoes = numPotatoes;
|
||||
this.numCarrots = numCarrots;
|
||||
this.numMeat = numMeat;
|
||||
this.numPeppers = numPeppers;
|
||||
}
|
||||
public void mix() {
|
||||
LOGGER.info("Mixing the stew we find: {} potatoes, {} carrots, {} meat and {} peppers",
|
||||
numPotatoes, numCarrots, numMeat, numPeppers);
|
||||
}
|
||||
public void taste() {
|
||||
LOGGER.info("Tasting the stew");
|
||||
if (numPotatoes > 0) {
|
||||
numPotatoes--;
|
||||
}
|
||||
if (numCarrots > 0) {
|
||||
numCarrots--;
|
||||
}
|
||||
if (numMeat > 0) {
|
||||
numMeat--;
|
||||
}
|
||||
if (numPeppers > 0) {
|
||||
numPeppers--;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now, we have `ImmutableStew` class, where its data is protected by `StewData` class. Now, the
|
||||
methods in are unable to manipulate the data of the `ImmutableStew` class.
|
||||
|
||||
```java
|
||||
public class StewData {
|
||||
private final int numPotatoes;
|
||||
private final int numCarrots;
|
||||
private final int numMeat;
|
||||
private final int numPeppers;
|
||||
public StewData(int numPotatoes, int numCarrots, int numMeat, int numPeppers) {
|
||||
this.numPotatoes = numPotatoes;
|
||||
this.numCarrots = numCarrots;
|
||||
this.numMeat = numMeat;
|
||||
this.numPeppers = numPeppers;
|
||||
}
|
||||
public int getNumPotatoes() {
|
||||
return numPotatoes;
|
||||
}
|
||||
public int getNumCarrots() {
|
||||
return numCarrots;
|
||||
}
|
||||
public int getNumMeat() {
|
||||
return numMeat;
|
||||
}
|
||||
public int getNumPeppers() {
|
||||
return numPeppers;
|
||||
}
|
||||
}
|
||||
public class ImmutableStew {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ImmutableStew.class);
|
||||
private final StewData data;
|
||||
public ImmutableStew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) {
|
||||
data = new StewData(numPotatoes, numCarrots, numMeat, numPeppers);
|
||||
}
|
||||
public void mix() {
|
||||
LOGGER
|
||||
.info("Mixing the immutable stew we find: {} potatoes, {} carrots, {} meat and {} peppers",
|
||||
data.getNumPotatoes(), data.getNumCarrots(), data.getNumMeat(), data.getNumPeppers());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's try creating an instance of each class and call their methods:
|
||||
|
||||
```java
|
||||
var stew = new Stew(1, 2, 3, 4);
|
||||
stew.mix(); // Mixing the stew we find: 1 potatoes, 2 carrots, 3 meat and 4 peppers
|
||||
stew.taste(); // Tasting the stew
|
||||
stew.mix(); // Mixing the stew we find: 0 potatoes, 1 carrots, 2 meat and 3 peppers
|
||||
var immutableStew = new ImmutableStew(2, 4, 3, 6);
|
||||
immutableStew.mix(); // Mixing the immutable stew we find: 2 potatoes, 4 carrots, 3 meat and 6 peppers
|
||||
```
|
||||
|
||||
## Class diagram
|
||||

|
||||
|
@@ -45,7 +45,7 @@ public final class InitializingOnDemandHolderIdiom {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sigleton instance.
|
||||
* Singleton instance.
|
||||
*
|
||||
* @return Singleton instance
|
||||
*/
|
||||
|
2
transaction-script/.gitignore
vendored
Normal file
2
transaction-script/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target/
|
||||
.idea/
|
119
transaction-script/README.md
Normal file
119
transaction-script/README.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Transaction Script
|
||||
folder: transaction-script
|
||||
permalink: /patterns/transaction-script/
|
||||
categories: Behavioral
|
||||
tags:
|
||||
- Data access
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
||||
Transaction Script organizes business logic by procedures where each procedure handles a single
|
||||
request from the presentation.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> You need to create a hotel room booking system. Since the requirements are quite simple we intend
|
||||
> to use the Transaction Script pattern here.
|
||||
|
||||
In plain words
|
||||
|
||||
> Transaction Script organizes business logic into transactions that the system needs to carry out.
|
||||
|
||||
Programmatic example
|
||||
|
||||
The `Hotel` class takes care of booking and cancelling room reservations.
|
||||
|
||||
```java
|
||||
public class Hotel {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
|
||||
private final HotelDaoImpl hotelDao;
|
||||
|
||||
public Hotel(HotelDaoImpl hotelDao) {
|
||||
this.hotelDao = hotelDao;
|
||||
}
|
||||
|
||||
public void bookRoom(int roomNumber) throws Exception {
|
||||
|
||||
Optional<Room> room = hotelDao.getById(roomNumber);
|
||||
|
||||
if (room.isEmpty()) {
|
||||
throw new Exception("Room number: " + roomNumber + " does not exist");
|
||||
} else {
|
||||
if (room.get().isBooked()) {
|
||||
throw new Exception("Room already booked!");
|
||||
} else {
|
||||
Room updateRoomBooking = room.get();
|
||||
updateRoomBooking.setBooked(true);
|
||||
hotelDao.update(updateRoomBooking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void cancelRoomBooking(int roomNumber) throws Exception {
|
||||
|
||||
Optional<Room> room = hotelDao.getById(roomNumber);
|
||||
|
||||
if (room.isEmpty()) {
|
||||
throw new Exception("Room number: " + roomNumber + " does not exist");
|
||||
} else {
|
||||
if (room.get().isBooked()) {
|
||||
Room updateRoomBooking = room.get();
|
||||
updateRoomBooking.setBooked(false);
|
||||
int refundAmount = updateRoomBooking.getPrice();
|
||||
hotelDao.update(updateRoomBooking);
|
||||
|
||||
LOGGER.info("Booking cancelled for room number: " + roomNumber);
|
||||
LOGGER.info(refundAmount + " is refunded");
|
||||
} else {
|
||||
throw new Exception("No booking for the room exists");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `Hotel` class has two methods, one for booking and cancelling a room respectively. Each one of
|
||||
them handles a single transaction in the system, making `Hotel` implement the Transaction Script
|
||||
pattern.
|
||||
|
||||
The `bookRoom` method consolidates all the needed steps like checking if the room is already booked
|
||||
or not, if not booked then books the room and updates the database by using the DAO.
|
||||
|
||||
The `cancelRoom` method consolidates steps like checking if the room is booked or not,
|
||||
if booked then calculates the refund amount and updates the database using the DAO.
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Transaction Script pattern when the application has only a small amount of logic and that
|
||||
logic won't be extended in the future.
|
||||
|
||||
## Consequences
|
||||
|
||||
* As the business logic gets more complicated,
|
||||
it gets progressively harder to keep the transaction script
|
||||
in a well-designed state.
|
||||
* Code duplication between transaction scripts can occur.
|
||||
* Normally not easy to refactor transactions script to other domain logic
|
||||
patterns.
|
||||
|
||||
## Related patterns
|
||||
|
||||
* Domain Model
|
||||
* Table Module
|
||||
* Service Layer
|
||||
|
||||
## Credits
|
||||
|
||||
* [Transaction Script Pattern](https://dzone.com/articles/transaction-script-pattern#:~:text=Transaction%20Script%20(TS)%20is%20the,need%20big%20architecture%20behind%20them.)
|
||||
* [Transaction Script](https://www.informit.com/articles/article.aspx?p=1398617)
|
||||
* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=18acc13ba60d66690009505577c45c04)
|
BIN
transaction-script/etc/transaction-script.png
Normal file
BIN
transaction-script/etc/transaction-script.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
65
transaction-script/etc/transaction-script.urm.puml
Normal file
65
transaction-script/etc/transaction-script.urm.puml
Normal file
@@ -0,0 +1,65 @@
|
||||
@startuml
|
||||
package com.ashishtrivedi16.transaction-script {
|
||||
class App {
|
||||
- H2_DB_URL : String {static}
|
||||
- LOGGER : Logger {static}
|
||||
- addRooms(hotelDaoImpl : HotelDaoImpl) {static}
|
||||
- createDataSource() : DataSource {static}
|
||||
- createSchema(dataSource : DataSource) {static}
|
||||
- deleteSchema(dataSource : DataSource) {static}
|
||||
- getRoomsStatus(hotelDaoImpl : HotelDaoImpl) {static}
|
||||
- generateSampleRooms() : List<Room> {static}
|
||||
+ main(args : String[]) {static}
|
||||
}
|
||||
class Room {
|
||||
- id: Int
|
||||
- roomType: String
|
||||
- price: Int
|
||||
- booked: Boolean
|
||||
+ Customer(id : int, roomType : String, price: Int, booked: Boolean)
|
||||
+ getId() : int
|
||||
+ getRoomType() : String
|
||||
+ getPrice() : Int
|
||||
+ isBooked() : Boolean
|
||||
+ setId(id : int)
|
||||
+ setRoomType(roomType : String)
|
||||
+ setPrice(price : Int)
|
||||
+ setBooked(booked : boolean)
|
||||
+ equals(that : Object) : boolean
|
||||
+ hashCode() : int
|
||||
+ toString() : String
|
||||
}
|
||||
interface HotelDao {
|
||||
+ add(Room) : boolean {abstract}
|
||||
+ delete(Room) : boolean {abstract}
|
||||
+ getAll() : Stream<Room> {abstract}
|
||||
+ getById(int) : Optional<Room> {abstract}
|
||||
+ update(Room) : boolean {abstract}
|
||||
}
|
||||
class RoomSchemaSql {
|
||||
+ CREATE_SCHEMA_SQL : String {static}
|
||||
+ DELETE_SCHEMA_SQL : String {static}
|
||||
- RoomSchemaSql()
|
||||
}
|
||||
class HotelDaoImpl {
|
||||
- dataSource : DataSource
|
||||
+ HotelDaoImpl(dataSource : DataSource)
|
||||
+ add(room : Room) : boolean
|
||||
- createRoom(resultSet : ResultSet) : Room
|
||||
+ delete(room : Room) : boolean
|
||||
+ getAll() : Stream<Room>
|
||||
+ getById(id : int) : Optional<Room>
|
||||
- getConnection() : Connection
|
||||
- mutedClose(connection : Connection, statement : PreparedStatement, resultSet : ResultSet)
|
||||
+ update(room : Room) : boolean
|
||||
}
|
||||
class Hotel {
|
||||
- LOGGER : Logger {static}
|
||||
- hotelDao: HotelDaoImpl
|
||||
+ Hotel(hotelDao: HotelDaoImpl)
|
||||
+ bookRoom(roomNumber: Int)
|
||||
+ cancelRoomBooking(roomNumber: Int)
|
||||
}
|
||||
}
|
||||
HotelDaoImpl ..|> HotelDao
|
||||
@enduml
|
73
transaction-script/pom.xml
Normal file
73
transaction-script/pom.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
The MIT License
|
||||
Copyright © 2014-2019 Ilkka Seppälä
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
-->
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<version>1.23.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>transaction-script</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.iluwatar.transactionscript.App</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.transactionscript;
|
||||
|
||||
import java.util.List;
|
||||
import javax.sql.DataSource;
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Transaction Script (TS) is one of the simplest domain logic pattern.
|
||||
* It needs less work to implement than other domain logic patterns and therefore
|
||||
* it’s perfect fit for smaller applications that don't need big architecture behind them.
|
||||
*
|
||||
* <p>In this example we will use the TS pattern to implement booking and cancellation
|
||||
* methods for a Hotel management App. The main method will initialise an instance of
|
||||
* {@link Hotel} and add rooms to it. After that it will book and cancel a couple of rooms
|
||||
* and that will be printed by the logger.</p>
|
||||
*
|
||||
* <p>The thing we have to note here is that all the operations related to booking or cancelling
|
||||
* a room like checking the database if the room exists, checking the booking status or the
|
||||
* room, calculating refund price are all clubbed inside a single transaction script method.</p>
|
||||
*/
|
||||
public class App {
|
||||
|
||||
private static final String H2_DB_URL = "jdbc:h2:~/test";
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
* Initialises an instance of Hotel and adds rooms to it.
|
||||
* Carries out booking and cancel booking transactions.
|
||||
* @param args command line arguments
|
||||
* @throws Exception if any error occurs
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
final var dataSource = createDataSource();
|
||||
deleteSchema(dataSource);
|
||||
createSchema(dataSource);
|
||||
final var dao = new HotelDaoImpl(dataSource);
|
||||
|
||||
// Add rooms
|
||||
addRooms(dao);
|
||||
|
||||
// Print room booking status
|
||||
getRoomStatus(dao);
|
||||
|
||||
var hotel = new Hotel(dao);
|
||||
|
||||
// Book rooms
|
||||
hotel.bookRoom(1);
|
||||
hotel.bookRoom(2);
|
||||
hotel.bookRoom(3);
|
||||
hotel.bookRoom(4);
|
||||
hotel.bookRoom(5);
|
||||
hotel.bookRoom(6);
|
||||
|
||||
// Cancel booking for a few rooms
|
||||
hotel.cancelRoomBooking(1);
|
||||
hotel.cancelRoomBooking(3);
|
||||
hotel.cancelRoomBooking(5);
|
||||
|
||||
getRoomStatus(dao);
|
||||
|
||||
deleteSchema(dataSource);
|
||||
|
||||
}
|
||||
|
||||
private static void getRoomStatus(HotelDaoImpl dao) throws Exception {
|
||||
try (var customerStream = dao.getAll()) {
|
||||
customerStream.forEach((customer) -> LOGGER.info(customer.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteSchema(DataSource dataSource) throws java.sql.SQLException {
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement()) {
|
||||
statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL);
|
||||
}
|
||||
}
|
||||
|
||||
private static void createSchema(DataSource dataSource) throws Exception {
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement()) {
|
||||
statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL);
|
||||
} catch (Exception e) {
|
||||
throw new Exception(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database.
|
||||
*
|
||||
* @return h2 datasource
|
||||
*/
|
||||
private static DataSource createDataSource() {
|
||||
var dataSource = new JdbcDataSource();
|
||||
dataSource.setUrl(H2_DB_URL);
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
private static void addRooms(HotelDaoImpl hotelDao) throws Exception {
|
||||
for (var room : generateSampleRooms()) {
|
||||
hotelDao.add(room);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate rooms.
|
||||
*
|
||||
* @return list of rooms
|
||||
*/
|
||||
private static List<Room> generateSampleRooms() {
|
||||
final var room1 = new Room(1, "Single", 50, false);
|
||||
final var room2 = new Room(2, "Double", 80, false);
|
||||
final var room3 = new Room(3, "Queen", 120, false);
|
||||
final var room4 = new Room(4, "King", 150, false);
|
||||
final var room5 = new Room(5, "Single", 50, false);
|
||||
final var room6 = new Room(6, "Double", 80, false);
|
||||
return List.of(room1, room2, room3, room4, room5, room6);
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.transactionscript;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Hotel {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
|
||||
private final HotelDaoImpl hotelDao;
|
||||
|
||||
public Hotel(HotelDaoImpl hotelDao) {
|
||||
this.hotelDao = hotelDao;
|
||||
}
|
||||
|
||||
/**
|
||||
* Book a room.
|
||||
*
|
||||
* @param roomNumber room to book
|
||||
* @throws Exception if any error
|
||||
*/
|
||||
public void bookRoom(int roomNumber) throws Exception {
|
||||
|
||||
var room = hotelDao.getById(roomNumber);
|
||||
|
||||
if (room.isEmpty()) {
|
||||
throw new Exception("Room number: " + roomNumber + " does not exist");
|
||||
} else {
|
||||
if (room.get().isBooked()) {
|
||||
throw new Exception("Room already booked!");
|
||||
} else {
|
||||
var updateRoomBooking = room.get();
|
||||
updateRoomBooking.setBooked(true);
|
||||
hotelDao.update(updateRoomBooking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a room booking.
|
||||
*
|
||||
* @param roomNumber room to cancel booking
|
||||
* @throws Exception if any error
|
||||
*/
|
||||
public void cancelRoomBooking(int roomNumber) throws Exception {
|
||||
|
||||
var room = hotelDao.getById(roomNumber);
|
||||
|
||||
if (room.isEmpty()) {
|
||||
throw new Exception("Room number: " + roomNumber + " does not exist");
|
||||
} else {
|
||||
if (room.get().isBooked()) {
|
||||
var updateRoomBooking = room.get();
|
||||
updateRoomBooking.setBooked(false);
|
||||
int refundAmount = updateRoomBooking.getPrice();
|
||||
hotelDao.update(updateRoomBooking);
|
||||
|
||||
LOGGER.info("Booking cancelled for room number: " + roomNumber);
|
||||
LOGGER.info(refundAmount + " is refunded");
|
||||
} else {
|
||||
throw new Exception("No booking for the room exists");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.transactionscript;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface HotelDao {
|
||||
|
||||
Stream<Room> getAll() throws Exception;
|
||||
|
||||
Optional<Room> getById(int id) throws Exception;
|
||||
|
||||
Boolean add(Room room) throws Exception;
|
||||
|
||||
Boolean update(Room room) throws Exception;
|
||||
|
||||
Boolean delete(Room room) throws Exception;
|
||||
}
|
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.transactionscript;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
import javax.sql.DataSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class HotelDaoImpl implements HotelDao {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
public HotelDaoImpl(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Room> getAll() throws Exception {
|
||||
try {
|
||||
var connection = getConnection();
|
||||
var statement = connection.prepareStatement("SELECT * FROM ROOMS");
|
||||
var resultSet = statement.executeQuery(); // NOSONAR
|
||||
return StreamSupport.stream(new Spliterators.AbstractSpliterator<Room>(Long.MAX_VALUE,
|
||||
Spliterator.ORDERED) {
|
||||
|
||||
@Override
|
||||
public boolean tryAdvance(Consumer<? super Room> action) {
|
||||
try {
|
||||
if (!resultSet.next()) {
|
||||
return false;
|
||||
}
|
||||
action.accept(createRoom(resultSet));
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e); // NOSONAR
|
||||
}
|
||||
}
|
||||
}, false).onClose(() -> {
|
||||
try {
|
||||
mutedClose(connection, statement, resultSet);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new Exception(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Room> getById(int id) throws Exception {
|
||||
ResultSet resultSet = null;
|
||||
|
||||
try (var connection = getConnection();
|
||||
var statement = connection.prepareStatement("SELECT * FROM ROOMS WHERE ID = ?")) {
|
||||
|
||||
statement.setInt(1, id);
|
||||
resultSet = statement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
return Optional.of(createRoom(resultSet));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new Exception(e.getMessage(), e);
|
||||
} finally {
|
||||
if (resultSet != null) {
|
||||
resultSet.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean add(Room room) throws Exception {
|
||||
if (getById(room.getId()).isPresent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try (var connection = getConnection();
|
||||
var statement = connection.prepareStatement("INSERT INTO ROOMS VALUES (?,?,?,?)")) {
|
||||
statement.setInt(1, room.getId());
|
||||
statement.setString(2, room.getRoomType());
|
||||
statement.setInt(3, room.getPrice());
|
||||
statement.setBoolean(4, room.isBooked());
|
||||
statement.execute();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
throw new Exception(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean update(Room room) throws Exception {
|
||||
try (var connection = getConnection();
|
||||
var statement =
|
||||
connection
|
||||
.prepareStatement("UPDATE ROOMS SET ROOM_TYPE = ?, PRICE = ?, BOOKED = ?"
|
||||
+ " WHERE ID = ?")) {
|
||||
statement.setString(1, room.getRoomType());
|
||||
statement.setInt(2, room.getPrice());
|
||||
statement.setBoolean(3, room.isBooked());
|
||||
statement.setInt(4, room.getId());
|
||||
return statement.executeUpdate() > 0;
|
||||
} catch (Exception e) {
|
||||
throw new Exception(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean delete(Room room) throws Exception {
|
||||
try (var connection = getConnection();
|
||||
var statement = connection.prepareStatement("DELETE FROM ROOMS WHERE ID = ?")) {
|
||||
statement.setInt(1, room.getId());
|
||||
return statement.executeUpdate() > 0;
|
||||
} catch (Exception e) {
|
||||
throw new Exception(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Connection getConnection() throws Exception {
|
||||
return dataSource.getConnection();
|
||||
}
|
||||
|
||||
private void mutedClose(Connection connection, PreparedStatement statement, ResultSet resultSet)
|
||||
throws Exception {
|
||||
try {
|
||||
resultSet.close();
|
||||
statement.close();
|
||||
connection.close();
|
||||
} catch (Exception e) {
|
||||
throw new Exception(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Room createRoom(ResultSet resultSet) throws Exception {
|
||||
return new Room(resultSet.getInt("ID"),
|
||||
resultSet.getString("ROOM_TYPE"),
|
||||
resultSet.getInt("PRICE"),
|
||||
resultSet.getBoolean("BOOKED"));
|
||||
}
|
||||
}
|
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.transactionscript;
|
||||
|
||||
/**
|
||||
* A room POJO that represents the data that will be read from the data source.
|
||||
*/
|
||||
public class Room {
|
||||
|
||||
private int id;
|
||||
private String roomType;
|
||||
private int price;
|
||||
private boolean booked;
|
||||
|
||||
/**
|
||||
* Create an instance of room.
|
||||
* @param id room id
|
||||
* @param roomType room type
|
||||
* @param price room price
|
||||
* @param booked room booking status
|
||||
*/
|
||||
public Room(int id, String roomType, int price, boolean booked) {
|
||||
this.id = id;
|
||||
this.roomType = roomType;
|
||||
this.price = price;
|
||||
this.booked = booked;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getRoomType() {
|
||||
return roomType;
|
||||
}
|
||||
|
||||
public void setRoomType(String roomType) {
|
||||
this.roomType = roomType;
|
||||
}
|
||||
|
||||
public int getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(int price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public boolean isBooked() {
|
||||
return booked;
|
||||
}
|
||||
|
||||
public void setBooked(boolean booked) {
|
||||
this.booked = booked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Room room = (Room) o;
|
||||
|
||||
if (id != room.id) {
|
||||
return false;
|
||||
}
|
||||
if (price != room.price) {
|
||||
return false;
|
||||
}
|
||||
if (booked != room.booked) {
|
||||
return false;
|
||||
}
|
||||
return roomType.equals(room.roomType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = id;
|
||||
result = 31 * result + roomType.hashCode();
|
||||
result = 31 * result + price;
|
||||
result = 31 * result + (booked ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Room{"
|
||||
+ "id=" + id
|
||||
+ ", roomType=" + roomType
|
||||
+ ", price=" + price
|
||||
+ ", booked=" + booked
|
||||
+ '}';
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.transactionscript;
|
||||
|
||||
/**
|
||||
* Customer Schema SQL Class.
|
||||
*/
|
||||
public final class RoomSchemaSql {
|
||||
|
||||
public static final String CREATE_SCHEMA_SQL =
|
||||
"CREATE TABLE ROOMS (ID NUMBER, ROOM_TYPE VARCHAR(100), PRICE INT(100), BOOKED VARCHAR(100))";
|
||||
public static final String DELETE_SCHEMA_SQL = "DROP TABLE ROOMS IF EXISTS";
|
||||
|
||||
private RoomSchemaSql() {
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.transactionscript;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests that Transaction script example runs without errors.
|
||||
*/
|
||||
public class AppTest {
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.transactionscript;
|
||||
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Tests {@link HotelDaoImpl}.
|
||||
*/
|
||||
public class HotelDaoImplTest {
|
||||
|
||||
private static final String DB_URL = "jdbc:h2:~/test";
|
||||
private HotelDaoImpl dao;
|
||||
private Room existingRoom = new Room(1, "Single", 50, false);
|
||||
|
||||
/**
|
||||
* Creates rooms schema.
|
||||
*
|
||||
* @throws SQLException if there is any error while creating schema.
|
||||
*/
|
||||
@BeforeEach
|
||||
public void createSchema() throws SQLException {
|
||||
try (var connection = DriverManager.getConnection(DB_URL);
|
||||
var statement = connection.createStatement()) {
|
||||
statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL);
|
||||
statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the scenario where DB connectivity is present.
|
||||
*/
|
||||
@Nested
|
||||
public class ConnectionSuccess {
|
||||
|
||||
/**
|
||||
* Setup for connection success scenario.
|
||||
*
|
||||
* @throws Exception if any error occurs.
|
||||
*/
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
var dataSource = new JdbcDataSource();
|
||||
dataSource.setURL(DB_URL);
|
||||
dao = new HotelDaoImpl(dataSource);
|
||||
var result = dao.add(existingRoom);
|
||||
Assertions.assertTrue(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the scenario when DAO operations are being performed on a non existing room.
|
||||
*/
|
||||
@Nested
|
||||
public class NonExistingRoom {
|
||||
|
||||
@Test
|
||||
public void addingShouldResultInSuccess() throws Exception {
|
||||
try (var allRooms = dao.getAll()) {
|
||||
assumeTrue(allRooms.count() == 1);
|
||||
}
|
||||
|
||||
final var nonExistingRoom = new Room(2, "Double", 80, false);
|
||||
var result = dao.add(nonExistingRoom);
|
||||
Assertions.assertTrue(result);
|
||||
|
||||
assertRoomCountIs(2);
|
||||
assertEquals(nonExistingRoom, dao.getById(nonExistingRoom.getId()).get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deletionShouldBeFailureAndNotAffectExistingRooms() throws Exception {
|
||||
final var nonExistingRoom = new Room(2, "Double", 80, false);
|
||||
var result = dao.delete(nonExistingRoom);
|
||||
|
||||
Assertions.assertFalse(result);
|
||||
assertRoomCountIs(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updationShouldBeFailureAndNotAffectExistingRooms() throws Exception {
|
||||
final var nonExistingId = getNonExistingRoomId();
|
||||
final var newRoomType = "Double";
|
||||
final var newPrice = 80;
|
||||
final var room = new Room(nonExistingId, newRoomType, newPrice, false);
|
||||
var result = dao.update(room);
|
||||
|
||||
Assertions.assertFalse(result);
|
||||
assertFalse(dao.getById(nonExistingId).isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrieveShouldReturnNoRoom() throws Exception {
|
||||
assertFalse(dao.getById(getNonExistingRoomId()).isPresent());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a scenario where DAO operations are being performed on an already existing
|
||||
* room.
|
||||
*/
|
||||
@Nested
|
||||
public class ExistingRoom {
|
||||
|
||||
@Test
|
||||
public void addingShouldResultInFailureAndNotAffectExistingRooms() throws Exception {
|
||||
var existingRoom = new Room(1, "Single", 50, false);
|
||||
var result = dao.add(existingRoom);
|
||||
|
||||
Assertions.assertFalse(result);
|
||||
assertRoomCountIs(1);
|
||||
assertEquals(existingRoom, dao.getById(existingRoom.getId()).get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deletionShouldBeSuccessAndRoomShouldBeNonAccessible() throws Exception {
|
||||
var result = dao.delete(existingRoom);
|
||||
|
||||
Assertions.assertTrue(result);
|
||||
assertRoomCountIs(0);
|
||||
assertFalse(dao.getById(existingRoom.getId()).isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updationShouldBeSuccessAndAccessingTheSameRoomShouldReturnUpdatedInformation() throws
|
||||
Exception {
|
||||
final var newRoomType = "Double";
|
||||
final var newPrice = 80;
|
||||
final var newBookingStatus = false;
|
||||
final var Room = new Room(existingRoom.getId(), newRoomType, newPrice, newBookingStatus);
|
||||
var result = dao.update(Room);
|
||||
|
||||
Assertions.assertTrue(result);
|
||||
|
||||
final var room = dao.getById(existingRoom.getId()).get();
|
||||
assertEquals(newRoomType, room.getRoomType());
|
||||
assertEquals(newPrice, room.getPrice());
|
||||
assertEquals(newBookingStatus, room.isBooked());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a scenario where DB connectivity is not present due to network issue, or DB service
|
||||
* unavailable.
|
||||
*/
|
||||
@Nested
|
||||
public class ConnectivityIssue {
|
||||
|
||||
private static final String EXCEPTION_CAUSE = "Connection not available";
|
||||
|
||||
/**
|
||||
* setup a connection failure scenario.
|
||||
*
|
||||
* @throws SQLException if any error occurs.
|
||||
*/
|
||||
@BeforeEach
|
||||
public void setUp() throws SQLException {
|
||||
dao = new HotelDaoImpl(mockedDatasource());
|
||||
}
|
||||
|
||||
private DataSource mockedDatasource() throws SQLException {
|
||||
var mockedDataSource = mock(DataSource.class);
|
||||
var mockedConnection = mock(Connection.class);
|
||||
var exception = new SQLException(EXCEPTION_CAUSE);
|
||||
doThrow(exception).when(mockedConnection).prepareStatement(Mockito.anyString());
|
||||
doReturn(mockedConnection).when(mockedDataSource).getConnection();
|
||||
return mockedDataSource;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addingARoomFailsWithExceptionAsFeedbackToClient() {
|
||||
assertThrows(Exception.class, () -> {
|
||||
dao.add(new Room(2, "Double", 80, false));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deletingARoomFailsWithExceptionAsFeedbackToTheClient() {
|
||||
assertThrows(Exception.class, () -> {
|
||||
dao.delete(existingRoom);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatingARoomFailsWithFeedbackToTheClient() {
|
||||
final var newRoomType = "Double";
|
||||
final var newPrice = 80;
|
||||
final var newBookingStatus = false;
|
||||
assertThrows(Exception.class, () -> {
|
||||
dao.update(new Room(existingRoom.getId(), newRoomType, newPrice, newBookingStatus));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrievingARoomByIdFailsWithExceptionAsFeedbackToClient() {
|
||||
assertThrows(Exception.class, () -> {
|
||||
dao.getById(existingRoom.getId());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrievingAllRoomsFailsWithExceptionAsFeedbackToClient() {
|
||||
assertThrows(Exception.class, () -> {
|
||||
dao.getAll();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete room schema for fresh setup per test.
|
||||
*
|
||||
* @throws SQLException if any error occurs.
|
||||
*/
|
||||
@AfterEach
|
||||
public void deleteSchema() throws SQLException {
|
||||
try (var connection = DriverManager.getConnection(DB_URL);
|
||||
var statement = connection.createStatement()) {
|
||||
statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertRoomCountIs(int count) throws Exception {
|
||||
try (var allRooms = dao.getAll()) {
|
||||
assertEquals(count, allRooms.count());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An arbitrary number which does not correspond to an active Room id.
|
||||
*
|
||||
* @return an int of a room id which doesn't exist
|
||||
*/
|
||||
private int getNonExistingRoomId() {
|
||||
return 999;
|
||||
}
|
||||
}
|
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.transactionscript;
|
||||
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests {@link Hotel}
|
||||
*/
|
||||
public class HotelTest {
|
||||
|
||||
private static final String H2_DB_URL = "jdbc:h2:~/test";
|
||||
|
||||
private Hotel hotel;
|
||||
private HotelDaoImpl dao;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
final var dataSource = createDataSource();
|
||||
deleteSchema(dataSource);
|
||||
createSchema(dataSource);
|
||||
dao = new HotelDaoImpl(dataSource);
|
||||
addRooms(dao);
|
||||
hotel = new Hotel(dao);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bookingRoomShouldChangeBookedStatusToTrue() throws Exception {
|
||||
hotel.bookRoom(1);
|
||||
assertTrue(dao.getById(1).get().isBooked());
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void bookingRoomWithInvalidIdShouldRaiseException() {
|
||||
assertThrows(Exception.class, () -> {
|
||||
hotel.bookRoom(getNonExistingRoomId());
|
||||
});
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void bookingRoomAgainShouldRaiseException() {
|
||||
assertThrows(Exception.class, () -> {
|
||||
hotel.bookRoom(1);
|
||||
hotel.bookRoom(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void NotBookingRoomShouldNotChangeBookedStatus() throws Exception {
|
||||
assertFalse(dao.getById(1).get().isBooked());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cancelRoomBookingShouldChangeBookedStatus() throws Exception {
|
||||
hotel.bookRoom(1);
|
||||
assertTrue(dao.getById(1).get().isBooked());
|
||||
hotel.cancelRoomBooking(1);
|
||||
assertFalse(dao.getById(1).get().isBooked());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cancelRoomBookingWithInvalidIdShouldRaiseException() {
|
||||
assertThrows(Exception.class, () -> {
|
||||
hotel.cancelRoomBooking(getNonExistingRoomId());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cancelRoomBookingForUnbookedRoomShouldRaiseException() {
|
||||
assertThrows(Exception.class, () -> {
|
||||
hotel.cancelRoomBooking(1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static void deleteSchema(DataSource dataSource) throws java.sql.SQLException {
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement()) {
|
||||
statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL);
|
||||
}
|
||||
}
|
||||
|
||||
private static void createSchema(DataSource dataSource) throws Exception {
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement()) {
|
||||
statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL);
|
||||
} catch (Exception e) {
|
||||
throw new Exception(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static DataSource createDataSource() {
|
||||
JdbcDataSource dataSource = new JdbcDataSource();
|
||||
dataSource.setUrl(H2_DB_URL);
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
private static void addRooms(HotelDaoImpl hotelDao) throws Exception {
|
||||
for (var room : generateSampleRooms()) {
|
||||
hotelDao.add(room);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Room> generateSampleRooms() {
|
||||
final var room1 = new Room(1, "Single", 50, false);
|
||||
final var room2 = new Room(2, "Double", 80, false);
|
||||
final var room3 = new Room(3, "Queen", 120, false);
|
||||
final var room4 = new Room(4, "King", 150, false);
|
||||
final var room5 = new Room(5, "Single", 50, false);
|
||||
final var room6 = new Room(6, "Double", 80, false);
|
||||
return List.of(room1, room2, room3, room4, room5, room6);
|
||||
}
|
||||
|
||||
/**
|
||||
* An arbitrary number which does not correspond to an active Room id.
|
||||
*
|
||||
* @return an int of a room id which doesn't exist
|
||||
*/
|
||||
private int getNonExistingRoomId() {
|
||||
return 999;
|
||||
}
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.transactionscript;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
/**
|
||||
* Tests {@link Room}.
|
||||
*/
|
||||
public class RoomTest {
|
||||
|
||||
private Room room;
|
||||
private static final int ID = 1;
|
||||
private static final String ROOMTYPE = "Single";
|
||||
private static final int PRICE = 50;
|
||||
private static final boolean BOOKED = false;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
room = new Room(ID, ROOMTYPE, PRICE, BOOKED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAndSetId() {
|
||||
final var newId = 2;
|
||||
room.setId(newId);
|
||||
assertEquals(newId, room.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAndSetRoomType() {
|
||||
final var newRoomType = "Double";
|
||||
room.setRoomType(newRoomType);
|
||||
assertEquals(newRoomType, room.getRoomType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAndSetLastName() {
|
||||
final var newPrice = 60;
|
||||
room.setPrice(newPrice);
|
||||
assertEquals(newPrice, room.getPrice());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEqualWithDifferentId() {
|
||||
final var newId = 2;
|
||||
final var otherRoom = new Room(newId, ROOMTYPE, PRICE, BOOKED);
|
||||
assertNotEquals(room, otherRoom);
|
||||
assertNotEquals(room.hashCode(), otherRoom.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWithSameObjectValues() {
|
||||
final var otherRoom = new Room(ID, ROOMTYPE, PRICE, BOOKED);
|
||||
assertEquals(room, otherRoom);
|
||||
assertEquals(room.hashCode(), otherRoom.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWithSameObjects() {
|
||||
assertEquals(room, room);
|
||||
assertEquals(room.hashCode(), room.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
assertEquals(String.format("Room{id=%s, roomType=%s, price=%s, booked=%s}",
|
||||
room.getId(), room.getRoomType(), room.getPrice(), room.isBooked()), room.toString());
|
||||
}
|
||||
}
|
@@ -9,13 +9,17 @@ tags:
|
||||
---
|
||||
|
||||
## Intent
|
||||
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
|
||||
|
||||
Represent an operation to be performed on the elements of an object structure. Visitor lets you
|
||||
define a new operation without changing the classes of the elements on which it operates.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> Consider a tree structure with army units. Commander has two sergeants under it and each sergeant has three soldiers under them. Given that the hierarchy implements the visitor pattern, we can easily create new objects that interact with the commander, sergeants, soldiers or all of them.
|
||||
> Consider a tree structure with army units. Commander has two sergeants under it and each sergeant
|
||||
> has three soldiers under them. Given that the hierarchy implements the visitor pattern, we can
|
||||
> easily create new objects that interact with the commander, sergeants, soldiers or all of them.
|
||||
|
||||
In plain words
|
||||
|
||||
@@ -23,7 +27,10 @@ In plain words
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying the structures.
|
||||
> In object-oriented programming and software engineering, the visitor design pattern is a way of
|
||||
> separating an algorithm from an object structure on which it operates. A practical result of this
|
||||
> separation is the ability to add new operations to existing object structures without modifying
|
||||
> the structures.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
@@ -111,7 +118,7 @@ public class Soldier extends Unit {
|
||||
}
|
||||
```
|
||||
|
||||
And then some concrete visitors.
|
||||
Here are then some concrete visitors.
|
||||
|
||||
```java
|
||||
public class CommanderVisitor implements UnitVisitor {
|
||||
@@ -175,32 +182,39 @@ public class SoldierVisitor implements UnitVisitor {
|
||||
}
|
||||
```
|
||||
|
||||
Finally we can show the power of visitors in action.
|
||||
Finally, we can show the power of visitors in action.
|
||||
|
||||
```java
|
||||
commander.accept(new SoldierVisitor());
|
||||
// Greetings soldier
|
||||
// Greetings soldier
|
||||
// Greetings soldier
|
||||
// Greetings soldier
|
||||
// Greetings soldier
|
||||
// Greetings soldier
|
||||
commander.accept(new SergeantVisitor());
|
||||
// Hello sergeant
|
||||
// Hello sergeant
|
||||
commander.accept(new CommanderVisitor());
|
||||
// Good to see you commander
|
||||
```
|
||||
|
||||
Program output:
|
||||
|
||||
```
|
||||
Greetings soldier
|
||||
Greetings soldier
|
||||
Greetings soldier
|
||||
Greetings soldier
|
||||
Greetings soldier
|
||||
Greetings soldier
|
||||
Hello sergeant
|
||||
Hello sergeant
|
||||
Good to see you commander
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Visitor pattern when
|
||||
|
||||
* An object structure contains many classes of objects with differing interfaces, and you want to perform operations on these objects that depend on their concrete classes
|
||||
* Many distinct and unrelated operations need to be performed on objects in an object structure, and you want to avoid "polluting" their classes with these operations. Visitor lets you keep related operations together by defining them in one class. When the object structure is shared by many applications, use Visitor to put operations in just those applications that need them
|
||||
* The classes defining the object structure rarely change, but you often want to define new operations over the structure. Changing the object structure classes requires redefining the interface to all visitors, which is potentially costly. If the object structure classes change often, then it's probably better to define the operations in those classes
|
||||
* An object structure contains many classes of objects with differing interfaces, and you want to perform operations on these objects that depend on their concrete classes.
|
||||
* Many distinct and unrelated operations need to be performed on objects in an object structure, and you want to avoid "polluting" their classes with these operations. Visitor lets you keep related operations together by defining them in one class. When the object structure is shared by many applications, use Visitor to put operations in just those applications that need them.
|
||||
* The classes defining the object structure rarely change, but you often want to define new operations over the structure. Changing the object structure classes requires redefining the interface to all visitors, which is potentially costly. If the object structure classes change often, then it's probably better to define the operations in those classes.
|
||||
|
||||
## Real world examples
|
||||
|
||||
|
Reference in New Issue
Block a user