visualworks

Refactoring Browser -Moving Methods from Class To Instance and Back With Undo

August 26, 2004 13:47:33.389

One of the annoyances I've had with myself is my amazing ability to make a method and then put it on the class side rather than the instance side. Or vice versa. Today I made myself a fix. It also ties into extending the refactoring browser nicely.



shiftParadigm
	<menuItem: 'Shift Paradigm...'
	     nameKey: nil
           enablement: #hasSelection
           indication: nil
           menu: #(#selectorMenu )
           position: 0.1>
	| targetClass changes |
	targetClass := self selectedClass isMeta 
				ifTrue: [self selectedClass soleInstance]
				ifFalse: [self selectedClass class].
	changes := CompositeRefactoryChange 
				named: #MoveToOtherSide << #browser >> 'Move To Class/Instance'.
	self selectors do: 
			[:selector | 
			changes 
				compile: (self selectedClass sourceCodeAt: selector)
				in: targetClass
				classified: self protocol.
			changes removeMethod: selector from: self selectedClass.
			changes addChange: (MoveMethodToPackageChange 
								class: targetClass
								selector: selector
								package: self package)].
	self performChangeWithUpdate: changes

The method may seem a bit unweildy, but lets break it down a bit.

the first thing you'll notice is how I decide where to move the method:

	targetClass := self selectedClass isMeta 
				ifTrue: [self selectedClass soleInstance]
				ifFalse: [self selectedClass class].

This is a nice programmatic way to decide if you're on the class or instance side. The isMeta test on a class tells you if this is an instance MetaClass or not. If so, grab the soleInstance of the instance of MetaClass, else grab the MetaClass instance.

Now take a look at this:

changes := CompositeRefactoryChange 
				named: #MoveToOtherSide << #browser >> 'Move To Class/Instance'.
The CompositeRefactoryChange is a change object which acts like a transaction. Add all the transactions you want, then process. This step just creates the object.
	self selectors do: 
			[:selector | 
			changes 
				compile: (self selectedClass sourceCodeAt: selector)
				in: targetClass
				classified: self protocol.
			changes removeMethod: selector from: self selectedClass.
			changes addChange: (MoveMethodToPackageChange 
								class: targetClass
								selector: selector
								package: self package)].
Now, we iterate over each method selector and do 3 things:
  • make a change, but don't execute, that will compile the method in the instance of MetaClass or class
  • make a change that removes the old method
  • move to the selected package. This is just a nicety so that you stay in the same package. This isn't functionally necessary.
self performChangeWithUpdate: changes
Now commit the changes and update the browser. There are many more subclasses of RefactoryChange which can do lots of neat things. Feel free to explore. The cool thing about doing a little extra overhead is that you'll notice under the Browser menu, there is an undo action named appropriately. So if you goof, just undo the change.