There is no hard and fast rule which methods an object must have.
Something is an object if you can talk about it as an entity with a name that is familiar to domain experts in either your problem domain or the solution domain.
In essence, if you have a related set of properties and behaviors that you can refer to by a single name, then that set of properties and behaviors is an object.
The behaviors that I refer to fall into two broad categories:
- Requests to take an action. The classic example of
Dog::bark()
falls in this category.
- Requests for information. Most getter functions fall in this category.
There is nothing wrong with an object that has mostly (or only) getters. Some objects in some domains are just model an entity that must exist but that does not have any active behavior. Your User
class might be one of those.
A class X with mostly getters becomes a code smell when
- each getter also has a setter to change the property, and
- either class X has invariants (invalid combinations of property values) that can be broken if you use the setters 'wrongly', or
- class Y is performing operations on the properties of X that would logically fall within the responsibility of X.
An example of the first class with a code smell would be a class like this:
class Square {
int x1, x2, y1, y2;
public:
void SetTopLeft(int x, int y);
void SetBottomRight(int x, int y);
};
Allowing to change the top-left and bottom-right coordinates independently makes it very easy to break the invariant that all sides of a square must have the same length and all corners are square.
An example of the second form of the code smell is a Dog
class where the client moves a Tail
object to make the dog wag its tail.
No, an object does not have to represent an entity.
In fact, I would argue that when you stop thinking about objects as physical entities is when you finally get the benefits that OOP promises.
This isn't the best example, but the Coffee Maker design is probably where the light started to come on for me.
Objects are about messages. They're about responsibilities. They're not about Cars, Users, or Orders.
I know we teach OO this way, but it becomes apparent after a few tries how fundamentally frustrating it is to figure out where things go when you try to do MVC, MVVM or MVWhatever. Either your models become ridiculously bloated or your controllers do. For navigability, it's great to know that anything that touches Vehicles is in the Vehicle.ext file, but when your application is about Vehicles, you inevitably end up with 3000 lines of spaghetti in that file.
When you have a new message to send, you have at least one new object, and perhaps a pair of them. So in your question about a bundle of methods, I would argue that you're potentially talking about a bundle of messages. And each one could be its own object, with it's own job to do. And that's ok. It will become apparent as you split things apart which things really, really need to be together. And you put them together. But you don't immediately drop every method in a vaguely appropriate drawer for convenience sake if you want to enjoy OO.
Let's talk about bags of functions
An object can be just a collection of methods and still be OO, but my "rules" are pretty strict.
The collection should have a single responsibility, and that responsibility can't be as generic as "Does stuff to motors". I might do such a thing as a service-layer facade, but I'm acutely aware that I'm being lazy for navigability/discovery reasons, not because I'm trying to write OO code.
All the methods should be at a consistent layer of abstraction. If one method retrieves Motor objects and another returns Horsepower, that's probably too far apart.
The object should work on the same "kind" of data. This object does stuff to motors(start/stop), this one does things with crank lengths, this one handles ignition sequencing, this one takes an html form. This data could conceivably be fields on the object and it would seem cohesive.
I generally build objects of this sort when I'm doing transforms, composition, or just don't want to worry about mutability.
I find focusing on object responsibilities leads me towards cohesion. There has to be some cohesion to be an object, but there doesn't need to be any fields nor very much behavior for it to be an object. If I was building a system that needed those 5 motor methods, I would start out with 5 different objects that do those things. As I found commonality, I would either start to merge things together or use common "helper" objects. That moves me into open/closed concerns - how can I extract this bit of functionality so I never have to modify that particular file again but still use it where needed?
Objects are about messages
Fields barely matter to an object - getting and setting registers doesn't change the world outside the program. Collaborating with other objects gets the work done. However, the strength of OO is that we can create abstractions so we don't have to think about all the individual details at once. Abstractions that leak or don't make sense are problematic, so we think deeply (too much, maybe) about creating objects that match our mental models.
Key question: Why do these two objects need to talk to each other?
Think of the object as an organ in a person - it has a default purpose and only changes behavior when it receives a specific message that it cares about.
Imagine a scenario where you're in the crosswalk and a car is coming fast. As the brain object, I detect a stressor. I tell the hypothalamus to send corticotrophin-releasing hormone. The pituitary gland gets that message and releases adrenal corticotrophic hormone. The adrenal glands get that message and create adrenaline. When the muscle object gets that adrenaline message it contracts. When the heart get the same message, it beats faster. There's a whole chain of players involved in starting the complex behavior of sprinting across the street and it's the messages that matter. The brain object knows how to get the hypothalamus to send out the alert, but it doesn't know the chain of objects that will eventually make the behavior happen. Likewise the heart has no idea where adrenaline comes from, it just knows to do something different when that shows up.
So in this (simplified) example, the adrenal gland object only needs to know how to take ACTH and make adrenaline. It doesn't need any fields to do that, yet it still seems like an object to me.
Now if our application is designed only to sprint across the street, I may not need the pituitary gland and the adrenal gland objects. Or I only need a pituitary gland object that only does a small part of what we might conceptually see as the "pituitary gland model". These concepts all exist as conceptual entities, but it's software and we can make the AdrenalineSender or MuscleContractor or whatever and not worry much about the "incompleteness" of our model.
Best Answer
This question is not specific to software engineering: it applies to all disciplines working with information.
In 1929, the Belgian surrealist painter René Magritte explained it very intuitively in a masterpiece of art called the treachery of images: the painting shows a pipe on a uniform background, and a caption in French "This is not a pipe". It looks totally absurd, because you see a pipe, so why shouldn’t it be a pipe? This is because it’s not a real pipe. If he’d expressed it positively, he would have written "This is a representation of a pipe".
You explained it appropriately for OOP: a representation of a
Car
is not a car; you can’t use theCar
in memory to drive home. In The Sims your avatar (your representation) could use it to go to the representation of your home. By the way, even in the game, TheCar
representation in memory (properties about the car, its state, and a 3D model) is different from the visual representation of the car representation on the screen (2D picture made with shapes and colors).But there’s more behind it. The information in memory is just a set of bits. We decide what it represents. Take for example a simple byte
0b1000001
. The same byte value could represent65
if we want it to be an integer,A
if we want it to be an ASCII character, a RES control code if we want to use it as EBCDIC character or even a set{ garden, terrace }
if we decide that it’s a bit encoding of a set where the 7th bit corresponds to a terrace and the first bit a garden.In memory, there are only bits. The representation is the mapping we do to give them some kind of meaning. For an OOP object, that mapping is done between the values in memory and the state of the object and the methods that make its behavior. How is, of course, language specific (examples: C++, Java).