Comment by mrkeen

9 months ago

> Your Order class has a collection of Product items, but you can update an order, cancel a order, repeat an order, etc. This behavior should be member functions.

This is how to fuck up OO and give it a bad name:

  order.update(..) // Now your Order knows about the database.

  order.cancel(..) // Now your Order can Email the Customer about a cancellation.

  order.repeat(..) // Now your Order knows about the Scheduler.

What else could Order know about? Maybe give it a JSON renderer .toJson(), a pricing mechanism .getCost(), discounting rules .applyDiscount(), and access to customer bank accounts for .directDebit(); Logging and backup too. And if a class has 10+ behaviours you've probably forgotten 5 more.

An Order is a piece of paper that arrived in your mailbox. You can't take a sharpie to it, you can't tell it to march itself into the filing cabinet. It's a piece of paper which you.read() so that you.pack() something into a box and take it to the post office. You have behaviours and the post office has behaviours. The Order and the Box do not. At best they have a few getters() or some mostly-static methods for returning aggregate data - but even then I'd probably steer clear. For instance: if the Order gave me a nice totalPrice() method, it simplifies things for later right? Well no, because in TaxCalculator (not order.calculateTax()) I will want to drill down into the details, not the aggregate. Likewise for DiscountApplier.

> Does it make sense to have objects without behavior? No, not in OO and elsewhere as well.

It does, just like in the Domain (real-world Orders). Incidentally, I believe objects-without-behaviours is one of the core Clojure tenets.

Since this is HN's monthly UB-bashing thread, I should point out that I learnt most of this stuff from him. (It's more from SOLID though, I don't think I have much to say on about cleanliness.)

The above examples violate SRP and DI.

"Single reason to change": If order.cancel(..) knows about email, then this is code I have to change if the cancellation rules change or if the email system changes. What if we don't notify over email anymore? Order has to become aware of SMS or some other tech which will cause more reasons for change.

"Dependency inversion": People know what Orders are, regardless of technical competence. They can exist without computers or any particular implementation. They are therefore (relative to other concerns here) high-level and abstract. Orders are processed using a database, Kafka and/or a bunch of other technologies (or implementation details). DI states that abstract things should not depend on concrete things.

We have a disagreement about the core of OOP. In English, a simple sentence like "The cat eats the rat" can be broken down as follows:

- Cat is the subject noun

- Eats is the verb

- Rat is the object noun

In object-oriented programming, the subject is most often the programmer, the program, the computer, the user agent, or the user. The object is... the object. The verb is the method.

So, imagine the sentence "the customer canceled the order."

- Customer is the subject noun

- Canceled is the verb

- Order is the object noun

In OOP style you do not express this as customer.cancel(order) even though that reads aloud left-to-right similarly to English. Instead, you orient the expression around the object. The order is the object noun, and is what is being canceled. Thus, order.cancel(). The subject noun is left implicit, because it is redundant. Nearly every subject noun in a given method (or even system) will be the same programmer, program, computer, user agent, or user.

For additional perspectives, I recommend reading Part I of "Object-Oriented Analysis and Design with Applications" (3rd edition) by Grady Booch et. al, and "Object Thinking" by David West.

---

That said, I think you're right about the single responsibility principle in this example. A class with too many behaviors should usually be decomposed into multiple classes, with the responsibilities distributed appropriately. However, the object should not be left behavior-less. It must still be an anthropomorphized object that encapsulates whatever data it owns with behavior.

  • > So, imagine the sentence "the customer canceled the order."

    > - Customer is the subject noun

    And this is wrong. Because the customer did not cancel the order. The customer actually asked for the order to be canceled. And the order was then canceled by "the system". Whatever that system is.

    And that is the reason why it is not expressed as customer.cancel(order) but rather system.cancel(order, reason = "customer asked for it").

    > Thus, order.cancel(). The subject noun is left implicit, because it is redundant.

    Ah, is that so? Then, I would like you to tell me: what happens if there are two systems (e.g. a legacy system and a new system, or even more systems) and the order needs to be sometimes cancelled in both, or just one of those systems? How does that work now in your world?

    • mrkeen mentioned dependency inversion (DI). I think it makes sense in oop for an order to have a cancel method, but the selection of this method might be better as something configured with DI. This is because the caller might not be aware of everything involved, as well.

      If the system is new and there's only one way to do it, it's not worth sweating over it. But if a new requirement comes up it makes sense to choose a way to handle that.

      For example, an order may be entered by a salesperson or maybe by a customer on the web. The cancellation process (a strategy, perhaps) might be different. Different users might have different permissions to cancel one order or another. The designer of the website probably shouldn't have to code all that in, maybe they just should have a cancel function for the order and let the business logic handle it. Each order object could be configured with the correct strategy.

      If you don't want to use OO, that's fine, but you still have to handle these situations. In what module do you put the function the web designer calls? And how do you choose the right process? These patterns will perhaps have other names in other paradigms but the model is effectively the same. The difference is where you stuff the complexity.

      1 reply →

    • > The customer actually asked for the order to be canceled.

      This is why many object-oriented programmers prefer to talk about message passing instead of method calling. It is indeed about asking for the order to be canceled, and the order can decide whether to fulfill that request.

      4 replies →

  • The comparison to English grammar is unnecessary. I didn't use it in my argument and you said it doesn't work that way either, so when you arrive at

    > The order is the object noun, and is what is being canceled. Thus, order.cancel()

    You've just restated the position I argued against, without an argument.