Java 中的复合视图模式:增强跨应用程序的 UI 一致性
复合视图设计模式的意图
复合视图设计模式的主要目标是将对象组合成树状结构,以表示部分-整体层次结构。这使得客户端能够以统一的方式处理单个对象和对象的组合,简化了复杂层次视图的管理。
带实际案例的复合视图模式详细解释
现实世界中的例子
复合视图设计模式在现实世界中的一个例子是 Web 应用程序中仪表板的布局。考虑一个金融仪表板,它显示各种小部件,例如股票图表、最近交易、账户余额和新闻提要。这些小部件中的每一个都是一个独立的视图组件,可以独立更新和管理。通过使用复合视图模式,这些独立的小部件被组合成一个统一的仪表板视图。这种方法允许轻松地重新组织仪表板,添加新的小部件而不影响现有的小部件,并一致地管理整体布局。这种视图的层次组合反映了仪表板的不同部分如何被视为单个实体以及更大整体的一部分。
通俗地说
复合视图模式是指一个主视图由多个较小的子视图组成。这个复合视图的布局基于一个模板。然后,一个视图管理器决定在该模板中包含哪些子视图。
维基百科说
由多个原子子视图组成的复合视图。模板的每个组件都可以动态地包含到整体中,并且页面的布局可以独立于内容进行管理。此解决方案通过鼓励模块化设计,提供基于包含和替换模块化动态和静态模板片段的复合视图的创建。它促进了视图原子部分的重用。
Java 中复合视图模式的编程示例
一个新闻网站希望根据用户的偏好向不同的用户显示当前日期和新闻。该新闻网站将根据用户的兴趣替换不同的新闻提要组件,默认使用本地新闻。
由于这是一个 Web 开发模式,因此需要一个服务器来演示它。此示例使用 Tomcat 10.0.13 来运行 servlet,并且此编程示例仅适用于 Tomcat 10 以上。
首先,有一个 AppServlet
,它是一个在 Tomcat 10 以上运行的 HttpServlet
。
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);
}
}
此 servlet 不是模式的一部分,只是将 GET 请求转发到正确的 JSP。不支持 PUT、POST 和 DELETE 请求,只会显示错误消息。
此示例中的视图管理通过一个 JavaBean 类 ClientPropertiesBean
完成,该类存储用户偏好。
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
}
此 JavaBean 有一个默认构造函数,以及另一个接受 HttpServletRequest
的构造函数。
第二个构造函数接受请求对象,解析出包含不同类型新闻的用户偏好的请求参数。
新闻页面的模板在 newsDisplay.jsp
中。
<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>
此 JSP 页面是模板。它声明了一个包含三行的表格,第一行包含一个组件,第二行包含两个组件,第三行包含一个组件。
文件中的脚本是视图管理策略的一部分,该策略根据 JavaBean 中的用户偏好包含不同的原子子视图。
以下是复合中使用的模拟原子子视图的两个示例:businessNews.jsp
<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>
<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>
不同的子视图(如 worldNews.jsp
、businessNews.jsp
等)根据请求参数有条件地包含。
如何使用
要尝试此示例,请确保已安装 Tomcat 10 以上。将您的 IDE 设置为从模块构建 WAR 文件,并将该文件部署到服务器。
IntelliJ
在 运行
和 编辑配置
下,确保 Tomcat 服务器是运行配置之一。转到部署选项卡,并确保正在构建一个名为 composite-view:war exploded
的工件。如果不存在,请添加一个。
确保工件是从 web
目录的内容和模块的编译结果构建的。将工件的输出指向一个方便的位置。运行配置并查看登录页面,按照该页面上的说明继续操作。
何时在 Java 中使用复合视图模式
当您需要以下情况时,请使用复合视图设计模式
- 您希望表示对象的“部分-整体”层次结构。
- 您预计复合结构将来可能会包含任何新的组件。
- 您希望客户端能够忽略对象组合和单个对象之间的差异。客户端将以统一的方式处理复合结构中的所有对象。
复合视图模式 Java 教程
Java 中复合视图模式的实际应用
- 小部件可以包含其他小部件的图形用户界面 (GUI)(例如,包含面板、按钮和文本字段的窗口)。
- 文档结构,例如包含行(进而包含单元格)的表的表示形式,所有这些都可以被视为统一层次结构中的元素。
复合视图模式的优缺点
优点
- 在添加新组件方面具有高度灵活性:由于复合和叶节点以统一的方式处理,因此更易于添加新类型的组件。
- 简化了客户端代码:客户端可以以统一的方式处理复合结构和单个元素,从而降低了客户端代码的复杂性。
缺点
- 过度泛化:如果将所有内容都设为复合,即使您的应用程序不需要,设计系统也可能会变得更加复杂。
- 难以执行约束:限制复合的组件仅为特定类型可能会更难。
相关的 Java 设计模式
- 复合:用于将单个对象和组合统一处理的一般结构模式,是复合视图的基础。
- 装饰器:增强单个视图的行为,而无需修改底层视图。
- 享元:可用于管理大量相似视图对象的内存消耗。
- 视图助手:将视图逻辑与业务逻辑分离,有助于清晰地组织和管理视图组件。