Center
of Excellence
Bài
8
Điểm
trung bình toàn môn học
I. Bài toán
Một giảng viên cho
học viên làm nhiều bài kiểm tra trong suốt một học kỳ.
Đến cuối kỳ, khi tính điểm trung bình cho một học
viên, giảng viên đó sẽ loại ra điểm kiểm tra thấp
nhất. Chẳng hạn, nếu tập các điểm kiểm tra của một
học viên là 85, 70, 40, và 95, thì khi tính trung bình, điểm
thấp nhất 40 sẽ được loại ra, và lúc đó điểm trung
bình môn sẽ là (85 + 70 + 95) / 3 = 83,3. Hãy phát triển một
web application nhằm giúp giảng viên thực hiện thao tác
tính trung bình cho một học viên.
Dưới đây là một
UI mẫu.
II. Giải
pháp
Project: AverageScoreJSF
index.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Average Score</title>
<script src="resources/clearInputTexts.js"/>
<style>
body {
margin: 0 20% 0 20%;
}
fieldset label {
width: 190px;
float: left;
text-align: right;
}
div.buttons {
margin-left: 205px;
}
</style>
</h:head>
<h:body>
<h2>Average Score</h2>
<h:form prependId="false">
<fieldset>
<label>List of scores:</label>
<h:inputText disabled="true"/><br/>
<label>Score to add:</label>
<h:inputText id="Score" required="true">
<f:validateLongRange minimum="0" maximum="100"/>
</h:inputText>
<h:message for="Score" style="color:red"/><br/>
<label>Average without min:</label>
<h:inputText disabled="true"/>
</fieldset>
<div class="buttons">
<h:commandButton value="Add"/>
<h:commandButton value="Average"/>
<h:commandButton value="Clear" action="#{index.clear()}"
immediate="true" onclick="clearInputTexts(this.form)"/>
</div>
</h:form>
</h:body>
</html>
2.1.
Thiết kế managed bean
Hành
vi của application này có điểm khác biệt với applications
ở các bài trước ở chỗ: Sau khi user nhập một score,
application cần nhớ lại score này để có thể dùng cho
thao tác tính trung bình sau này. Khi đó, ta không thể đặt
managed vào request scope vì scope này chỉ có khả năng nhớ
ngắn hạn, trong khoảng thời gian từ lúc user nhập data,
clicks button để truyền yêu cầu về server, cho đến khi
server hồi đáp về browser là kết thúc. Để duy trì
managed bean dài hạn hơn, ta cần đặt nó vào session
scope. Scope này sẽ duy trì managed bean cho đến khi kết
thúc phiên làm việc (session).
2.2.
Tạo fields và methods cho managed bean
package controller;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
@Named(value = "index")
@SessionScoped
public class Index implements Serializable {
private List<Integer> scores = new ArrayList();
private Integer scoreToAdd;
private Double averageWithoutMin;
public Index() {
}
public void averageWithoutMin() {
// TODO
}
public void add() {
scores.add(scoreToAdd);
scoreToAdd = null;
}
public void clear() {
scores.clear();
scoreToAdd = null;
averageWithoutMin = null;
}
public List<Integer> getScores() {
return scores;
}
public Integer getScoreToAdd() {
return scoreToAdd;
}
public void setScoreToAdd(Integer scoreToAdd) {
this.scoreToAdd = scoreToAdd;
}
public Double getAverageWithoutMin() {
return averageWithoutMin;
}
}
2.3.
Ràng buộc UI screen với managed bean
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Average Score</title>
<script src="resources/clearInputTexts.js"/>
<style>
body {
margin: 0 20% 0 20%;
}
fieldset label {
width: 190px;
float: left;
text-align: right;
}
div.buttons {
margin-left: 205px;
}
</style>
</h:head>
<h:body>
<h2>Average Score</h2>
<h:form prependId="false">
<fieldset>
<label>List of scores:</label>
<h:inputText disabled="true" value="#{index.scores}"/><br/>
<label>Score to add:</label>
<h:inputText id="Score" required="true" value="#{index.scoreToAdd}">
<f:validateLongRange minimum="0" maximum="100"/>
</h:inputText>
<h:message for="Score" style="color:red"/><br/>
<label>Average without min:</label>
<h:inputText disabled="true" value="#{index.averageWithoutMin}"/>
</fieldset>
<div class="buttons">
<h:commandButton value="Add" action="#{index.add()}"/>
<h:commandButton value="Average" action="#{index.averageWithoutMin()}"/>
<h:commandButton value="Clear" action="#{index.clear()}"
immediate="true" onclick="clearInputTexts(this.form)"/>
</div>
</h:form>
</h:body>
</html>
3.1.
Design
3.2.
Test class
package model;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class ScoreCollectionTest {
@Test
public void averageWithoutMin() {
List<Integer> scores = Arrays.asList(85, 70, 40, 95);
assertEquals(83.3, new ScoreCollection(scores).averageWithoutMin(), 0.04);
}
}
3.3.
Main class
package model;
import java.util.Collections;
import java.util.List;
public class ScoreCollection {
private List<Integer> scores;
public ScoreCollection(List<Integer> scores) {
this.scores = scores;
}
public double averageWithoutMin() {
return (double)this.sumWithoutMin() / (scores.size() - 1);
}
private int sumWithoutMin() {
return sum() - Collections.min(scores);
}
private int sum() {
int acc = 0;
for (int score : this.scores)
acc += score;
return acc;
}
}
4.
Kết nối managed bean với model
@Named(value = "index")
@SessionScoped
public class Index implements Serializable {
private List<Integer> scores = new ArrayList();
private Integer scoreToAdd;
private Double averageWithoutMin;
public Index() {
}
public void averageWithoutMin() {
averageWithoutMin = new ScoreCollection(scores).averageWithoutMin();
}
public void add() {
scores.add(scoreToAdd);
scoreToAdd = null;
}
public void clear() {
scores.clear();
scoreToAdd = null;
averageWithoutMin = null;
}
// getter, setter
}
Khi
thi hành application, nhập vào một số giá trị, rồi click
Average, ta nhận được thông báo yêu cầu không được
để trống ô Score to add (!) Ta xử lý trường hợp
này bằng cách thêm attribute immediate=”true” vào
Average button.
<div class="buttons">
<h:commandButton value="Add" action="#{index.add()}"/>
<h:commandButton value="Average" action="#{index.averageWithoutMin()}"
immediate="true"/>
<h:commandButton value="Clear" action="#{index.clear()}"
immediate="true" onclick="clearInputTexts(this.form)"/>
Ngoài ra, ta có thể qui định là điểm trung bình được hiển thị dưới dạng có một chữ số sau dấu chấm thập phân.
<label>Average without min:</label>
<h:inputText disabled="true" value="#{index.averageWithoutMin}">
<f:convertNumber pattern="#0.0"/>
</h:inputText>
5.
Kiểm tra tính hợp lệ của input từ managed bean
Trong
index.xhtml ta đã qui định rằng user chỉ có thể
nhập các điểm số từ 1 đến 100. Tuy nhiên, nếu user
không nhập điểm mà clicks ngay Average, ta sẽ gặp
ngoại lệ NoSuchElementException. Lý do là application
không có gì để tính trung bình. Như vậy, ta cần phát ra
một thông báo lỗi trong trường hợp này hoặc trường
hợp tập hợp điểm chỉ chứa một phần tử.
Trong
averageWithoutMin() thuộc managed bean, trước khi tính
trung bình, ta bổ sung code kiểm tra. Nếu input không hợp
lệ, ta trả về một thông báo lỗi.
public void averageWithoutMin() {
if (scores.size() <= 1) {
String msg = "A score is expected.";
FacesContext.getCurrentInstance().addMessage("Score",
new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg));
return;
}
averageWithoutMin = new ScoreCollection(scores).averageWithoutMin();
}
III.
Tổng kết
Bài viết đã vận dụng session scope để tính điểm trung
bình của toàn môn học. Ngoài ra, bài viết còn trình bày
cách xử lý trường hợp data không hợp lệ trong managed
bean.
IV.
Tài liệu tham khảo
- Gaddis T. (2010) Starting Out with Java From Control Structures Through Objects (4th edition), Pearson Education, Boston. Chương 8.
Không có nhận xét nào:
Đăng nhận xét