docs: update composite view

This commit is contained in:
Ilkka Seppälä committed 2024-03-30 22:06:41 +02:00
1 parent fd109955dc
commit d3e7401f70
1 file changed
+259 -195
+259 -195
View File
@@ -1,36 +1,39 @@
--- ---
title: Composite View title: Composite View
category: Structural category: Structural
language: en language: en
tag: tag:
- Enterprise patterns - Enterprise patterns
- Presentation - Presentation
--- ---
## Name
**Composite View**
## Intent ## 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 The primary goal of the Composite View design pattern is to compose objects into tree structures to represent part-whole
or layout of the page without impacting the other. This pattern also allows content to be easily reused across different views easily. hierarchies. This allows clients to treat individual objects and compositions of objects uniformly, simplifying the
management of complex structures.
## Explanation ## Explanation
Real World Example Real World Example
> A news site wants to display the current date and news to different users > 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 > 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. > components depending on the user's interest, defaulting to local news.
In Plain Words In Plain Words
> Composite View Pattern is having a main view being composed of smaller subviews. > 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 > The layout of this composite view is based on a template. A View-manager then decides which
> subviews to include in this template. > subviews to include in this template.
Wikipedia Says 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. > Composite views that are composed of multiple atomic subviews. Each component of
> This solution provides for the creation of a composite view based on the inclusion and substitution of > the template may be included dynamically into the whole and the layout of the page may be managed independently of the
> modular dynamic and static template fragments. > 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. > It promotes the reuse of atomic portions of the view by encouraging modular design.
**Programmatic Example** **Programmatic Example**
@@ -39,229 +42,272 @@ 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+. 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+. Firstly there is `AppServlet` which is an `HttpServlet` that runs on Tomcat 10+.
```java ```java
public class AppServlet extends HttpServlet { public class AppServlet extends HttpServlet {
private String msgPartOne = "<h1>This Server Doesn't Support"; private String msgPartOne = "<h1>This Server Doesn't Support";
private String msgPartTwo = "Requests</h1>\n" private String msgPartTwo = "Requests</h1>\n"
+ "<h2>Use a GET request with boolean values for the following parameters<h2>\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>"; + "<h3>'name'</h3>\n<h3>'bus'</h3>\n<h3>'sports'</h3>\n<h3>'sci'</h3>\n<h3>'world'</h3>";
private String destination = "newsDisplay.jsp"; private String destination = "newsDisplay.jsp";
public AppServlet() { public AppServlet() {
} }
@Override @Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { throws ServletException, IOException {
RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination); RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination);
ClientPropertiesBean reqParams = new ClientPropertiesBean(req); ClientPropertiesBean reqParams = new ClientPropertiesBean(req);
req.setAttribute("properties", reqParams); req.setAttribute("properties", reqParams);
requestDispatcher.forward(req, resp); requestDispatcher.forward(req, resp);
} }
@Override @Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { throws ServletException, IOException {
resp.setContentType("text/html"); resp.setContentType("text/html");
PrintWriter out = resp.getWriter(); PrintWriter out = resp.getWriter();
out.println(msgPartOne + " Post " + msgPartTwo); out.println(msgPartOne + " Post " + msgPartTwo);
} }
@Override @Override
public void doDelete(HttpServletRequest req, HttpServletResponse resp) public void doDelete(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { throws ServletException, IOException {
resp.setContentType("text/html"); resp.setContentType("text/html");
PrintWriter out = resp.getWriter(); PrintWriter out = resp.getWriter();
out.println(msgPartOne + " Delete " + msgPartTwo); out.println(msgPartOne + " Delete " + msgPartTwo);
} }
@Override @Override
public void doPut(HttpServletRequest req, HttpServletResponse resp) public void doPut(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { throws ServletException, IOException {
resp.setContentType("text/html"); resp.setContentType("text/html");
PrintWriter out = resp.getWriter(); PrintWriter out = resp.getWriter();
out.println(msgPartOne + " Put " + msgPartTwo); out.println(msgPartOne + " Put " + msgPartTwo);
} }
} }
``` ```
This servlet is not part of the pattern, and simply forwards GET requests to the correct JSP. 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. 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. The view management in this example is done via a javabean class: `ClientPropertiesBean`, which stores user preferences.
```java ```java
public class ClientPropertiesBean implements Serializable { public class ClientPropertiesBean implements Serializable {
private static final String WORLD_PARAM = "world"; private static final String WORLD_PARAM = "world";
private static final String SCIENCE_PARAM = "sci"; private static final String SCIENCE_PARAM = "sci";
private static final String SPORTS_PARAM = "sport"; private static final String SPORTS_PARAM = "sport";
private static final String BUSINESS_PARAM = "bus"; private static final String BUSINESS_PARAM = "bus";
private static final String NAME_PARAM = "name"; private static final String NAME_PARAM = "name";
private static final String DEFAULT_NAME = "DEFAULT_NAME"; private static final String DEFAULT_NAME = "DEFAULT_NAME";
private boolean worldNewsInterest; private boolean worldNewsInterest;
private boolean sportsInterest; private boolean sportsInterest;
private boolean businessInterest; private boolean businessInterest;
private boolean scienceNewsInterest; private boolean scienceNewsInterest;
private String name; private String name;
public ClientPropertiesBean() { public ClientPropertiesBean() {
worldNewsInterest = true; worldNewsInterest = true;
sportsInterest = true; sportsInterest = true;
businessInterest = true; businessInterest = true;
scienceNewsInterest = true; scienceNewsInterest = true;
name = DEFAULT_NAME; 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;
} public ClientPropertiesBean(HttpServletRequest req) {
// getters and setters generated by Lombok 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 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 This second constructor takes the request object, parses out the request parameters which contain the
user preferences for different types of news. user preferences for different types of news.
The template for the news page is in `newsDisplay.jsp` The template for the news page is in `newsDisplay.jsp`
```html ```html
<html> <html>
<head> <head>
<style> <style>
h1 { text-align: center;} h1 {
h2 { text-align: center;} text-align: center;
h3 { text-align: center;} }
h2 {
text-align: center;
}
h3 {
text-align: center;
}
.centerTable { .centerTable {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
table {border: 1px solid black;}
tr {text-align: center;} table {
td {text-align: center;} border: 1px solid black;
}
tr {
text-align: center;
}
td {
text-align: center;
}
</style> </style>
</head> </head>
<body> <body>
<%ClientPropertiesBean propertiesBean = (ClientPropertiesBean) request.getAttribute("properties");%> <%ClientPropertiesBean propertiesBean = (ClientPropertiesBean) request.getAttribute("properties");%>
<h1>Welcome <%= propertiesBean.getName()%></h1> <h1>Welcome <%= propertiesBean.getName()%></h1>
<jsp:include page="header.jsp"></jsp:include> <jsp:include page="header.jsp"></jsp:include>
<table class="centerTable"> <table class="centerTable">
<tr> <tr>
<td></td> <td></td>
<% if(propertiesBean.isWorldNewsInterest()) { %> <% if(propertiesBean.isWorldNewsInterest()) { %>
<td><%@include file="worldNews.jsp"%></td> <td><%@include file="worldNews.jsp"%></td>
<% } else { %> <% } else { %>
<td><%@include file="localNews.jsp"%></td> <td><%@include file="localNews.jsp"%></td>
<% } %> <% } %>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<% if(propertiesBean.isBusinessInterest()) { %> <% if(propertiesBean.isBusinessInterest()) { %>
<td><%@include file="businessNews.jsp"%></td> <td><%@include file="businessNews.jsp"%></td>
<% } else { %> <% } else { %>
<td><%@include file="localNews.jsp"%></td> <td><%@include file="localNews.jsp"%></td>
<% } %> <% } %>
<td></td> <td></td>
<% if(propertiesBean.isSportsInterest()) { %> <% if(propertiesBean.isSportsInterest()) { %>
<td><%@include file="sportsNews.jsp"%></td> <td><%@include file="sportsNews.jsp"%></td>
<% } else { %> <% } else { %>
<td><%@include file="localNews.jsp"%></td> <td><%@include file="localNews.jsp"%></td>
<% } %> <% } %>
</tr> </tr>
<tr> <tr>
<td></td> <td></td>
<% if(propertiesBean.isScienceNewsInterest()) { %> <% if(propertiesBean.isScienceNewsInterest()) { %>
<td><%@include file="scienceNews.jsp"%></td> <td><%@include file="scienceNews.jsp"%></td>
<% } else { %> <% } else { %>
<td><%@include file="localNews.jsp"%></td> <td><%@include file="localNews.jsp"%></td>
<% } %> <% } %>
<td></td> <td></td>
</tr> </tr>
</table> </table>
</body> </body>
</html> </html>
``` ```
This JSP page is the template. It declares a table with three rows, with one component in the first row, 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. two components in the second row, and one component in the third row.
The scriplets in the file are part of the 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. 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: Here are two examples of the mock atomic subviews used in the composite:
`businessNews.jsp` `businessNews.jsp`
```html ```html
<html> <html>
<head> <head>
<style> <style>
h2 { text-align: center;} h2 {
table {border: 1px solid black;} text-align: center;
tr {text-align: center;} }
td {text-align: center;}
</style> table {
</head> border: 1px solid black;
<body> }
<h2>
Generic Business News tr {
</h2> text-align: center;
<table style="margin-right: auto; margin-left: auto"> }
<tr>
<td>Stock prices up across the world</td> td {
<td>New tech companies to invest in</td> text-align: center;
</tr> }
<tr> </style>
<td>Industry leaders unveil new project</td> </head>
<td>Price fluctuations and what they mean</td> <body>
</tr> <h2>
</table> Generic Business News
</body> </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> </html>
``` ```
`localNews.jsp` `localNews.jsp`
```html ```html
<html> <html>
<body> <body>
<div style="text-align: center"> <div style="text-align: center">
<h3> <h3>
Generic Local News Generic Local News
</h3> </h3>
<ul style="list-style-type: none"> <ul style="list-style-type: none">
<li> <li>
Mayoral elections coming up in 2 weeks Mayoral elections coming up in 2 weeks
</li> </li>
<li> <li>
New parking meter rates downtown coming tomorrow New parking meter rates downtown coming tomorrow
</li> </li>
<li> <li>
Park renovations to finish by the next year Park renovations to finish by the next year
</li> </li>
<li> <li>
Annual marathon sign ups available online Annual marathon sign ups available online
</li> </li>
</ul> </ul>
</div> </div>
</body> </body>
</html> </html>
``` ```
The results are as such: The results are as such:
1) The user has put their name as `Tammy` in the request parameters and no preferences: 1) The user has put their name as `Tammy` in the request parameters and no preferences:
![alt text](./etc/images/noparam.png) ![alt text](./etc/images/noparam.png)
2) The user has put their name as `Johnny` in the request parameters and has a preference for world, business, and science news: 2) The user has put their name as `Johnny` in the request parameters and has a preference for world, business, and
![alt text](./etc/images/threeparams.png) science news:
![alt text](./etc/images/threeparams.png)
The different subviews such as `worldNews.jsp`, `businessNews.jsp`, etc. are included conditionally The different subviews such as `worldNews.jsp`, `businessNews.jsp`, etc. are included conditionally
based on the request parameters. based on the request parameters.
@@ -273,12 +319,13 @@ Set up your IDE to build a WAR file from the module and deploy that file to the
IntelliJ: IntelliJ:
Under `Run` and `edit configurations` Make sure Tomcat server is one of the run configurations. 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`. Go to the deployment tab, and make sure there is one artifact being built called `composite-view:war exploded`.
If not present, add one. 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. Ensure that the artifact is being built from the content of the `web` directory and the compilation results of the
Point the output of the artifact to a convenient place. Run the configuration and view the landing page, 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. follow instructions on that page to continue.
## Class diagram ## Class diagram
@@ -288,35 +335,52 @@ follow instructions on that page to continue.
The class diagram here displays the Javabean which is the view manager. The class diagram here displays the Javabean which is the view manager.
The views are JSP's held inside the web directory. The views are JSP's held inside the web directory.
## Applicability ## Applicability:
This pattern is applicable to most websites that require content to be displayed dynamically/conditionally. Use the Composite View design pattern when:
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 * You want to represent part-whole hierarchies of objects.
* You expect that the composite structures might include any new components in the future.
* You want clients to be able to ignore the difference between compositions of objects and individual objects. Clients
will treat all objects in the composite structure uniformly.
Most modern websites use composite views in some shape or form, as they have templates for views and small atomic components ## Known Uses
that are included in the page dynamically. Most modern Javascript libraries, like React, support this design pattern
with components. * Graphical User Interfaces (GUIs) where widgets can contain other widgets (e.g., a window containing panels, buttons,
and text fields).
* Document structures, such as the representation of tables containing rows, which in turn contain cells, all of which
can be treated as elements in a unified hierarchy.
## Consequences ## Consequences
**Pros**
* Easy to re-use components
* Change layout/content without affecting the other
* Reduce code duplication
* Code is more maintainable and modular
**Cons** Benefits:
* Overhead cost at runtime
* Slower response compared to directly embedding elements
* Increases potential for display errors
## Related patterns * High flexibility in adding new components: Since composites and leaf nodes are treated uniformly, it's easier to add
* [Composite (GoF)](https://java-design-patterns.com/patterns/composite/) new kinds of components.
* Simplified client code: Clients can treat composite structures and individual elements uniformly, reducing the
complexity in client code.
Trade-offs:
* Overgeneralization: Designing the system might become more complex if you make everything composite, especially if
your application doesn't require it.
* Difficulty in constraint enforcement: It can be harder to restrict the components of a composite to only certain
types.
## Related Patterns
* [Decorator](https://java-design-patterns.com/patterns/decorator/): While Decorator is used to add responsibilities to
objects, Composite is meant for building structures of objects.
* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Composite can often be combined with Flyweight to
implement shared leaf nodes in a composite structure, reducing the memory footprint.
* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/): Can be used with
Composite to let components pass requests through the hierarchy.
* [Composite](https://java-design-patterns.com/patterns/composite/)
* [View Helper](https://www.oracle.com/java/technologies/viewhelper.html) * [View Helper](https://www.oracle.com/java/technologies/viewhelper.html)
## Credits ## Credits
* [Core J2EE Patterns - Composite View](https://www.oracle.com/java/technologies/composite-view.html) * [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/) * [Composite View Design Pattern – Core J2EE Patterns](https://www.dineshonjava.com/composite-view-design-pattern/)
* [Patterns of Enterprise Application Architecture](https://amzn.to/49jpQG3)
* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/3xfntGJ)