質問

I'm not very familiar with smalltalk but what I'm trying to do is override another classes 'new' when I initialize my own class. Does that have anything to do with Class>>bindingOf:?

Edit:

What I'm trying to achieve is: if ObjectA calls new, then ObjectB handles the request.
ObjectB is not related to ObjectA.
Can this be done by only changing ObjectB's implementation?

Edit: My ObjectB in this story is an ObjectTracer and what I want it to do is behave kind of like a wrapper class for ObjectA. Do I change ObjectA's implementation for new using the class's method dictionary and how would that be done?

Edit: Here's what I would like to make possible:

| obj |
obj := ObjectA new.
obj aMethod.

and what's really going on is that when new was sent to ObjectA, it was replaced with with an implementation provided by ObjectB (the wrapper) and like aka.nice and Hernan mentioned in their answers, ObjectB makes #doesNotUnderstand handle the messages intended for ObjectA.
Essentially, is it possible then that all I need is to get ObjectB to replace ObjectA's #new?

役に立ちましたか?

解決

Most of the tracers, profilers and samplers monitorize the execution of a system. If you want to develop a tracer which actually modifies the system you have to know very well its dynamics to avoid collateral effects.

This is the basic formula for tracers:

  • You will wrap anObjectA with Tracer (anObjectB)
  • Create a Tracer class with "object" instance variable (most of Tracers subclass from nil or so)
  • Implement #initialize in Tracer's class side removing the superclass pointer (superclass := nil)
  • Implement #on: anObjectA in Tracer's class side for storing anObjectA in the Tracer's object i.v.

Implement #doesNotUnderstand: aMessage in Tracer's instance side, like this template:

doesNotUnderstand: aMessage
"trace the selector then pass the message on"

| result |
aMessage arguments size = 0
  ifTrue:
    [result := object perform: aMessage selector]
  ifFalse:
    [result := object perform: aMessage selector withArguments: aMessage arguments].
^result

in your case before the #perform: send you might access the ObjectA method dictionary and replace the "aMessage" CompiledMethod with another one. To access the method dictionary of a class just sent #methodDictionary or #>> in some Smalltalks.

You may put a new compiled method or even replace the whole dictionary in you know how to do reflection:

| methodDictionary |
methodDictionary := MethodDictionary new.
methodDictionary at: #basicNew put: (Object compilerClass new
                                compile: 'basicNew ^Point basicNew'
                                in: Object
                                notifying: nil
                                ifFail: []) generate.

Note you don't need a method to reside in a MethodDictionary to be evaluated. See senders of #valueWithReceiver:arguments:

Finally you make sends to your new object

anObjectA := MyTracer on: ObjectA new.
anObjectA example1.

and yes, you could put that in the ObjectA's new method too. You better read some chapters on Reflection in Smalltalk, there are a lot of contents in the web as Smalltalk is the best platform for doing computational reflection.

他のヒント

No, #bindingOf: is related to compiling a method.

Variables that are accessible thru several methods, like global, class variables or pool variables are shared by storing the same binding in method literals. A binding is a kind of Association whose key is variable name and value is variable value.

When your code use the variable the method send #value to the binding under the hood, and when you store a value into the variable it sends #value:

Note however that - depending on Smalltalk flavour - these operations might be optimized in byte code and replaced with a direct access to 2nd instance variable (the value) of the binding.

So the Compiler needs to retrieve the bindingOf: aSymbol in order to access any shared variable, where aSymbol is the name of the variable. The class into which the method is compiled is queried for that information because the scope of variable access depends on the class (only a class and its subclasses can access the class variables...).

If you want to override instance creation in YourClass, just override #new at class side (we say YourClass class>>#new). If you use Squeak/Pharo dialect, most of the time, you can achieve specific instantiation by overriding #initialize at instance side (YourClass>>#initialize) especially , since #new will invoke #initialize.

EDIT

If you want to catch the sending of #new to ObjectA with an ObjectTracer, here is what you could do:

| theTrueObjectA |
theTrueObjectA := ObjectA.
[Smalltalk globals at: #ObjectA put: (ObjectTracer on: ObjectA).
"insert the code you want to test here"
ObjectA new]
    ensure: [Smalltalk globals at: #ObjectA put: theTrueObjectA].

EDIT2 last sentence can be replaced with ensure: [ObjectA xxxUnTrace]

However, modern squeak debugger is intrusive and will itself send many messages to the ObjectTracer leading to other debuggers popping... You should first open a Preferences window and disable logDebuggerStackToFile.

Note that the mechanism involved is that the message #doesNotUnderstand: is sent by an object when it does not understand a message. The ObjectTracer overrides #doesNotUnderstand: to pop up a debugger.

You can subclass ProtoObject to install your own #doesNotUnderstand: handling (like just writing something in a Transcript or a file).

Also note that ObjectTracer #inheritsFrom: ProtoObject and that ProtoObject itself #respondsTo: many messages that won't be caught by ProtoObject>>#doesNotUnderstand:

Last note: I used # above to indicate messages that can be understood.

EDIT 3: a possible solution is to define a new kind of ObjectTracer with two instance variables tracedObject and messageMapping and this instance creation:

MessageInterceptor class>>on: anObject interceptMessages: aDictionary
    "Create an interceptor intercepting some messages sent to anObject.
    aDictionary keys define message selectors that should be intercepted.
    aDictionary values define block of code that should be evaluated in place.
    These blocks always take one argument for passing the traced Object,
    plus one argument per message parameter.
    snip..."

MessageInterceptor>>doesNotUnderstand: aMessage
    mapping := messageMapping at: aMessage selector
        ifAbsent:
            ["We don't intercept this message, let the tracedObject handle it"
            ^aMessage sendTo: tracedObject].
    ^mapping valueWithArguments: {tracedObject} , aMessage arguments

For example, you would use it like that:

| interceptor |
interceptor := MessageInterceptor on: ObjectA interceptMessages:
    ({#new -> [:class | ObjectTracer on: class new]} as: Dictionary).
[Smalltalk globals at: #ObjectA put: interceptor.
"insert the code you want to test here"
ObjectA new yourself]
    ensure: [interceptor xxxUnTrace].

Methods an object can understand are stored in that object's class. If you want to override instance creation #new method sent to a class you have operated on the class's class, i.e. its metaclass

meta := ObjectA class.

You can read about metaclasses here: CSE 341: Smalltalk classes and metaclasses.

Each class, including classes of classes (metaclasses), store messages their instance can understand in the method dictionary, in Pharo it is the instance variable called methodDict. If you want an object to be able to receive a message you have to insert into that dictionary a CompiledMethod that will execute as the result of your message send. Classes in Smalltalk have a handy compilation method that can be used to install CompiledMethods in a class, e.g.

ObjectA 
    compile: 'new
        ^ObjectB new'
    classified: 'instance creation'

If your ObjectA already defines #new method this will override it. You would have to cache the old CompiledMethod to preserve it for later restoration.

In real life you would use Method Wrappers for something you are trying to achieve here, have a look at Wrappers to the Rescue.

In the current Moose (Pharo) image I happen to have open, there are 171 implementations of new, and 1207 of initialize. That suggests that you're much more likely to need to override initialize than new. When browsing through them, I find the following common occurences for wanting to override new :

  • abstract class, only create subclasses of me
  • singleton, call uniqueInstance instead
  • created with default values
  • valueobject
  • compatibility with other dialects

It sounds to me like you just need to implement ObjectA class>>new. You could do something like this:

new
  inst := self basicNew.
  ^ ObjectB wrapping: inst
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top