7

Lets suppose that I have a class which represent an image and has a number of methods.

class Image
{
    circle(x,y,radius,color);
    square(x,y,w,h,color);
    floodfill(x,y,color)
    clear();
}

Furthermore, I want to have undo functionality. A simple way of implementing this is to keep a list of all the actions that have been performed. When I undo, I just re-run all of the actions. One solution would be to implement an aspect, something like this:

aspect ImageUndo
{
    on Image::circle, Image::square, Image::floodfill
    precall(object, args)
    {
         object.actions_list.add(args)
    }
}

Essentially, this aspect has now modified the behavior of Image. That gives me concern. In particular, another programmer unfamiliar with the existence of ImageUndo aspect may run into the following problems:

  1. He adds a method, and it does not work with the undo functionality.
  2. Attempting to debug the undo mechanism, it is not clear where the actions_list get added to.

On the other hand we could have

class Image
{
    @undoable_action
    circle(x,y,radius,color);

    @undoable_action
    square(x,y,w,h,color);

    @undoable_action
    floodfill(x,y,color)

    @undoable_action
    clear();
}

Which does not bother me as much because it gives an idea of where to look for the undo code and makes is so that the new coder will probably notice it an automatically add it to a new method.

To summarize: aspects (at least those like the one I showed) seems to bring "implicit magic" into what code does. It seems to me that the implicitness is dangerous and we should really make it explicit.

Are there good reasons for the implicitness? Do people who actually use AOP write code that does this sort of modification?

Note: this is a reworking of Are certain problems solved more elegantly with AOP? which was closed because my previous version came across, unintentionally, as ranting.

3
  • 2
    This seems like it would be a better fit on StackOverflow.
    – Walter
    Nov 16, 2010 at 15:20
  • If you are looking for undo functionality specifically, you might want to study Rockford Lhotka's CSLA framework. It has undo functionality baked in. Nov 16, 2010 at 16:59
  • 1
    @Robert Harvey, no I'm just pondering AOP and trying to get a better understanding of why you would want to do it. Nov 16, 2010 at 17:36

2 Answers 2

3

Classes that implement cross-cutting concerns have nothing to say about the core functionality of classes being AOP'd. That's the core idea here.

Your example is one of an undo action. Let's take it a step further: we will make a deep copy of your class and store it somewhere. If we want to perform an undo, all we have to do is reverse the deep copy. This will "roll back" the class to its original state. You don't even need to annotate the class members to make it work (although you could annotate the class itself if you wanted the deep copy to operate automatically).

Here's my question: Does this deep copy process have anything at all to do with the primary functionality of the class being deep copied? Does the deep copy even care what the class does?

Serializing and deserializing objects are another example of this. The serialization process has no knowlege of the behavior of the class, and the objects being serialized have no knowledge of the serialization process. It's a powerful decoupling tool.

6
  • This does not really get at my concern. My concern is that one piece of code is modifying the other. As long as the functionality is explicitly invoked, I don't see a problem with it. Nov 16, 2010 at 16:31
  • 1) One piece of code is not modifying another. It is not self-modifying code. 2) What do you mean by "explicitly invoked?" Nov 16, 2010 at 16:42
  • Implicitness happens all the time in programming. The Ruby on Rails crowd even has a formalized name for a particular kind of implicitness: they call it Configuration by Convention. Type inference is another kind of implicitness. Nov 16, 2010 at 16:55
  • We agree that we'd like to separate the undo and image logic. But the code clearly has to indicate somewhere that they belong together. It seems to me there are three possiblilites. 1) The Image class could explicitly indicate that it is undoable thereby invoking the Undo code. 2) The undo code could indicate that it should be "injected" into the Image code. 3) A separate configuration code could combine them. Many aspects I've seen seem to be doing #2. That is what I have concerns about. From the perspective of the image code, its functionality is modified externally. Is that a good idea? Nov 16, 2010 at 17:31
  • With respect to 2), the AOP framework puts "hooks" into the class's method calls, so that when you call a method, the AOP code also gets called. There is no code in the original class that is being modified. Nov 16, 2010 at 19:17
1

One thing to point ou tis that the code isn't being modified via the aspects, it's being extended. It may be semantic, but the distinction is important, especially when considereing the open/closed principle.

Now, from what I can tell (looks like aspectj maybe?), your first example applies the aspect to the methods at the declaration of the aspect, wherease your second example applies the aspect via attributes on the methods. The problem you seem to have is one where anyone extending your code might not be able to figure out how to include undo into their additions using the first example.

  1. Documentation is there for a reason. So write some for your consumers.
  2. Use a different design to implement the functionality for them.

I believe that something like the following will work:

public abstract class ImageOperation
{
    @undoable_action
    public abstract void Action(Image img);
}

public class Flood_Fill : ImageOperation
{
    public void Action(Image img)
    {
        // flood-fill
    }
}

Now, having abstracted away the image operations, you can supply ImageOperation in a different .dll than your program, and you can use it as the base for ImageOperation plugins, so that others don't need to see your code at all to produce an operation, and you still implicitly provide undo for them.

2
  • I guess I see what the aspects are doing as modification not extension. Hence, I have a problem with them doing it. I would see it as extension if I can use my Undo code to create an UndoableImage class from an Image class. However, it is amodification if I change the original Image class to support undo. But you seem to be operating under a different definition. Nov 16, 2010 at 17:34
  • As for example, thats probably a good way to design things. But in that case there is no need to have @undoable_action or aspect as the function responsible for applying an ImageOperation will simply keep track of all the image operations it applies. The same problems don't arise. Nov 16, 2010 at 17:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.