cuts 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby +52 -0
- data/{RELEASE → HISTORY.rdoc} +35 -2
- data/NOTICE.rdoc +33 -0
- data/RCR.textile +457 -0
- data/README.rdoc +82 -0
- data/lib/cuts/aop.rb +26 -32
- data/test/test_aop.rb +11 -9
- data/test/test_cut.rb +18 -16
- metadata +81 -71
- data/HISTORY +0 -18
- data/LICENSE +0 -168
- data/MANIFEST +0 -23
- data/README +0 -47
- data/Rakefile +0 -10
- data/meta/abstract +0 -3
- data/meta/contact +0 -1
- data/meta/created +0 -1
- data/meta/homepage +0 -1
- data/meta/license +0 -1
- data/meta/package +0 -1
- data/meta/project +0 -1
- data/meta/summary +0 -1
- data/meta/version +0 -1
data/.ruby
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
---
|
2
|
+
source:
|
3
|
+
- meta
|
4
|
+
authors:
|
5
|
+
- name: Thomas Sawyer
|
6
|
+
email: transfire@gmail.com
|
7
|
+
- name: Peter Vanbroekhoven
|
8
|
+
copyrights:
|
9
|
+
- holder: Thomas Sawyer
|
10
|
+
year: '2006'
|
11
|
+
license: BSD-2-Clause
|
12
|
+
- holder: Peter Vanbroekhoven
|
13
|
+
year: '2006'
|
14
|
+
license: BSD-2-Clause
|
15
|
+
replacements: []
|
16
|
+
alternatives: []
|
17
|
+
requirements:
|
18
|
+
- name: detroit
|
19
|
+
groups:
|
20
|
+
- build
|
21
|
+
development: true
|
22
|
+
- name: microtest
|
23
|
+
groups:
|
24
|
+
- test
|
25
|
+
development: true
|
26
|
+
- name: ae
|
27
|
+
groups:
|
28
|
+
- test
|
29
|
+
development: true
|
30
|
+
dependencies: []
|
31
|
+
conflicts: []
|
32
|
+
repositories:
|
33
|
+
- uri: git://github.com/rubyworks/cuts.git
|
34
|
+
scm: git
|
35
|
+
name: upstream
|
36
|
+
resources:
|
37
|
+
home: http://rubyworks.github.com/cuts
|
38
|
+
code: http://github.com/rubyworks/cuts
|
39
|
+
mail: http://groups.google.com/groups/rubyworks-mailinglist
|
40
|
+
extra: {}
|
41
|
+
load_path:
|
42
|
+
- lib
|
43
|
+
revision: 0
|
44
|
+
created: '2008-02-12'
|
45
|
+
summary: Cut-based AOP for Ruby
|
46
|
+
title: Cuts
|
47
|
+
version: 1.1.0
|
48
|
+
name: cuts
|
49
|
+
description: Cuts is an expiremental implementation of cut-based AOP for Ruby written
|
50
|
+
in pure Ruby.
|
51
|
+
organization: Rubyworks
|
52
|
+
date: '2011-10-26'
|
data/{RELEASE → HISTORY.rdoc}
RENAMED
@@ -1,3 +1,20 @@
|
|
1
|
+
= Release History
|
2
|
+
|
3
|
+
== 1.1.0 / 2011-10-24
|
4
|
+
|
5
|
+
This release modernizes the build configuration and
|
6
|
+
switches the license to BSD-2-Clause. At the same time
|
7
|
+
it fixes a couple of issues with the aop.rb library
|
8
|
+
when running Ruby 1.9+.
|
9
|
+
|
10
|
+
Changes:
|
11
|
+
|
12
|
+
* Modernize build system.
|
13
|
+
* Fix stack error with aop.rb on Ruby 1.9+.
|
14
|
+
|
15
|
+
|
16
|
+
== 1.0.0 / 2008-11-24
|
17
|
+
|
1
18
|
This release completely overhauls how the the original
|
2
19
|
version of cuts was implemented.
|
3
20
|
|
@@ -18,7 +35,23 @@ properly (ie. use super or copy the cut extension
|
|
18
35
|
logic) everthing will work as expected.
|
19
36
|
|
20
37
|
|
21
|
-
|
38
|
+
== 0.0.4 (2008-03-05)
|
39
|
+
|
40
|
+
Changes:
|
41
|
+
|
42
|
+
* Added Rakefile to run tests.
|
43
|
+
|
44
|
+
|
45
|
+
== 0.0.3 (2008-04-06)
|
46
|
+
|
47
|
+
Changes:
|
48
|
+
|
49
|
+
* Working release.
|
50
|
+
|
51
|
+
|
52
|
+
== 0.0.1 (2008-03-05)
|
53
|
+
|
54
|
+
Changes:
|
22
55
|
|
23
|
-
*
|
56
|
+
* First release.
|
24
57
|
|
data/NOTICE.rdoc
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
= COPYRIGHT NOTICES
|
2
|
+
|
3
|
+
== Cuts
|
4
|
+
|
5
|
+
Copyright:: (c) 2007 Thomas Sawyer, Peter Van Broekhoven
|
6
|
+
License:: BSD-2-Clause
|
7
|
+
Website:: http://rubyworks.github.com/cuts
|
8
|
+
|
9
|
+
Copyright (c) 2007 Thomas Sawyer
|
10
|
+
Copyright (c) 2007 Peter Van Broekhoven.
|
11
|
+
All rights reserved.
|
12
|
+
|
13
|
+
Redistribution and use in source and binary forms, with or without
|
14
|
+
modification, are permitted provided that the following conditions are met:
|
15
|
+
|
16
|
+
1. Redistributions of source code must retain the above copyright notice,
|
17
|
+
this list of conditions and the following disclaimer.
|
18
|
+
|
19
|
+
2. Redistributions in binary form must reproduce the above copyright
|
20
|
+
notice, this list of conditions and the following disclaimer in the
|
21
|
+
documentation and/or other materials provided with the distribution.
|
22
|
+
|
23
|
+
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
24
|
+
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
25
|
+
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
26
|
+
COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
27
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
28
|
+
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
29
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
30
|
+
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
31
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
32
|
+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
|
data/RCR.textile
ADDED
@@ -0,0 +1,457 @@
|
|
1
|
+
h1. Cut-base AOP
|
2
|
+
|
3
|
+
by Trans and Peter Vanbroekhoven (rev. 70)
|
4
|
+
|
5
|
+
<a href="http://creativecommons.org/licenses/by-sa/3.0/">© CA BY-SA</a>
|
6
|
+
|
7
|
+
|
8
|
+
h2. ABSTRACT
|
9
|
+
|
10
|
+
This RCR presents <i>cut-based</i> AOP, an efficient and easy-to-use approach to Aspect Oriented Programming for Ruby.
|
11
|
+
|
12
|
+
The work herein is the culmination of multi-year discussion and inquiry on the topic of AOP for Ruby. It has been carried-out with the ultimate hope of establishing Ruby as a premier AOP language, if not <i>the</i> AOP language of choice. Since AOP is a very powerful paradigm for abstracting programming solutions into <i>separate concerns</i>, and shows great promise for improvements in <i>code maintenance</i> and <i>reusability</i>, it seems only natural that an agile language such as Ruby could provide strong support for this increasing popular pattern of design.
|
13
|
+
|
14
|
+
IMPORTANT! This is a new edition of Cut-based AOP and is different from previous editions. The significant change, is that it simplifies the definition of a Cut to it's essential character --a transparent subclass. The remaining AOP support structure are transferred to an Aspect class, which is built on top of these pure Cuts.
|
15
|
+
|
16
|
+
|
17
|
+
h2. PROBLEM
|
18
|
+
|
19
|
+
While Ruby's meta-programming facilities are powerful enough to allow for AOP-esque techniques, Ruby's lack of any <i>dedicated</i> AOP support makes it difficult and inefficient to apply Aspect Oriented Programming principles to application development and makes it practically impossible to do so in any conventional and thus generally reusable way.
|
20
|
+
|
21
|
+
h3. Overview of AOP
|
22
|
+
|
23
|
+
In AOP, one considers <i>aspects of concern</i> applicable across multiple classes and methods. Thus AOP is said to address <i>cross-cutting</i> concerns. Aspects consist of <i>advice</i>, which are methods designed to intercept other methods or events according to specified criteria. This criteria is called a <i>point-cut</i> and it designates a set of <i>join-points</i>. A join-point (or <i>code-point</i>) is the specific place within a program's execution where the advice can be inserted. In this way, AOP is thought to provide a means of organizing code <b>orthogonal</b> to OOP techniques.
|
24
|
+
|
25
|
+
<pre>
|
26
|
+
^
|
27
|
+
|
|
28
|
+
OOP | Prob Set.
|
29
|
+
|
|
30
|
+
+------------->
|
31
|
+
AOP
|
32
|
+
</pre>
|
33
|
+
|
34
|
+
The overall concept is very powerful, but likewise it can be difficult to integrate into an underlying system, easily succumbing to limitations in efficiency and subverting the intended ease-of-use and reusability. For these reasons we believe AOP has not yet become widespread. Our design addresses these issues.
|
35
|
+
|
36
|
+
h3. Qualifications for AOP
|
37
|
+
|
38
|
+
To qualify as an AOP capable language, the following criteria must be given considerable support:
|
39
|
+
|
40
|
+
* <b>Interception</b>. This is the interjection of advice, adding new processing into certain locations in a system. The locations are called join-points, and advice are typically applied to a set of these points, the point-cut. While there are different types of interception, the most common by far is method-interception, whereby a method call can be supplemented before and/or after its execution. This form of interception is the minimum required of any AOP implementation. In an 100% OOP-based system, it is also the only form of interception required.
|
41
|
+
* <b>Introduction</b> Where interception is behaviour, introduction is state. Introduction makes it possible to add further behaviour to an object, but in contrast to interception, this behaviour is not interleaved with the existing code, allowing AOP "modules" to store there own specific state.
|
42
|
+
* <b>Inspection</b> It's important to have access to as much "control" information about a program as possible, over and above the normal internal state. In other words, meta-information. Arity is a good example of this. Other information, like what a method does, what attributes it modifies, what methods it calls, who calls the method and so on, to the greatest degree available, all further enhance the capabilities of AOP.
|
43
|
+
* <b>Modularization</b> Not only must it be possible to intercept, introduce and inspect, it must also be possible to encapsulate. This encapsulation is the <i>aspect</i>. Aspects modularize individual cross-cutting concerns (such as persistence, undo, transactions, locking, caching and so on) into individual modules; possibly consisting of several sub-aspects, by delegation, inheritance or composition.
|
44
|
+
|
45
|
+
The above four points are the functional criteria of any implementation of AOP. In addition there are three major <i>means of implementation</i>:
|
46
|
+
|
47
|
+
* <b>Compile-time Preprocessing</b> With this implementation, advice are weaved into a program prior to compilation or execution. As such, advice are akin to macros. This basis of AOP is the most efficient, for obvious reasons, but is also the least flexible, allowing no alteration based on runtime data.
|
48
|
+
* <b>Runtime Method Weaving</b> Similar to Compile-time Preprocessing, but advice intercept methods dynamically at runtime. This itself can be accomplished in a few ways including simple hooks, subclassing or delegation. This is typically the most useful implementation of AOP in that it is both reasonably efficient and flexible.
|
49
|
+
* <b>Runtime Event Tracing</b> In this form callbacks and/or tracing functions are used to intercept events, or tracepoints. While clearly the most capable basis of implementation, it also tends to be the least efficient.
|
50
|
+
|
51
|
+
While the capabilities of these basis largely overlap, they admit of enough distinctions to justify independent support in accordance to the needs of the language. The first of these is generally ill suited to a highly dynamic language like Ruby (although we have recently determined that a hybrid of the first and last may be feasible), and Ruby already has some support for the third basis, albeit limited, via set_trace_func, but Ruby is hampered on the second count. This RCR focuses on the second basis, which is really the most suitable to a dynamic language like Ruby.
|
52
|
+
|
53
|
+
h3. Design Principles
|
54
|
+
|
55
|
+
A mention before getting into the heart of this proposal: The development of this RCR has been guided by the following two important principles:
|
56
|
+
|
57
|
+
* <b>Consistent and Intuitive</b> The initial spark of this work was the realization that AOP wrapping is equivalent to anonymous subclassing (somewhat similar to singleton classes). Utilizing this equivalency offers advantages in formal design, implementation, syntax and ease of use.
|
58
|
+
* <b>Make the Common Easy, and the Uncommon Possible</b> The vast majority of advice is applicable to specific classes and method wrap join-points. This proposal therefore makes these convenient, while still allowing for more elaborate possibilities.
|
59
|
+
|
60
|
+
|
61
|
+
h2. PROPOSAL
|
62
|
+
|
63
|
+
h3. The Cut
|
64
|
+
|
65
|
+
The first and foremost requirement of AOP is <i>interception</i>. A few years ago it occurred to us that subclassing itself is very similar to interception. The difference was merely a matter of the visibility of the subclass. With interception, the subclass needed to have its effect <i>transparently</i>. Indeed, <i>Transparent subclassing is the fundamental proposition of this RCR.</i> To accomplish it in Ruby we propose to introduce a new class called the <i>Cut</i>. A <i>cut</i> is a primitive unit of aspecting. It is used to encapsulate <i>advice for a single class</i>. Cuts are self-contained units much like classes and therefore can have their own state (introduction) as well as private auxiliary methods. Although the Cut class is very similar to the Class class, it cannot be instantiated. Rather it is used solely as an "invisible overrider". An example will help clarify.
|
66
|
+
|
67
|
+
Given a class C:
|
68
|
+
|
69
|
+
<pre>
|
70
|
+
class C
|
71
|
+
def f(*args); 1; end
|
72
|
+
def g(*args); 2; end
|
73
|
+
end
|
74
|
+
</pre>
|
75
|
+
|
76
|
+
One would normally subclass C in order to gain new functionality.
|
77
|
+
|
78
|
+
<pre>
|
79
|
+
class A < C
|
80
|
+
def f
|
81
|
+
print '{', super, '}'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
A.new.f #=> {1}
|
86
|
+
</pre>
|
87
|
+
|
88
|
+
But unlike a regular subclass, a cut acts transparently. So we introduce the 'cut' construction as follows.
|
89
|
+
|
90
|
+
<pre>
|
91
|
+
cut A < C
|
92
|
+
def f
|
93
|
+
print '{', super, '}'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
C.new.f #=> {1}
|
98
|
+
</pre>
|
99
|
+
|
100
|
+
Now, even though we have instantiated class C, we have the functional equivalent of the subclass of C, namely A. Another way of saying this is that we have <i>cut-across</i> the behaviour of C with A. The cut is advantageous in its fine control of how advice interact with the intercepted class and its simple conformity to OOP design. By utilization of the cut AOP begins to flow naturally into ones programs.
|
101
|
+
|
102
|
+
Because the Cut is essentially Class, like a Class it can also be defined anonymously, either through instantiation or as a special singleton. The anonymous definition can be especially convenient for internal wraps; useful for assertion checks, temporary tests, etc.
|
103
|
+
|
104
|
+
<pre>
|
105
|
+
class C
|
106
|
+
def f; 9; end
|
107
|
+
|
108
|
+
Cut.new(self) do
|
109
|
+
def f
|
110
|
+
'{' + super + '}'
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
C.new.f #=> {9}
|
116
|
+
</pre>
|
117
|
+
|
118
|
+
Or through the special singleton form,
|
119
|
+
|
120
|
+
<pre>
|
121
|
+
c = Object.new
|
122
|
+
|
123
|
+
def c.f; 8; end
|
124
|
+
|
125
|
+
cut << c
|
126
|
+
def f
|
127
|
+
'{' + super + '}'
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
c.f #=> {8}
|
132
|
+
</pre>
|
133
|
+
|
134
|
+
Additionally, Cuts exist in proxy form to allow modules to be "premixed". This is analogous to proxy classes which allow modules to mixin to the class hierarchy. So too does a proxy-cut include a module, albeit preclusive rather the inclusive in its effect. We offer the module command #preclude to serve as designator of this purpose.
|
135
|
+
|
136
|
+
<pre>
|
137
|
+
module A
|
138
|
+
def f ; "<#{super}>" ; end
|
139
|
+
end
|
140
|
+
|
141
|
+
Class T
|
142
|
+
preclude A
|
143
|
+
def f ; "okay" ; end
|
144
|
+
end
|
145
|
+
|
146
|
+
T.new.f #=> "<okay>"
|
147
|
+
</pre>
|
148
|
+
|
149
|
+
|
150
|
+
The Cut class is at the heart of this proposal. The remaining sections build on this basic device, demonstrating how to use it for AOP, and offers some important complementary suggestions to make Ruby more convenient with regard to it and AOP requirements in general.
|
151
|
+
|
152
|
+
h4. Crosscutting & Targeting
|
153
|
+
|
154
|
+
A cut is useful for applying advice which intercept the methods of a single class. But to provide the full advantage of AOP we must also be able to cut-across multiple classes. The simplest means of cross-cutting is by use of a shared module. A shared module can serve as a simple <i>aspect</i> by its inclusion in a cut for each class.
|
155
|
+
|
156
|
+
<pre>
|
157
|
+
class C
|
158
|
+
def f; 'C'; end
|
159
|
+
end
|
160
|
+
|
161
|
+
class D
|
162
|
+
def f; 'D'; end
|
163
|
+
end
|
164
|
+
|
165
|
+
module A
|
166
|
+
def f
|
167
|
+
'{' + super + '}'
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
cut Ac < C ; include A ; end
|
172
|
+
cut Ad < D ; include A ; end
|
173
|
+
|
174
|
+
C.new.f #-> {C}
|
175
|
+
D.new.f #-> {D}
|
176
|
+
</pre>
|
177
|
+
|
178
|
+
|
179
|
+
Using a cut, advice intercept methods of the same name and use #super to call back to those methods --the basics of subclassing. But for advice to be fully reusable it must be possible to designate alternate method-to-advice mapping. The simplest way to do this is by calling secondary methods, as one might normally do within a class.
|
180
|
+
|
181
|
+
<pre>
|
182
|
+
cut A < C
|
183
|
+
def f
|
184
|
+
bracket
|
185
|
+
end
|
186
|
+
def g
|
187
|
+
bracket
|
188
|
+
end
|
189
|
+
def bracket
|
190
|
+
'{' + super + '}' # PROBLEM!
|
191
|
+
end
|
192
|
+
end
|
193
|
+
</pre>
|
194
|
+
|
195
|
+
But notice the problem that arises. Super will not be directed to <code>f</code> or <code>g</code> in class C, but to <code>bracket</code> which isn't defined in C. This is not the desired result. A presently possible way to correct this is to pass a closure on the super call of the <i>target method</i>.
|
196
|
+
|
197
|
+
<pre>
|
198
|
+
cut A < C
|
199
|
+
def f
|
200
|
+
bracket( lambda{super} )
|
201
|
+
end
|
202
|
+
def g
|
203
|
+
bracket( lambda{super} )
|
204
|
+
end
|
205
|
+
def bracket( target )
|
206
|
+
'{' + target.call + '}'
|
207
|
+
end
|
208
|
+
end
|
209
|
+
</pre>
|
210
|
+
|
211
|
+
This works well enough, though one must be careful to avoid name clashes between advice and methods in classes being cut, but it is a rather brutish; nor does it provide any significant <i>inspection</i>. We can improvement upon this by passing the target method itself, but <i>enhanced</i> to provide the current super context, and usefully, its own name. We might define a method to provide this with something like:
|
212
|
+
|
213
|
+
def target_method(name,&block)
|
214
|
+
m = method(name)
|
215
|
+
m.send(:define_method, :name, name)
|
216
|
+
m.send(:define_method, :super, &block)
|
217
|
+
m
|
218
|
+
end
|
219
|
+
|
220
|
+
Then we can use it as follows.
|
221
|
+
|
222
|
+
<pre>
|
223
|
+
cut A < C
|
224
|
+
def f
|
225
|
+
bracket( target(:f){super} )
|
226
|
+
end
|
227
|
+
def g
|
228
|
+
bracket( target(:g){super} )
|
229
|
+
end
|
230
|
+
def bracket( target )
|
231
|
+
puts 'Advising #{target.name}...'
|
232
|
+
'{' + target.super + '}'
|
233
|
+
end
|
234
|
+
end
|
235
|
+
</pre>
|
236
|
+
|
237
|
+
This technique may be common enough to warrant the introduction of a keyword just for the purpose, perhaps the term <code>this</code> would be a good choice. With "this" in place, the above example can be nicely simplified.
|
238
|
+
|
239
|
+
<pre>
|
240
|
+
cut A < C
|
241
|
+
def f
|
242
|
+
bracket( this )
|
243
|
+
end
|
244
|
+
def g
|
245
|
+
bracket( this )
|
246
|
+
end
|
247
|
+
def bracket( target )
|
248
|
+
puts 'Advising #{target.name}...
|
249
|
+
'{' + target.super + '}'
|
250
|
+
end
|
251
|
+
end
|
252
|
+
</pre>
|
253
|
+
|
254
|
+
The special call #this could also carry a method's call parameters and block if given; it could even be queried as <code>this.block_given?</code>.
|
255
|
+
|
256
|
+
|
257
|
+
h4. Limitations of Cuts
|
258
|
+
|
259
|
+
At this point we reached the extent to which Cuts can provide AOP. Cuts are a robust technique provide <i>unit-AOP</i>, ie. per-class interception. This is a powerful tool applicabe to many uses cases. However, to go further we need to look at the two limitations of cuts.
|
260
|
+
|
261
|
+
FIRST. Advising multiple methods with a single advice, as we have done in the above examples, is a common case of AOP, a convenient means of redirecting target methods to advice is essential. It is trivial to define a method like the following <code>Cut#redirect_advice</code>:
|
262
|
+
|
263
|
+
<pre>
|
264
|
+
class Cut
|
265
|
+
def redirect_advice( h )
|
266
|
+
c = h.collect { |k,v|
|
267
|
+
"def #{k}(*a,&b) #{v}(this,*a, &b); end"
|
268
|
+
}
|
269
|
+
module_eval c.join("\n")
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
cut A < C
|
274
|
+
redirect_advice :f => :bracket, :g => :bracket
|
275
|
+
def bracket( target )
|
276
|
+
'{' + target.super + '}'
|
277
|
+
end
|
278
|
+
end
|
279
|
+
</pre>
|
280
|
+
|
281
|
+
However, it not sufficient for dealing with Ruby's dynamicism. It will only handles methods defined in the target class at the moment the cut is defined. Complete AOP support requires the advice always stay in sync even under dynamic alteration of the targeted class. Ruby already provides means for this via the Module#method_added hook, but robust use of this technique is inconvenient at best. So a proper advice-oriented techinique is neede
|
282
|
+
|
283
|
+
SECOND. When using redirected advice or, more importantly, when using modules as reusable aspects: care must be taken in choosing method names so as not to inadvertently interfere with the methods of the class(es) being cut. This can be a problem because it inhibits code reuse, i.e. the ability to design components without regard to where they may be applied. For example:
|
284
|
+
|
285
|
+
<pre>
|
286
|
+
class C
|
287
|
+
def m ; "M" ; end
|
288
|
+
def w ; "W" ; end
|
289
|
+
def d ; w ; end
|
290
|
+
end
|
291
|
+
|
292
|
+
module MA
|
293
|
+
def w( target )
|
294
|
+
'{' + target.super + '}'
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
cut A < C
|
299
|
+
include MA
|
300
|
+
def m ; w( this ) ; end
|
301
|
+
end
|
302
|
+
|
303
|
+
C.new.d #=> "{W}"
|
304
|
+
</pre>
|
305
|
+
|
306
|
+
In this case, #d does not return "W" as expected, but rather "{W}" because the advice in MA caused an unexpected name clash with the #w method in C. To fulfil the true abstraction and re-usability potential of AOP this issue <i>must</i> be remedied.
|
307
|
+
|
308
|
+
One remedy comes from Ruby's ability to dynamically manipulate class/module definitions on the fly, in other words, "sub-classing" the aspect module and applying any required name revisions to avoid the unwanted name clash.
|
309
|
+
|
310
|
+
<pre>
|
311
|
+
class C
|
312
|
+
def m ; "M" ; end
|
313
|
+
def w ; "W" ; end
|
314
|
+
def d ; w ; end
|
315
|
+
end
|
316
|
+
|
317
|
+
module MA
|
318
|
+
def w( target )
|
319
|
+
'{' + target.super + '}'
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
module MArC
|
324
|
+
include MA
|
325
|
+
rename_method :q, :w
|
326
|
+
end
|
327
|
+
|
328
|
+
cut A < C
|
329
|
+
include MArC
|
330
|
+
def m ; q( this ) ; end
|
331
|
+
end
|
332
|
+
</pre>
|
333
|
+
|
334
|
+
The @#rename_method@ effectively alias the original method and undefines it in one call. This solves the clash problem in a very controllable way, which is nice. We can even make it more convenient by defining some helper traits like methods. For instance:
|
335
|
+
|
336
|
+
<pre>
|
337
|
+
cut A < C
|
338
|
+
include MA * { :q => :w }
|
339
|
+
def m ; q( this ) ; end
|
340
|
+
end
|
341
|
+
</pre>
|
342
|
+
|
343
|
+
|
344
|
+
This kind of solution largely address the name clash issue, but it is still less then optimal.
|
345
|
+
|
346
|
+
h3. The Aspect
|
347
|
+
|
348
|
+
To address the limitation of the Cut, we take the next natural step in supporting AOP and create the Aspect. Aspects are similar to Cuts, in fact they can be built via delegation to Cuts, but they are a higher-level structure and support all the AOP features most are accustom, such a pointcuts, join-points and multi-class cross-cutting. An Aspect basically takes the Cut class, builds-in all the target features we handled by hand in Cuts, adds flow control methods for handling Ruby's dynamicism and provides a wholly separate area of encapsulation, which avoids any name clashing.
|
349
|
+
|
350
|
+
The primary distinction of Aspects is the #join method, which identifies which methods are to be advised but what advice.
|
351
|
+
|
352
|
+
<pre>
|
353
|
+
class C
|
354
|
+
def f ; "F" ; end
|
355
|
+
def g ; "G" ; end
|
356
|
+
end
|
357
|
+
|
358
|
+
aspect A
|
359
|
+
join :f => :bracket, :g => :bracket
|
360
|
+
def bracket( target )
|
361
|
+
'{' + target.super + '}'
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
A.apply_to(C)
|
366
|
+
</pre>
|
367
|
+
|
368
|
+
The #join method would also accept wild cards.
|
369
|
+
|
370
|
+
<pre>
|
371
|
+
aspect A
|
372
|
+
join '*' => :bracket
|
373
|
+
def bracket( target )
|
374
|
+
'{' + target.super + '}'
|
375
|
+
end
|
376
|
+
end
|
377
|
+
</pre>
|
378
|
+
|
379
|
+
An it can also take a block which allows us to work with join-points. In the code below, @jp@ is a JoinPoint object.
|
380
|
+
|
381
|
+
Xa = Aspect.new do
|
382
|
+
join :x do |jp|
|
383
|
+
jp.name == :f or jp.name == :g
|
384
|
+
end
|
385
|
+
|
386
|
+
def x(target); '{' + target.super + '}'; end
|
387
|
+
end
|
388
|
+
|
389
|
+
A JoinPoint object is very similar to an internal Ruby frame, and provides parameters based on the targeted method plus many of the same parameters that #set_trace_func can use: event, file, line, id, binding, classname. Though some of these may be omitted for performance reasons.
|
390
|
+
|
391
|
+
We don't necessarily need Aspects to cross-cut large swaths of classes. Ruby's built-in reflexion provides means via ObjectSpace.
|
392
|
+
|
393
|
+
<pre>
|
394
|
+
ObjectSpace.each_object(Class) { |c|
|
395
|
+
if c.instance_methods(false).include?(:to_s)
|
396
|
+
Cut.new(c) do
|
397
|
+
def :to_s
|
398
|
+
super.upcase + "!"
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
"a lot of shouting for joy".to_s #=> "A LOT OF SHOUTING FOR JOY!"
|
405
|
+
</pre>
|
406
|
+
|
407
|
+
However system-wide effects must by definition be more robust as we can't always account for the nature of each class. So Aspects are much more appropriate to this use. To facilitate this, Aspects offer the #pointcut method.
|
408
|
+
|
409
|
+
<pre>
|
410
|
+
aspect A
|
411
|
+
join :to_s => :to_s
|
412
|
+
def to_s(target)
|
413
|
+
target.super.upcase + "!"
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
A.pointcut do |pc|
|
418
|
+
true if pc.instance_methods(false).include?(:to_s)
|
419
|
+
end
|
420
|
+
</pre>
|
421
|
+
|
422
|
+
There are plenty of great applications for broad cross-cutting like this, especially in the way of code inspection, unit testing, debugging, etc. The Aspect is a vital part to AOP as it provides the dynamic flexibility that is required of complete Ruby AOP solution.
|
423
|
+
|
424
|
+
|
425
|
+
h2. ANALYSIS
|
426
|
+
|
427
|
+
The Cut and its supporting infrastructure as described above is designed to be a very robust, easy to use, and efficient, providing better overall AOP support than any other language presently in common use.
|
428
|
+
|
429
|
+
In contrast, the traditional approach taken by the most AOP systems today, largely propagated by early implementations like Aspect/J, have proven unwieldy and ironically end-up inhibiting code reuse. Infact, the limited reusabiliy has been speculated elsewhere as a potential primary culprit in the limited penetration of AOP to date. This proposal circumvents these issues by offering a general solution directly integrated into the OOP system, rather than attempting to operate wholly beyond it.
|
430
|
+
|
431
|
+
Cuts also trump simple method-wrapping mechanisms, like those proposed in Matz' RUbyConf 2004 presentation. While method hooks are especially convenient, they are weak with regards to SOC (Separation Of Concerns); most notably, method hooks lack <i>introduction</i> altogether. They also suffer from order of execution ambiguities that must be dealt with by imposing limitations or adding increasingly specialized declarations. Cuts again circumvent these issues by utilizing an inherent OOP construct --the subclass, rather than adding on a new, wholly "other" entity.
|
432
|
+
|
433
|
+
h3. Pros
|
434
|
+
|
435
|
+
* Provides a robust method-wrapping solution, devoid of order ambiguities.
|
436
|
+
* Clear separation of concerns using separate cuts and modular aspects.
|
437
|
+
* Based on standard OOP devices, i.e. cuts are essentially subclasses.
|
438
|
+
* Cut-based AOP is very easy to understand and thus to use.
|
439
|
+
* Implementation is efficient.
|
440
|
+
|
441
|
+
h3. Cons
|
442
|
+
|
443
|
+
* Cut syntax is a slightly more verbose and entails more overhead than simple method hooks. [Counterpoint: But the difference <i>is</i> minor. Simple methods hooks could, in point of fact, be implemented via cuts with efficiencies quite close to a simple hook solution.]
|
444
|
+
* Cannot advise large numbers of classes in a single <i>dedicated</i> clause. [Counterpoint: Cuts are specifically designed <i>not</i> to do this as the inability also has advantage, not the least of which is performance efficiency. Moreover it has been discovered that such large swaths of cross-cutting are in actuality the uncommon usecases, more suitable to specialized applications like unit-testing and profiling.]
|
445
|
+
* AOP traditionalists might be a bit taken aback by the Cut-based approach in that it does not specifically reference join-points and pointcuts. [Counerpoint: As above, cuts provide a better approach for the most common AOP usecases.]
|
446
|
+
* Until local instance variables are available, and/or local instance methods, <i>introduction</i> is not as strong as it really needs to be for good AOP coverage. [Counterpoint: We suspect this is likely to be addressed in a future version of Ruby.]
|
447
|
+
|
448
|
+
|
449
|
+
|
450
|
+
h2. IMPLEMENTAITON
|
451
|
+
|
452
|
+
One implementation detail, not specifically decided by this proposal, is whether cuts may or may not be applied to other cuts. If not allowed, once a cut is applied to a class, a subsequent cut can not be slipped in between it and that class. Cuts are intended to work transparently and offering this feature could thwart this principle. On the other hand, if allowed, it would provided a means for a cut to "underwrite" another cut providing greater flexibility in "meta"-controlling the effects of cuts.
|
453
|
+
|
454
|
+
Another implementation detail to consider that falls outside the strict scope of this proposal, but that goes a long way toward bolstering it, is the limits on <i>introduction</i> due to the non-locality of instance variables and methods. Presently cuts will only be able to provide introduction through class varaibles --useful but weak by comparision. With the advent of locals in a future version of Ruby, cuts would gain robust introduction strengths.
|
455
|
+
|
456
|
+
The first real step in implementation is, of course, the creation of the transparent subclass, the Cut. This requires an addition in the structure of an object's class hierarchy; essentially a new pointer to a chain of cuts, the last pointing back at the cut class itself --a very simpleton explanation to be sure. But fortunately, a well written Ruby patch has been coded by Peter Vanbroekhoven. It implements most of the core funtionality described here, and should serve as a means to investigate and test the potential utility of this RCR. It may also serve as a basis for including these AOP features into Ruby proper, should this RCR be accepted. At this time the patch applys to Ruy 1.8.2 and can be download from "here":HTTP://rubyforge.org/projects/suby/ under "transparent subclass (cut)".
|
457
|
+
|