Compare commits
1 Commits
master
...
updateTram
Author | SHA1 | Date | |
---|---|---|---|
|
5681340ec7 |
@ -1737,8 +1737,7 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/47126749?v=4",
|
||||
"profile": "http://no website",
|
||||
"contributions": [
|
||||
"review",
|
||||
"code"
|
||||
"review"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -1786,24 +1785,6 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ShivanshCharak",
|
||||
"name": "ShivanshCharak",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/96943825?v=4",
|
||||
"profile": "https://github.com/ShivanshCharak",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "HattoriHenzo",
|
||||
"name": "HattoriHenzo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5141285?v=4",
|
||||
"profile": "https://github.com/HattoriHenzo",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,7 +12,6 @@ tmp/
|
||||
local.properties
|
||||
.loadpath
|
||||
.recommenders
|
||||
.DS_Store
|
||||
|
||||
####### Java annotation processor (APT) ########
|
||||
.factorypath
|
||||
|
43
.mvn/wrapper/maven-wrapper.properties
vendored
43
.mvn/wrapper/maven-wrapper.properties
vendored
@ -1,18 +1,25 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
|
||||
#
|
||||
# The MIT License
|
||||
# Copyright © 2014-2021 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.
|
||||
#
|
||||
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
||||
|
@ -10,7 +10,7 @@
|
||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
<br/>
|
||||
@ -320,17 +320,13 @@ This project is licensed under the terms of the MIT license.
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/abhinav-vashisth-06613b208/"><img src="https://avatars.githubusercontent.com/u/89785800?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Abhinav Vashisth</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=vashisthabhinav" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://no website"><img src="https://avatars.githubusercontent.com/u/47126749?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AKevinyl3" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=Kevinyl3" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://no website"><img src="https://avatars.githubusercontent.com/u/47126749?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AKevinyl3" title="Reviewed Pull Requests">👀</a></td>
|
||||
<td align="center"><a href="https://github.com/Shrirang97"><img src="https://avatars.githubusercontent.com/u/28738668?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shrirang</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AShrirang97" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=Shrirang97" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/interactwithankush"><img src="https://avatars.githubusercontent.com/u/18613127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>interactwithankush</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=interactwithankush" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/yuhangbin"><img src="https://avatars.githubusercontent.com/u/17566866?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CharlieYu</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=yuhangbin" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Leisterbecker"><img src="https://avatars.githubusercontent.com/u/20650323?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Leisterbecker</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Leisterbecker" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://rosaecrucis.cn"><img src="https://avatars.githubusercontent.com/u/35420129?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DragonDreamer</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=castleKing1997" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/ShivanshCharak"><img src="https://avatars.githubusercontent.com/u/96943825?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ShivanshCharak</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ShivanshCharak" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/HattoriHenzo"><img src="https://avatars.githubusercontent.com/u/5141285?v=4?s=100" width="100px;" alt=""/><br /><sub><b>HattoriHenzo</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=HattoriHenzo" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
@ -29,7 +29,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
* Hayes class implements its accept method.
|
||||
*/
|
||||
@Slf4j
|
||||
public class Hayes implements Modem {
|
||||
public class Hayes extends Modem {
|
||||
|
||||
/**
|
||||
* Accepts all visitors but honors only HayesVisitor.
|
||||
|
@ -24,9 +24,8 @@
|
||||
package com.iluwatar.acyclicvisitor;
|
||||
|
||||
/**
|
||||
* //Modem abstract class.
|
||||
* converted to an interface
|
||||
* Modem abstract class.
|
||||
*/
|
||||
public interface Modem {
|
||||
void accept(ModemVisitor modemVisitor);
|
||||
public abstract class Modem {
|
||||
public abstract void accept(ModemVisitor modemVisitor);
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
* Zoom class implements its accept method.
|
||||
*/
|
||||
@Slf4j
|
||||
public class Zoom implements Modem {
|
||||
public class Zoom extends Modem {
|
||||
|
||||
/**
|
||||
* Accepts all visitors but honors only ZoomVisitor.
|
||||
|
@ -1,325 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Composite View
|
||||
folder: composite-view
|
||||
permalink: /patterns/composite-view/
|
||||
categories: Structural
|
||||
language: en
|
||||
tags:
|
||||
- Enterprise Integration Pattern
|
||||
- Presentation
|
||||
---
|
||||
|
||||
## Name
|
||||
**Composite View**
|
||||
|
||||
## Intent
|
||||
The purpose of the Composite View Pattern is to increase re-usability and flexibility when creating views for websites/webapps.
|
||||
This pattern seeks to decouple the content of the page from its layout, allowing changes to be made to either the content
|
||||
or layout of the page without impacting the other. This pattern also allows content to be easily reused across different views easily.
|
||||
|
||||
## Explanation
|
||||
Real World Example
|
||||
> A news site wants to display the current date and news to different users
|
||||
> based on that user's preferences. The news site will substitute in different news feed
|
||||
> components depending on the user's interest, defaulting to local news.
|
||||
|
||||
In Plain Words
|
||||
> Composite View Pattern is having a main view being composed of smaller subviews.
|
||||
> The layout of this composite view is based on a template. A View-manager then decides which
|
||||
> subviews to include in this template.
|
||||
|
||||
Wikipedia Says
|
||||
> Composite views that are composed of multiple atomic subviews. Each component of
|
||||
> the template may be included dynamically into the whole and the layout of the page may be managed independently of the content.
|
||||
> This solution provides for the creation of a composite view based on the inclusion and substitution of
|
||||
> modular dynamic and static template fragments.
|
||||
> It promotes the reuse of atomic portions of the view by encouraging modular design.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Since this is a web development pattern, a server is required to demonstrate it.
|
||||
This example uses Tomcat 10.0.13 to run the servlet, and this programmatic example will only work with Tomcat 10+.
|
||||
|
||||
Firstly there is `AppServlet` which is an `HttpServlet` that runs on Tomcat 10+.
|
||||
```java
|
||||
public class AppServlet extends HttpServlet {
|
||||
private String msgPartOne = "<h1>This Server Doesn't Support";
|
||||
private String msgPartTwo = "Requests</h1>\n"
|
||||
+ "<h2>Use a GET request with boolean values for the following parameters<h2>\n"
|
||||
+ "<h3>'name'</h3>\n<h3>'bus'</h3>\n<h3>'sports'</h3>\n<h3>'sci'</h3>\n<h3>'world'</h3>";
|
||||
|
||||
private String destination = "newsDisplay.jsp";
|
||||
|
||||
public AppServlet() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination);
|
||||
ClientPropertiesBean reqParams = new ClientPropertiesBean(req);
|
||||
req.setAttribute("properties", reqParams);
|
||||
requestDispatcher.forward(req, resp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/html");
|
||||
PrintWriter out = resp.getWriter();
|
||||
out.println(msgPartOne + " Post " + msgPartTwo);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDelete(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/html");
|
||||
PrintWriter out = resp.getWriter();
|
||||
out.println(msgPartOne + " Delete " + msgPartTwo);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doPut(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/html");
|
||||
PrintWriter out = resp.getWriter();
|
||||
out.println(msgPartOne + " Put " + msgPartTwo);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
This servlet is not part of the pattern, and simply forwards GET requests to the correct JSP.
|
||||
PUT, POST, and DELETE requests are not supported and will simply show an error message.
|
||||
|
||||
The view management in this example is done via a javabean class: `ClientPropertiesBean`, which stores user preferences.
|
||||
```java
|
||||
public class ClientPropertiesBean implements Serializable {
|
||||
|
||||
private static final String WORLD_PARAM = "world";
|
||||
private static final String SCIENCE_PARAM = "sci";
|
||||
private static final String SPORTS_PARAM = "sport";
|
||||
private static final String BUSINESS_PARAM = "bus";
|
||||
private static final String NAME_PARAM = "name";
|
||||
|
||||
private static final String DEFAULT_NAME = "DEFAULT_NAME";
|
||||
private boolean worldNewsInterest;
|
||||
private boolean sportsInterest;
|
||||
private boolean businessInterest;
|
||||
private boolean scienceNewsInterest;
|
||||
private String name;
|
||||
|
||||
public ClientPropertiesBean() {
|
||||
worldNewsInterest = true;
|
||||
sportsInterest = true;
|
||||
businessInterest = true;
|
||||
scienceNewsInterest = true;
|
||||
name = DEFAULT_NAME;
|
||||
|
||||
}
|
||||
|
||||
public ClientPropertiesBean(HttpServletRequest req) {
|
||||
worldNewsInterest = Boolean.parseBoolean(req.getParameter(WORLD_PARAM));
|
||||
sportsInterest = Boolean.parseBoolean(req.getParameter(SPORTS_PARAM));
|
||||
businessInterest = Boolean.parseBoolean(req.getParameter(BUSINESS_PARAM));
|
||||
scienceNewsInterest = Boolean.parseBoolean(req.getParameter(SCIENCE_PARAM));
|
||||
String tempName = req.getParameter(NAME_PARAM);
|
||||
if (tempName == null || tempName == "") {
|
||||
tempName = DEFAULT_NAME;
|
||||
}
|
||||
name = tempName;
|
||||
}
|
||||
// getters and setters generated by Lombok
|
||||
}
|
||||
```
|
||||
This javabean has a default constructor, and another that takes an `HttpServletRequest`.
|
||||
This second constructor takes the request object, parses out the request parameters which contain the
|
||||
user preferences for different types of news.
|
||||
|
||||
The template for the news page is in `newsDisplay.jsp`
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
h1 { text-align: center;}
|
||||
h2 { text-align: center;}
|
||||
h3 { text-align: center;}
|
||||
.centerTable {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
table {border: 1px solid black;}
|
||||
tr {text-align: center;}
|
||||
td {text-align: center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%ClientPropertiesBean propertiesBean = (ClientPropertiesBean) request.getAttribute("properties");%>
|
||||
<h1>Welcome <%= propertiesBean.getName()%></h1>
|
||||
<jsp:include page="header.jsp"></jsp:include>
|
||||
<table class="centerTable">
|
||||
|
||||
<tr>
|
||||
<td></td>
|
||||
<% if(propertiesBean.isWorldNewsInterest()) { %>
|
||||
<td><%@include file="worldNews.jsp"%></td>
|
||||
<% } else { %>
|
||||
<td><%@include file="localNews.jsp"%></td>
|
||||
<% } %>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<% if(propertiesBean.isBusinessInterest()) { %>
|
||||
<td><%@include file="businessNews.jsp"%></td>
|
||||
<% } else { %>
|
||||
<td><%@include file="localNews.jsp"%></td>
|
||||
<% } %>
|
||||
<td></td>
|
||||
<% if(propertiesBean.isSportsInterest()) { %>
|
||||
<td><%@include file="sportsNews.jsp"%></td>
|
||||
<% } else { %>
|
||||
<td><%@include file="localNews.jsp"%></td>
|
||||
<% } %>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<% if(propertiesBean.isScienceNewsInterest()) { %>
|
||||
<td><%@include file="scienceNews.jsp"%></td>
|
||||
<% } else { %>
|
||||
<td><%@include file="localNews.jsp"%></td>
|
||||
<% } %>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
This JSP page is the template. It declares a table with three rows, with one component in the first row,
|
||||
two components in the second row, and one component in the third row.
|
||||
|
||||
The scriplets in the file are part of the
|
||||
view management strategy that include different atomic subviews based on the user preferences in the Javabean.
|
||||
|
||||
Here are two examples of the mock atomic subviews used in the composite:
|
||||
`businessNews.jsp`
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
h2 { text-align: center;}
|
||||
table {border: 1px solid black;}
|
||||
tr {text-align: center;}
|
||||
td {text-align: center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>
|
||||
Generic Business News
|
||||
</h2>
|
||||
<table style="margin-right: auto; margin-left: auto">
|
||||
<tr>
|
||||
<td>Stock prices up across the world</td>
|
||||
<td>New tech companies to invest in</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Industry leaders unveil new project</td>
|
||||
<td>Price fluctuations and what they mean</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
`localNews.jsp`
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<div style="text-align: center">
|
||||
<h3>
|
||||
Generic Local News
|
||||
</h3>
|
||||
<ul style="list-style-type: none">
|
||||
<li>
|
||||
Mayoral elections coming up in 2 weeks
|
||||
</li>
|
||||
<li>
|
||||
New parking meter rates downtown coming tomorrow
|
||||
</li>
|
||||
<li>
|
||||
Park renovations to finish by the next year
|
||||
</li>
|
||||
<li>
|
||||
Annual marathon sign ups available online
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
The results are as such:
|
||||
|
||||
1) The user has put their name as `Tammy` in the request parameters and no preferences:
|
||||

|
||||
2) The user has put their name as `Johnny` in the request parameters and has a preference for world, business, and science news:
|
||||

|
||||
|
||||
The different subviews such as `worldNews.jsp`, `businessNews.jsp`, etc. are included conditionally
|
||||
based on the request parameters.
|
||||
|
||||
**How To Use**
|
||||
|
||||
To try this example, make sure you have Tomcat 10+ installed.
|
||||
Set up your IDE to build a WAR file from the module and deploy that file to the server
|
||||
|
||||
IntelliJ:
|
||||
|
||||
Under `Run` and `edit configurations` Make sure Tomcat server is one of the run configurations.
|
||||
Go to the deployment tab, and make sure there is one artifact being built called `composite-view:war exploded`.
|
||||
If not present, add one.
|
||||
|
||||
Ensure that the artifact is being built from the content of the `web` directory and the compilation results of the module.
|
||||
Point the output of the artifact to a convenient place. Run the configuration and view the landing page,
|
||||
follow instructions on that page to continue.
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
The class diagram here displays the Javabean which is the view manager.
|
||||
The views are JSP's held inside the web directory.
|
||||
|
||||
## Applicability
|
||||
|
||||
This pattern is applicable to most websites that require content to be displayed dynamically/conditionally.
|
||||
If there are components that need to be re-used for multiple views, or if the project requires reusing a template,
|
||||
or if it needs to include content depending on certain conditions, then this pattern is a good choice.
|
||||
|
||||
## Known uses
|
||||
|
||||
Most modern websites use composite views in some shape or form, as they have templates for views and small atomic components
|
||||
that are included in the page dynamically. Most modern Javascript libraries, like React, support this design pattern
|
||||
with components.
|
||||
|
||||
## Consequences
|
||||
**Pros**
|
||||
* Easy to re-use components
|
||||
* Change layout/content without affecting the other
|
||||
* Reduce code duplication
|
||||
* Code is more maintainable and modular
|
||||
|
||||
**Cons**
|
||||
* Overhead cost at runtime
|
||||
* Slower response compared to directly embedding elements
|
||||
* Increases potential for display errors
|
||||
|
||||
## Related patterns
|
||||
* [Composite (GoF)](https://java-design-patterns.com/patterns/composite/)
|
||||
* [View Helper](https://www.oracle.com/java/technologies/viewhelper.html)
|
||||
|
||||
## Credits
|
||||
* [Core J2EE Patterns - Composite View](https://www.oracle.com/java/technologies/composite-view.html)
|
||||
* [Composite View Design Pattern – Core J2EE Patterns](https://www.dineshonjava.com/composite-view-design-pattern/)
|
||||
|
@ -1,29 +0,0 @@
|
||||
@startuml
|
||||
package com.iluwatar.compositeview {
|
||||
class ClientPropertiesBean {
|
||||
- BUSINESS_PARAM : String {static}
|
||||
- DEFAULT_NAME : String {static}
|
||||
- NAME_PARAM : String {static}
|
||||
- SCIENCE_PARAM : String {static}
|
||||
- SPORTS_PARAM : String {static}
|
||||
- WORLD_PARAM : String {static}
|
||||
- businessInterest : boolean
|
||||
- name : String
|
||||
- scienceNewsInterest : boolean
|
||||
- sportsInterest : boolean
|
||||
- worldNewsInterest : boolean
|
||||
+ ClientPropertiesBean()
|
||||
+ ClientPropertiesBean(req : HttpServletRequest)
|
||||
+ getName() : String
|
||||
+ isBusinessInterest() : boolean
|
||||
+ isScienceNewsInterest() : boolean
|
||||
+ isSportsInterest() : boolean
|
||||
+ isWorldNewsInterest() : boolean
|
||||
+ setBusinessInterest(businessInterest : boolean)
|
||||
+ setName(name : String)
|
||||
+ setScienceNewsInterest(scienceNewsInterest : boolean)
|
||||
+ setSportsInterest(sportsInterest : boolean)
|
||||
+ setWorldNewsInterest(worldNewsInterest : boolean)
|
||||
}
|
||||
}
|
||||
@enduml
|
Binary file not shown.
Before Width: | Height: | Size: 19 KiB |
Binary file not shown.
Before Width: | Height: | Size: 71 KiB |
Binary file not shown.
Before Width: | Height: | Size: 77 KiB |
@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
The MIT License
|
||||
Copyright © 2014-2021 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="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
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.26.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>composite-view</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<version>5.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>4.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.iluwatar.compositeview.App</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -1,64 +0,0 @@
|
||||
package com.iluwatar.compositeview;
|
||||
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* A servlet object that extends HttpServlet.
|
||||
* Runs on Tomcat 10 and handles Http requests
|
||||
*/
|
||||
|
||||
public final class AppServlet extends HttpServlet {
|
||||
private String msgPartOne = "<h1>This Server Doesn't Support";
|
||||
private String msgPartTwo = "Requests</h1>\n"
|
||||
+ "<h2>Use a GET request with boolean values for the following parameters<h2>\n"
|
||||
+ "<h3>'name'</h3>\n<h3>'bus'</h3>\n<h3>'sports'</h3>\n<h3>'sci'</h3>\n<h3>'world'</h3>";
|
||||
|
||||
private String destination = "newsDisplay.jsp";
|
||||
|
||||
public AppServlet() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination);
|
||||
ClientPropertiesBean reqParams = new ClientPropertiesBean(req);
|
||||
req.setAttribute("properties", reqParams);
|
||||
requestDispatcher.forward(req, resp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/html");
|
||||
try (PrintWriter out = resp.getWriter()) {
|
||||
out.println(msgPartOne + " Post " + msgPartTwo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDelete(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/html");
|
||||
try (PrintWriter out = resp.getWriter()) {
|
||||
out.println(msgPartOne + " Delete " + msgPartTwo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doPut(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.setContentType("text/html");
|
||||
try (PrintWriter out = resp.getWriter()) {
|
||||
out.println(msgPartOne + " Put " + msgPartTwo);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
package com.iluwatar.compositeview;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.Serializable;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
|
||||
/**
|
||||
* A Java beans class that parses a http request and stores parameters.
|
||||
* Java beans used in JSP's to dynamically include elements in view.
|
||||
* DEFAULT_NAME = a constant, default name to be used for the default constructor
|
||||
* worldNewsInterest = whether current request has world news interest
|
||||
* sportsInterest = whether current request has a sportsInterest
|
||||
* businessInterest = whether current request has a businessInterest
|
||||
* scienceNewsInterest = whether current request has a scienceNewsInterest
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ClientPropertiesBean implements Serializable {
|
||||
|
||||
private static final String WORLD_PARAM = "world";
|
||||
private static final String SCIENCE_PARAM = "sci";
|
||||
private static final String SPORTS_PARAM = "sport";
|
||||
private static final String BUSINESS_PARAM = "bus";
|
||||
private static final String NAME_PARAM = "name";
|
||||
|
||||
private static final String DEFAULT_NAME = "DEFAULT_NAME";
|
||||
private boolean worldNewsInterest = true;
|
||||
private boolean sportsInterest = true;
|
||||
private boolean businessInterest = true;
|
||||
private boolean scienceNewsInterest = true;
|
||||
private String name = DEFAULT_NAME;
|
||||
|
||||
/**
|
||||
* Constructor that parses an HttpServletRequest and stores all the request parameters.
|
||||
*
|
||||
* @param req the HttpServletRequest object that is passed in
|
||||
*/
|
||||
public ClientPropertiesBean(HttpServletRequest req) {
|
||||
worldNewsInterest = Boolean.parseBoolean(req.getParameter(WORLD_PARAM));
|
||||
sportsInterest = Boolean.parseBoolean(req.getParameter(SPORTS_PARAM));
|
||||
businessInterest = Boolean.parseBoolean(req.getParameter(BUSINESS_PARAM));
|
||||
scienceNewsInterest = Boolean.parseBoolean(req.getParameter(SCIENCE_PARAM));
|
||||
String tempName = req.getParameter(NAME_PARAM);
|
||||
if (tempName == null || tempName.equals("")) {
|
||||
tempName = DEFAULT_NAME;
|
||||
}
|
||||
name = tempName;
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
package com.iluwatar.compositeview;
|
||||
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/* Written with reference from https://stackoverflow.com/questions/5434419/how-to-test-my-servlet-using-junit
|
||||
and https://stackoverflow.com/questions/50211433/servlets-unit-testing
|
||||
*/
|
||||
|
||||
public class AppServletTest extends Mockito{
|
||||
private String msgPartOne = "<h1>This Server Doesn't Support";
|
||||
private String msgPartTwo = "Requests</h1>\n"
|
||||
+ "<h2>Use a GET request with boolean values for the following parameters<h2>\n"
|
||||
+ "<h3>'name'</h3>\n<h3>'bus'</h3>\n<h3>'sports'</h3>\n<h3>'sci'</h3>\n<h3>'world'</h3>";
|
||||
private String destination = "newsDisplay.jsp";
|
||||
|
||||
@Test
|
||||
public void testDoGet() throws Exception {
|
||||
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
||||
HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class);
|
||||
RequestDispatcher mockDispatcher = Mockito.mock(RequestDispatcher.class);
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(stringWriter);
|
||||
when(mockResp.getWriter()).thenReturn(printWriter);
|
||||
when(mockReq.getRequestDispatcher(destination)).thenReturn(mockDispatcher);
|
||||
AppServlet curServlet = new AppServlet();
|
||||
curServlet.doGet(mockReq, mockResp);
|
||||
verify(mockReq, times(1)).getRequestDispatcher(destination);
|
||||
verify(mockDispatcher).forward(mockReq, mockResp);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoPost() throws Exception {
|
||||
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
||||
HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class);
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(stringWriter);
|
||||
when(mockResp.getWriter()).thenReturn(printWriter);
|
||||
|
||||
AppServlet curServlet = new AppServlet();
|
||||
curServlet.doPost(mockReq, mockResp);
|
||||
printWriter.flush();
|
||||
assertTrue(stringWriter.toString().contains(msgPartOne + " Post " + msgPartTwo));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoPut() throws Exception {
|
||||
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
||||
HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class);
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(stringWriter);
|
||||
when(mockResp.getWriter()).thenReturn(printWriter);
|
||||
|
||||
AppServlet curServlet = new AppServlet();
|
||||
curServlet.doPut(mockReq, mockResp);
|
||||
printWriter.flush();
|
||||
assertTrue(stringWriter.toString().contains(msgPartOne + " Put " + msgPartTwo));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoDelete() throws Exception {
|
||||
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
||||
HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class);
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(stringWriter);
|
||||
when(mockResp.getWriter()).thenReturn(printWriter);
|
||||
|
||||
AppServlet curServlet = new AppServlet();
|
||||
curServlet.doDelete(mockReq, mockResp);
|
||||
printWriter.flush();
|
||||
assertTrue(stringWriter.toString().contains(msgPartOne + " Delete " + msgPartTwo));
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package com.iluwatar.compositeview;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class JavaBeansTest {
|
||||
@Test
|
||||
public void testDefaultConstructor() {
|
||||
ClientPropertiesBean newBean = new ClientPropertiesBean();
|
||||
assertEquals("DEFAULT_NAME", newBean.getName());
|
||||
assertTrue(newBean.isBusinessInterest());
|
||||
assertTrue(newBean.isScienceNewsInterest());
|
||||
assertTrue(newBean.isSportsInterest());
|
||||
assertTrue(newBean.isWorldNewsInterest());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameGetterSetter() {
|
||||
ClientPropertiesBean newBean = new ClientPropertiesBean();
|
||||
assertEquals("DEFAULT_NAME", newBean.getName());
|
||||
newBean.setName("TEST_NAME_ONE");
|
||||
assertEquals("TEST_NAME_ONE", newBean.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBusinessSetterGetter() {
|
||||
ClientPropertiesBean newBean = new ClientPropertiesBean();
|
||||
assertTrue(newBean.isBusinessInterest());
|
||||
newBean.setBusinessInterest(false);
|
||||
assertFalse(newBean.isBusinessInterest());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScienceSetterGetter() {
|
||||
ClientPropertiesBean newBean = new ClientPropertiesBean();
|
||||
assertTrue(newBean.isScienceNewsInterest());
|
||||
newBean.setScienceNewsInterest(false);
|
||||
assertFalse(newBean.isScienceNewsInterest());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSportsSetterGetter() {
|
||||
ClientPropertiesBean newBean = new ClientPropertiesBean();
|
||||
assertTrue(newBean.isSportsInterest());
|
||||
newBean.setSportsInterest(false);
|
||||
assertFalse(newBean.isSportsInterest());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWorldSetterGetter() {
|
||||
ClientPropertiesBean newBean = new ClientPropertiesBean();
|
||||
assertTrue(newBean.isWorldNewsInterest());
|
||||
newBean.setWorldNewsInterest(false);
|
||||
assertFalse(newBean.isWorldNewsInterest());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestConstructor(){
|
||||
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
||||
ClientPropertiesBean newBean = new ClientPropertiesBean((mockReq));
|
||||
assertEquals("DEFAULT_NAME", newBean.getName());
|
||||
assertFalse(newBean.isWorldNewsInterest());
|
||||
assertFalse(newBean.isBusinessInterest());
|
||||
assertFalse(newBean.isScienceNewsInterest());
|
||||
assertFalse(newBean.isSportsInterest());
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
|
||||
version="4.0">
|
||||
<servlet>
|
||||
<servlet-name>appServlet</servlet-name>
|
||||
<servlet-class>com.iluwatar.compositeview.AppServlet</servlet-class>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>appServlet</servlet-name>
|
||||
<url-pattern>/news</url-pattern>
|
||||
</servlet-mapping>
|
||||
</web-app>
|
@ -1,33 +0,0 @@
|
||||
<%--
|
||||
Created by IntelliJ IDEA.
|
||||
User: Kevin
|
||||
Date: 11/29/2021
|
||||
Time: 2:51 PM
|
||||
To change this template use File | Settings | File Templates.
|
||||
--%>
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
h2 { text-align: center;}
|
||||
table {border: 1px solid black;}
|
||||
tr {text-align: center;}
|
||||
td {text-align: center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>
|
||||
Generic Business News
|
||||
</h2>
|
||||
<table style="margin-right: auto; margin-left: auto">
|
||||
<tr>
|
||||
<td>Stock prices up across the world</td>
|
||||
<td>New tech companies to invest in</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Industry leaders unveil new project</td>
|
||||
<td>Price fluctuations and what they mean</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
@ -1,23 +0,0 @@
|
||||
<%--
|
||||
Created by IntelliJ IDEA.
|
||||
User: Kevin
|
||||
Date: 11/29/2021
|
||||
Time: 1:28 PM
|
||||
To change this template use File | Settings | File Templates.
|
||||
--%>
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<%@ page import="java.util.Date"%>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
h1 { text-align: center;}
|
||||
h2 { text-align: center;}
|
||||
h3 { text-align: center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<% String todayDateStr = (new Date().toString()); %>
|
||||
<h1>Today's Personalized Frontpage</h1>
|
||||
<h2><%=todayDateStr%></h2>
|
||||
</body>
|
||||
</html>
|
@ -1,20 +0,0 @@
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
h1 { text-align: center;}
|
||||
h2 { text-align: center;}
|
||||
h3 { text-align: center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome To The Composite Patterns Mock News Site</h1>
|
||||
<h2>Send a GET request to the "/news" path to see the composite view with mock news</h2>
|
||||
<h2>Use the following parameters:</h2>
|
||||
<h3>name: string name to be dynamically displayed</h3>
|
||||
<h3>bus: boolean for whether you want to see the mock business news</h3>
|
||||
<h3>world: boolean for whether you want to see the mock world news</h3>
|
||||
<h3>sci: boolean for whether you want to see the mock world news</h3>
|
||||
<h3>sport: boolean for whether you want to see the mock world news</h3>
|
||||
</body>
|
||||
</html>
|
@ -1,25 +0,0 @@
|
||||
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<body>
|
||||
<div style="text-align: center">
|
||||
<h3>
|
||||
Generic Local News
|
||||
</h3>
|
||||
<ul style="list-style-type: none">
|
||||
<li>
|
||||
Mayoral elections coming up in 2 weeks
|
||||
</li>
|
||||
<li>
|
||||
New parking meter rates downtown coming tomorrow
|
||||
</li>
|
||||
<li>
|
||||
Park renovations to finish by the next year
|
||||
</li>
|
||||
<li>
|
||||
Annual marathon sign ups available online
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,57 +0,0 @@
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<%@ page import="com.iluwatar.compositeview.ClientPropertiesBean"%>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
h1 { text-align: center;}
|
||||
h2 { text-align: center;}
|
||||
h3 { text-align: center;}
|
||||
.centerTable {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
table {border: 1px solid black;}
|
||||
tr {text-align: center;}
|
||||
td {text-align: center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%ClientPropertiesBean propertiesBean = (ClientPropertiesBean) request.getAttribute("properties");%>
|
||||
<h1>Welcome <%= propertiesBean.getName()%></h1>
|
||||
<jsp:include page="header.jsp"></jsp:include>
|
||||
<table class="centerTable">
|
||||
|
||||
<tr>
|
||||
<td></td>
|
||||
<% if(propertiesBean.isWorldNewsInterest()) { %>
|
||||
<td><%@include file="worldNews.jsp"%></td>
|
||||
<% } else { %>
|
||||
<td><%@include file="localNews.jsp"%></td>
|
||||
<% } %>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<% if(propertiesBean.isBusinessInterest()) { %>
|
||||
<td><%@include file="businessNews.jsp"%></td>
|
||||
<% } else { %>
|
||||
<td><%@include file="localNews.jsp"%></td>
|
||||
<% } %>
|
||||
<td></td>
|
||||
<% if(propertiesBean.isSportsInterest()) { %>
|
||||
<td><%@include file="sportsNews.jsp"%></td>
|
||||
<% } else { %>
|
||||
<td><%@include file="localNews.jsp"%></td>
|
||||
<% } %>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<% if(propertiesBean.isScienceNewsInterest()) { %>
|
||||
<td><%@include file="scienceNews.jsp"%></td>
|
||||
<% } else { %>
|
||||
<td><%@include file="localNews.jsp"%></td>
|
||||
<% } %>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
@ -1,34 +0,0 @@
|
||||
<%--
|
||||
Created by IntelliJ IDEA.
|
||||
User: Kevin
|
||||
Date: 11/29/2021
|
||||
Time: 4:18 PM
|
||||
To change this template use File | Settings | File Templates.
|
||||
--%>
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<body>
|
||||
<div style="text-align: center">
|
||||
<h3>
|
||||
Generic Science News
|
||||
</h3>
|
||||
<ul>
|
||||
<li>
|
||||
New model of gravity proposed for dark matter
|
||||
</li>
|
||||
<li>
|
||||
Genetic modifying technique proved on bacteria
|
||||
</li>
|
||||
<li>
|
||||
Neurology study maps brain with new precision
|
||||
</li>
|
||||
<li>
|
||||
Survey of rainforest discovers 15 new species
|
||||
</li>
|
||||
<li>
|
||||
New signalling pathway for immune system discovered
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,32 +0,0 @@
|
||||
<%--
|
||||
Created by IntelliJ IDEA.
|
||||
User: Kevin
|
||||
Date: 11/29/2021
|
||||
Time: 3:53 PM
|
||||
To change this template use File | Settings | File Templates.
|
||||
--%>
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
h2 { text-align: center;}
|
||||
table {border: 1px solid black;}
|
||||
tr {text-align: center;}
|
||||
td {text-align: center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>
|
||||
Generic Sports News
|
||||
</h2>
|
||||
<div style="margin-left: auto; margin-right: auto; padding: 20px">
|
||||
International football match delayed due to weather, will be held next week
|
||||
</div>
|
||||
<div style="margin-left: auto; margin-right: auto; padding: 20px">
|
||||
New rising stars in winter sports, ten new athletes that will shake up the scene
|
||||
</div>
|
||||
<div style="margin-left: auto; margin-right: auto; padding: 20px">
|
||||
Biggest upset in basketball history, upstart team sweeps competition
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,34 +0,0 @@
|
||||
<%--
|
||||
Created by IntelliJ IDEA.
|
||||
User: Kevin
|
||||
Date: 11/29/2021
|
||||
Time: 2:51 PM
|
||||
To change this template use File | Settings | File Templates.
|
||||
--%>
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
h2 { text-align: center;}
|
||||
table {border: 1px solid black;}
|
||||
tr {text-align: center;}
|
||||
td {text-align: center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>
|
||||
Generic World News
|
||||
</h2>
|
||||
<table style="margin-right: auto; margin-left: auto">
|
||||
<tr>
|
||||
<td>New trade talks happening at UN on Thursday</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>European Union to announce new resolution next week</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>UN delivers report on world economic status</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
@ -17,10 +17,10 @@ the user to specify only what to do with the resource.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
Real world example
|
||||
|
||||
> A class needs to be provided for writing text strings to files. To make it easy for
|
||||
> the user, the service class opens and closes the file automatically. The user only has to
|
||||
> We need to provide a class that can be used to write text strings to files. To make it easy for
|
||||
> the user we let our service class open and close the file automatically, the user only has to
|
||||
> specify what is written into which file.
|
||||
|
||||
In plain words
|
||||
@ -35,50 +35,35 @@ In plain words
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
`SimpleFileWriter` class implements the Execute Around idiom. It takes `FileWriterAction` as a
|
||||
constructor argument allowing the user to specify what gets written into the file.
|
||||
Let's introduce our file writer class.
|
||||
|
||||
```java
|
||||
@FunctionalInterface
|
||||
public interface FileWriterAction {
|
||||
|
||||
void writeFile(FileWriter writer) throws IOException;
|
||||
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
public class SimpleFileWriter {
|
||||
public SimpleFileWriter(String filename, FileWriterAction action) throws IOException {
|
||||
LOGGER.info("Opening the file");
|
||||
try (var writer = new FileWriter(filename)) {
|
||||
LOGGER.info("Executing the action");
|
||||
action.writeFile(writer);
|
||||
LOGGER.info("Closing the file");
|
||||
}
|
||||
|
||||
public SimpleFileWriter(String filename, FileWriterAction action) throws IOException {
|
||||
try (var writer = new FileWriter(filename)) {
|
||||
action.writeFile(writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following code demonstrates how `SimpleFileWriter` is used. `Scanner` is used to print the file
|
||||
contents after the writing finishes.
|
||||
To utilize the file writer the following code is needed.
|
||||
|
||||
```java
|
||||
FileWriterAction writeHello = writer -> {
|
||||
writer.write("Gandalf was here");
|
||||
};
|
||||
new SimpleFileWriter("testfile.txt", writeHello);
|
||||
|
||||
var scanner = new Scanner(new File("testfile.txt"));
|
||||
while (scanner.hasNextLine()) {
|
||||
LOGGER.info(scanner.nextLine());
|
||||
}
|
||||
```
|
||||
|
||||
Here's the console output.
|
||||
|
||||
```
|
||||
21:18:07.185 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Opening the file
|
||||
21:18:07.188 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Executing the action
|
||||
21:18:07.189 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Closing the file
|
||||
21:18:07.199 [main] INFO com.iluwatar.execute.around.App - Gandalf was here
|
||||
FileWriterAction writeHello = writer -> {
|
||||
writer.write("Hello");
|
||||
writer.append(" ");
|
||||
writer.append("there!");
|
||||
};
|
||||
new SimpleFileWriter("testfile.txt", writeHello);
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
@ -89,7 +74,8 @@ Here's the console output.
|
||||
|
||||
Use the Execute Around idiom when
|
||||
|
||||
* An API requires methods to be called in pairs such as open/close or allocate/deallocate.
|
||||
* You use an API that requires methods to be called in pairs such as open/close or
|
||||
allocate/deallocate.
|
||||
|
||||
## Credits
|
||||
|
||||
|
@ -23,14 +23,10 @@
|
||||
|
||||
package com.iluwatar.execute.around;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Scanner;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* The Execute Around idiom specifies executable code before and after a method. Typically
|
||||
* The Execute Around idiom specifies some code to be executed before and after a method. Typically
|
||||
* the idiom is used when the API has methods to be executed in pairs, such as resource
|
||||
* allocation/deallocation or lock acquisition/release.
|
||||
*
|
||||
@ -38,7 +34,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
* the user. The user specifies only what to do with the file by providing the {@link
|
||||
* FileWriterAction} implementation.
|
||||
*/
|
||||
@Slf4j
|
||||
public class App {
|
||||
|
||||
/**
|
||||
@ -46,16 +41,11 @@ public class App {
|
||||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
// create the file writer and execute the custom action
|
||||
FileWriterAction writeHello = writer -> {
|
||||
writer.write("Gandalf was here");
|
||||
writer.write("Hello");
|
||||
writer.append(" ");
|
||||
writer.append("there!");
|
||||
};
|
||||
new SimpleFileWriter("testfile.txt", writeHello);
|
||||
|
||||
// print the file contents
|
||||
var scanner = new Scanner(new File("testfile.txt"));
|
||||
while (scanner.hasNextLine()) {
|
||||
LOGGER.info(scanner.nextLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,24 +26,18 @@ package com.iluwatar.execute.around;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* SimpleFileWriter handles opening and closing file for the user. The user only has to specify what
|
||||
* to do with the file resource through {@link FileWriterAction} parameter.
|
||||
*/
|
||||
@Slf4j
|
||||
public class SimpleFileWriter {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public SimpleFileWriter(String filename, FileWriterAction action) throws IOException {
|
||||
LOGGER.info("Opening the file");
|
||||
try (var writer = new FileWriter(filename)) {
|
||||
LOGGER.info("Executing the action");
|
||||
action.writeFile(writer);
|
||||
LOGGER.info("Closing the file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,115 +10,19 @@ tags:
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
||||
Define a factory of immutable content with separated builder and factory interfaces.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
|
||||
> Imagine a magical weapon factory that can create any type of weapon wished for. When the factory
|
||||
> is unboxed, the master recites the weapon types needed to prepare it. After that, any of those
|
||||
> weapon types can be summoned in an instant.
|
||||
|
||||
In plain words
|
||||
|
||||
> Factory kit is a configurable object builder.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Let's first define the simple `Weapon` hierarchy.
|
||||
|
||||
```java
|
||||
public interface Weapon {
|
||||
}
|
||||
|
||||
public enum WeaponType {
|
||||
SWORD,
|
||||
AXE,
|
||||
BOW,
|
||||
SPEAR
|
||||
}
|
||||
|
||||
public class Sword implements Weapon {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Sword";
|
||||
}
|
||||
}
|
||||
|
||||
// Axe, Bow, and Spear are defined similarly
|
||||
```
|
||||
|
||||
Next, we define a functional interface that allows adding a builder with a name to the factory.
|
||||
|
||||
```java
|
||||
public interface Builder {
|
||||
void add(WeaponType name, Supplier<Weapon> supplier);
|
||||
}
|
||||
```
|
||||
|
||||
The meat of the example is the `WeaponFactory` interface that effectively implements the factory
|
||||
kit pattern. The method `#factory` is used to configure the factory with the classes it needs to
|
||||
be able to construct. The method `#create` is then used to create object instances.
|
||||
|
||||
```java
|
||||
public interface WeaponFactory {
|
||||
|
||||
static WeaponFactory factory(Consumer<Builder> consumer) {
|
||||
var map = new HashMap<WeaponType, Supplier<Weapon>>();
|
||||
consumer.accept(map::put);
|
||||
return name -> map.get(name).get();
|
||||
}
|
||||
|
||||
Weapon create(WeaponType name);
|
||||
}
|
||||
```
|
||||
|
||||
Now, we can show how `WeaponFactory` can be used.
|
||||
|
||||
```java
|
||||
var factory = WeaponFactory.factory(builder -> {
|
||||
builder.add(WeaponType.SWORD, Sword::new);
|
||||
builder.add(WeaponType.AXE, Axe::new);
|
||||
builder.add(WeaponType.SPEAR, Spear::new);
|
||||
builder.add(WeaponType.BOW, Bow::new);
|
||||
});
|
||||
var list = new ArrayList<Weapon>();
|
||||
list.add(factory.create(WeaponType.AXE));
|
||||
list.add(factory.create(WeaponType.SPEAR));
|
||||
list.add(factory.create(WeaponType.SWORD));
|
||||
list.add(factory.create(WeaponType.BOW));
|
||||
list.stream().forEach(weapon -> LOGGER.info("{}", weapon.toString()));
|
||||
```
|
||||
|
||||
Here is the console output when the example is run.
|
||||
|
||||
```
|
||||
21:15:49.709 [main] INFO com.iluwatar.factorykit.App - Axe
|
||||
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Spear
|
||||
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Sword
|
||||
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Bow
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Factory Kit pattern when
|
||||
|
||||
* The factory class can't anticipate the types of objects it must create
|
||||
* A new instance of a custom builder is needed instead of a global one
|
||||
* The types of objects that the factory can build need to be defined outside the class
|
||||
* The builder and creator interfaces need to be separated
|
||||
|
||||
## Related patterns
|
||||
|
||||
* [Builder](https://java-design-patterns.com/patterns/builder/)
|
||||
* [Factory](https://java-design-patterns.com/patterns/factory/)
|
||||
* a class can't anticipate the class of objects it must create
|
||||
* you just want a new instance of a custom builder instead of the global one
|
||||
* you explicitly want to define types of objects, that factory can build
|
||||
* you want a separated builder and creator interface
|
||||
|
||||
## Credits
|
||||
|
||||
* [Design Pattern Reloaded by Remi Forax](https://www.youtube.com/watch?v=-k2X7guaArU)
|
||||
* [Design Pattern Reloaded by Remi Forax: ](https://www.youtube.com/watch?v=-k2X7guaArU)
|
||||
|
@ -23,16 +23,14 @@
|
||||
|
||||
package com.iluwatar.factorykit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Factory kit is a creational pattern that defines a factory of immutable content with separated
|
||||
* Factory-kit is a creational pattern which defines a factory of immutable content with separated
|
||||
* builder and factory interfaces to deal with the problem of creating one of the objects specified
|
||||
* directly in the factory kit instance.
|
||||
* directly in the factory-kit instance.
|
||||
*
|
||||
* <p>In the given example {@link WeaponFactory} represents the factory kit, that contains four
|
||||
* <p>In the given example {@link WeaponFactory} represents the factory-kit, that contains four
|
||||
* {@link Builder}s for creating new objects of the classes implementing {@link Weapon} interface.
|
||||
*
|
||||
* <p>Each of them can be called with {@link WeaponFactory#create(WeaponType)} method, with
|
||||
@ -54,11 +52,7 @@ public class App {
|
||||
builder.add(WeaponType.SPEAR, Spear::new);
|
||||
builder.add(WeaponType.BOW, Bow::new);
|
||||
});
|
||||
var list = new ArrayList<Weapon>();
|
||||
list.add(factory.create(WeaponType.AXE));
|
||||
list.add(factory.create(WeaponType.SPEAR));
|
||||
list.add(factory.create(WeaponType.SWORD));
|
||||
list.add(factory.create(WeaponType.BOW));
|
||||
list.stream().forEach(weapon -> LOGGER.info("{}", weapon.toString()));
|
||||
var axe = factory.create(WeaponType.AXE);
|
||||
LOGGER.info(axe.toString());
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@
|
||||
<zk.version>9.0.0</zk.version>
|
||||
<guava.version>19.0</guava.version>
|
||||
<jetty-maven-plugin.version>9.4.28.v20200408</jetty-maven-plugin.version>
|
||||
<maven-war-plugin.version>3.3.2</maven-war-plugin.version>
|
||||
<maven-war-plugin.version>2.1.1</maven-war-plugin.version>
|
||||
<maven-assembly-plugin.version>2.2</maven-assembly-plugin.version>
|
||||
<maven.build.timestamp.format>yyyy-MM-dd</maven.build.timestamp.format>
|
||||
<packname>-${project.version}-FL-${maven.build.timestamp}</packname>
|
||||
|
@ -15,18 +15,18 @@ Registry
|
||||
|
||||
## Intent
|
||||
|
||||
Ensure a class only has a limited number of instances and provide a global point of access to them.
|
||||
Ensure a class only has limited number of instances and provide a global point of access to them.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
Real world example
|
||||
|
||||
> The Nazgûl, also called ringwraiths or the Nine Riders, are Sauron's most terrible servants. By
|
||||
> definition, there's always nine of them.
|
||||
> definition there's always nine of them.
|
||||
|
||||
In plain words
|
||||
|
||||
> Multiton pattern ensures there are a predefined amount of instances available globally.
|
||||
> Multiton pattern ensures there's predefined amount of instances available globally.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
@ -81,54 +81,29 @@ public final class Nazgul {
|
||||
And here's how we access the `Nazgul` instances.
|
||||
|
||||
```java
|
||||
// eagerly initialized multiton
|
||||
LOGGER.info("Printing out eagerly initialized multiton contents");
|
||||
LOGGER.info("KHAMUL={}", Nazgul.getInstance(NazgulName.KHAMUL));
|
||||
LOGGER.info("MURAZOR={}", Nazgul.getInstance(NazgulName.MURAZOR));
|
||||
LOGGER.info("DWAR={}", Nazgul.getInstance(NazgulName.DWAR));
|
||||
LOGGER.info("JI_INDUR={}", Nazgul.getInstance(NazgulName.JI_INDUR));
|
||||
LOGGER.info("AKHORAHIL={}", Nazgul.getInstance(NazgulName.AKHORAHIL));
|
||||
LOGGER.info("HOARMURATH={}", Nazgul.getInstance(NazgulName.HOARMURATH));
|
||||
LOGGER.info("ADUNAPHEL={}", Nazgul.getInstance(NazgulName.ADUNAPHEL));
|
||||
LOGGER.info("REN={}", Nazgul.getInstance(NazgulName.REN));
|
||||
LOGGER.info("UVATHA={}", Nazgul.getInstance(NazgulName.UVATHA));
|
||||
|
||||
// enum multiton
|
||||
LOGGER.info("Printing out enum-based multiton contents");
|
||||
LOGGER.info("KHAMUL={}", NazgulEnum.KHAMUL);
|
||||
LOGGER.info("MURAZOR={}", NazgulEnum.MURAZOR);
|
||||
LOGGER.info("DWAR={}", NazgulEnum.DWAR);
|
||||
LOGGER.info("JI_INDUR={}", NazgulEnum.JI_INDUR);
|
||||
LOGGER.info("AKHORAHIL={}", NazgulEnum.AKHORAHIL);
|
||||
LOGGER.info("HOARMURATH={}", NazgulEnum.HOARMURATH);
|
||||
LOGGER.info("ADUNAPHEL={}", NazgulEnum.ADUNAPHEL);
|
||||
LOGGER.info("REN={}", NazgulEnum.REN);
|
||||
LOGGER.info("UVATHA={}", NazgulEnum.UVATHA);
|
||||
LOGGER.info("KHAMUL={}", Nazgul.getInstance(NazgulName.KHAMUL));
|
||||
LOGGER.info("MURAZOR={}", Nazgul.getInstance(NazgulName.MURAZOR));
|
||||
LOGGER.info("DWAR={}", Nazgul.getInstance(NazgulName.DWAR));
|
||||
LOGGER.info("JI_INDUR={}", Nazgul.getInstance(NazgulName.JI_INDUR));
|
||||
LOGGER.info("AKHORAHIL={}", Nazgul.getInstance(NazgulName.AKHORAHIL));
|
||||
LOGGER.info("HOARMURATH={}", Nazgul.getInstance(NazgulName.HOARMURATH));
|
||||
LOGGER.info("ADUNAPHEL={}", Nazgul.getInstance(NazgulName.ADUNAPHEL));
|
||||
LOGGER.info("REN={}", Nazgul.getInstance(NazgulName.REN));
|
||||
LOGGER.info("UVATHA={}", Nazgul.getInstance(NazgulName.UVATHA));
|
||||
```
|
||||
|
||||
Program output:
|
||||
|
||||
```
|
||||
20:35:07.413 [main] INFO com.iluwatar.multiton.App - Printing out eagerly initialized multiton contents
|
||||
20:35:07.417 [main] INFO com.iluwatar.multiton.App - KHAMUL=com.iluwatar.multiton.Nazgul@48cf768c
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - MURAZOR=com.iluwatar.multiton.Nazgul@7960847b
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - DWAR=com.iluwatar.multiton.Nazgul@6a6824be
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - JI_INDUR=com.iluwatar.multiton.Nazgul@5c8da962
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - AKHORAHIL=com.iluwatar.multiton.Nazgul@512ddf17
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - HOARMURATH=com.iluwatar.multiton.Nazgul@2c13da15
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - ADUNAPHEL=com.iluwatar.multiton.Nazgul@77556fd
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - REN=com.iluwatar.multiton.Nazgul@368239c8
|
||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - UVATHA=com.iluwatar.multiton.Nazgul@9e89d68
|
||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - Printing out enum-based multiton contents
|
||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - KHAMUL=KHAMUL
|
||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - MURAZOR=MURAZOR
|
||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - DWAR=DWAR
|
||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - JI_INDUR=JI_INDUR
|
||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - AKHORAHIL=AKHORAHIL
|
||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - HOARMURATH=HOARMURATH
|
||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - ADUNAPHEL=ADUNAPHEL
|
||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - REN=REN
|
||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - UVATHA=UVATHA
|
||||
KHAMUL=com.iluwatar.multiton.Nazgul@2b214b94
|
||||
MURAZOR=com.iluwatar.multiton.Nazgul@17814b1c
|
||||
DWAR=com.iluwatar.multiton.Nazgul@7ac9af2a
|
||||
JI_INDUR=com.iluwatar.multiton.Nazgul@7bb004b8
|
||||
AKHORAHIL=com.iluwatar.multiton.Nazgul@78e89bfe
|
||||
HOARMURATH=com.iluwatar.multiton.Nazgul@652ce654
|
||||
ADUNAPHEL=com.iluwatar.multiton.Nazgul@522ba524
|
||||
REN=com.iluwatar.multiton.Nazgul@29c5ee1d
|
||||
UVATHA=com.iluwatar.multiton.Nazgul@15cea7b0
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
@ -139,5 +114,5 @@ Program output:
|
||||
|
||||
Use the Multiton pattern when
|
||||
|
||||
* There must be a specific number of instances of a class, and they must be accessible to clients from
|
||||
* There must be specific number of instances of a class, and they must be accessible to clients from
|
||||
a well-known access point.
|
||||
|
@ -26,13 +26,13 @@ package com.iluwatar.multiton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Whereas Singleton design pattern introduces single globally accessible object, the Multiton
|
||||
* Whereas Singleton design pattern introduces single globally accessible object the Multiton
|
||||
* pattern defines many globally accessible objects. The client asks for the correct instance from
|
||||
* the Multiton by passing an enumeration as a parameter.
|
||||
* the Multiton by passing an enumeration as parameter.
|
||||
*
|
||||
* <p>There is more than one way to implement the multiton design pattern. In the first example
|
||||
* {@link Nazgul} is the Multiton and we can ask single {@link Nazgul} from it using {@link
|
||||
* NazgulName}. The {@link Nazgul}s are statically initialized and stored in a concurrent hash map.
|
||||
* NazgulName}. The {@link Nazgul}s are statically initialized and stored in concurrent hash map.
|
||||
*
|
||||
* <p>In the enum implementation {@link NazgulEnum} is the multiton. It is static and mutable
|
||||
* because of the way java supports enums.
|
||||
@ -47,7 +47,6 @@ public class App {
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
// eagerly initialized multiton
|
||||
LOGGER.info("Printing out eagerly initialized multiton contents");
|
||||
LOGGER.info("KHAMUL={}", Nazgul.getInstance(NazgulName.KHAMUL));
|
||||
LOGGER.info("MURAZOR={}", Nazgul.getInstance(NazgulName.MURAZOR));
|
||||
LOGGER.info("DWAR={}", Nazgul.getInstance(NazgulName.DWAR));
|
||||
@ -59,7 +58,6 @@ public class App {
|
||||
LOGGER.info("UVATHA={}", Nazgul.getInstance(NazgulName.UVATHA));
|
||||
|
||||
// enum multiton
|
||||
LOGGER.info("Printing out enum-based multiton contents");
|
||||
LOGGER.info("KHAMUL={}", NazgulEnum.KHAMUL);
|
||||
LOGGER.info("MURAZOR={}", NazgulEnum.MURAZOR);
|
||||
LOGGER.info("DWAR={}", NazgulEnum.DWAR);
|
||||
|
@ -48,4 +48,5 @@ public class NazgulTest {
|
||||
assertEquals(name, nazgul.getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
18
mvnw
vendored
18
mvnw
vendored
@ -36,10 +36,6 @@
|
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
if [ -f /usr/local/etc/mavenrc ] ; then
|
||||
. /usr/local/etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f /etc/mavenrc ] ; then
|
||||
. /etc/mavenrc
|
||||
fi
|
||||
@ -149,7 +145,7 @@ if [ -z "$JAVACMD" ] ; then
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
else
|
||||
JAVACMD="`\\unset -f command; \\command -v java`"
|
||||
JAVACMD="`which java`"
|
||||
fi
|
||||
fi
|
||||
|
||||
@ -216,9 +212,9 @@ else
|
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||
fi
|
||||
if [ -n "$MVNW_REPOURL" ]; then
|
||||
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
||||
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
else
|
||||
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
||||
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
fi
|
||||
while IFS="=" read key value; do
|
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||
@ -237,9 +233,9 @@ else
|
||||
echo "Found wget ... using wget"
|
||||
fi
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
wget "$jarUrl" -O "$wrapperJarPath"
|
||||
else
|
||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
|
||||
fi
|
||||
elif command -v curl > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
@ -309,8 +305,6 @@ WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
$MAVEN_DEBUG_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.home=${M2_HOME}" \
|
||||
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
||||
|
66
mvnw.cmd
vendored
66
mvnw.cmd
vendored
@ -1,21 +1,25 @@
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM The MIT License
|
||||
@REM Copyright © 2014-2021 Ilkka Seppälä
|
||||
@REM
|
||||
@REM Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
@REM of this software and associated documentation files (the "Software"), to deal
|
||||
@REM in the Software without restriction, including without limitation the rights
|
||||
@REM to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
@REM copies of the Software, and to permit persons to whom the Software is
|
||||
@REM furnished to do so, subject to the following conditions:
|
||||
@REM
|
||||
@REM The above copyright notice and this permission notice shall be included in
|
||||
@REM all copies or substantial portions of the Software.
|
||||
@REM
|
||||
@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
@REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
@REM FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
@REM AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
@REM LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
@REM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
@REM THE SOFTWARE.
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven Start Up Batch script
|
||||
@ -46,8 +50,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
|
||||
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
|
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
@ -120,9 +124,9 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
|
||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||
)
|
||||
|
||||
@ -134,7 +138,7 @@ if exist %WRAPPER_JAR% (
|
||||
)
|
||||
) else (
|
||||
if not "%MVNW_REPOURL%" == "" (
|
||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
)
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
@ -158,13 +162,7 @@ if exist %WRAPPER_JAR% (
|
||||
@REM work with both Windows and non-Windows executions.
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
%MAVEN_JAVA_EXE% ^
|
||||
%JVM_CONFIG_MAVEN_PROPS% ^
|
||||
%MAVEN_OPTS% ^
|
||||
%MAVEN_DEBUG_OPTS% ^
|
||||
-classpath %WRAPPER_JAR% ^
|
||||
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
|
||||
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
@ -174,15 +172,15 @@ set ERROR_CODE=1
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
|
||||
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
|
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%"=="on" pause
|
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||
|
||||
cmd /C exit /B %ERROR_CODE%
|
||||
exit /B %ERROR_CODE%
|
||||
|
1
naked-objects/README
Normal file
1
naked-objects/README
Normal file
@ -0,0 +1 @@
|
||||
This is an Apache Isis application created with the SimpleApp archetype. The usage instructions can be found at http://isis.apache.org/guides/ug.html#_ug_getting-started_simpleapp-archetype
|
@ -150,7 +150,7 @@
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>2.4</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.mortbay.jetty</groupId>
|
||||
|
1
pom.xml
1
pom.xml
@ -228,7 +228,6 @@
|
||||
<module>lockable-object</module>
|
||||
<module>fanout-fanin</module>
|
||||
<module>domain-model</module>
|
||||
<module>composite-view</module>
|
||||
<module>metadata-mapping</module>
|
||||
</modules>
|
||||
<repositories>
|
||||
|
@ -18,11 +18,11 @@ threads and eliminating the latency of creating new threads.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
Real world example
|
||||
|
||||
> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes
|
||||
> and serve a mighty amount of coffee cups. Creating a new thread for each task would be a waste so
|
||||
> we establish a thread pool.
|
||||
> and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we
|
||||
> establish a thread pool.
|
||||
|
||||
In plain words
|
||||
|
||||
@ -99,7 +99,7 @@ public class PotatoPeelingTask extends Task {
|
||||
}
|
||||
```
|
||||
|
||||
Next, we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
|
||||
Next we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
|
||||
peeling and coffee making.
|
||||
|
||||
```java
|
||||
|
@ -16,12 +16,10 @@ Ensure that a given client is not able to access service resources more than the
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
Real world example
|
||||
|
||||
> A young human and an old dwarf walk into a bar. They start ordering beers from the bartender.
|
||||
> The bartender immediately sees that the young human shouldn't consume too many drinks too fast
|
||||
> and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can
|
||||
> be higher.
|
||||
> A large multinational corporation offers API to its customers. The API is rate-limited and each
|
||||
> customer can only make certain amount of calls per second.
|
||||
|
||||
In plain words
|
||||
|
||||
@ -35,25 +33,30 @@ In plain words
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
`BarCustomer` class presents the clients of the `Bartender` API. `CallsCount` tracks the number of
|
||||
calls per `BarCustomer`.
|
||||
Tenant class presents the clients of the API. CallsCount tracks the number of API calls per tenant.
|
||||
|
||||
```java
|
||||
public class BarCustomer {
|
||||
public class Tenant {
|
||||
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final int allowedCallsPerSecond;
|
||||
private final String name;
|
||||
private final int allowedCallsPerSecond;
|
||||
|
||||
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
}
|
||||
this.name = name;
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
}
|
||||
this.name = name;
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAllowedCallsPerSecond() {
|
||||
return allowedCallsPerSecond;
|
||||
}
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
@ -73,14 +76,14 @@ public final class CallsCount {
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
LOGGER.debug("Resetting the map.");
|
||||
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
||||
LOGGER.info("reset counters");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Next, the service that the tenants are calling is introduced. To track the call count, a throttler
|
||||
timer is used.
|
||||
Next we introduce the service that the tenants are calling. To track the call count we use the
|
||||
throttler timer.
|
||||
|
||||
```java
|
||||
public interface Throttler {
|
||||
@ -108,103 +111,71 @@ public class ThrottleTimerImpl implements Throttler {
|
||||
}, 0, throttlePeriod);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Bartender` offers the `orderDrink` service to the `BarCustomer`s. The customers probably don't
|
||||
know that the beer serving rate is limited by their appearances.
|
||||
class B2BService {
|
||||
|
||||
```java
|
||||
class Bartender {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
|
||||
private final CallsCount callsCount;
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
|
||||
private final CallsCount callsCount;
|
||||
public B2BService(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
}
|
||||
|
||||
public Bartender(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
public int dummyCustomerApi(Tenant tenant) {
|
||||
var tenantName = tenant.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
|
||||
if (count >= tenant.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("API access per second limit reached for: {}", tenantName);
|
||||
return -1;
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
||||
public int orderDrink(BarCustomer barCustomer) {
|
||||
var tenantName = barCustomer.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
if (count >= barCustomer.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
|
||||
return -1;
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count+1);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
||||
private int getRandomCustomerId() {
|
||||
return ThreadLocalRandom.current().nextInt(1, 10000);
|
||||
}
|
||||
private int getRandomCustomerId() {
|
||||
return ThreadLocalRandom.current().nextInt(1, 10000);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now it is possible to see the full example in action. `BarCustomer` young human is rate-limited to 2
|
||||
calls per second and the old dwarf to 4.
|
||||
Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per
|
||||
second and Nike to 6.
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
public static void main(String[] args) {
|
||||
var callsCount = new CallsCount();
|
||||
var human = new BarCustomer("young human", 2, callsCount);
|
||||
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
|
||||
var adidas = new Tenant("Adidas", 5, callsCount);
|
||||
var nike = new Tenant("Nike", 6, callsCount);
|
||||
|
||||
var executorService = Executors.newFixedThreadPool(2);
|
||||
|
||||
executorService.execute(() -> makeServiceCalls(human, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
|
||||
|
||||
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(nike, callsCount));
|
||||
executorService.shutdown();
|
||||
|
||||
try {
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Executor service terminated: {}", e.getMessage());
|
||||
LOGGER.error("Executor Service terminated: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(1000, callsCount);
|
||||
var service = new Bartender(timer, callsCount);
|
||||
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(10, callsCount);
|
||||
var service = new B2BService(timer, callsCount);
|
||||
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
||||
IntStream.range(0, 50).forEach(i -> {
|
||||
service.orderDrink(barCustomer);
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
IntStream.range(0, 20).forEach(i -> {
|
||||
service.dummyCustomerApi(tenant);
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An excerpt from the example's console output:
|
||||
|
||||
```
|
||||
18:46:36.218 [Timer-0] INFO com.iluwatar.throttling.CallsCount - reset counters
|
||||
18:46:36.218 [Timer-1] INFO com.iluwatar.throttling.CallsCount - reset counters
|
||||
18:46:36.242 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [1 consumed]
|
||||
18:46:36.242 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [1 consumed]
|
||||
18:46:36.342 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [2 consumed]
|
||||
18:46:36.342 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [2 consumed]
|
||||
18:46:36.443 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.443 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [3 consumed]
|
||||
18:46:36.544 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.544 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [4 consumed]
|
||||
18:46:36.645 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.645 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.745 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.745 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.846 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.846 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.947 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.947 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:37.048 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:37.048 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:37.148 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:37.148 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||
@ -214,7 +185,7 @@ An excerpt from the example's console output:
|
||||
|
||||
The Throttling pattern should be used:
|
||||
|
||||
* When service access needs to be restricted not to have high impact on the performance of the service.
|
||||
* When a service access needs to be restricted to not have high impacts on the performance of the service.
|
||||
* When multiple clients are consuming the same service resources and restriction has to be made according to the usage per client.
|
||||
|
||||
## Credits
|
||||
|
@ -34,11 +34,11 @@ import lombok.extern.slf4j.Slf4j;
|
||||
* complete service by users or a particular tenant. This can allow systems to continue to function
|
||||
* and meet service level agreements, even when an increase in demand places load on resources.
|
||||
* <p>
|
||||
* In this example there is a {@link Bartender} serving beer to {@link BarCustomer}s. This is a time
|
||||
* In this example we have ({@link App}) as the initiating point of the service. This is a time
|
||||
* based throttling, i.e. only a certain number of calls are allowed per second.
|
||||
* </p>
|
||||
* ({@link BarCustomer}) is the service tenant class having a name and the number of calls allowed.
|
||||
* ({@link Bartender}) is the service which is consumed by the tenants and is throttled.
|
||||
* ({@link Tenant}) is the Tenant POJO class with which many tenants can be created ({@link
|
||||
* B2BService}) is the service which is consumed by the tenants and is throttled.
|
||||
*/
|
||||
@Slf4j
|
||||
public class App {
|
||||
@ -50,35 +50,33 @@ public class App {
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
var callsCount = new CallsCount();
|
||||
var human = new BarCustomer("young human", 2, callsCount);
|
||||
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
|
||||
var adidas = new Tenant("Adidas", 5, callsCount);
|
||||
var nike = new Tenant("Nike", 6, callsCount);
|
||||
|
||||
var executorService = Executors.newFixedThreadPool(2);
|
||||
|
||||
executorService.execute(() -> makeServiceCalls(human, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(nike, callsCount));
|
||||
|
||||
executorService.shutdown();
|
||||
try {
|
||||
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
executorService.shutdownNow();
|
||||
LOGGER.error("Executor Service terminated: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make calls to the bartender.
|
||||
* Make calls to the B2BService dummy API.
|
||||
*/
|
||||
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(1000, callsCount);
|
||||
var service = new Bartender(timer, callsCount);
|
||||
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(10, callsCount);
|
||||
var service = new B2BService(timer, callsCount);
|
||||
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
||||
IntStream.range(0, 50).forEach(i -> {
|
||||
service.orderDrink(barCustomer);
|
||||
IntStream.range(0, 20).forEach(i -> {
|
||||
service.dummyCustomerApi(tenant);
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
|
@ -29,32 +29,33 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Bartender is a service which accepts a BarCustomer (tenant) and throttles
|
||||
* the resource based on the time given to the tenant.
|
||||
* A service which accepts a tenant and throttles the resource based on the time given to the
|
||||
* tenant.
|
||||
*/
|
||||
class Bartender {
|
||||
class B2BService {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
|
||||
private final CallsCount callsCount;
|
||||
|
||||
public Bartender(Throttler timer, CallsCount callsCount) {
|
||||
public B2BService(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders a drink from the bartender.
|
||||
* Calls dummy customer api.
|
||||
*
|
||||
* @return customer id which is randomly generated
|
||||
*/
|
||||
public int orderDrink(BarCustomer barCustomer) {
|
||||
var tenantName = barCustomer.getName();
|
||||
public int dummyCustomerApi(Tenant tenant) {
|
||||
var tenantName = tenant.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
if (count >= barCustomer.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
|
||||
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
|
||||
if (count >= tenant.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("API access per second limit reached for: {}", tenantName);
|
||||
return -1;
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count + 1);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ public final class CallsCount {
|
||||
* Resets the count of all the tenants in the map.
|
||||
*/
|
||||
public void reset() {
|
||||
LOGGER.debug("Resetting the map.");
|
||||
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
||||
LOGGER.info("reset counters");
|
||||
}
|
||||
}
|
||||
|
@ -25,26 +25,22 @@ package com.iluwatar.throttling;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* BarCustomer is a tenant with a name and a number of allowed calls per second.
|
||||
* A Pojo class to create a basic Tenant with the allowed calls per second.
|
||||
*/
|
||||
public class BarCustomer {
|
||||
public class Tenant {
|
||||
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final int allowedCallsPerSecond;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param name Name of the BarCustomer
|
||||
* @param allowedCallsPerSecond The number of calls allowed for this particular tenant.
|
||||
* @param name Name of the tenant
|
||||
* @param allowedCallsPerSecond The number of calls allowed for a particular tenant.
|
||||
* @throws InvalidParameterException If number of calls is less than 0, throws exception.
|
||||
*/
|
||||
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
}
|
||||
@ -52,4 +48,12 @@ public class BarCustomer {
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAllowedCallsPerSecond() {
|
||||
return allowedCallsPerSecond;
|
||||
}
|
||||
}
|
@ -32,18 +32,19 @@ import org.junit.jupiter.api.Test;
|
||||
/**
|
||||
* B2BServiceTest class to test the B2BService
|
||||
*/
|
||||
public class BartenderTest {
|
||||
public class B2BServiceTest {
|
||||
|
||||
private final CallsCount callsCount = new CallsCount();
|
||||
|
||||
@Test
|
||||
void dummyCustomerApiTest() {
|
||||
var tenant = new BarCustomer("pirate", 2, callsCount);
|
||||
var tenant = new Tenant("testTenant", 2, callsCount);
|
||||
// In order to assure that throttling limits will not be reset, we use an empty throttling implementation
|
||||
var timer = (Throttler) () -> {};
|
||||
var service = new Bartender(timer, callsCount);
|
||||
var timer = (Throttler) () -> {
|
||||
};
|
||||
var service = new B2BService(timer, callsCount);
|
||||
|
||||
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::orderDrink);
|
||||
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::dummyCustomerApi);
|
||||
var counter = callsCount.getCount(tenant.getName());
|
||||
assertEquals(2, counter, "Counter limit must be reached");
|
||||
}
|
@ -23,21 +23,20 @@
|
||||
|
||||
package com.iluwatar.throttling;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* TenantTest to test the creation of Tenant with valid parameters.
|
||||
*/
|
||||
public class BarCustomerTest {
|
||||
public class TenantTest {
|
||||
|
||||
@Test
|
||||
void constructorTest() {
|
||||
assertThrows(InvalidParameterException.class, () -> {
|
||||
new BarCustomer("sirBrave", -1, new CallsCount());
|
||||
new Tenant("FailTenant", -1, new CallsCount());
|
||||
});
|
||||
}
|
||||
}
|
@ -12,20 +12,20 @@ tags:
|
||||
|
||||
## Intent
|
||||
|
||||
When a business transaction is completed, all the updates are sent as one big unit of work to be
|
||||
When a business transaction is completed, all the the updates are sent as one big unit of work to be
|
||||
persisted in one go to minimize database round-trips.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
Real world example
|
||||
|
||||
> Arms dealer has a database containing weapon information. Merchants all over the town are
|
||||
> constantly updating this information and it causes a high load on the database server. To make the
|
||||
> We have a database containing student information. Administrators all over the country are
|
||||
> constantly updating this information and it causes high load on the database server. To make the
|
||||
> load more manageable we apply to Unit of Work pattern to send many small updates in batches.
|
||||
|
||||
In plain words
|
||||
|
||||
> Unit of Work merges many small database updates in a single batch to optimize the number of
|
||||
> Unit of Work merges many small database updates in single batch to optimize the number of
|
||||
> round-trips.
|
||||
|
||||
[MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says
|
||||
@ -35,20 +35,37 @@ In plain words
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Here's the `Weapon` entity that is being persisted in the database.
|
||||
Here's the `Student` entity that is being persisted to the database.
|
||||
|
||||
```java
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class Weapon {
|
||||
private final Integer id;
|
||||
private final String name;
|
||||
public class Student {
|
||||
private final Integer id;
|
||||
private final String name;
|
||||
private final String address;
|
||||
|
||||
public Student(Integer id, String name, String address) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The essence of the implementation is the `ArmsDealer` implementing the Unit of Work pattern.
|
||||
The essence of the implementation is the `StudentRepository` implementing the Unit of Work pattern.
|
||||
It maintains a map of database operations (`context`) that need to be done and when `commit` is
|
||||
called it applies them in a single batch.
|
||||
called it applies them in single batch.
|
||||
|
||||
```java
|
||||
public interface IUnitOfWork<T> {
|
||||
@ -67,117 +84,96 @@ public interface IUnitOfWork<T> {
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ArmsDealer implements IUnitOfWork<Weapon> {
|
||||
public class StudentRepository implements IUnitOfWork<Student> {
|
||||
|
||||
private final Map<String, List<Weapon>> context;
|
||||
private final WeaponDatabase weaponDatabase;
|
||||
private final Map<String, List<Student>> context;
|
||||
private final StudentDatabase studentDatabase;
|
||||
|
||||
@Override
|
||||
public void registerNew(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for insert in context.", weapon.getName());
|
||||
register(weapon, UnitActions.INSERT.getActionValue());
|
||||
public StudentRepository(Map<String, List<Student>> context, StudentDatabase studentDatabase) {
|
||||
this.context = context;
|
||||
this.studentDatabase = studentDatabase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerNew(Student student) {
|
||||
LOGGER.info("Registering {} for insert in context.", student.getName());
|
||||
register(student, IUnitOfWork.INSERT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerModified(Student student) {
|
||||
LOGGER.info("Registering {} for modify in context.", student.getName());
|
||||
register(student, IUnitOfWork.MODIFY);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDeleted(Student student) {
|
||||
LOGGER.info("Registering {} for delete in context.", student.getName());
|
||||
register(student, IUnitOfWork.DELETE);
|
||||
}
|
||||
|
||||
private void register(Student student, String operation) {
|
||||
var studentsToOperate = context.get(operation);
|
||||
if (studentsToOperate == null) {
|
||||
studentsToOperate = new ArrayList<>();
|
||||
}
|
||||
studentsToOperate.add(student);
|
||||
context.put(operation, studentsToOperate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
if (context == null || context.size() == 0) {
|
||||
return;
|
||||
}
|
||||
LOGGER.info("Commit started");
|
||||
if (context.containsKey(IUnitOfWork.INSERT)) {
|
||||
commitInsert();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerModified(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for modify in context.", weapon.getName());
|
||||
register(weapon, UnitActions.MODIFY.getActionValue());
|
||||
|
||||
if (context.containsKey(IUnitOfWork.MODIFY)) {
|
||||
commitModify();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDeleted(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for delete in context.", weapon.getName());
|
||||
register(weapon, UnitActions.DELETE.getActionValue());
|
||||
if (context.containsKey(IUnitOfWork.DELETE)) {
|
||||
commitDelete();
|
||||
}
|
||||
LOGGER.info("Commit finished.");
|
||||
}
|
||||
|
||||
private void register(Weapon weapon, String operation) {
|
||||
var weaponsToOperate = context.get(operation);
|
||||
if (weaponsToOperate == null) {
|
||||
weaponsToOperate = new ArrayList<>();
|
||||
}
|
||||
weaponsToOperate.add(weapon);
|
||||
context.put(operation, weaponsToOperate);
|
||||
private void commitInsert() {
|
||||
var studentsToBeInserted = context.get(IUnitOfWork.INSERT);
|
||||
for (var student : studentsToBeInserted) {
|
||||
LOGGER.info("Saving {} to database.", student.getName());
|
||||
studentDatabase.insert(student);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All UnitOfWork operations are batched and executed together on commit only.
|
||||
*/
|
||||
@Override
|
||||
public void commit() {
|
||||
if (context == null || context.size() == 0) {
|
||||
return;
|
||||
}
|
||||
LOGGER.info("Commit started");
|
||||
if (context.containsKey(UnitActions.INSERT.getActionValue())) {
|
||||
commitInsert();
|
||||
}
|
||||
|
||||
if (context.containsKey(UnitActions.MODIFY.getActionValue())) {
|
||||
commitModify();
|
||||
}
|
||||
if (context.containsKey(UnitActions.DELETE.getActionValue())) {
|
||||
commitDelete();
|
||||
}
|
||||
LOGGER.info("Commit finished.");
|
||||
private void commitModify() {
|
||||
var modifiedStudents = context.get(IUnitOfWork.MODIFY);
|
||||
for (var student : modifiedStudents) {
|
||||
LOGGER.info("Modifying {} to database.", student.getName());
|
||||
studentDatabase.modify(student);
|
||||
}
|
||||
}
|
||||
|
||||
private void commitInsert() {
|
||||
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
|
||||
for (var weapon : weaponsToBeInserted) {
|
||||
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName());
|
||||
weaponDatabase.insert(weapon);
|
||||
}
|
||||
}
|
||||
|
||||
private void commitModify() {
|
||||
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue());
|
||||
for (var weapon : modifiedWeapons) {
|
||||
LOGGER.info("Scheduling {} for modification work.", weapon.getName());
|
||||
weaponDatabase.modify(weapon);
|
||||
}
|
||||
}
|
||||
|
||||
private void commitDelete() {
|
||||
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
|
||||
for (var weapon : deletedWeapons) {
|
||||
LOGGER.info("Scrapping {}.", weapon.getName());
|
||||
weaponDatabase.delete(weapon);
|
||||
}
|
||||
private void commitDelete() {
|
||||
var deletedStudents = context.get(IUnitOfWork.DELETE);
|
||||
for (var student : deletedStudents) {
|
||||
LOGGER.info("Deleting {} to database.", student.getName());
|
||||
studentDatabase.delete(student);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here is how the whole app is put together.
|
||||
Finally, here's how we use the `StudentRepository` and `commit` the transaction.
|
||||
|
||||
```java
|
||||
// create some weapons
|
||||
var enchantedHammer = new Weapon(1, "enchanted hammer");
|
||||
var brokenGreatSword = new Weapon(2, "broken great sword");
|
||||
var silverTrident = new Weapon(3, "silver trident");
|
||||
|
||||
// create repository
|
||||
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(), new WeaponDatabase());
|
||||
|
||||
// perform operations on the weapons
|
||||
weaponRepository.registerNew(enchantedHammer);
|
||||
weaponRepository.registerModified(silverTrident);
|
||||
weaponRepository.registerDeleted(brokenGreatSword);
|
||||
weaponRepository.commit();
|
||||
```
|
||||
|
||||
Here is the console output.
|
||||
|
||||
```
|
||||
21:39:21.984 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering enchanted hammer for insert in context.
|
||||
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering silver trident for modify in context.
|
||||
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering broken great sword for delete in context.
|
||||
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit started
|
||||
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Inserting a new weapon enchanted hammer to sales rack.
|
||||
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scheduling silver trident for modification work.
|
||||
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scrapping broken great sword.
|
||||
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit finished.
|
||||
studentRepository.registerNew(ram);
|
||||
studentRepository.registerModified(shyam);
|
||||
studentRepository.registerDeleted(gopi);
|
||||
studentRepository.commit();
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
@ -190,7 +186,7 @@ Use the Unit Of Work pattern when
|
||||
|
||||
* To optimize the time taken for database transactions.
|
||||
* To send changes to database as a unit of work which ensures atomicity of the transaction.
|
||||
* To reduce the number of database calls.
|
||||
* To reduce number of database calls.
|
||||
|
||||
## Tutorials
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<class-diagram version="1.2.1" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
|
||||
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
|
||||
<class id="1" language="java" name="com.iluwatar.unitofwork.WeaponDatabase" project="unit-of-work"
|
||||
<class id="1" language="java" name="com.iluwatar.unitofwork.StudentDatabase" project="unit-of-work"
|
||||
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentDatabase.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="170" y="406"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
@ -28,7 +28,7 @@
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="4" language="java" name="com.iluwatar.unitofwork.ArmsDealer" project="unit-of-work"
|
||||
<class id="4" language="java" name="com.iluwatar.unitofwork.StudentRepository" project="unit-of-work"
|
||||
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java" binary="false"
|
||||
corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="377" y="166"/>
|
||||
@ -38,7 +38,7 @@
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="5" language="java" name="com.iluwatar.unitofwork.Weapon" project="unit-of-work"
|
||||
<class id="5" language="java" name="com.iluwatar.unitofwork.Student" project="unit-of-work"
|
||||
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/Student.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="696" y="130"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
|
@ -27,7 +27,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link App} Application demonstrating unit of work pattern.
|
||||
* {@link App} Application for managing student data.
|
||||
*/
|
||||
public class App {
|
||||
/**
|
||||
@ -37,19 +37,17 @@ public class App {
|
||||
*/
|
||||
|
||||
public static void main(String[] args) {
|
||||
// create some weapons
|
||||
var enchantedHammer = new Weapon(1, "enchanted hammer");
|
||||
var brokenGreatSword = new Weapon(2, "broken great sword");
|
||||
var silverTrident = new Weapon(3, "silver trident");
|
||||
var ram = new Student(1, "Ram", "Street 9, Cupertino");
|
||||
var shyam = new Student(2, "Shyam", "Z bridge, Pune");
|
||||
var gopi = new Student(3, "Gopi", "Street 10, Mumbai");
|
||||
|
||||
// create repository
|
||||
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(),
|
||||
new WeaponDatabase());
|
||||
var context = new HashMap<String, List<Student>>();
|
||||
var studentDatabase = new StudentDatabase();
|
||||
var studentRepository = new StudentRepository(context, studentDatabase);
|
||||
|
||||
// perform operations on the weapons
|
||||
weaponRepository.registerNew(enchantedHammer);
|
||||
weaponRepository.registerModified(silverTrident);
|
||||
weaponRepository.registerDeleted(brokenGreatSword);
|
||||
weaponRepository.commit();
|
||||
studentRepository.registerNew(ram);
|
||||
studentRepository.registerModified(shyam);
|
||||
studentRepository.registerDeleted(gopi);
|
||||
studentRepository.commit();
|
||||
}
|
||||
}
|
||||
|
@ -27,12 +27,14 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* {@link Weapon} is an entity.
|
||||
* {@link Student} is an entity.
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class Weapon {
|
||||
public class Student {
|
||||
|
||||
private final Integer id;
|
||||
private final String name;
|
||||
private final String address;
|
||||
|
||||
}
|
@ -24,19 +24,19 @@
|
||||
package com.iluwatar.unitofwork;
|
||||
|
||||
/**
|
||||
* Act as database for weapon records.
|
||||
* Act as Database for student records.
|
||||
*/
|
||||
public class WeaponDatabase {
|
||||
public class StudentDatabase {
|
||||
|
||||
public void insert(Weapon weapon) {
|
||||
public void insert(Student student) {
|
||||
//Some insert logic to DB
|
||||
}
|
||||
|
||||
public void modify(Weapon weapon) {
|
||||
public void modify(Student student) {
|
||||
//Some modify logic to DB
|
||||
}
|
||||
|
||||
public void delete(Weapon weapon) {
|
||||
public void delete(Student student) {
|
||||
//Some delete logic to DB
|
||||
}
|
||||
}
|
@ -30,41 +30,41 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* {@link ArmsDealer} Weapon repository that supports unit of work for weapons.
|
||||
* {@link StudentRepository} Student database repository. supports unit of work for student data.
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ArmsDealer implements IUnitOfWork<Weapon> {
|
||||
public class StudentRepository implements IUnitOfWork<Student> {
|
||||
|
||||
private final Map<String, List<Weapon>> context;
|
||||
private final WeaponDatabase weaponDatabase;
|
||||
private final Map<String, List<Student>> context;
|
||||
private final StudentDatabase studentDatabase;
|
||||
|
||||
@Override
|
||||
public void registerNew(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for insert in context.", weapon.getName());
|
||||
register(weapon, UnitActions.INSERT.getActionValue());
|
||||
public void registerNew(Student student) {
|
||||
LOGGER.info("Registering {} for insert in context.", student.getName());
|
||||
register(student, UnitActions.INSERT.getActionValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerModified(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for modify in context.", weapon.getName());
|
||||
register(weapon, UnitActions.MODIFY.getActionValue());
|
||||
public void registerModified(Student student) {
|
||||
LOGGER.info("Registering {} for modify in context.", student.getName());
|
||||
register(student, UnitActions.MODIFY.getActionValue());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDeleted(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for delete in context.", weapon.getName());
|
||||
register(weapon, UnitActions.DELETE.getActionValue());
|
||||
public void registerDeleted(Student student) {
|
||||
LOGGER.info("Registering {} for delete in context.", student.getName());
|
||||
register(student, UnitActions.DELETE.getActionValue());
|
||||
}
|
||||
|
||||
private void register(Weapon weapon, String operation) {
|
||||
var weaponsToOperate = context.get(operation);
|
||||
if (weaponsToOperate == null) {
|
||||
weaponsToOperate = new ArrayList<>();
|
||||
private void register(Student student, String operation) {
|
||||
var studentsToOperate = context.get(operation);
|
||||
if (studentsToOperate == null) {
|
||||
studentsToOperate = new ArrayList<>();
|
||||
}
|
||||
weaponsToOperate.add(weapon);
|
||||
context.put(operation, weaponsToOperate);
|
||||
studentsToOperate.add(student);
|
||||
context.put(operation, studentsToOperate);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,26 +90,26 @@ public class ArmsDealer implements IUnitOfWork<Weapon> {
|
||||
}
|
||||
|
||||
private void commitInsert() {
|
||||
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
|
||||
for (var weapon : weaponsToBeInserted) {
|
||||
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName());
|
||||
weaponDatabase.insert(weapon);
|
||||
var studentsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
|
||||
for (var student : studentsToBeInserted) {
|
||||
LOGGER.info("Saving {} to database.", student.getName());
|
||||
studentDatabase.insert(student);
|
||||
}
|
||||
}
|
||||
|
||||
private void commitModify() {
|
||||
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue());
|
||||
for (var weapon : modifiedWeapons) {
|
||||
LOGGER.info("Scheduling {} for modification work.", weapon.getName());
|
||||
weaponDatabase.modify(weapon);
|
||||
var modifiedStudents = context.get(UnitActions.MODIFY.getActionValue());
|
||||
for (var student : modifiedStudents) {
|
||||
LOGGER.info("Modifying {} to database.", student.getName());
|
||||
studentDatabase.modify(student);
|
||||
}
|
||||
}
|
||||
|
||||
private void commitDelete() {
|
||||
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
|
||||
for (var weapon : deletedWeapons) {
|
||||
LOGGER.info("Scrapping {}.", weapon.getName());
|
||||
weaponDatabase.delete(weapon);
|
||||
var deletedStudents = context.get(UnitActions.DELETE.getActionValue());
|
||||
for (var student : deletedStudents) {
|
||||
LOGGER.info("Deleting {} to database.", student.getName());
|
||||
studentDatabase.delete(student);
|
||||
}
|
||||
}
|
||||
}
|
@ -36,102 +36,102 @@ import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* tests {@link ArmsDealer}
|
||||
* tests {@link StudentRepository}
|
||||
*/
|
||||
|
||||
class ArmsDealerTest {
|
||||
private final Weapon weapon1 = new Weapon(1, "battle ram");
|
||||
private final Weapon weapon2 = new Weapon(1, "wooden lance");
|
||||
class StudentRepositoryTest {
|
||||
private final Student student1 = new Student(1, "Ram", "street 9, cupertino");
|
||||
private final Student student2 = new Student(1, "Sham", "Z bridge, pune");
|
||||
|
||||
private final Map<String, List<Weapon>> context = new HashMap<>();
|
||||
private final WeaponDatabase weaponDatabase = mock(WeaponDatabase.class);
|
||||
private final ArmsDealer armsDealer = new ArmsDealer(context, weaponDatabase);;
|
||||
private final Map<String, List<Student>> context = new HashMap<>();
|
||||
private final StudentDatabase studentDatabase = mock(StudentDatabase.class);
|
||||
private final StudentRepository studentRepository = new StudentRepository(context, studentDatabase);;
|
||||
|
||||
@Test
|
||||
void shouldSaveNewStudentWithoutWritingToDb() {
|
||||
armsDealer.registerNew(weapon1);
|
||||
armsDealer.registerNew(weapon2);
|
||||
studentRepository.registerNew(student1);
|
||||
studentRepository.registerNew(student2);
|
||||
|
||||
assertEquals(2, context.get(UnitActions.INSERT.getActionValue()).size());
|
||||
verifyNoMoreInteractions(weaponDatabase);
|
||||
verifyNoMoreInteractions(studentDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveDeletedStudentWithoutWritingToDb() {
|
||||
armsDealer.registerDeleted(weapon1);
|
||||
armsDealer.registerDeleted(weapon2);
|
||||
studentRepository.registerDeleted(student1);
|
||||
studentRepository.registerDeleted(student2);
|
||||
|
||||
assertEquals(2, context.get(UnitActions.DELETE.getActionValue()).size());
|
||||
verifyNoMoreInteractions(weaponDatabase);
|
||||
verifyNoMoreInteractions(studentDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveModifiedStudentWithoutWritingToDb() {
|
||||
armsDealer.registerModified(weapon1);
|
||||
armsDealer.registerModified(weapon2);
|
||||
studentRepository.registerModified(student1);
|
||||
studentRepository.registerModified(student2);
|
||||
|
||||
assertEquals(2, context.get(UnitActions.MODIFY.getActionValue()).size());
|
||||
verifyNoMoreInteractions(weaponDatabase);
|
||||
verifyNoMoreInteractions(studentDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveAllLocalChangesToDb() {
|
||||
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
|
||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
|
||||
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
|
||||
|
||||
armsDealer.commit();
|
||||
studentRepository.commit();
|
||||
|
||||
verify(weaponDatabase, times(1)).insert(weapon1);
|
||||
verify(weaponDatabase, times(1)).modify(weapon1);
|
||||
verify(weaponDatabase, times(1)).delete(weapon1);
|
||||
verify(studentDatabase, times(1)).insert(student1);
|
||||
verify(studentDatabase, times(1)).modify(student1);
|
||||
verify(studentDatabase, times(1)).delete(student1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotWriteToDbIfContextIsNull() {
|
||||
var weaponRepository = new ArmsDealer(null, weaponDatabase);
|
||||
var studentRepository = new StudentRepository(null, studentDatabase);
|
||||
|
||||
weaponRepository.commit();
|
||||
studentRepository.commit();
|
||||
|
||||
verifyNoMoreInteractions(weaponDatabase);
|
||||
verifyNoMoreInteractions(studentDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotWriteToDbIfNothingToCommit() {
|
||||
var weaponRepository = new ArmsDealer(new HashMap<>(), weaponDatabase);
|
||||
var studentRepository = new StudentRepository(new HashMap<>(), studentDatabase);
|
||||
|
||||
weaponRepository.commit();
|
||||
studentRepository.commit();
|
||||
|
||||
verifyNoMoreInteractions(weaponDatabase);
|
||||
verifyNoMoreInteractions(studentDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotInsertToDbIfNoRegisteredStudentsToBeCommitted() {
|
||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
|
||||
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
|
||||
|
||||
armsDealer.commit();
|
||||
studentRepository.commit();
|
||||
|
||||
verify(weaponDatabase, never()).insert(weapon1);
|
||||
verify(studentDatabase, never()).insert(student1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotModifyToDbIfNotRegisteredStudentsToBeCommitted() {
|
||||
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
|
||||
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
|
||||
|
||||
armsDealer.commit();
|
||||
studentRepository.commit();
|
||||
|
||||
verify(weaponDatabase, never()).modify(weapon1);
|
||||
verify(studentDatabase, never()).modify(student1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotDeleteFromDbIfNotRegisteredStudentsToBeCommitted() {
|
||||
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
|
||||
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
|
||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
|
||||
|
||||
armsDealer.commit();
|
||||
studentRepository.commit();
|
||||
|
||||
verify(weaponDatabase, never()).delete(weapon1);
|
||||
verify(studentDatabase, never()).delete(student1);
|
||||
}
|
||||
}
|
@ -10,80 +10,19 @@ tags:
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
||||
Provide objects which follow value semantics rather than reference semantics.
|
||||
This means value objects' equality is not based on identity. Two value objects are
|
||||
This means value objects' equality are not based on identity. Two value objects are
|
||||
equal when they have the same value, not necessarily being the same object.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
|
||||
> There is a class for hero statistics in a role-playing game. The statistics contain attributes
|
||||
> such as strength, intelligence, and luck. The statistics of different heroes should be equal
|
||||
> when all the attributes are equal.
|
||||
|
||||
In plain words
|
||||
|
||||
> Value objects are equal when their attributes have the same value
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In computer science, a value object is a small object that represents a simple entity whose
|
||||
> equality is not based on identity: i.e. two value objects are equal when they have the same
|
||||
> value, not necessarily being the same object.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Here is the `HeroStat` class that is the value object. Notice the use of
|
||||
[Lombok's `@Value`](https://projectlombok.org/features/Value) annotation.
|
||||
|
||||
```java
|
||||
@Value(staticConstructor = "valueOf")
|
||||
class HeroStat {
|
||||
|
||||
int strength;
|
||||
int intelligence;
|
||||
int luck;
|
||||
}
|
||||
```
|
||||
|
||||
The example creates three different `HeroStat`s and compares their equality.
|
||||
|
||||
```java
|
||||
var statA = HeroStat.valueOf(10, 5, 0);
|
||||
var statB = HeroStat.valueOf(10, 5, 0);
|
||||
var statC = HeroStat.valueOf(5, 1, 8);
|
||||
|
||||
LOGGER.info(statA.toString());
|
||||
LOGGER.info(statB.toString());
|
||||
LOGGER.info(statC.toString());
|
||||
|
||||
LOGGER.info("Is statA and statB equal : {}", statA.equals(statB));
|
||||
LOGGER.info("Is statA and statC equal : {}", statA.equals(statC));
|
||||
```
|
||||
|
||||
Here's the console output.
|
||||
|
||||
```
|
||||
20:11:12.199 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0)
|
||||
20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0)
|
||||
20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=5, intelligence=1, luck=8)
|
||||
20:11:12.202 [main] INFO com.iluwatar.value.object.App - Is statA and statB equal : true
|
||||
20:11:12.203 [main] INFO com.iluwatar.value.object.App - Is statA and statC equal : false
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Value Object when
|
||||
|
||||
* The object's equality needs to be based on the object's value
|
||||
* You need to measure the objects' equality based on the objects' value
|
||||
|
||||
## Known uses
|
||||
## Real world examples
|
||||
|
||||
* [java.util.Optional](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)
|
||||
* [java.time.LocalDate](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html)
|
||||
@ -92,7 +31,6 @@ Use the Value Object when
|
||||
## Credits
|
||||
|
||||
* [Patterns of Enterprise Application Architecture](http://www.martinfowler.com/books/eaa.html)
|
||||
* [ValueObject](https://martinfowler.com/bliki/ValueObject.html)
|
||||
* [VALJOs - Value Java Objects : Stephen Colebourne's blog](http://blog.joda.org/2014/03/valjos-value-java-objects.html)
|
||||
* [Value Object : Wikipedia](https://en.wikipedia.org/wiki/Value_object)
|
||||
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94)
|
||||
|
@ -43,7 +43,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public class App {
|
||||
|
||||
/**
|
||||
* This example creates three HeroStats (value objects) and checks equality between those.
|
||||
* This practice creates three HeroStats(Value object) and checks equality between those.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
var statA = HeroStat.valueOf(10, 5, 0);
|
||||
@ -51,8 +51,6 @@ public class App {
|
||||
var statC = HeroStat.valueOf(5, 1, 8);
|
||||
|
||||
LOGGER.info(statA.toString());
|
||||
LOGGER.info(statB.toString());
|
||||
LOGGER.info(statC.toString());
|
||||
|
||||
LOGGER.info("Is statA and statB equal : {}", statA.equals(statB));
|
||||
LOGGER.info("Is statA and statC equal : {}", statA.equals(statC));
|
||||
|
@ -23,7 +23,10 @@
|
||||
|
||||
package com.iluwatar.value.object;
|
||||
|
||||
import lombok.Value;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* HeroStat is a value object.
|
||||
@ -32,10 +35,23 @@ import lombok.Value;
|
||||
* http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html
|
||||
* </a>
|
||||
*/
|
||||
@Value(staticConstructor = "valueOf")
|
||||
class HeroStat {
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public class HeroStat {
|
||||
|
||||
// Stats for a hero
|
||||
|
||||
private final int strength;
|
||||
private final int intelligence;
|
||||
private final int luck;
|
||||
|
||||
// Static factory method to create new instances.
|
||||
public static HeroStat valueOf(int strength, int intelligence, int luck) {
|
||||
return new HeroStat(strength, intelligence, luck);
|
||||
}
|
||||
|
||||
// The clone() method should not be public. Just don't override it.
|
||||
|
||||
int strength;
|
||||
int intelligence;
|
||||
int luck;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user