When to define your own strategies
In Replacers and strategies as first class objects describes the main advantages of having strategies as first class objects. This section presents an example that shows how to use strategies. Although the example presented shows a dumping strategy, the same reasoning applies to loading strategies.
Resolving method conflicts
Classes may already define their own implementation of dumpingReplacementForReplacer:. For example CompiledMethods shown in Class-based replacement does this. However, you may want to define a new dumping behavior for CompiledMethod. Modifying the existing dumpingReplacementForReplacer: is not a solution for the following reasons:
It forces each user to create a new version of the Swapper.
It has a side-effect of changing the dumping replacement for all ObjectDumpers used.
A simple solution for this is to use a new strategy class, NewDumpingStrategy, subclass of EswDumpingStrategy, and override dumpingReplacementFor:for:. In EswDumpingStrategy, the implementation is shown in the following example.
Example: Default implementation for EswDumpingStrategy
dumpingReplacementFor: anObject for: replacer
"Return the object that will replace anObject when dumping. This is useful
for replacing instances of a given class by another object, of another class."
^anObject dumpingReplacementForReplacer: replacer
However, you can redefine it in the new subclass, NewDumpingStrategy, as shown in the following example.
Example: Redefining the selector used with NewDumpingStrategy
dumpingReplacementFor: anObject
"Return the object that will replace anObject when dumping. This is useful
for replacing instances of a given class by another object, of another class."
^anObject newDumpingReplacementForReplacer: replacer
In order to use this new kind of strategy with an ObjectDumper, you must use the API swappingStrategy:. The next example shows how.
Example: Plugging new dumping strategy in ObjectDumper
dumper := ObjectDumper new.
"This creates an ObjectDumper with default dumping strategy"
dumper swappingStrategy: (NewDumpingStrategy forSwapper: dumper).
"And this changes the dumper's strategy to be the new instance,
a NewDumpingStrategy."
NewDumpingStrategy implements dumpingReplacementFor:for:, which is part of the API used by Replacer to request replacement of an object based on its class.
Now, when the ObjectDumper in the above example is used, the new message newDumpingReplacementForReplacer: is sent from the strategy to the target object (object to be dumped). This message must be defined in Object. The following example shows an obvious implementation.
Example: Redefining the dumping behavior of objects
newDumpingReplacementForReplacer: aClassBasedReplacer
"Return a possibly new object that will replace the receiver when dumping.
Just run the previous replacement code."
^self dumpingReplacementForReplacer: aClassBasedReplacer
You do not need to call the newDumpingReplacementForReplacer: message. When implementing a new strategy, you can choose whatever name you like for the new message. The set of messages the replacers and strategies understand is part of the public framework API. The messages the strategies use to interact with target objects is defined by the application.
So far you have just implemented a new kind of strategy that uses a different message to ask objects for their dumping replacement and this message defaults to the old implementation. Thus, you have not solved the problem of dumping a CompiledMethod in a different way yet. To do that, you must define CompiledMethod instance method newDumpingReplacementForReplacer:. The following example shows a possible implementation.
Example: Redefining the dumping behavior of CompiledMethods
newDumpingReplacementForReplacer: aClassBasedReplacer
"Return a replacement object for a CompiledMethod."
forClassNamed: self methodClass name asString
selector: self selector asString
The code above combined with the new strategy used in an ObjectDumper (see "Example: Plugging new dumping strategy in ObjectDumper" on page ***) replaces all instances of CompiledMethod with a CompiledMethodDescriptor that carries the class name (a String) where the CompiledMethod was compiled and the selector itself (a String as well).
Of course, you must rebind the descriptor to the local CompiledMethod in the image when the object is loaded. Finally, you must define CompiledMethodDescriptor instance method loadingReplacementForReplacer: as in Another look at Redefining methods to implement class-based replacement.
Using the loading replacement for CompiledMethodDescriptor will ensure that a descriptor for a compiled method is properly mapped back to the local CompiledMethod in the image. The example code swaps only the minimum information necessary, just enough to rebind the object to an equivalent object in the image where it will load.
Last modified date: 06/01/2020