Most active commenters
  • gigatexal(3)
  • bccdee(3)

←back to thread

249 points mattcollins | 21 comments | | HN request time: 1.622s | source | bottom
1. gigatexal ◴[] No.42190772[source]
I’m gonna buy the book but I prefer composition over OOP. I prefer to have an init that takes some params where those params are fully baked clients of whatever services I need and then the class just uses them as needed. I don’t see a lot of value in having a Python class that might have a few or more classes that it extends where all the functions from all the classes crowd up the classes namespace.

Class Foo.__init__(self, db, blob_storage, secrets_manager, …)

Instead of class Foo(DB, BlobStorer, SecretsMgr)

Etc

replies(4): >>42190800 #>>42190856 #>>42191086 #>>42193088 #
2. yxhuvud ◴[] No.42190800[source]
Why on earth do you put composition and OOP as opposing techniques? Composition is just one more technique in the OOP toolbox and there is nothing in OOP that mandates an inheritance based architecture.
replies(1): >>42191144 #
3. inopinatus ◴[] No.42190856[source]
These are complementary not contradictory ideas. One of the principal takeaways from the Ruby edition (and many of Sandi Metz’s conference talks) is undoubtedly a mindset of, and techniques for, writing compositional OO code.
4. Toutouxc ◴[] No.42191086[source]
Then you're going to be pleasantly surprised, because composition is actually a genuine OOP technique and Sandi Metz advocates for exactly this kind of sane OOP focused on encapsulation and objects making sense, instead of masturbating with class hierarchies.
replies(3): >>42191387 #>>42192918 #>>42196015 #
5. crabmusket ◴[] No.42191144[source]
Mainstream OOP languages (looking at you Java) have failed to make composition as convenient as inheritance.
replies(4): >>42191297 #>>42192325 #>>42192756 #>>42193209 #
6. flakes ◴[] No.42191297{3}[source]
The common toolkits today (spring boot, google guice, etc) are much more focused on composition over inheritance, by injecting arguments and implementing pure interfaces rather than extending base classes. Older legacy Java frameworks and bad teachers are more at fault than the Java language itself IMO.
replies(1): >>42191310 #
7. crabmusket ◴[] No.42191310{4}[source]
I take your point, though having `extends` as a first-class language feature surely encouraged that culture and approach in older frameworks right?
replies(1): >>42191327 #
8. flakes ◴[] No.42191327{5}[source]
There are some valid cases where extends really can help, and IMO the language would feel limited without it. Maybe if the language designers had their time back they could have taken an approach like Golang with nested structs and syntactic sugar for calling their attributes/methods.

The main reason I see new devs opt for extends, is because that was 99% of the content in their Java 101 programming course, not because it exists in the language. Imagine how many more `friend`s we would have in cpp if that was crammed down everyone's throats? :)

9. gigatexal ◴[] No.42191387[source]
I used to work at a flask shop that did views with 3-5 or more inherited classes. Nobody could really follow how everything worked. It was insane.

Anyways yeah give me composition and flat classes all day long.

10. yxhuvud ◴[] No.42192325{3}[source]
How is composition inconvenient?
replies(1): >>42197858 #
11. _old_dude_ ◴[] No.42192756{3}[source]
Very true, in Java, at least in the last 20 years, inheritance is de-facto deprecated, all new bits and bolts like enums, annotations, lambdas or records do not support inheritance.

So you have to use composition.

12. gigatexal ◴[] No.42192918[source]
What’s funny is I did composition for a take home project when interviewing at a place and they said the approach was too complicated and failed me for it. They wanted multiple inheritance instead. Fair enough. Their codebase probably had a lot of it and my not showing it probably told them I didn’t understand it.
13. vram22 ◴[] No.42193088[source]
>I’m gonna buy the book but I prefer composition over OOP.

The GoF book (the design patterns book) says in a page right near the start, "Prefer composition over inheritance", in the middle of an otherwise blank page, presumably to emphasize the importance of that advice.

As others have replied, composition is one technique you can use in OOP, not something that is the opposite of OOP.

You can also use composition in non-OOP procedural languages like C, by having a struct within a struct.

https://www.google.com/search?q=can+you+have+nested+structs+...

14. watwut ◴[] No.42193209{3}[source]
How is composition in Java inconvenient?
15. bccdee ◴[] No.42196015[source]
But I've read the book, and her solution to the "bottles of beer" problem involves encoding all the logic into an elaborate class hierarchy!

I'm not rabidly anti-OOP, but the point at which I turn against it is when the pursuit of "properly" modelling your domain with objects obscures the underlying logic. I feel like this book reaches that point. This is her stance on polymorphism:

> As an OO practitioner, when you see a conditional, the hairs on your neck should stand up. Its very presence ought to offend your sensibilities. You should feel entitled to send messages to objects, and look for a way to write code that allows you to do so. The above pattern means that objects are missing, and suggests that subsequent refactorings are needed to reveal them.

Absolutely not! You should not, as a rule, be replacing conditional statements with polymorphic dispatch. Polymorphism can be a useful tool for separating behaviour into modules, but that trade-off is only worthwhile when the original behaviour is too bloated to be legible as a unit. I don't see an awareness of that trade-off here. That's my problem.

replies(1): >>42196432 #
16. Toutouxc ◴[] No.42196432{3}[source]
Well, the entire book is focused on solving a laughably trivial problem, any solution is going to feel excessive. The elaborate object hierarchy that she uses would obviously feel different in real world, complicated domain.

I found the excerpt in the book and I don't see her mentioning traditional class-level polymorphism (of the Java kind) anywhere around it. What SM generally advocates for is using OBJECT hierarchies to implement behaviors and encapsulate logic, the objects usually being instances of simple (and final!) free-standing classes. All thanks to the ability of any Ruby object to send messages to (call methods of) a different object, without knowing or caring about its type or origin, and the other object supplying the behavior without having to check its own type (because the correct behavior is the only one that the object, being a specialized object, even knows). This is done at runtime and is called "composition" (as in "composition over inheritance") and is different from using pre-built CLASS hierarchies to implement behaviors, aka "inheritance" (as in "composition over inheritance"). In Ruby, composition is Dog.new(Woofing.new), whereas using inheritance (class hierarchies) is Dog.new after you've done "include Woofing" inside the class.

I don't know Python well, but it seems like the person in the top-level comment expressed their dislike for the second kind.

replies(1): >>42199802 #
17. mdaniel ◴[] No.42197858{4}[source]
Contrast the Java way

    class Delegated implements Base {
        final Base b;
        public Delegated(Base b) { this.b = b; }
        @Override
        public void printMessage() { b.printMessage(x); }
        @Override
        public void printMessageLine() { b.printMessageLine(x); }
with the Kotlin way https://kotlinlang.org/docs/delegation.html#overriding-a-mem...

OT1H, yes, sane people using IJ would just alt-Insert, choose delegate to, and move on with life. But those misguided folks using VS Code, vim, or a magnetized needle and a steady hand would for sure find delegating to a broader interface to be a huge PITA

18. bccdee ◴[] No.42199802{4}[source]
I should clarify that the elaborate class hierarchy in the book is inheritance-based. When there's one bottle of beer on the wall, she instantiates `new BottleNumber1()`, which inherits from `BottleNumber` and overrides the method `container()` to return the singular "bottle" rather than the plural "bottles" which the base class's `BottleNumber::bottle()` would return (in the javascript edition, at least).
replies(1): >>42205852 #
19. mekoka ◴[] No.42205852{5}[source]
Inheritance is not a banned practice. It should just be your second choice when there's a better path through composition. Do you see one here? Interfacing to address Liskov's substitution is a perfectly valid reason to "extend" in many older languages, since their inheritance and interface mechanism are conflated. The way it's done here is fine. Single parent, shallow and only for the purpose of overriding and specializing.

Also, the real issue SM is trying to address is actually single responsibility and open-close, which aren't just an OO thing.

As you'll design your own libraries' functional APIs, you'll have to decide whether to publish fewer functions, with a rich set of behaviors controlled through the passing of (many) parameters (and conditionals in the function body); or take a finer grain approach with multiple functions that abide as much as possible to single responsibility and only take few input about the state. I'd bet that the former will quickly raise complaints, by both maintainers and users alike, because of all the ifs and buts typically associated with it.

For the same reasons, you don't want your methods to have divergent behavior based on state. You want multiple types.

replies(1): >>42209044 #
20. bccdee ◴[] No.42209044{6}[source]
> Inheritance is not a banned practice.

Maybe it should be. Go and Rust do not provide implementation inheritance, and I think that's for the best. Few language features have led to so much spaghetti code.

> It should just be your second choice when there's a better path through composition. Do you see one here?

I don't think this logic should be split over a graph of objects at all. This is highly cohesive code; it shouldn't be factored apart. If Metz made it clear that this was an example just for the purposes of illustration, that'd be one thing. However, the stance taken is "this is good code, and you should try to write all your code like this." It's not, though, and you shouldn't.

replies(1): >>42210118 #
21. mekoka ◴[] No.42210118{7}[source]
> Maybe it should be. Go and Rust do not provide implementation inheritance, and I think that's for the best. Few language features have led to so much spaghetti code.

I agree, but there are reasons inheritance becomes problematic. Just throwing the baby is not helpful. Go, Elixir, Rust are all relatively young languages and although they did away with inheritance, they make use of interfaces/protocols/traits, hinting at that idea very much worth preserving. That is, regardless of which language you work with, if you have access to facilities that can make the concept of interface work decently, use them. Older languages (like Ruby, Python, Java) tended to use the inheritance mechanism to accomplish the same.

> If Metz made it clear that this was an example just for the purposes of illustration

She did. Perhaps read the book's introduction. She explains why she wrote a whole book that uses a banal example as a teaching tool to illustrate how SOLID should map to real world OOP. Her painstakingly going through refactoring and providing reasons for each decisions is not, in fact, to teach us to write code that generates a song.

> I don't think this logic should be split over a graph of objects at all [...] However, the stance taken is "this is good code, and you should try to write all your code like this." It's not, though, and you shouldn't.

Fair enough, but these are your very own and very isolated opinions. As an OO skeptic myself, I'll side with others like me, along with the FP enthusiasts, who originally approached this book with reserve, but came out with positive impressions.

Regarding the object graph, whether in Go, Elixir, or Rust, "No Bottle", "One Bottle", "Six Pack", and "Many Bottles" are distinct things and should be represented accordingly. Conflating them is a violation of principles that also apply in those languages. A very common, yet equally banal example that should put the debate to rest about this is the trifecta: Shape.Area(), Square.Area(), and Circle.Area(). Of course, it remains the programmer's prerogative whether they indulge with if/else in their implementation, but it should still be considered the exception rather than the rule.