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

Filed Under: Ruby | Tags: aop aspect ruby

Comments

Have your say

A name is required. You may use HTML in your comments.




codegent: we're hiring