GUIProject
Object storage
เวลาเก็บข้อมูลใน static fields คลาสอื่นจะสามารถเข้ามาเรียกได้ ดังนี้
class Store{
public static String data = "hello world";
}
class Main{
public static void main(String[] args){
System.out.println(Store.data);
}
}
จะสังเกตว่า ไม่ต้อง new Store()
ก่อนก็สามารถเรียกข้อมูลใน data
ได้เพราะเป็น static
โปรแกรมนี้ใช้สมบัตินี้ในการเก็บข้อมูล คือ
public class Store{
private static Store instance = new Store();
public static Store getInstance(){
return instance;
}
}
เมธอด getInstance
ชื่ออาจจะฟังดูยาก แต่จริงๆ ก็คือ getter ธรรมดา
เมื่อคลาสอื่นๆ อยากได้ Store ก็ Store.getInstance()
ก็จะได้ store เดียวกันตลอด ไม่ว่า class ไหนจะเรียก ถ้าใช้ new ก็จะได้ store คนละอัน ส่งผลให้การข้อมูลแต่ละหน้าไม่ตรงกัน
วิธีเก็บข้อมูลแบบนี้มีชื่อเรียกว่า Singleton pattern (ชื่อไม่สำคัญ จำวิธีทำงานก็พอ) มาจากที่ว่า instance นั้นโปรแกรมนึงมีแค่อันเดียว (single) คือที่ return จาก getInstance()
ไม่ว่าคลาสไหนเรียกก็จะได้ instance ตัวเดียวกันเสมอ
Shopping Cart ก็ใช้วิธีนี้เช่นกันในการเก็บข้อมูล ส่วน Bills นั้นใช้ ArrayList
ที่เป็น static field (ตัวแปร Bill.bills
เป็น ArrayList
)
Serializable
ทีนี้เวลาเซฟข้อมูล กลุ่มอื่นๆ จะใช้วิธีเอาข้อมูลแต่ละแถวโยนลงไฟล์ คั่นด้วย space แล้วใช้ Scanner อ่าน แต่เพื่อความรวดเร็วในการพัฒนาเราเลยใช้ระบบ Serializable ซึ่งมีอยู่ใน Java บันทึกแทน
ระบบนี้จะทำให้คลาสอะไรก็ได้ที่ implements Serializable
สามารถบันทึกลงไฟล์ได้ โค้ดไปดูในอ่านเพิ่มเติมเอา
ทีนี้เวลาโหลดข้อมูลกลับมา ก็จะได้ object ตัวเดิมกลับมาเลย เสมือนไม่มีการปิดโปรแกรมเกิดขึ้น ตรงนี้ทำให้เราไม่ต้องเสียเวลาใช้ scanner อ่านข้อมูลแล้วแปลงเป็น object เอง
ในโปรแกรมนี้ Store
, Stuff
, Customer
ต่างเก็บข้อมูลใน Serializable
หมด และเนื่องจาก array และ ArrayList
ก็เป็น Serializable
ก็เลยเอา Bill.bills
โยนลงไฟล์ได้ด้วย
เพิ่มเติม: http://pagist.info/6f66495127be09d45180
HashMap
ทีนี้มีไฟล์ๆ เดียว จะเก็บข้อมูลยังไงตั้งสี่ห้าอย่าง (store, stuff, customer, bills, cloud key) วิธีการก็คือเก็บข้อมูลเป็นตาราง ซึ่ง java มีระบบตาราง 2 column ให้ชื่อว่า HashMap ซึ่งจะเหมือน dict ใน Python
สมบัติของ HashMap คือ มี key กับ value ถ้ารู้ key สามารถหา value เดิมได้เสมอ
HashMap<String, String> hm = new HashMap<String, String>(); // ระบุว่า key เป็น String, value เป็น String
hm.add("key", "value");
System.out.println(hm.get("key")); // แสดงคำว่า value
โปรแกรมนี้ใช้วิธีโยนข้อมูลทั้งหมดไปใส่ใน hashmap ตัวเดียว แล้วเอา hashmap นั้นเขียนลงไฟล์ผ่านระบบ serializable ทั้งหมดนี้คือการทำงานของคลาส storage.FileStorage
แต่คลาสนี้นั้นตัว instance กลับไปเก็บที่คลาส object.File
(เหตุผลที่แยกกันเก็บคือจะล้อการทำงานของระบบ cloud)
เพิ่มเติมในสไลด์
Facade
ต่อมาเราพบว่า เวลาจะเซฟข้อมูลเนี่ย ระบบที่เก็บข้อมูลใน HashMap มันมีตั้งหลายอย่าง จะเขียนแบบนี้หรอ?
Store.save();
Customer.save();
Bills.save();
แล้วจุดที่เซฟได้มีเยอะมาก ทั้งหน้า take order, restock, clear profit ถ้าต่อไปเพิ่มการเก็บข้อมูลอีกคลาสนึงแล้วลืมใส่ให้ครบทุกหน้าก็ซวยล่ะสิ
วิธีการคือทำคลาสๆ นึง ซึ่งมีเมธอดที่เรียกเมธอดพวกนี้อีกที
public class Persistent{
public static void save(){
Customer.save();
Bill.save();
Store.save();
File.getInstance().save();
}
}
(ในโค้ดคือ storage.Persistent
)
เวลาจะเซฟก็แทนที่จะต้องก๊อปแปะการเซฟ เราก็แค่เรียก Persistent.save();
(ไม่ต้อง new เพราะเป็น static)
วิธีนี้มีชื่อเรียกว่า Facade pattern มาจาก Facade ที่แปลว่าผนังด้านหน้า คือทำให้เมธอดที่เราเรียกมันดูเรียบร้อยน่ารักมาก โดยไม่ต้องสนใจการทำงานด้านหลัง
Adapter
ทีนี้ในหน้าที่เราทำจะเห็นว่ามันใช้ JTable
ซึ่งตัว JTable
การใช้มันซับซ้อนนิดนึง คือมันไม่ทราบว่าตารางที่เราอยากได้มีกี่แถวมีกี่ช่อง ในช่องมีข้อมูลอะไรบ้าง มันก็เลยใช้ระบบ adapter มาแปลงโดยกำหนดให้ extend จาก class AbstractTableModel
ของ Java
ใน class ของเราจะมีเมธอด getColumnCount
getRowCount
getColumnName
มีหน้าที่ก็ตามชื่อเลย และเมธอด getValueAt(int row, int col)
ก็คือให้แถวนี้คอลัมน์นี้มา แล้ว return ไปว่าต้องการแสดงค่าอะไรในช่องนั้นๆ
ในโค้ดอยู่ที่ไฟล์ view.BillUI
และ object.ShoppingCart
และเนื่องจากว่า class นี้สามารถใช้ดึงข้อมูลทั้งหมดในตารางออกมาได้อยู่แล้ว เราเลยเอาสมบัตินี้ไปใช้ในการทำระบบ search ด้วย ก็คือเวลา search
- for ไปทีละแถวจาก
getRowCount
- for ไปทีละคอลัมน์จาก
getColumnCount
- เรียกค่าในช่องนั้นจาก
getValueAt
- เช็คว่าค่านั้นมีส่วนใดส่วนหนึ่งตรงตามคำค้นที่ผู้ใช้กรอกมาหรือไม่
- ถ้าใช่ก็ให้เน้นแถวนั้น
(โค้ดอยู่ใน actionPerformed
ของปุ่ม Search ใน view.BillUI
)
วิธีนี้มีชื่อเรียกว่า Adapter pattern คือ ทำให้คลาสหนึ่งมีเมธอดตรงตาม interface ได้ โดยเขียนอีกคลาสหนึ่งมาครอบไว้อีกที (ชื่อ pattern ไม่ต้องจำก็ได้ จำการทำงานของมันก็พอว่าคลาสไหนทำงานยังไง)
Reflection
ความเห็นส่วนตัวถ้าว่ามันเยอะจำไม่ได้ reflection ในโค้ดนี้ใช้อยู่สองบรรทัด ข้ามไปเลยไม่ต้องจำก็ได้
อันนี้ต้องขอโทษด้วยว่า design โปรแกรมในส่วนของเราก็รีบจนมันห่วย คือ view.BillUI
มันถูก extend ไปยังหลายๆ หน้า แล้วเราต้องการเพิ่มปุ่ม export เฉพาะหน้า Bill ซึ่งวิธีที่ดีที่สุดคือเขียนคลาสที่หน้า Bill จะ extend มา แต่เรากลัวจะไม่ทันเลยใช้วิธีนี้แทน
ถ้าดูในเมธอด initComponents
หรือ tableDblClick
จะเห็น this.getClass().getName() == BillUI.class.getName()
มันคืออะไร
มันคือการเช็คว่าคลาสนี้คือ BillUI
หรือไม่นั่นแหละ แต่มันใช้ instanceof
เช็คไม่ได้ เพราะคลาสลูกเช็คด้วย instanceof
ก็ถือว่าเป็นคลาสแม่ด้วย เลยต้องใช้วิธีนี้ (อาจจะมีวิธีดีกว่า นึกไม่ทัน)
เมธอด .getClass().getName()
จะ return ชื่อคลาสเต็มๆ ออกมาเช่น view.BillUI
แล้วเราก็ไปเทียบกับ BillUI ของจริงว่ามีชื่อว่าอะไร ถ้าตรงกันก็โอเคใช่
CSV Export
ขึ้นส่วนที่เข้าใจง่ายดีกว่า ในส่วนนี้คือปุ่ม CSV Export ที่อาจจะตะลึงว่าทำไมมันเปิดกับ Excel ได้ฟะ
ถ้าศึกษาฟอร์แมต CSV จะพบว่า excel เปิดไฟล์นามสกุล .csv ได้ โดยรูปแบบของไฟล์เป็นดังนี้
col1,col2,col3
val1,val2,val3
val1,val2,val3
พูดง่ายๆ คือตารางที่เอาคอมม่าคั่นนั่นแหละ (csv = comma-seperated values) ฉะนั้นแล้วเราก็เลยใช้ FileWriter
(ในเล็คเชอร์บทเดียวกับ ArrayList กลุ่มอื่นๆ ก็ใช้คลาสนี้เซฟข้อมูล)
(โค้ดอยู่ที่ view.BillUI
คลาส CSVExportHandler
อาจจะดูหรู จริงๆ ก็คือ ActionListener
ของปุ่ม export นั่นแหละ)
ถ้าเปิดไฟล์ export.csv
ดูก็จะเห็นรูปแบบประมาณนี้
No,Date,Customer Name, Customer Type,Customer Email,Discount %,#MacBook Air 11",Price/Unit,Total Price,#MacBook Air 13",Price/Unit,Total Price,#MacBook Pro 13",Price/Unit,Total Price,#MacBook Pro Retina 13",Price/Unit,Total Price,#MacBook Pro Retina 15",Price/Unit,Total Price,#Mac Mini,Price/Unit,Total Price,#iMac 21.5",Price/Unit,Total Price,#Mac Pro,Price/Unit,Total Price,#Apple TV,Price/Unit,Total Price,#iPad 2,Price/Unit,Total Price,#iPad Air,Price/Unit,Total Price,#iPhone 4s,Price/Unit,Total Price,#iPhone 5c,Price/Unit,Total Price,#iPhone 5s,Price/Unit,Total Price,#iPod Classic,Price/Unit,Total Price,#iPod Shuffle,Price/Unit,Total Price,#iPod Nano,Price/Unit,Total Price,#iPod Touch,Price/Unit,Total Price,Discount,Bill total
1,Sun Dec 01 23:36:58 ICT 2013,user,1,user@example.com,10.00,0,31900.00,0.00,0,34900.00,0.00,0,37900.00,0.00,0,43000.00,0.00,1,66900.00,1.00,0,19900.00,0.00,0,44900.00,0.00,0,99000.00,0.00,0,3800.00,0.00,0,13500.00,0.00,0,16900.00,0.00,0,14900.00,0.00,0,19900.00,0.00,0,23900.00,0.00,0,8900.00,0.00,0,2200.00,0.00,0,5500.00,0.00,0,9900.00,0.00,6690.0,66900.0
ลองนั่งนับคอมม่าแถวหนึ่งกับแถวสองดูก็น่าจะพอเดาได้ว่าเลขเยอะๆ แต่ละตัวคืออะไรบ้าง หรือถ้างงก็เอา Excel เปิดเถอะ
สุดท้ายเพื่อความอลังนิดหน่อย เลยให้ Java เปิด excel ให้ด้วย โค้ดก็ไม่ยาก
Desktop.getDesktop().open(file);
มันก็จะเปิดไฟล์ที่เขียนเสร็จแล้วใน Excel
เราคิดว่าถ้าบอกกลุ่มอื่นว่า excel มันอ่านแบบนี้ได้นี่ใครๆ ก็เขียนได้เลยนะ มันอยู่ในเรื่องที่สอนแล้ว 100% ที่เค้างงกันก็เพราะไม่รู้จักไฟล์ csv เท่านั้นเอง
โค้ด print มีอยู่สองส่วน คือในส่วน GUI
Bill bill = Bill.bills.get(row);
PrinterJob job = PrinterJob.getPrinterJob();
job.setPrintable(bill.print());
if(job.printDialog()){
try{
job.print();
}catch(PrinterException e){
}
}
(view.BillUI
เมธอด tableDblClick
)
ในส่วนนี้ก็คือสร้างคลาส PrinterJob
มา (อยู่ใน Java) แล้วกำหนดหน้าที่จะพิมพ์ เสร็จแล้วก็ขึ้นหน้าต่างพิมพ์ปกติ ถ้าผู้ใช้ตกลงพิมพ์ก็สั่งพิมพ์ ไม่พิมพ์ก็เลิก
สำหรับไฟล์ที่เป็นเนื้อหาในงานพิมพ์ก็อยู่ที่คลาส print.Bill
วิธีการคือสร้างคลาสมาที่ implements Printable
(อันนี้ Java มีให้) แล้วมันจะมีเมธอดบังคับอันนึงมา ในเมธอดนี้จะให้ Graphics
มา ที่เหลือก็สามารถใช้คำสั่งวาดใน Graphics
ได้ เหมือนกับการบ้าน Histogram ฉะนั้นโค้ดอาจจะดูยาวและอลังหน่อยๆ ก็ไม่ต้องตกใจเพราะการบ้าน Histogram ก็น่าจะเป็นแบบนั้นกัน