From: Alexandru Popescu Date: 2005-10-20T19:11:07+09:00 Subject: Re: [ANN] [RCR] Cut-based AOP Hi! I would like to comment on this proposal. I can agree right from the beginning that the points here will be presented from a not-so-rubiesc perspective. the proposal looks like an method-interception framework, rather than a full blown aop solution. The proposal is mixin together theoretical aspects of aop with Ruby specific implementation problems. While this cannot be considered a real problem, what I have noticed is that one of the main concepts of the AOP pointcuts are completely ignored. The main idea behind AOP is to be able to define crosscutting concerns. This looks pieces of functionality that can be used disregarding the OO nature of the project. While at the first glance the method interception mechanism is one of most important aspects of AOP, there are a few distinction that must be considered: call vs execution, before, around, after pieces of advice. While, maybe leaving alone all these aspects, may be a Ruby implementation constraint, they are valid AOP principles that will miss from the implementation. cheers, ./alex -- .w( the_mindstorm )p. #: Trans changed the world a bit at a time by saying on 10/19/2005 1:56 AM :# > This is to "officially" announce an RCR that I posted to RCR archive > two days ago. I realize the RCR itself is a bit dense and techincial, > so (with thanks to ES) I thought it might be a good idea to provide a > little summary and some examples of what it's all about and why it's a > such a good approach to AOP for Ruby. > > You can read the RCR #321 href="http://www.rcrchive.net/rcr/show/321">here. To touch on it's > history: The RCR was developed by Peter Vanbroekhoven and myself over > the course of the last two years[1]. In that time we covered a lot of > territory with regards to AOP, and the RCR itself has undergone a great > deal of scrunity, revision and refinement. > > To summaraize the RCR's senitment: The transparent subclass, witch we > have dubbed the Cut, is the best basis for adding AOP to Ruby because > it is fully harmonious with OOP. This is unlike other solutions which > are add-on abstractions tossed on top of the underlying OOP framework. > With cuts one has a building block to construct all sorts of AOP > systems, from simple method hooks to full blown context-oriented > programs. Put simply, the cut is a foundation for AOP, just as the > class is a foundation for OOP. > > To demonstrate what you can do with cuts, I present a few simple > examples. I'll start with a very basci one: how to take a prexiting > class and wrap a user interface around it. Nothing fancy. I'll just use > a very small class and the console. > > class Race > > attr_accessor :distance, :speed, :track > > def initialize( distance, speed ) > @distance = distance > @speed = speed > @track = 0 > end > > def reset > @track = 0 > end > > def run > while track < distance do > self.track += 1; > sleep( 1.0/speed ) > end > track > end > > end > > Simple enough. We can run a race: > > Race.new( 10, 2 ).run > > Though it goes about it's racey busniess just fine, we have no > indication of any progress of the race. But that's okay actually; it > keeps the Race class clean and focused on its core functionality. > Instead we can use a cut to watch the race. > > cut Race::ConsoleViewer < Race > def track=(x) > r = super(x) > print "\e[0E" > print "+" * r > $stdout.flush > r > end > end > > Race.new( 10, 2 ).run > > This outputs an exra '+' at a time, finishing with 10: > > ++++++++++ > > So we see the progress of the race "live" without having to change the > Race class itself in any way. And we can just as easily throw any other > type of interface (gtk, qt, fox, wx, html, and so) around the Race > class in the same manner. > > Now lets try a slightly more advance example; one made by Peter quite > some time ago, which demostrates the basis of how cuts could be used > for creating logging aspects. Lets say we have an ftp server class: > > class FTPServer > > def login(username, passwd) > # code for logging in, return ID for the connection > return connID > end > > def logout(connID) > # code for logging out > end > > def upload(connID, filename, data) > # code for writing a file > end > > def download(connID, finename) > # code for reading a file, returns the data read > end > > end > > We can create an aspect for it: > > module FTPLogging > > def login(username, *args) > connID = super > log.print("#{connID}: #{username} logging in on #{Time.new}\n") > connID > end > > def logout(connID) > result = super # In case logout returns some result > log.print("#{connID}: logging out on #{Time.new}\n") > result > end > > def upload(connID, filename, *args) > result = super # In case logout returns some result > log.print("#{connID}: uploading #{filename} on #{Time.new}\n") > result > end > > def download(connID, filename) > data = super > log.print("#{connID}: downloading #{filename} on #{Time.new}\n") > data > end > > end > > if logging_enabled > cut FTPLoggingCut < FTPServer > include FTPLogging > end > end > > Notice the use of a separate module to house the aspect. That way it is > reusable. In this case the aspect's design was made to match the > duck-type of the target class, FTPServer, so we don't need to redirect > any advice in the cut. Though if we wanted to use the aspect on a class > not having the same interface we could easily redirect the advice on a > case by case basis. But even better, using an annotations system we > could create a routine to cut any class so annotated. > > Finally, here's a example of how one might use cuts for dynamic > context-oriented programming. > > class Account > attr_reader :id, :current > def initialize( id, current ) > @id > @current = current > end > def debit( amount ) > @current -= amount > end > def credit( amount ) > @current -= amount > end > end > > module Activiation > def active? ; @active ; end > def activate ; @active = true ; end > def deactivate ; @active = false ; end > end > > cut AccountLogging < Account > extend Activation > def debit( amount ) > r = super > if AccountLogging.active? > log "Debited account #{id} #{amount} with result #{r}" > end > r > end > end > > cut AccountDatabase < Account > extend Activation > def debit( amount ) > super > if AccountDatabase.active? > DB.transaction { > record_transaction( -amount ) > record_total > } > end > end > def credit( amount ) > super > if AccountDatabase.active? > DB.transaction { > record_transaction( amount ) > record_total > } > end > end > def record_transaction( amount ) > type = amount > 0 ? 'credit' : 'debit' > DB.exec "INSERT INTO transactions (account,#{type}) VALUES > (#{id},#{amount.abs});" > end > def record_total > DB.exec "UPDATE accounts SET total=#{current} WHERE id=#{id};" > end > end > > Notice how we can activate and deactivate the aspects on the fly with > the activation module. If we wished we could go even further and create > a completely general purpose (and relatively efficient) context > switching mechinisms. > > Oh, one more quick example taken from the RCR itself for those > wondering where the pointcut is, and how one might cross-cut a whole > slew of classes: > > ObjectSpace.each_object(Class) { |c| > if c.instance_methods(false).include?(:to_s) > Cut.new(c) do > def :to_s > super.upcase + "!" > end > end > end > end > "a lot of shouting for joy".to_s #=> "A LOT OF SHOUTING FOR JOY!" > > Okay, I think that should give a pretty good taste of what "AOP can do > for you" and how Cuts provide a solid, yet easy to use, foundation for > employing AOP on a wide scale. To understand more about why Cuts are > right for the task, covering all the criteria of AOP including > *introduction* and *inspection*, please give the RCR a read. > > We hope you find our proposal rewarding and will show your supprt on > RCRchive. > > Thanks, > T. > > > [1] I want to also thank Jamis Buck and everyone else who has spent > time exploring AOP for Ruby with us. Thank You! > > >