Hacker News new | past | comments | ask | show | jobs | submit login

So what is the natural and clean design for an operation that represents the sale of a property? Say we have these objects: the buyer, the seller, the agent, the property and the contract. Which one of these would you prefer?

property.sell(buyer, seller, agent, contract)

seller.sell(property, buyer, agent, contract)

buyer.buy(property, seller, agent, contract)

agent.sell(property, buyer, seller, contract)

contract.sign(property, buyer, seller, agent)

The state of all the objects may be modified, and there are different types of properties, buyers, sellers and agents. So you might want polymorphism along any of those hierarchies.




  contract = agent.makeContract(property, seller, buyer)
  seller.signContract(contract)
  buyer.signContract(contract)
  agent.registerSale(contract)
Nothing complicated here, really. Just follow the "natural" flow and respect each actor's role. Don't try to group all operations into a single one when there are independant actors involved.

Note : I see the "contract.sign()" solution coming back a lot. I will admit this is "pure OO", but to me it doesn't make any sense. A contract here is a data object, not an actor. It shouldn't process anything. How many times do you see contracts sign themselves (or even do anything) in reality?


Your "data object" versus "actor" argument is intersting. Does that mean an object model should mimic certain properties of the real world that aren't even part of the system, like the knowledge that agents can act whereas contracts can not?

There are clearly conflicting goals here. It could be that you need polymorphism along one hierarchy but that would make the design look very unnatural in the eyes someone who knows the problem domain.

In my experience, OO models tend to diverge greatly from the real world over time, because after all we're not modelling the world, we're modelling a solution to a problem and we shouldn't fight the tendency for the language of the solution domain to dominate the language of the problem domain.

Sometimes this can mitigated by having interfaces on different levels of abstraction, but that often leads to bloated and slow systems.


Clearly the goal here is not to simulate an ecosystem where agents, buyers and sellers happily live together.

However, the idea here is to organize your object model exactly as you would organize a group of employees. Each of these employees has a specific job and a specific set of responsibilities - ideally only one. You can describe the solution to your problem as the result of their interaction. These employees are actor objects.

To interact efficiently, they need to exchange information, in the form of data objects. These data objects don't do anything except hold a piece of information - exactly as you would have two employees exchanging notes or emails.

Most of the time these data objects correspond to the language of your problem domain. The actor objects however may have nothing to do with any "real world" activity, depending on the job you want them to do in the process. So yes, the object model diverge from reality over time because we introduce new actors with specific roles that have no "real world" equivalent. But that's just fine.

The important thing is to keep seeing the distribution of responsibilities among your objects and their interaction as the work of a team of independent experts, not as "things doing stuff".


In this case, I don't consider signContract part of the seller's or buyer's role in that signing is more closely aligned with a contract than a person. That is, a person does a lot more things - views house, negotiates, etc. If you put all these methods on person you could end up with hundreds of methods on person and violate SRP. Thus, it seems to make sense to me to have

contract.acceptSignature(seller); contract.acceptSignature(buyer);


To a certain extent it may depend on context (eg code for a conveyancing firm would have a different focus to that of a landlord or mortgage company), but a lot of those instances would already be properties of other instances. In particular, since the contract is the object which ties them all together, I would imagine the best syntax would be closer to:

  contract = new Contract(property, seller, buyer, agent)
  contract.sign()
You would then have contract.sign() call property.change_owner(), agent.register_sale() etc. Different classes of properties, buyers, sellers and agents can then implement those methods however they want.

The Contract never needs to know anything about those other classes, so you create the different types of property etc by subclassing the appropriate base class, and customising the functions that the contract calls.

Sure, OO may not be the right choice for every occasion, but it is just another way of doing things - it does not suck, it makes some things easier and some things harder. Granted, in certain languages it makes some things a lot harder - but just avoid those languages. I think a good programmer would be able to see the benefits of OO and decide whether it was the most appropriate choice for their current project, rather than dismissing the entire concept outright based on a poor understanding or bad experience in some languages.


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?

sell_property(contract, property, buyer, seller, agent)


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:

  contract = property.create_contract(seller, buyer, agent)
  contract.sign()
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.


The usual answer, as always, is to add another layer of abstraction. Enterprise Java:

    @Resource("ejb/PropertySale")
    PropertySaleManager psm;

    psm.sell(property, buyer, seller, agent, contract);
This style of code is so popular in the code I've seen, that I almost think EJB3 is just conspiracy to allow procedural programming in Java, and to keep people thinking they still do OO to satisfy their egos.


Which one is best: cats, alligators or pencils?

You're question is about how best to model a domain of which you've given the scantest of details. Domain modelling is something you have to do in any programming paradigm and is equally as obtuse if you don't have proper knowledge of the domain.

Come back with a more detailed description of the domain and then we can start comparing the emergant designs from OO and other paradigms.


The point is, if several objects are involved, all of them get mutated and all of them are in a type hierarchy, you're going to have a hard time justifying which object should be the receiver of the message based on what feels more natural.


Without knowing any more about the system I'd suggest: contract.sign(property, buyer, seller, agent)

It seems that properties, buyers, sellers and agents could exist without 'knowing' about contracts, but a contract at some point needs to reference the other entities. Also, it seems odd that the contract can exist without knowing the details of buyer, seller, etc. I'd prefer: new Contract(property, buyer, seller, agent) and if possible make it immutable.


I would tend towards this solution as well, but does it meet the "looks natural" criteria? Contracts that sign themselves?

I think many classes we invent are nothing more than processes in disguise and we could just as well model them as functions.

A simpler example. Should the BankAccount class have a transferTo(BankAccount other) method to transfer money into another account? What if there are different account types and the exact process depends on the types of both accounts?

Of course it's possible to do it that way, but is it really the cleanest way to imagine this? I don't think so.


A simpler example. Should the BankAccount class have a transferTo(BankAccount other) method to transfer money into another account?

A BankAccount is a data object, so no processing in there. You could have a Banker object (or a MoneyExchanger object, which is "less real world" solution) which would be the actor object responsible for this task. So the natural message is myBanker.transferMoney(sourceBankAccount, targetBankAccount, amount).

What if there are different account types and the exact process depends on the types of both accounts?

You would need to handle of the possible cases into your actor object - which is why it is interesting here to have an actor object specialized in just that. There are several techniques you could apply to dispatch the call to the appropriate implementation of transferMoney().

I think many classes we invent are nothing more than processes in disguise and we could just as well model them as functions.

Well, yes, classes do things - at least actor objects do things. Btw actor objects generally have no state, so you can see them as "super functions", with many advantages over basic functions (inheritance, polymorphism etc).


OO purists actually frown upon the kind of stateless Manager objects you suggest. But I can imagine a scenario in which a money transfer would be something very complex that merits its own process class.

I just don't think it makes sense to mandate that kind of heavy weight function class for every operation or be forced to subordinate an operation to an arbitrary class. For instance, formatting a date in Java works like this:

  Date date = ...;
  DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);
  String s = df.format(date);
So the date format formats the date. Why is that? Why is date subordinated to date format here? It could just as well be the other way around. There may be some implementation related reason for that but conceptually it makes no sense at all, it's impossible to guess and hard to remember.

  s = format(date, date_format) 
makes a lot more sense to me.


OO purists actually frown upon the kind of stateless Manager objects you suggest.

Yes, but pure OOP has been proven impractical many times over the last 20 years. What we are looking for here are practical rules that will help us organize our code in an OOP framework.

So the date format formats the date. Why is that? Why is date subordinated to date format here?

Actually the name "DateFormat" is unfortunate. It should have been "DateFormatter", because it is clearly an actor object - while a date is a data object. Suppose you have a fire in your house, you want help to extinguish it. So you call a fireman, and you basically say to him "here's a fire : do your job". In the present case you have a date, and you want to format it. So you "call" a DateFormat(ter) and you say "here is a date : do your job". That's exactly the same, natural principle of delegation. If you need something to be done, call an expert to do it for you.

Of course it gets quickly tedious having to explicitly call the actor objects for everything. So it might be useful to add "convenience methods" to the data objects, which would simply call the appropriate default actor and pass themselves as arguments to the work operation. So here you would then be able to call date.format(DateFormat.LONG), which means any date would basically become able to format itself. It has good and bad sides, and there is no clear answer (that I know of) to determine in which cases it's okay to do that or not.

s = format(date, date_format) makes a lot more sense to me

That's because you see formatting as an action, and you're thinking action => function. In an OO environment you should rather be seeing formatting as a responsibility, and thinking responsibility => class.


That's because you see formatting as an action, and you're thinking action => function. In an OO environment you should rather be seeing formatting as a responsibility, and thinking responsibility => class.

I understand what you're saying and I understand OO design very well because I used it for decades. I just don't agree. The question we're asking here, I think, is not "how should you behave in an OO environment?", the question is "is OO a good software design principle".

My answer to that is no, and your insistance on dividing classes into data and actor classes tells me that you're well on your way to joining my opinion soon ;-)


PropertySale is a thing. It should be modeled. You'll probably want to store some state with it (when the sale happened, to whom, the price, etc etc).


Yes property sale is a thing. It's a process. Why can that thing not be modelled as a function? Property sales are rather complicated processes so having a class for it could be justified. But OO forces us to do it that way for every tiny operation. It's pure bloat.


It's bloat, until you realise that you need to persist a lot of information about the "thing".

Then you realise that the "thing" isn't actually that little at all.

BTW modern OO has constructs which allow a "Verb"-like structure: Generics. You write logic for a certain class of "things", and your compiler makes sure that what you're doing is actually possible.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: