That could be a useful solution (and the one I would probably choose as well), but what if you primarily need polymorphism along the property type hierarchy because the sale of a home is so different from the sale of a mall?
Also, you get the objection that contracts don't sign themselves. I remember very well that in the early 90s, OO models were promoted as a means for business people to talk to software designers. It never worked out that way.
The real world knows processes. Processes are not some appendage of any of the objects involved. So why not model a process as a function?
Like I said, your implementation would depend on context, but to adapt my example, I'd probably put a function on the Property class to determine which Contract class to use - something like:
However, I think from what you're saying that your sell_property() function would need awareness of all property types, so adding a property type not only requires changes to the part of the code which contains the property data structure, but also to the sell_property() function - and any other function throughout your code base which uses properties. That is of course possible, but OO does make it easier to find where to implement logic specific to different property types.
As I said, I'm assuming that the operation depends on the types of _all_ objects involved. So what you actually want is multimethods.
In an OO system you have to simulate multimethods by chaining the calls through all objects, but that obscures the actual functionality of the operation. OO works well if method resolution depends on exactly one type. That's why I chose an example where it's not clear that one type dominates.
In the absence of multimethods I find that 95% of the time a simple if else construct does the trick just fine. But I do value the OO style single type dispatch where it really fits. It is just overused.
I think I see where you're going with this, but it seems like primarily just a semantic improvement. You have a bunch of objects (collections of state), and an action that's going to modify some or all of them. While I agree that namespacing the operation as an independent function instead of arbitrarily attaching it to one object or another is ideal, I don't see how the operation itself has necessarily been simplified. Is it because now instead of modifying the objects/holding state, we can just process and persist whatever needs processing and persistence and be done with it?
The operation itself isn't necessarily simpler. The system as a whole may become easier to understand, more productive to build and less error prone if design decisions can be justified in a rational way.
After all, the classes and functions we create become the language in which we think about the system. If there is no clear reason why a particular operation belongs to one class more than to three others, it should not belong to any class.
Adding an operation to a class has implications like method resolution and dependence on internal state. We make assumptions based on that and those assumptions should turn out to be true.
For instance, it is entirely clear why List.add(Object) is a method in the List class. It is that list and only that list that is modified. It is only the List class hiearchy along which specializations of that method should be searched for. The operation depends on the internal state of the list and not on the internal state of the Object parameter. So all our assumptions hold and we will be able to remember to which class this add method belongs.
In my property sale example it's not like that. Any and all class hierarchies could conceivably be used for method resolution. The state of any and all objects could be modified.
Also, you get the objection that contracts don't sign themselves. I remember very well that in the early 90s, OO models were promoted as a means for business people to talk to software designers. It never worked out that way.
The real world knows processes. Processes are not some appendage of any of the objects involved. So why not model a process as a function?
sell_property(contract, property, buyer, seller, agent)