Center of Excellence
Bài 9
Doanh thu toàn công ty
Hãy gắng để lại
thế giới này tốt hơn một tí
so với thời điểm
bạn phát hiện ra nó
-- Robert Stephenson
Smyth Baden-Powell
I. Bài toán
Hãy phát triển chương
trình tính tổng doanh thu toàn năm của một công ty thương
mại. Sau đây là ví dụ về doanh thu 4 quí trong năm của
3 chi nhánh thuộc công ty
Quí 1
|
Quí 2
|
Quí 3
|
Quí 4
|
|
Chi nhánh 1
|
$35,698.77
|
$36,148.63
|
$31,258.95
|
$30,864.12
|
Chi nhánh 2
|
$41,289.64
|
$43,278.52
|
$40,928.18
|
$42,818.98
|
Chi nhánh 3
|
$28914.56
|
$27631.52
|
$30596.64
|
$29834.21
|
II. Giải
pháp
Tính tổng
doanh thu toàn năm (total sales)
- Input: doanh thu hàng quí trong năm của 3 chi nhánh (quarterly sales)
- Output: total sales
Xác định tên
method và parameter
Hình 1
Thử chuyển
parameter thành field
Hình 2
Xác định data
type cho field
Do
quarterlySales là tập
hợp doanh thu của 3 chi nhánh, mỗi chi nhánh cung cấp doanh
thu của 4 quí, ta có thể biểu diễn quarterlySales
ở dạng ma trận (matrix)
hay mảng hai chiều (two-dimensional array)
của những số double.
Khác với mảng một chiều (one-dimensional array, gọi tắt
là array), ta cần đặt hai cặp ngoặc vuông vào sau kiểu
double khi khai báo
data type cho quarterlySales.
Hình 3
Xác định tên
class dựa vào field và method
Ta
cần trả lời câu hỏi: Đối tượng nào chứa doanh
thu hàng quí trong năm, rồi từ đó tính được tổng
doanh thu? Đó chính là doanh thu cả năm (yearly sales).
Hình 4
Một
khi xác định được tên class, ta có thể đơn giản hóa
tên method như sau
Hình 5
- Input: số liệu đã cho trong bài
- Output: 419,262.72
Bước
4. Phát triển test class
- Project: TotalSales
- Package: totalsales
4.1.
Phát triển YearlySalesTest
package totalsales;
import org.junit.Test;
import static org.junit.Assert.*;
public class YearlySalesTest {
@Test public void sum() {
assertEquals(419_262.72, new YearlySales(???).sum(), 0.0);
}
}
Ta
thay thế cụm ??? bằng một two-dimensional array với tên là
quarterlySales.
public class YearlySalesTest {
@Test public void sum() {
double[][] quarterlySales = ???;
assertEquals(419_262.72, new YearlySales(quarterlySales).sum(), 0.0);
}
}
Một
two-dimensional array có thể được xem là một
one-dimensional array, trong đó mỗi phần tử của
one-dimensional array lại là một one-dimensional array. Ta
triển khai tiếp
public class YearlySalesTest {
@Test public void sum() {
double[][] quarterlySales = { ???, ???, ??? };
assertEquals(419_262.72, new YearlySales(quarterlySales).sum(), 0.0);
}
}
Mỗi
một cụm ??? ở trên là một one-dimensional array doanh thu
của một chi nhánh, ta cần đặt chúng vào trong một cặp
ngoặc móc khác.
public class YearlySalesTest {
@Test public void sum() {
double[][] quarterlySales = {
{ ??? },
{ ??? },
{ ??? } };
assertEquals(419_262.72, new YearlySales(quarterlySales).sum(), 0.0);
}
}
Đến
đây ta tiến hành thay thế ??? bằng những giá trị cụ
thể.
public class YearlySalesTest {
@Test public void sum() {
double[][] quarterlySales = {
{ 35_698.77, 36_148.63, 31_258.95, 30_864.12 },
{ 41_289.64, 43_278.52, 40_928.18, 42_818.98 },
{ 28_914.56, 27_631.52, 30_596.64, 29_834.21 } };
assertEquals(419_262.72, new YearlySales(quarterlySales).sum(), 0.0);
}
}
4.2.
Phát triển YearlySales
Từ
YearlySalesTest, ta tạo YearlylySales class.
package totalsales;
public class YearlySales {
private final double[][] quarterlySales;
public YearlySales(double[][] quarterlySales) {
this.quarterlySales = quarterlySales;
}
public double sum() {
return ???;
}
}
Như
thường lệ, khi tính tổng của nhiều giá trị, ta sử
dụng một accumulator lấy tên là acc.
public class YearlySales {
private final double[][] quarterlySales;
public YearlySales(double[][] quarterlySales) {
this.quarterlySales = quarterlySales;
}
public double sum() {
double acc = 0.0;
???
return acc;
}
}
Kế
đến, ta dùng vòng lặp for-each để lấy ra từng
phần tử (dòng) trong quarterlySales, tính tổng trên
dòng đó, rồi tích lũy vào acc. Lưu ý rằng mỗi
dòng là một one-dimensional array (double[]) gồm doanh
thu 4 quí của một chi nhánh.
public class YearlySales {
private final double[][] quarterlySales;
public YearlySales(double[][] quarterlySales) {
this.quarterlySales = quarterlySales;
}
public double sum() {
double acc = 0.0;
for (double[] divisionalSales : quarterlySales)
acc += this.sum(divisionalSales);
return acc;
}
private double sum(double[] divisionalSales) {
return ???;
}
}
Để
ý rằng lúc này ta có hai methods sum
trùng tên, song với số parameters khác nhau: method đầu
không có, method sau có một parameter. Kỹ thuật cho
phép một class có nhiều methods trùng tên, nhưng với
parameters khác nhau, gọi là overloading. Overloading
được dịch sang tiếng Việt là nạp chồng hoặc
quá tải, có lẽ quá tải sát nghĩa hơn, nhưng
ta cứ nên gọi là overloading và hiểu ý nghĩa của thuật
ngữ này là được.
Việc
tính tổng một one-dimensional array thì tương tự như Bài
Doanh thu trong tuần.
public class YearlySales {
private final double[][] quarterlySales;
public YearlySales(double[][] quarterlySales) {
this.quarterlySales = quarterlySales;
}
public double sum() {
double acc = 0.0;
for (double[] divisionalSales : quarterlySales)
acc += this.sum(divisionalSales);
return acc;
}
private double sum(double[] divisionalSales) {
double acc = 0.0;
for (double each : divisionalSales)
acc += each;
return acc;
}
}
Nếu
thi hành YearlySalesTest class, ta gặp tín hiệu đỏ
với thông báo lỗi: expected:<419262.72> but
was:<419262.72000000003>. Ta điều chỉnh độ lệch
cho phép giữa hai giá trị về 0.00000000006, hoặc 6 x
10-11. Trong Java, ta viết 6E-11.
4.3.
Loại bỏ code duplication
Như
đã trình bày ở phần trên, thao tác tính tổng trên một
one-dimensional array rất giống với bài Doanh thu trong
tuần. Đây là dạng code duplication diễn ra ở hai
projects khác nhau. Để loại bỏ, ta khai báo sum() là
một static method và đặt vào một class mới, lấy tên là
ArrayUtils. Nhiệm vụ của ArrayUtils là nơi
chứa những thao tác nho nhỏ (utilities) xử lý trên arrays.
Cách tạo ra class chỉ chứa các static methods đã được
trình bày trong bài Tổ chức giải bóng đá.
package util;
public class ArrayUtils {
private ArrayUtils() {
throw new RuntimeException(this.getClass() +
" is a noninstantiable utility class");
}
public static double sum(double[] values) {
double acc = 0.0;
for (double each : values)
acc += each;
return acc;
}
}
Sau
khi đã tạo sum(double[])
bên trong ArrayUtils,
ta tiến hành loại bỏ method này ra khỏi YearlySales
class và điều chỉnh lệnh gọi đến method này cho phù
hợp với tình hình mới.
package totalsales;
import util.ArrayUtils;
public class YearlySales {
private final double[][] quarterlySales;
public YearlySales(double[][] quarterlySales) {
this.quarterlySales = quarterlySales;
}
public double sum() {
double acc = 0.0;
for (double[] divisionalSales : quarterlySales)
acc += ArrayUtils.sum(divisionalSales);
return acc;
}
}
III.
Tổng kết
Bài viết đã trình
bày kỹ thuật lập trình trên cấu trúc dữ liệu
two-dimensional arrays cùng với khái niệm overloading áp dụng
trong trường hợp một class chứa nhiều methods có cùng
tên, nhưng khác parameters. Vấn đề code duplication diễn
tra ở hai projects khác nhau đã được xử lý bằng cách
chuyển phần code bị trùng lặp (duplicated code) vào trong
một utility class riêng biệt.
IV.
Bài tập
Bài
1
Hãy
hoàn thành ArrayUtils
class như được mô tả dưới đây. Nhớ kèm theo test
class.
package util;
public class ArrayUtils {
private ArrayUtils() {
throw new RuntimeException(this.getClass() +
" is a noninstantiable utility class");
}
public static double sum(double[] values) {
double acc = 0.0;
for (double each : values)
acc += each;
return acc;
}
public static double sum(double[][] values) {
return ???;
}
public static double average(double[] values) {
return ???;
}
public static double min(double[] values) {
return ???;
}
public static double max(double[] values) {
return ???;
}
}
Đáp án
ArrayUtilsTest.java
package util;
import org.junit.Test;
import static org.junit.Assert.*;
public class ArrayUtilsTest {
private double[] values = {
1_000.0, 500.0, 3_000.0, 4_000.0, 9_000.0, 6_000.0, 7_000.0 };
@Test public void sum1DArray() {
assertEquals(30_500.0, ArrayUtils.sum(this.values), 0.0);
}
@Test public void sum2DArray() {
double[][] values = {
{ 35_698.77, 36_148.63, 31_258.95, 30_864.12 },
{ 41_289.64, 43_278.52, 40_928.18, 42_818.98 },
{ 28_914.56, 27_631.52, 30_596.64, 29_834.21 } };
assertEquals(419262.72, ArrayUtils.sum(values), 6E-11);
}
@Test public void average() {
assertEquals(4357.14, ArrayUtils.average(this.values), 0.009);
}
@Test public void max() {
assertEquals(9000.0, ArrayUtils.max(this.values), 0.0);
}
@Test public void min() {
assertEquals(500.0, ArrayUtils.min(this.values), 0.0);
}
}
ArrayUtils.java
package util;
public class ArrayUtils {
private ArrayUtils() {
throw new RuntimeException(this.getClass() +
" is a noninstantiable utility class");
}
public static double sum(double[] values) {
double acc = 0.0;
for (double each : values)
acc += each;
return acc;
}
public static double sum(double[][] values) {
double acc = 0.0;
for (double[] row : values)
acc += sum(row);
return acc;
}
public static double average(double[] values) {
return sum(values) / values.length;
}
public static double min(double[] values) {
double currentMin = Double.POSITIVE_INFINITY;
for (double value : values) {
if (value < currentMin)
currentMin = value;
}
return currentMin;
}
public static double max(double[] values) {
double currentMax = Double.NEGATIVE_INFINITY;
for (double value : values) {
if (value > currentMax)
currentMax = value;
}
return currentMax;
}
}
Bài
2
Thường
thì chúng ta không muốn lật lại vấn đề đã diễn ra
trong quá khứ để cải thiện. Hãy chế
ngự tâm
thức lười biếng này bằng cách sửa
đổi code trong Bài
Doanh
thu trong tuần
nhằm tận dụng ArrayUtils
class.
Đáp
án
package weeklysales;
import util.ArrayUtils;
public class WeeklySales {
private double[] figures;
public WeeklySales(double... figures) {
this.figures = figures;
}
public double sum() {
return ArrayUtils.sum(this.figures);
}
public double average() {
return this.sum() / this.figures.length;
}
public double max() {
return ArrayUtils.max(this.figures);
}
public double min() {
return ArrayUtils.min(this.figures);
}
}
V.
Tài liệu tham khảo
- Tony Gaddis (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