Thursday, September 08, 2005

Link (2005-09-08)



Note:
วันนี้เข้าไปวาดรูปเล่นมา 1 รูป เพลินดี แก้เครียดได้หมือนกัน
http://artpad.art.com/?imiapk946w4

Related link from Roti

Tuesday, September 06, 2005

Ruby กับ Design by Concern

ผมได้ทดลองเขียน program เพื่อทำการ plot graph แสดงการใช้งานของ user
โดยใช้ Ruby language เขียน

ตัวโปรแกรมมี Requirement ดังนี้
  • R1 สามารถอ่านข้อมูลจาก file wtmp ใน aix ได้โดยตรง
  • R2 สามารถสรุปข้อมูลนำมา plot เป็น graph แสดงเวลาการทำงานของ user
    ในช่วงวันที่กำหนดได้
  • R3 แสดงช่วงเวลาที่มีจำนวน concurrent user เกินกว่าค่าที่กำหนด


output หน้าตาประมาณนี้



ออกแบบ class diagram ดังนี้



ใน class diagram จะแสดงว่า Requirement ข้อไหน implement
ใน class อะไรบ้าง

ปัญหาที่เรามักเจอในการ design โปรแกรม ก็คือ Requirement หนึ่งๆ
มักจะกระจายตัวอยู่ใน class (เผอิญตัวอย่างนี้เป็นโปรแกรมเล็กๆ ก็เลยไม่ค่อยเห็นประเด็นนี้เท่าไร)
ทำให้เมื่อมีการเปลี่ยนแปลง requirement เกิดขึ้นต้องกระทบกับหลายๆ class
หรือไม่ก็ ถ้ามีการเพิ่ม requirement ใหม่ๆ เข้ามา Requirement
นั้นก็มักจะทำให้เกิดการเปลี่ยนแปลง class หลายๆ class

ตัว Ruby เอง เปิดโอกาสให้เรา implement class แยกกระจายหลายๆ
ที่ได้ ทำให้เราสามารถจัดกลุ่ม file ตาม concern (requirement)
แทนที่จะแยก 1 file 1 class

ในโปรแกรมที่ผมลองทำ ผมทดลองแยก Requirement R3
ออกเป็นเป็นอีก file
ภายใน file นี้ผมก็ extend class Plotter กับ class UserTimeLine
เฉพาะส่วนที่เกี่ย่วข้องกับ Requirement ที่ 3 นี้เท่านั้น



ซ้ายบนเป็น class UserTimeLine ส่วนซ้ายล่างเป็น class Plotter
ทางขวาเป็น file ที่ implement Requirement R3
เส้นสีแดง แสดงว่ามีการ extend class
ส่วนสีน้ำเงิน แสดง dependency ภายในของ Requirement R3


ข้อดีของวิธีนี้ อืมม์ ไม่ค่อยแน่ใจนัก
มันทำให้เรา concentrate บน Requirement มากขึ้น
เห็น class ที่ต้อง implement feature นั้นๆ อยู่เป็นกลุ่มก้อนเดียวกัน
(อาจจะทำให้ง่ายต่อการจัดการ)
เวลาอ่าน class ต่าง ก็อาจจะอ่านง่ายขึ้น เพราะจะเห็นแต่ส่วนที่ตัวเองเกี่ยวข้องเท่านั้น
(อันนี้อาจเป็นข้อเสียก็ได้ เห็นแต่ส่วนตัวเอง ทำให้ลืมนึกไปว่า
อาจจะมีคนอื่นใช้อยู่ก็ได้ ทำให้แก้ไขไม่ระวัง)

Note: extend ในที่นี้ไม่ได้หมายถึง extends แบบที่เราทำใน Java
แต่มีความหมายเป็นการ Merge เสียมากกว่า

Related link from Roti

Monday, September 05, 2005

ตัวอย่างการ extend class ของ Ruby

ช่วง 2 วันนี้เมามันอยู่กับการเขียน Ruby
โดยพยายามจะเขียนโปรแกรมที่ generate Graph แสดงภาพรวมการ
ทำงานของ User ในระบบ

ตอนนี้เริ่มเขียน version ที่ 2 แล้ว หลังจากปล้ำให้ version แรกทำงานได้แล้ว
ใน version ใหม่นี้ สิ่งที่พยายามจะทำ ก็คือการพยายามใช้ feature
ของ Ruby ให้มากขึ้น (แทนที่จะเขียนโดยใช้กรอบความคิดที่เคยชิน
จากการเขียน Java)
feature หนึ่งที่พยายามจะดู หรือหาว่า จะนำมาใช้อย่างไรก็คือ
การ extend class

ตัวอย่างที่เจอแล้วรู้สึกชอบก็คือ ใน module RMagick
(ซึ่งเป็น api ที่ใช้ในการ manipulate image)

Class RVG (Ruby Vector Graphic) ใน RMagic
จะมี constructor ที่รับตัวเลข 2 ตัว เพื่อใช้
ในการกำหนด ขนาดของ image ที่จะสร้างขึ้น

rvg = RVG.new(width,height)

คำถามของคนที่จะใช้ api นี้ก็คือ เราจะ pass ค่าอะไรเข้าไป, และเป็นหน่วยอะไร
เช่นถ้าผมต้องการรูปขนาด 4x5 นิ้ว ผมควรจะใช้ width, height เป็นเท่าไรดี
และถ้ามีหลายๆหน่วย api ควรจะ implement Unit Converter Function
นี้ไว้ที่ไหนดี

RVG เลือกใช้วิธีที่ดูเข้าทีมาก เขาเปิดโอกาสให้เราเขียนได้ดังนี้

rvg1 = RVG.new(4.in, 5.in) # หน่วยเป็นนิ้ว
rvg2 = RVG.new(30.cm, 15.5.cm) # หน่วยเป็นเซนติเมตร

ที่นี้ ruby จะเข้าใจ syntax นี้อย่างไร
ruby จะมองว่า 4.in ก็คือ การเรียกใช้ method ที่ชื่อ in
ของ Object Fixnum (4 ก็คือ Fixnum Object)
ส่วน 15.5.cm ก็คือ การเรียกใช้ method cm ของ
Float Object นั้น

ประเด็นก็คือ method in หรือ cm
มันไม่ได้ build-in มาใน Fixnum หรือ Float ตั้งแต่ต้น
แต่ ruby เปิดโอกาสให้เรา modify class definition ได้
โดยในกรณีนี้
RMagic กำหนด syntax มาให้เราเรียกใช้ดังนี้

RVG::dpi = 72

ถ้ามีการกำหนดค่านี้เมื่อไร RVG จะเข้าไป add method ใน
Fixnum กับ Float Class ให้เราทันที โดยอาศัยข้อมูล dpi ที่กำหนด
เป็นค่าที่ใช้ในการ convert

ถ้าตามไปดูใน source code ของ RVG จะเห็นวิธีที่เขาเขียน

def dpi=(n)
if !defined?(@dpi)
[Float, Fixnum].each do |c|
c.class_eval do
# the default measurement - 1px is 1 pixel
def px
self
end
# inches
def in
self * ::Magick::RVG.dpi
end
# millimeters
def mm
self * ::Magick::RVG.dpi / 25.4
end
# centimeters
def cm
self * ::Magick::RVG.dpi / 2.54
end
.....

จะเห็นว่ามีการ iterate Float class กับ Fixnum class
เพื่อเพิ่ม method เข้าไป (ผ่านทางคำสั่ง class_eval)

ดูแล้วชอบจริงๆ

Related link from Roti