« Back to Index

AOP (Aspect-Oriented Programming)

View original Gist on GitHub

AOP.md

What is AOP?

Aspect Oriented Programming is a means to change the behaviour of – or add behaviour to – methods and functions (including constructors) non-invasively. The added behaviour is called “advice” and can be added before, after, or around the function it advises.

This description is similar to the Extract Surrounding refactoring method. The difference is in the direction of the change. It seems AOP is more focused at modifying existing behaviour non-invasively; where as the Extract Surrounding Method actually changes the source code to allow this type of behavioural modification.

Libraries

JavaScript.js

// Variation 1 (AOP format -> modifies behaviour without changing the `foo` method code)

var obj = {
    foo: function(a, b, c) {
        console.log('foo', a, b, c);
    }
};

var originalMethod = obj.foo;

originalMethod(1, 2, 3) // => foo, a, b, c

obj.foo = function(a, b, c) {
    console.log('before');
    originalMethod.apply(this, arguments)
    console.log('after');
};

obj.foo(1, 2, 3) // => before; foo, a, b, c; after

// Variation 2 (Same as the first but abstracted into reusable helper functions)

function before(f, advice) {
    return function () {
        advice.apply(this, arguments);
        return f.apply(this, arguments);
    };
}
function after(f, advice) {
    return function () {
        var result = f.apply(this, arguments);
        advice.call(this, result);
        return result;
    };
}

var obj = {
    foo: function(a, b, c) {
        console.log('foo', a, b, c);
    }
};

obj.foo = before(obj.foo, function(){
    console.log('Before!!');
});

obj.foo = after(obj.foo, function(){
    console.log('After!!');
});

obj.foo(1, 2, 3) // => Before!!; foo, a, b, c; After!!

// Variation 3 ("Extract Surrounding" format -> not AOP as it modifies the source `foo` method)

var obj = {
    foo: function(before, after) {
        if (before) before();
        console.log('foo');
        if (after) after();
    }
};

function before(){
    console.log('before');
}

function after(){
    console.log('after');
}

object.foo(before, after);

// Different example of the second variation

function logsArguments (fn) {
    return function () {
      console.log.apply(this, arguments);
      return fn.apply(this, arguments)
    }
}

function sum (a, b) {
    return a + b;
}

var logsSum = logsArguments(sum);

console.log(
    logsSum(1, 3)
);

Ruby Consumer.rb

module AOP
  def around(fn_name)
    old_method = instance_method(fn_name)
    define_method(fn_name) do |*args|
      yield :before, args if block_given?
      old_method.bind(self).call *args
      yield :after, args if block_given?
    end
  end
end
 
class Foo
  extend AOP
 
  def process(msg)
    puts msg
  end
end
 
Foo.around('process') do |state, args|
  if state == :before
    puts 'Blah 1'
  elsif state == :after
    puts 'Blah 2'
  end
end
 
Foo.new.process('hai')

Ruby.rb

module Around
  def around(fn_name)
    old_method = instance_method(fn_name)
    define_method(fn_name) do |*args|
      puts "before"
      old_method.bind(self).call *args
      puts "after"
    end
  end
end
 
class Foo
  extend Around
 
  def bar
    puts "Bar!"
  end
 
  bar = around(:bar)
end
 
Foo.new.bar # => before; Bar!; after