Hacker News new | ask | show | jobs
by dragly 3008 days ago
You have a good point, but there are some cases where I have a hard time seeing how you would avoid getters and setters completely.

Say you are creating a UI library which has a TextField class. What should the TextField API have in place of the usual getText/setText and isEnabled/setEnabled methods? Say, if the library should support developers who want to create a text field that is automatically filled with data on a button press and can be disabled when a checkbox is ticked.

Note that I'm not arguing against you - I'm honestly curious about what the TextField should be replaced with or what its API should look like.

1 comments

In this case I would separate the data (probably the model the text field is wrapping) from the UI widgets that expose it... the model being a dumb struct and the text field being a smart object with methods that look (if you squint) more like getters/setters.

But that’s just the thing, the text field in this case isn’t something you’d ever confuse with a dumb struct... it has methods that accept key input, or disable/enable it, or calculate its clipping area, etc. I don’t look at these as “getters” or “setters”, but instead just methods on an object like any other.

It may seem like I’m shifting the goalpost here and I’m sorry, I really do have trouble articulating this, but getters/setters seem like an anti pattern to me precisely because they let the consumer think they’re just dumb properties (along with invariants like the data you get out matching the data you put in, which you can never actually assume) when they’re anything but.

Thanks for clarifying! I really don't think you're shifting the goalpost. What you're saying is basically that the getText/setText methods are part of the API of the visual TextField, like a moveLeft method would be part of the API of a robot controller. A dumb struct with mutable members, such as a Point with public x, y, z members, is on the other hand both the API and the data in the same object.

It seems to be important to separate data from logic, and API from implementation in discussions like these. Sure, I can make a nice API for a visual Rectangle object with dynamic width and height, and add a check to my setWidth method to make sure the width is never negative and the changes trigger a repaint. But if I need a Rectangle struct to store data in the implementation of my library, it can be way simpler to just make the Rectangle have a public final/const width member, and set and check the width in the constructor.

Sometimes you need the smart Rectangle, and sometimes you need a dumb struct with width and height.

Why wouldn't you want to keep the constraints as close to the data as possible? That way you avoid the issue that the Rectangle struct is reused somewhere else, but without the correct constraints.
IMO the constraints should be as close to the thing that needs the data, as possible. Your object is an expression of data that may be valid in some contexts and invalid in others, and trying to pick any one of them is difficult (and leads to things like complex inheritance trees to express all the different flavors of constraints.)

I think this is the essence of the "composition over inheritance" idea that seems to be the most violated piece of sound OO advice out there.

These constraints should ideality be in the type system, or other statically checked constraints.

Most OO languages have type systems not fitting for such checks, though. Thus runtime checks, often a part of a setter.