Hacker News new | ask | show | jobs
by throwaway894345 2104 days ago
> And somehow the idea that DoD is a replacement for OOP when it’s really an orthogonal concern.

I’m sure this is true for some definition of OOP, but I’ve seen too much OOP code that insists on hanging every method off of the most plain-old-data of classes. A Book class has to have a buyFromAmazon() method and consequently each instance has a private reference to an Amazon SDK client, and if you want to buy 10K books, you iterate over a list and invoke this method 10K times.

Of course, some will argue that this is bad OOP and true OOP doesn’t look like this, and anyway you can write bad code in any paradigm! Of course, OOP is uniquely plagued with bad code (much more so than other paradigms) and this pretty transparent no-True-Scotsman argument is just moving semantic goalposts (as such arguments do).

2 comments

Whilst it’s impressive you’ve managed to hold an argument with yourself I think it’s more the case that anything as broad as a programming paradigm will naturally hold multiple approaches. Is immutability a defining feature of FP? Let the battles commence.

Likewise critics might focus heavily on inheritance or some other feature of OOP that is easy to critique.

And whilst I’m not particularly interested in defending OOP I think describing it as uniquely plagued with bad code is not something anyone should just take axiomatically.

> Whilst it’s impressive you’ve managed to hold an argument with yourself

I like to address the boringly predictable responses up front so we can avoid rehashing the same silly arguments over and over. I apologize if that spoiled your fun.

> Is immutability a defining feature of FP? Let the battles commence.

I think most would agree that immutability is more prevalent in FP even if struct immutability isn’t required. Moreover, everyone would agree that first class functions and functional composition are key characteristics. Contrast that with OOP where you have some OOP enthusiasts arguing that inheritance is a defining feature and others who argue it isn’t. Some argue that the “kingdom of nouns”, banana-gorilla-jungle design is inherently OOP and others argue it’s “bad programming and not true OOP”. Others argue that message passing is required, but many others argue to the contrary. While other programming paradigms have fuzzy edges, OOP has no discernible shape at all.

> And whilst I’m not particularly interested in defending OOP I think describing it as uniquely plagued with bad code is not something anyone should just take axiomatically.

Why are there no equivalent criticisms of FP or DO? We might find FP codebases that are overly abstract or a bit slow, but we don’t tend to find (m)any that are designed such that a Book object holds a reference to an Amazon SDK client or a banana with a reference to the gorilla holding it with a reference to the entire jungle. There aren’t prevalent guidance to write code like that in other communities like there is (or perhaps “was”) in OOP circles. Similarly, there aren’t enterprise-y abstract factory beans or anything like that in the FP world.

Mind you, I’m not dumping on OOP—indeed I couldn’t if I wanted to because it has no agreed upon definition, per its proponents.

alankay on June 20, 2016

Object oriented to me has always been about encapsulation, sending messages, and late-binding.

https://news.ycombinator.com/item?id=11940050

I’m well aware of Alan’s definition. Nevertheless relatively few people hold that view.
I think the problem with OOP is that the paradigm itself doesn't offer much. It's a very generic paradigm with a massive sandbox. It needs to be more opinionated.
This is only "bad OOP" if you need to buy 10K books at once and it is actually too slow and there is a significant amount of fat to trim (if you need to call a web service once for every book, incoherent memory access is likely to be negligible).

Otherwise it's obvious, cheap to write, easier to get right than more complicated approaches, and good enough. True OOP is OOP that meets goals.

Other approaches aren’t more complicated, and this is worse even if you never need to send books in batch because it tightly couples “book” to the Amazon AWS SDK client. Anything that interacts with books now has to take a dependency on the Amazon SDK. A much simpler, better design would be to just call client.buyBook(book) or client.buyBooks(books).

But more importantly, my point is that you have your definition of whether this is OOP or not but lots of OOP proponents will say that this is not true OOP.

Actually, the Amazon client could be a component that is managed by a sane dependency injection system (e.g. a factory of "books that can be bought on Amazon") and is used by a book to implement the abstract book operation "buy a copy of me". This would be basically equivalent to a "book buyer" object with a "try to buy this book" operation, with small advantages and disadvantages (either the books or the bookstores are privileged as the main entity in the many to many "can be bought at" relationship).
Better, but the book still needs a dependency on your "book buyer" (let's call it "Retailer" for sanity) interface even though there are likely lots of things to do with a book besides buy it, and those applications shouldn't have to care about the details of book buying. For every verb, the book field needs at least one new field to support that verb (e.g., the `Book.retailer` field to support the `buyBook()` method), even though each thing you might want to do with a book involves at most a few of those fields.

Moreover, inevitably someone downstream from the author of the Book class will have a use case for dealing with books that the author hasn't accounted for, so they have to extend the book class for their own use case, tacking ever more fields onto that book even though their use case only cares about a few of the fields.

Additionally, some of the things you might want to do with a book might also involve some other plain-old-data-structure--how do you determine which plain-old-data should host the method? Why is it `Book.doThingWithCar(Car c);` and not `Car.doThingWithBook(Book b);` or simply a static method `doThing(Car c, Book b);`?

Further still, why create a Book.buy() method that's just going to delegate to `Retailer.buyBook(this)` anyway? If the advantage is that you don't have to explicitly pass the `retailer` around, that's fine enough but there are ways to do that without baking retailer details into every book instance (e.g., a closure: `buyBook = function() { retailer.buyBook(book) }` or if you're a glutton for punishment, you can create a class weds a book and a retailer together:

    class BuyBookOperation {
        private Retailer _retailer;
        private Book _book;

        public BuyBookOperation(Retailer retailer, Book book) {
            this._retailer = retailer;
            this._book = book;
        }

        public do() { this._retailer.buyBook(this._book); }
    }
Further, you might want to buy a book from many retailers--why should you have to do `book.setRetailer(amazonClient); book.buyBook(); book.setRetailer(barnesAndNobleClient); book.buyBook();`? Why not simply `amazonClient.buyBook(book); barnesAndNobleClient.buyBook(book);`? Even if you abstract away the mutation by creating a `Book.buyFromRetailer(Retailer r)` method that sets the retailer and then calls Book.buy(), you now have a potential race condition in parallel code and you still have no advantage over retailer.buyBook(book).

Lastly, if at some point you do need to buy books in batch, how do you support that? Does each book class also need a reference to every List<Book> that references it so it can do book.buyInBatch()? Do you make a book.buyInBatch(List<Book> otherBooks) method? Do you just eat the performance hit and make individual calls to book.buy() even though the Retailer interface has a buyManyBooks(List<Book> books) method that could buy all books in a single HTTP request? What advantages do these approaches have over `retailer.buyManyBooks(books)`?

So this "plain old data needs to depend on everything that might be necessary for any activity involving a book" pattern has no obvious benefits, but it creates a lot of unnecessary coupling, creates a lot of awkward interface decisions when there are other objects involved in an action (including efficiently dealing with collections of your plain old data structure), and it probably pushes you into mutation unnecessarily which makes it a lot more difficult to parallelize your code.

This pattern seems to be a pretty transparent anti-pattern to me, and if it's "inherently OOP" then OOP is problematic.