AOP :: Ruby on AspectR
Posted by PunNeng, Sun Apr 15 20:16:00 UTC 2007
มาลอง implement ต่อจากคราวที่แล้วกัน โดยผมทดลองบน Ubuntu
เริ่มต้นด้วย download AspectR มาติดตั้งกันก่อน
wget http://rubyforge.org/frs/download.php/18623/aspectr-0-3-7.tar.gz tar xvf aspectr-0-3-7.tar.gz cd aspectr-0-3-7 sudo ruby install.rb
จากนั้นสร้าง file มาตัวนึงก่อน ผมจะตั้งชื่อว่า logging_test.rb แล้วก็เพิ่ม code ดังนี้
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | require 'aspectr' include AspectR class Product attr_reader :price def initialize @price = 0.0 # logger = Logger.new end def setPrice(p) # @logger.writeLog("#{price} is changed to #{p}") @price = p end end class Logger def initialize # open log file end def writeLog(msg) # write msg to log file puts msg end end class Logging < Aspect def initialize @logger = Logger.new end # def tick; "#{Time.now.strftime('%Y-%m-%d %X')}"; end def log_enter(method, object, exitstatus, *args) @logger.writeLog("#{object.price} is changed to #{args.first}") end # def log_exit(method, object, exitstatus, *args) # @logger.writeLog("#{tick} #{self.class}##{method}: returned #{exitstatus} and exited") # end end logging = Logging.new logging.wrap(Product, :log_enter, nil, /set/) # logging.wrap(Product, nil,:log_exit, /set/) # logging.wrap(Product, :log_enter, :log_exit, /set/) # advice = around product = Product.new.setPrice(13) |
หลังจาก require และ include มาแล้ว ก็ประกาศเหมือนเดิม เพียงแต่เอาส่วนของ logger ออก แล้วเพิ่มส่วนของ aspect(Advice) เข้าไป
1 2 3 4 5 6 7 8 9 | class Logging < Aspect def initialize @logger = Logger.new end def log_enter(method, object, exitstatus, *args) @logger.writeLog("#{object.price} is changed to #{args.first}") end end |
parameters ที่บังคับมีอยู่สี่ตัว คือ
- method ที่ถูกระบุเป็น join point
- object ที่เรียกใช้ method ที่ถูกระบุเป็น join point
- exitstatus เป็นค่าที่ method ที่ถูกระบุเป็น join point ทำการ return ออกมา
- *args เป็น arguments ที่ส่งเข้ามาใน method ที่ถูกระบุเป็น join point
มาดูตอนเรียกใช้บ้าง(Weave)
1 2 | logging = Logging.new logging.wrap(Product, :log_enter, nil, /set/) |
ประกาศ object ที่เป็น aspect ก่อน แล้วทำการสร้าง pointcut ด้วยการห่อ(wrap) join point, advice เข้าไป ต้องใส่ Class , before, after, regular expression ตามลำดับ โดยวิธีการเลือก join point มันจะทำการเลือกโดยใช้ regular expression เช่นในกรณีนี้ จะเรียกใช้ log_enter ก็ต่อเมื่อ join point เข้าเงื่อนไขของ reg exp ที่ใส่ลงไป ในี้ที่จะเป็น method ทุกตัว ที่มี 'set' อยู่ในชื่อของ method ต่างจาก AspectJ เพราะมันจะใช้อีกรูปแบบนึง เช่น
pointcut set() : execution(* set*(..) ) && this(Point);
ต้องเล่ารูปแบบของ AspectJ ก่อน บนนี้ มันจะแยก code ไว้สองส่วน คือ class ปกติ กับ aspect แต่ตอน compile มันจะใช้การผสาน(Weave) รวมก้อน object และ และก้อน aspect เข้าด้วยกัน เวลาเรียกใช้งาน ก็เรียกใช้งานตามปกติ ไม่ต้องมาทำการประกาศแบบ AspectR เพราะมันถูกประกาศไปแล้วในก้อน aspect
มาดูผลลัพธ์

ในส่วนของ advice ที่เป็น after อาจจะทำการเพิ่ม(ผม comment ไว้)ได้ดังนี้
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Logging < Aspect def initialize @logger = Logger.new end def tick; "#{Time.now.strftime('%Y-%m-%d %X')}"; end def log_enter(method, object, exitstatus, *args) @logger.writeLog("#{object.price} is changed to #{args.first}") end def log_exit(method, object, exitstatus, *args) @logger.writeLog("#{tick} #{self.class}##{method}: returned #{exitstatus} and exited") end end |
โดยในนี้จะมี Inner-Type Declaration อยู่ด้วย คือ tick method ตอน wrap เราอาจจะใส่ดังนี้
1 2 3 | # logging.wrap(Product, nil,:log_exit, /set/) # or logging.wrap(Product, :log_enter, :log_exit, /set/) # advice = around |
มาดูผลลัพธ์
ปล. เห็นโครงของ advice บน Ruby on Rails คร่าวๆ แฮะ คือ before_filter, after_filter และ around_filter