Displaying articles with tag aspect

AOP :: ActionScript 2.0 on As2lib

Posted by PunNeng, Wed Apr 18 21:58:00 UTC 2007

หลังจากลองบน Ruby ไปแล้ว คราวนี้มาถึงตาของ ActionScript บ้าง(ให้ตายเถอะ นานๆ จะได้มาเล่นอะไรบน Flash คิดถึงจริงๆ)

ฝั่ง Ruby ใช้ตัวช่วยคือ AspectR
ฝั่ง ActionScript 2.0 ใช้ตัวช่วยคือ As2lib

เล็กน้อย สำหรับ As2lib
As2lib เป็น open source framework ตัวนึงที่ถูกออกแบบมาให้ทำงานกับ Flash MX 2004+(หนักไปทาง ActionScript 2.0) โดยมันจะไปช่วยในเรื่อง event handling, error handling, logging, overloading and reflections และยังบรรจุ testing framework,input/output(io) framework, aspect-oriented programming (AOP) framework และ regular expression (RegExp) framework(เป็นอีกหนึ่งอย่างที่ผมจะลอง)

เริ่มต้นด้วยการ download As2lib มาติดตั้งกันก่อน

จากนั้น extract เอา src folder มาใช้ โดยการ copy ไปวางไว้ที่ path เดียวกับ file.fla ที่เราจะใช้งาน แล้วก็สร้าง file.fla มาเลย แล้วไป set classpath ก่อน ไปที่ File > Publish Settings เลือก tab ที่เป็น Flash กดปุ่ม Settings (ข้างๆ ActionScript version dropdown) จากนั้นปุ่ม + บนหน้าต่างที่เด้งขึ้นมา แล้วเพิ่ม ./src เข้าไปแล้วกด ok แล้ว save file นี้ไว้บน path เดียวกับ src folder

มาลง code กันบ้าง เริ่มต้นจาก Product class

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
class Product{
  private var price:Number;
  // private var logger:Logger;
  public function Product(){
    price = 0;
    // logger = new Logger();
  }
  public function setPrice(p:Number):Number{
    // logger.writeLog(price + " is changed to " + p);
    price = p;
    return price;
  }
  public function getPrice():Number{
    return price;
  }
}

save เป็น Product.as ไว้ที่ path เดียวกับ file.fla ของเรา มาดู Logger class บ้าง

  1
  2
  3
  4
  5
  6
  7
  8
  9
class Logger{
  function Logger(){
    // open log file
  }
  public function writeLog(msg:String){
    // write msg to log file
    trace(msg);
  }
}

save เป็น Logger.as ไว้ที่ path เดียวกับ file.fla ของเรา จริงๆ แล้ว flash เขียนต่อ file ไม่ได้ แต่มันเป็นแค่ตัวอย่างครับ จะได้เหมือนกับของ Ruby

บน Product class ถ้าเอา comment ออก ก็จะทำงานได้ตามปกติ โดยไม่ต้องอาศัย aspect

ทีนี้ จะมาลองสร้าง aspect กันบ้าง

  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
import org.as2lib.aop.Aspect;
import org.as2lib.aop.aspect.AbstractAspect;
import org.as2lib.aop.advice.AbstractAdvice;
import org.as2lib.aop.JoinPoint;

class LoggingAspect extends AbstractAspect implements Aspect {
  private var logger:Logger;
  function LoggingAspect(){
    logger = new Logger();
    addAdvice(AbstractAdvice.BEFORE, getBeforeOperationsPointcut(), beforeOperationsAdvice);
    // addAdvice(AbstractAdvice.AROUND, getOperationsPointcut(), aroundOperationsAdvice);
  }
  private function getOperationsPointcut(Void):String {
    return "execution(Product.setPrice())";
    // or you can use execution(Product.setPrice()) || execution(Product.getPrice()) instead, 
    // an advice will be operated when setPrice or getPrice are invoked.
  }

  private function beforeOperationsAdvice(joinPoint:JoinPoint, args:FunctionArguments) {

    //trace(joinPoint.getInfo().getDeclaringType().getName() + "." + joinPoint.getInfo().getName() + "(" + args + ")");
    //trace("Before: " + Product(joinPoint.getThis()).getPrice());
    logger.writeLog(Product(joinPoint.getThis()).getPrice() + " is changed to " + args);
  }

  private function aroundOperationsAdvice(joinPoint:JoinPoint, args:FunctionArguments) {
    logger.writeLog(Product(joinPoint.getThis()).getPrice() + " is changed to " + args);
    var result = joinPoint.proceed(args);
    logger.writeLog(tick() + " " + joinPoint.getInfo().getDeclaringType().getName() + "#" + joinPoint.getInfo().getName() + ": returned " + result + " and exited");
    return result;
  }
  private function tick():String{
    return new Date().getFullYear() + "-" + new Date().getMonth() + "-" + new Date().getDate();
  }
}

ที่ comment ใน beforeOperationsAdvice ไว้ จะเป็นวิธีการเอาข้อมูลออกมาใช้งาน ไม่ว่าจะเป็น classname, method name หรือว่า arguments ต่างๆ ที่รับเข้ามา เริ่มต้นที่การ import class ต่างๆ เข้ามาใช้งาน ใน constructor

  1
addAdvice(AbstractAdvice.BEFORE, getOperationsPointcut(), beforeOperationsAdvice);

อันนี้เป็นการ set pointcut โดยการใส่ advice และ join point เข้าไป ที่เหลือก็เป็นการประกาศการทำงาน

จากนั้นมาดูการ weave กันบ้าง

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
import org.as2lib.aop.Aspect;
import org.as2lib.aop.Weaver;
import org.as2lib.aop.weaver.SimpleWeaver;

var logging_aspect = new LoggingAspect();
var weaver:SimpleWeaver = new SimpleWeaver();
weaver.addAspect(logging_aspect, [Product]);
weaver.weave();

var product:Product = new Product();
product.setPrice(7);
product.setPrice(5);

code ตรงนี้ ก็ import library ต่างๆ ที่จำเป็น แล้วมาประกาศการ weave (ถ้ามีหลาย class ให้เพิ่มตรง addAspect เอา เป็น [Product, AnyClass] เป็นต้น) แล้วค่อยมาเป็นการประกาศการทำงานบน class ของเรา ซึ่งจะคล้ายๆ กับของ Ruby แต่จริงๆ ผมชอบแบบ AspectJ มากกว่า ที่ไป weave กันตอน compile เอา(บน Ruby เด๋วมีแบบนี้เหมือนกัน ใจเย็นพี่น้อง)

มาดูผลที่ได้

มาลอง Around บ้าง ก็สลับ comment เป็น

  1
  2
// addAdvice(AbstractAdvice.BEFORE, getBeforeOperationsPointcut(), beforeOperationsAdvice);
addAdvice(AbstractAdvice.AROUND, getOperationsPointcut(), aroundOperationsAdvice);

มาดูผลที่ได้

ข้อมูลจาก www.simonwacker.com (as2lib version ใหม่มันมีปัญหา ถ้าลองตามใน site ของ simon wacker จะรันไม่ออก ลองตามของไอ่เหน่งดีกว่า modify ให้แล้ว)

0 comments | Filed Under: General | Tags: aspect

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

0 comments | Filed Under: Ruby | Tags: aspect

AOP :: Aspect-Oriented Programming

Posted by PunNeng, Thu Apr 12 03:30:00 UTC 2007

Aspect-Oriented Programming หรือ การโปรแกรมเชิงลักษณะ(พี่ใหม่เรียกว่างั้น) อธิบายยากจริงวุ้ย เอากรณีตัวอย่างเลยละกัน สุดฮิตเรื่อง logging

เริ่มต้นจาก อยากจะจัดการกับ product ของตัวเอง โดยมีความต้องการให้มันทำหน้าที่(concern) ดังนี้

  1. เก็บราคาขายของ product ต่างๆ
  2. เมื่อมีการเปลี่ยนแปลงราคา จะต้องถูกบันทึกข้อมูล

ก็สร้าง product กันก่อน

  1
  2
  3
  4
  5
  6
  7
  8
  9
class Product
  attr_reader :price  
  def initialize
    price = 0.0
  end
  def setPrice(p)
    price = p
  end
end

concern ข้อที่หนึ่ง ก็จัดการไปได้ละ (ในความเป็นจริงจะมี method ที่ีจำเป็นมากกว่านี้) มาดูข้อสองบ้าง จะต้องบันทึกข้อมูล เอาเป็นเขียนใส่ log file ละกัน(สุดฮิต) เวลามันเปลี่ยนแปลง ก็ให้มันบันทึกทีนึง

  1
  2
  3
  4
  5
  6
  7
  8
class Logger
  def initialize
    # open log file
  end
  def writeLog(msg)
    # write msg to log file
  end
end

ทีนี้ก็เรียก writeLog ใน Product class

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
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

มาถึงตรงนี้ มันก็ยังไม่มีปัญหาหรอกครับ แต่มันดูผิดแนวคิดจาก Encapsulation ไปหน่อย จาก Managing Software Defects in an Object-Oriented Environment ตรงส่วนของ encapsulation เขาบอกประมาณว่า

ใน encapsulation นั้น ก้อน object จะถูกประกาศเป็นชุดหรือกลุ่มของ concern ที่ีมีความสัมพันธ์กัน

หรืออีกนัยหนึ่งคือ Object น้ันๆ ก็ควรจะเกี่ยวกับ Object น้ันๆ(เป็น modular) อย่าเอาอย่างอื่นมาเกี่ยว(เป็นไปได้ยาก) แต่ดู concern ตัวที่สองแล้ว มันไม่ค่อยจะสัมพันธ์กันเท่าไหร่ class นี้ จึงเกิดการ crosscut ขึ้น ซึ่งเกิดจากไอ้ concern ตัวที่สองนี่แหละ ไอ้นี่แหละ เรียกว่า crosscutting concern

crosscutting concern หมายความว่า ลักษณะหนึ่งๆ ที่ไปขวาง ไปกระทบ ไปกวนประสาท concern หลัก ซึ่งในที่นี้ ก็น่าจะเกี่ยวกับ product เท่านั้น

เมื่อมี concern เพิ่มมาเรื่อยๆ ทั้งที่สัมพันธ์และไม่สัมพันธ์กับ concern หลัก จะทำให้ code เรามันกระจัดกระจาย(Code Scattering) และยุ่งเหยิง(Tangled code) เพิ่มนั่น ปะนี่ เอาไอ้นั่นเข้า เอาไอ้นี่ออก เพื่อทำให้ concern มันสมบูรณ์ คนออกแบบกับโปรแกรมเมอร์มันก็ตายสิครับ =='

โอ้ววว กิจการขายดิบขายดี จนต้องขายของอย่างอื่นอีก ก็ต้องขอขอบคุณ OO ละครับ ที่มันกลไก Inheritance ไว้ให้ใช้ ก็ทำการ extend Product class ไปเลย
ผมเล็งเห็นว่าการบันทึกข้อมูลต่างๆ มันสำคัญ เพราะฉะนั้น ใน product ชิ้นใหม่ ผมจะเพิ่ม concern ไปอีก คือ จะเพิ่มการบันทึกเพิ่มมากขึ้นในส่วนที่จำเป็น และจะมี method อย่างอื่นเสริมเข้ามาด่้วย แต่ไม่เกี่ยวกับ Product class ตัวแรกนะ
ปัญหามาแล้ว มองกันไกลๆ code มันจะไม่น้อยอย่างนี้ มันจะอิรุงตุงนัง ยุ่งเหยิงไปหมด เพียงแค่เติมเต็มเจ้า concern ที่เพิ่มเติมเข้ามาเท่าน้ัน ตัว AOP นี้แล จะมาช่วยลดความยุ่งเหยิงพวกนี้ออกไป

จัดการมันใหม่(refactor) ดีไหม ??

มาดูว่า AOP จะช่วยแก้ไขปัญหานี้ได้ยังไง

ถ้าใช้ AOP แล้ว เราจะต้องแยกไอ้ concern ต่างๆ นานา ออกมาจัดการได้ ตามความเหมาะสมก่อน แล้วค่อยเตรียมกลไกการจัดการ concern ที่มันเกิดการ crosscut

AOP ต้องใช้ tools ช่วย โดยมีสองส่วนหลักๆ คือ

  • ภาษาที่ใช้เขียน code ปกติ เรียกว่า component language
  • ภาษาที่ใช้เขียน code ฝั่ง concern เพื่อเอาไปต่อกับ component language จะเรียกว่า aspect language
ซึ่งผมเลือก Ruby และ AspectR เป็นตัวช่วย

หลังจากได้ tools มาแล้ว จะทำอะไรกับมันได้บ้าง1 เราต้องมี

  • Join point คือ จุดเชื่อม เป็นจุดที่เราจะระบุว่าจะให้ aspect ของเราทำงานที่ไหนบน source code ซึ่งเราจะระบุได้ ณ จุดที่เห็นกันจะๆ เช่น class, method หรือ exception เป็นต้น
  • Pointcut คือ จุดตัด เป็นที่ที่เราจะระบุ join point ว่าจะให้ join point อันไหน ทำงานอะไร(จัดการกับ concern อะไร) ก็ตรงนี้แล
  • Advice คือ การลำดับเหตุการณ์ เป็นตัวบอกว่าจะให้ทำก่อน(before) หรือหลัง(after)การเรียก pointcut ซึ่งใน AspectJ มีหลายรูปแบบ เช่น before, after หรือ around เป็นต้น
  • Inner-Type Declaration คือ การประกาศในก้อน aspect ของเรา อาจจะเป็น method หรือตัวแปร ที่ไว้ใช้ในการทำงานที่เราระบุไว้กับ pointcut

คราวหน้าผมจะมา implement ให้ดู

สรุปเล็กน้อย
AOP เป็น concept ของการเขียนโปรแกรมอีกอย่างหนึ่ง ถูกคิดค้นโดย Gregor Kiczales และทีมของเขาที่ Xerox PARC มีจุดประสงค์เพื่อแยก concern ออกมา โดยเฉพาะสิ่งที่เรียกว่า crosscutting concern ให้ตัว class ที่เราใช้งานมันมีลักษณะเป็น module มากขึ้น

AOP จะไม่มาแทนที่ OOP แต่มันเป็นส่วนเสริมที่ทำให้ OOP มีประสิทธิภาพมากยิ่งขึ้น

1ข้ออ้างอิงมาจาก AspectJ ก่อนละกัน เพราะที่ผมใช้ จะใช้ AspectR มันค่อนข้างจะเหมือนกัน

ข้อมูลจาก
http://en.wikipedia.org/wiki/Cross-cutting_concern http://en.wikipedia.org/wiki/Aspect-oriented_programming http://en.wikipedia.org/wiki/Cross-cutting_concern http://en.wikipedia.org/wiki/Aspectj ฯลฯ มันเยอะครับ 4 เว็บหลัก แล้วตาม link ใน page ไปเรื่อยๆ ละกันนะครับ

1 comment | Filed Under: Ruby | Tags: aspect

codegent: we're hiring