2009-04-23

Prism AOP - 4

I’ve played a little more with Prism. I find it a little difficult to mentally code on two levels. Level one being the code I am writing for the aspect, and level two being the code I am writing which will executed by the target. Having said that, as soon as I ran my app and saw the output everything was worthwhile.

As you can see the implementation of the aspect "BusinessClass" is very specific to ECO and saves a lot of writing. What is good too is that I could easily remove the project’s reference to the BusinessClass aspect which generates ECO changes and replace it with a DLL which has a BusinessClass aspect for something else - maybe a blank one which does nothing so that you can use the same class definitions as data-transer-objects.

But my interest in this technology goes far beyond easily implementing an object relational mapper in an abstract way, supporting the ORM is only the first step towards achieving what I really want - archetypes. Once I have the experience to create all of the meta-information required for mapping I can start creating my models out of patterns. I do this a lot at the moment, but all manually. For example in one project I had to allow my Employee, Van, and VendingMachineColumn classes to all hold stock. Each of these would need to hold stock, record stock adjustments such a stock found/lost during stock checks, and also due to stock transfers.

It would be a bad design to descend all of these classes from a StockHolder class. Holding stock is something you DO, and not something you ARE, so inheritance here is wrong. What I would typically do here is

1: Create a StockHolder class which holds the stock + adjustment history.2: Employee, Van, and VendingMachineColumn would all own an instance of StockHolder.3: Each class would implement

public interface IStockHolder{ StockHolder GetStockHolder();}

This is a one-way relationship, if I needed for example to find all stock holders with X amount of a certain stock item so that I could request a transfer this would not be sufficient. In which case I would introduce an abstract method to StockHolder

object GetOwner();

Then I’d have a descendant of StockHolder for each owner. EmployeeStockHolder, VanStockHolder, VendingMachineColumnStockHolder; each would have an association back to their owning object (Employee, Van, VendingMachineColumn) which they would return by overriding GetOwner. Now this is not a lot of work, but it is repetitive. You see the pattern in the UML but do you instantly recognise it? Is it really self-descriptive?

The StockHolder aspect would create the descendant (TargetClassName)StockHolder with an association back to the target, and override GetOwner. The thing is, HOW the aspect is implemented is not relevant, all I am saying is "this holds stock". It is short, descriptive, and instantly understandable. It’s also only a few seconds of work to make a class hold stock.

That’s the kind of thing I’d like to end up with. Much more descriptive than UML I think :-)

Here is the code. It’s just proof of concept code at the moment. I’ve decided to prefix Type_ Method_ Property_ etc to the start of types, methods, and property definitions where they are referring to values from the model the aspect is being applied to; this was something I decided to do to help me to mentally split the "this code" scenario and "target code" scenario.

//If it is an ITypeDefinition that means it is declared as source in the current //binary and we can therefore modify it - so we can attach .NET attributes. //If it isn’t an ITypeDefinition but only an ITypeReference then it is immutible //and we cannot change it. Type_PackageDefinition := Type_PackageReference as ITypeDefinition; if (Type_PackageDefinition = nil) then begin Services.EmitError(’Package class cannot be modified, it is not part of the same project: ’ + PackageName); exit; end;

//Explicitly tie our GetValueByIndex to ILoopBack2.GetValueByIndex. This ensures they are linked //even though our method is protected. This hides the method when using code-completion on the //target (because it is protected), but exposes it via the interface - much cleaner! aType.AddImplements(Method_GetValueByIndex, Type_ILoopBack2, Method_ILoopBack2_GetValueByIndex);

//Create an exit statement which is basically - exit Self.Property.Read //So that we exit the method, returning the result of reading the property value var Statement_Exit : ExitStatement := new ExitStatement(Property_Read);

//Set the body of the GetValueByIndex method we created. Normally we can just write the exact //code we need between the begin/end identifiers, but in this case we have generated the statements //to execute dynamically, so we need to "unquote" them - which basically means "expand" or "compile". Method_GetValueByIndex.SetBody(Services, method begin unquote(Statement_CaseIndexOf); end);end;

//Create a statement based on this expression. We can use AssignementStatement without passing a value because we have //already specified the value in the previous expression. var Statement_SetPropertyValue : AssignmentStatement := new AssignmentStatement(Property_Write);

//Add this assignment to the Begin/End block statement Statement_CaseItemBegin.Add(Statement_SetPropertyValue);

//And add a plain "Exit" after it within the Begin/End block. var ExitMethod : ExitStatement := new ExitStatement(); Statement_CaseItemBegin.Add(ExitMethod);

//Craete a CaseItem for the current property index which will execute our Begin/End block. var CaseItem_Index : CaseItem := new CaseItem(Statement_CaseItemBegin, CaseIndex);