92

I'm having something of a hard time with designing classes in an oo way. I've read that objects expose their behavior, not their data; therefore, rather than using getter/setters to modify data, the methods of a given class should be "verbs" or actions operating on the object. For example, in an 'Account' object, we would have the methods Withdraw() and Deposit() rather than setAmount() etc. See: Why getter and setter methods are evil.

So for example, given a Customer class that keeps alot of information about the customer, e.g. Name, DOB, Tel, Address etc., how would one avoid getter/setters for getting and setting all those attributes? What 'Behavior' type method can one write to populate all that data?

26
  • 3
    possible duplicate of Is there any reason to use "plain old data" classes?
    – gnat
    Commented May 18, 2015 at 15:29
  • 3
    see also: When are Getters and Setters Justified
    – gnat
    Commented May 18, 2015 at 15:29
  • 9
    I would point out that if you have to deal with Java Beans specification, you will be having getters and setters. Lots of things use Java Beans (Expression Language in jsps) and trying to avoid such will likely be... challenging.
    – user40980
    Commented May 18, 2015 at 16:32
  • 5
    ...and, from the opposite perspective from MichaelT: if you're not using the JavaBeans spec (and I personally think you should avoid it unless you're using your object in a context where it's necessary), then there's no need for the "get" wart on getters, especially for properties that don't have any corresponding setter. I think a method called name() on Customer is as clear, or clearer, than a method called getName(). Commented May 18, 2015 at 21:05
  • 2
    @Daniel Pryden: name() can mean both to set or to get?... Commented May 19, 2015 at 16:51

10 Answers 10

63
+50

As stated in quite a few answers and comments, DTOs are appropriate and useful in some situations, especially in transferring data across boundaries (e.g. serializing to JSON to send through a web service). For the rest of this answer, I'll more or less ignore that and talk about domain classes, and how they can be designed to minimize (if not eliminate) getters and setters, and still be useful in a large project. I also won't talk about why remove getters or setters, or when to do so, because those are questions of their own.

As an example, imagine that your project is a board game like Chess or Battleship. You might have various ways of representing this in a presentation layer (console app, web service, GUI,etc.), but you also have a core domain. One class you might have is Coordinate, representing a position on the board. The "evil" way to write it would be:

public class Coordinate
{
    public int X {get; set;}
    public int Y {get; set;}
}

(I'm going to be writing code examples in C# rather than Java, for brevity and because I'm more familiar with it. Hopefully that's not a problem. The concepts are the same and the translation should be simple.)

Removing Setters: Immutability

While public getters and setters are both potentially problematic, setters are the far more "evil" of the two. They're also usually the easier to eliminate. The process is a simple one- set the value from within the constructor. Any methods which previously mutated the object should instead return a new result. So:

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}

    public Coordinate(int x, int y)
    {
        X = x;
        Y = y;
    }
}

Note that this doesn't protect against other methods in the class mutating X and Y. To be more strictly immutable, you could use readonly (final in Java). But either way- whether you make your properties truly immutable or just prevent direct public mutation through setters- it does the trick of removing your public setters. In the vast majority of situations, this works just fine.

Removing Getters, Part 1: Designing for Behavior

The above is all well and good for setters, but in terms of getters, we actually shot ourselves in the foot before even starting. Our process was to think of what a coordinate is- the data it represents- and create a class around that. Instead, we should have started with what behavior we need from a coordinate. This process, by the way, is aided by TDD, where we only extract classes like this once we have a need for them, so we start with the desired behavior and work from there.

So let's say that the first place you found yourself needing a Coordinate was for collision detection: you wanted to check if two pieces occupy the same space on the board. Here's the "evil" way (constructors omitted for brevity):

public class Piece
{
    public Coordinate Position {get; private set;}
}

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}
}

    //...And then, inside some class
    public bool DoPiecesCollide(Piece one, Piece two)
    {
        return one.X == two.X && one.Y == two.Y;
    }

And here's the good way:

public class Piece
{
    private Coordinate _position;
    public bool CollidesWith(Piece other)
    {
        return _position.Equals(other._position);
    }
}

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;
    public bool Equals(Coordinate other)
    {
        return _x == other._x && _y == other._y;
    }
}

(IEquatable implementation abbreviated for simplicity). By designing for behavior rather than modelling data, we've managed to remove our getters.

Note this is also relevant to your example. You may be using an ORM, or display customer information on a website or something, in which case some kind of Customer DTO would probably make sense. But just because your system includes customers and they are represented in the data model does not automatically mean you should have a Customer class in your domain. Maybe as you design for behavior, one will emerge, but if you want to avoid getters, don't create one pre-emptively.

Removing Getters, Part 2: External Behaviour

So the above is a good start, but sooner or later you will probably run into a situation where you have behavior which is associated with a class, which in some way depends on the class's state, but which doesn't belong on the class. This sort of behavior is what typically lives in the service layer of your application.

Taking our Coordinate example, eventually you'll want to represent your game to the user, and that might mean drawing to the screen. You might, for example, have a UI project which uses Vector2 to represent a point on the screen. But it would be inappropriate for the Coordinate class to take charge of converting from a coordinate to a point on the screen- that would be bringing all sorts of presentation concerns into your core domain. Unfortunately this type of situation is inherent in OO design.

The first option, which is very commonly chosen, is just expose the damn getters and say to hell with it. This has the advantage of simplicity. But since we're talking about avoiding getters, let's say for argument's sake we reject this one and see what other options there are.

A second option is to add some kind of .ToDTO() method on your class. This- or similar- may well be needed anyway, for example when you want to save the game you need to capture pretty much all of your state. But the difference between doing this for your services and just accessing the getter directly is more or less aesthetic. It still has just as much "evil" to it.

A third option- which I've seen advocated by Zoran Horvat in a couple of Pluralsight videos- is to use a modified version of the visitor pattern. This is a pretty unusual use and variation of the pattern and I think people's mileage will vary massively on whether it's adding complexity for no real gain or whether it's a nice compromise for the situation. The idea is essentially to use the standard visitor pattern, but have the Visit methods take the state they need as parameters, instead of the class they're visiting. Examples can be found here.

For our problem, a solution using this pattern would be:

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;

    public T Transform<T>(IPositionTransformer<T> transformer)
    {
        return transformer.Transform(_x,_y);
    }
}

public interface IPositionTransformer<T>
{
    T Transform(int x, int y);
}

//This one lives in the presentation layer
public class CoordinateToVectorTransformer : IPositionTransformer<Vector2>
{
    private readonly float _tileWidth;
    private readonly float _tileHeight;
    private readonly Vector2 _topLeft;

    Vector2 Transform(int x, int y)
    {
        return _topLeft + new Vector2(_tileWidth*x + _tileHeight*y);
    }
}

As you can probably tell, _x and _y aren't really encapsulated any more. We could extract them by creating an IPositionTransformer<Tuple<int,int>> which just returns them directly. Depending on taste, you may feel this makes the entire exercise pointless.

However, with public getters, it's very easy to do things the wrong way, just pulling data out directly and using it in violation of Tell, Don't Ask. Whereas using this pattern it's actually simpler to do it the right way: when you want to create behaviour, you'll automatically start by creating a type associated with it. Violations of TDA will be very obviously smelly and probably require working around a simpler, better solution. In practice, these points make it much easier to do it the right, OO, way than the "evil" way that getters encourage.

Finally, even if it isn't initially obvious, there may in fact be ways to expose enough of what you need as behavior to avoid needing to expose state. For example, using our previous version of Coordinate whose only public member is Equals() (in practice it would need a full IEquatable implementation), you could write the following class in your presentation layer:

public class CoordinateToVectorTransformer
{
    private Dictionary<Coordinate,Vector2> _coordinatePositions;

    public CoordinateToVectorTransformer(int boardWidth, int boardHeight)
    {
        for(int x=0; x<boardWidth; x++)
        {
            for(int y=0; y<boardWidth; y++)
            {
                _coordinatePositions[new Coordinate(x,y)] = GetPosition(x,y);
            }
        }
    }

    private static Vector2 GetPosition(int x, int y)
    {
        //Some implementation goes here...
    }

    public Vector2 Transform(Coordinate coordinate)
    {
        return _coordinatePositions[coordinate];
    }
}

It turns out, perhaps surprisingly, that all the behavior we really needed from a coordinate to achieve our goal was equality checking! Of course, this solution is tailored to this problem, and makes assumptions about acceptable memory usage/performance. It's just an example that fits this particular problem domain, rather than a blueprint for a general solution.

And again, opinions will vary on whether in practice this is needless complexity. In some cases, no such solution like this might exist, or it might be prohibitively weird or complex, in which case you can revert to the above three.

13
  • Beautifully answered! I would like to accept, but first some comments:1. I do think the toDTO() is great, bcuz ur not accessing the get/set, which allows u to change the fields given to DTO w/o breaking existing code. 2. Say Customer has enough behaviour to justify making it an entity, how would u access props to modify them, e.g. address/tel change etc. Commented May 28, 2015 at 17:02
  • @IntelliData 1. When you say "change the fields", you mean change the class definition or mutate the data? The latter can just be avoided by removing public setters but leaving the getters, so the dto aspect is irrelevant. The former isn't really the (whole) reason that public getters are "evil". See programmers.stackexchange.com/questions/157526/… for example. Commented May 28, 2015 at 17:58
  • @IntelliData 2. This is difficult to answer without knowing the behaviour. But probably, the answer is: you wouldn't. What behaviour could a Customer class have that requires being able to mutate its telephone number? Perhaps the customer's telephone number changes and I need to persist that change in the database, but none of that is the responsibility of a behaviour-providing domain object. That's a data-access concern, and would probably be handled with a DTO and, say, a repository. Commented May 28, 2015 at 18:03
  • 1
    @IntelliData Keeping the Customer domain object's data relatively fresh (in sync with the db) is a matter of managing its lifecycle, which is also not its own responsibility, and would again probably end up living in a repository or a factory or an IOC container or whatever instantiates Customers. Commented May 28, 2015 at 18:03
  • 2
    I really like the Design for Behavior concept. This informs the underlying "data structure" and helps avoid the all too common anemic, hard-to-use classes. plus one.
    – radarbob
    Commented Jun 3, 2015 at 15:38
77

The simplest way to avoid setters is to hand the values to the constructor method when you new up the object. This is also the usual pattern when you want to make an object immutable. That said, things are not always that clear in the real world.

It is true that methods should be about behavior. However, some objects, like Customer, exist primarily to hold information. Those are the kinds of objects that benefit the most from getters and setters; were there no need at all for such methods, we would simply eliminate them altogether.

Further Reading
When are Getters and Setters Justified

18
  • 3
    so why all the hype about 'Getter/Setters' are evil? Commented May 18, 2015 at 15:03
  • 47
    Just the usual pontification by software developers who should know better. For the article you linked, the author is using the phrase "getters and setters are evil" to get your attention, but that doesn't mean the statement is categorically true. Commented May 18, 2015 at 15:04
  • 13
    @IntelliData some people will tell you that java itself is evil.
    – null
    Commented May 18, 2015 at 15:09
  • 19
    @MetaFight setEvil(null);
    – user40980
    Commented May 18, 2015 at 16:26
  • 4
    Certainly eval is Evil Incarnate. Or a near cousin.
    – bishop
    Commented May 18, 2015 at 16:47
64

It is perfectly fine to have an object which exposes data rather than behavior. We just call it a "data object". The pattern exists under names like Data Transfer Object or Value Object. If the purpose of the object is to hold data, then getters and setters are valid to access the data.

So why would someone say "getter and setter methods are evil"? You will see this a lot - someone takes a guideline which is perfectly valid in a specific context and then remove the context in order to get a more hard-hitting headline. For example "favor composition over inheritance" is a fine principle, but soon enough someone is going to remove the context and write "Why extends is evil" (hey, same author, what a coincidence!) or "inheritance is evil and must be destroyed".

If you look at the content of the article it actually have some valid points, it just stretches the point to make a click-baity headline. For example, the article states that implementation details should not be exposed. This is the principles of encapsulation and data hiding which are fundamental in OO. However, a getter method does not by definition expose implementation details. In the case of a Customer data object, the properties of Name, Address etc. are not implementation details but rather the whole purpose of the object and should be part of the public interface.

Read the continuation of the article you link to, to see how he suggest actually setting properties like 'name' and 'salary' on a 'Employee'-object without the use of the evil setters. Turns out he uses a pattern with an 'Exporter' which is populated with methods called addName, addSalary which in turn sets fields of the same name... So in the end he ends up using exactly the setter pattern, just with a different naming convention.

This is like thinking you avoid the pitfalls of singletons by renaming them therecanbeonlyonethings while keeping the same implementation.

12
  • 8
    Then again, some people have said 'Never use OOP': harmful.cat-v.org/software/OO_programming
    – JacquesB
    Commented May 18, 2015 at 18:57
  • 7
    FTR, I think more “experts” still teach to meaninglessly create getters/setters for everything, than to never create any at all. IMO, the latter is less misguiding advice. Commented May 19, 2015 at 7:44
  • 5
    @leftaroundabout: OK, but I'm suggesting a middle ground between 'always' and 'never' which is 'use when appropriate'.
    – JacquesB
    Commented May 19, 2015 at 9:29
  • 1
    @JacquesB A Value Object can have behavior and be more than a dumb data structure. It's an orthogonal concept. Commented May 19, 2015 at 13:09
  • 5
    I think the issue is that many programmers turn every object (or too many objects) into a DTO. Sometimes they are necessary, but avoid them as much as possible since they separate data from behavior. (Assuming you are a devout OOPer)
    – user949300
    Commented May 19, 2015 at 17:19
12

To transform the Customer-class from a data object, we can ask ourselves the following questions about the data fields:

How do we want to use {data field}? Where is {data field} used? Can and should the use of {data field} be moved to the class?

E.g.:

  • What is the purpose of Customer.Name?

    Possible answers, display the name in a login web page, use the name in mailings to the customer.

    Which leads to methods:

    • Customer.FillInTemplate(…)
    • Customer.IsApplicableForMailing(…)
  • What is the purpose of Customer.DOB?

    Validating the customer's age. Discounts on the customer's birthday. Mailings.

    • Customer.IsApplicableForProduct()
    • Customer.GetPersonalDiscount()
    • Customer.IsApplicableForMailing()

Given the comments, the example object Customer – both as a data object and as "real" object with its own responsibilities – is too broad; i.e. it has too much properties/responsibilities. Which leads to either lots of components depending on Customer (by reading its properties) or to Customer depending on lots of components. Perhaps there exist different views of customer, perhaps each should have its own distinct class1:

  • The customer in the context of Account and monetary transactions is probably only used to:

    • help humans identify that their money transfer goes to the correct person; and
    • group Accounts.

    This customer does not need fields like DOB, FavouriteColour, Tel, and perhaps not even Address.

  • The customer in the context of a user logging in to a banking website.

    Relevant fields are:

    • FavouriteColour, which might come in the form of personalised theming;
    • LanguagePreferences, and
    • GreetingName

    Instead of properties with getters and setters, these might be captured in a single method:

    • PersonaliseWebPage(Template page);
  • The customer in the context of marketing and and personalised mailing.

    Here not relying on the properties of a dataobject, but instead starting from the responsibilities of the object; e.g.:

    • IsCustomerInterestedInAction(); and
    • GetPersonalDiscounts().

    The fact that this customer object has a FavouriteColour property, and/or an Address property becomes irrelevant: perhaps the implementation uses these properties; but it might also use some machine learning techniques and use previous interactions with the customer to discover in which products the customer might be interested.


1. Of course, the Customer and Account classes were examples, and for a simple example or homework exercise, splitting this customer might be overkill, but with the example of splitting, I hope to demonstrate that the method of turning a data object into an object with responsibilities will work.

6
  • 5
    Upvote because you are actually answering the question :) However it is also obvious that the proposed solutions are much worse than just having gettes/setters - e.g. FillInTemplate clearly breaks the separation of concerns principle. Which just goes to show that the premise of the question is flawed.
    – JacquesB
    Commented May 18, 2015 at 18:48
  • @Kasper van den Berg: And when you have many attributes in the Customer as is usually the case, how would you initially set them? Commented May 18, 2015 at 19:00
  • 2
    @IntelliData your values are likely coming from a database, XML, etc. The Customer, or a CustomerBuilder, reads them, then sets using (i.e. in Java) private / package / inner class access, or, (ick) reflection. Not perfect, but you can usually avoid public setters. (See my answer for some more details)
    – user949300
    Commented May 18, 2015 at 20:21
  • 6
    I don't think the customer class should know about any of these things. Commented May 19, 2015 at 10:13
  • What about something like Customer.FavoriteColor?
    – Gabe
    Commented May 20, 2015 at 7:38
9

TL;DR

  • Modeling for behavior is good.

  • Modeling for good(!) abstractions is better.

  • Sometimes data objects are required.


Behavior and Abstraction

There are several reasons to avoid getters and setters. One is, as you noted, to avoid modeling data. This is actually the minor reason. The bigger reason is to provide abstraction.

In your example with the bank account that is clear: A setBalance() method would be really bad because setting a balance is not what an account should be used for. The behavior of the account should abstract from its current balance as much as possible. It may take the balance into account when deciding whether to fail a withdrawal, it may give access to the current balance, but modifying interaction with a bank account should not require the user to calculate the new balance. That's what the account should do itself.

Even a pair of deposit() and withdraw() methods is not ideal to model a bank account. A better way would be to provide only one transfer() method that takes another account and an amount as arguments. This would allow the account class to trivially ensure that you don't accidentally create/destroy money in your system, it would provide a very usable abstraction, and it would actually provide users with more insight because it would force the use of special accounts for earned/invested/lost money (see double-accounting). Of course, not every use of an account needs this level of abstraction, but it is definitely worth considering how much abstraction your classes can provide.

Note that providing abstraction and hiding data internals is not always the same thing. Almost any application contains classes that are effectively just data. Tuples, dictionaries, and arrays are frequent examples. You don't want to hide the x-coordinate of a point from the user. There is very little abstraction that you can/should be doing with a point.


The Customer Class

A customer is certainly an entity in your system that should try to provide useful abstractions. For instance, it should likely be associated with a shopping cart, and the combination of the cart and the customer should allow committing a purchase, which might kick off actions like sending him the requested products, charging him money (taking into account his selected payment method), etc.

The catch is, that all the data that you mentioned is not only associated with a customer, all of that data is also mutable. The customer may move. They may change their credit card company. They may change their email-address and phone number. Heck, they may even change their name and/or sex! So, a full featured customer class indeed has to provide full modifying access to all these data items.

Still, the setters can/should provide non-trivial services: They can ensure correct format of email-adresses, verification of postal adresses, etc. Likewise, the "getters" can provide high-level services like providing email-adresses in the Name <[email protected]> format using the name fields and the deposited email address, or provide a correctly formatted postal address, etc. Of course, what of this high level functionality makes sense depends heavily on your use-case. It might be complete overkill, or it might call for another class to do it right. The choice of abstraction level is not an easy one.

1
  • sounds right, although I disagree on the sex part... ;) Commented May 19, 2015 at 16:56
7

Trying to expand on Kasper's answer, it's easiest to rant against and eliminate setters. In a rather vague, handwaving (and hopefully humorous) argument:

When would Customer.Name ever change?

Seldom. Maybe they got married. Or went into witness protection. But in that case you'd also want to check on and possibly change their residence, next of kin, and other information.

When would DOB ever change?

Only on initial creation, or on a data entry screwup. Or if they are a Domincan baseball player. :-)

These fields should not be accessible with routine, normal setters. Maybe you have a Customer.initialEntry() method, or a Customer.screwedUpHaveToChange() method that requires special permissions. But don't have a public Customer.setDOB() method.

Usually a Customer is read from a database, a REST API, some XML, whatever. Have a method Customer.readFromDB(), or, if you are stricter about SRP / separation of concerns, you'd have a separate builder, e.g. a CustomerPersister object with a read() method. Internally, they somehow set the fields (I prefer using package access or an inner class, YMMV). But again, avoid public setters.

(Addendum as Question has changed somewhat...)

Let's say that your application makes heavy use of relational databases. It would be foolish to have Customer.saveToMYSQL() or Customer.readFromMYSQL() methods. That creates undesireable coupling to a concrete, non-standard, and likely to change entity. For example, when you change the schema, or change to Postgress or Oracle.

However, IMO, it's perfectly acceptable to couple Customer to an abstract standard, ResultSet. A separate helper object (I'll call it CustomerDBHelper, which is probably a subclass of AbstractMySQLHelper) knows about all the complex connections to your DB, knows the tricky optimization details, knows the tables, query, joins, etc... (or uses a ORM like Hibernate) to generate the ResultSet. Your object talks to the ResultSet, which is an abstract standard, unlikely to change. When you change the underlying database, or change the schema, Customer doesn't change, but the CustomerDBHelper does. If you are lucky, it's only AbstractMySQLHelper that changeswhich automatically makes the changes for Customer, Merchant, Shipping etc...

This way you can (perhaps) avoid or lessen the need for getters and setters.

And, the main point of the Holub article, compare and contrast the above to how it would be if you used getters and setters for everything and changed the database.

Similarly, let's say you use a lot of XML. IMO, it's fine to couple your Customer to an abstract standard, such as a Python xml.etree.ElementTree or a Java org.w3c.dom.Element. Customer gets and sets itself from that. Again, you can (perhaps) lessen the need for getters and setters.

3
  • would you say it is advisable to use a Builder Pattern? Commented May 19, 2015 at 16:55
  • Builder is useful for making the construction of an object easier and more robust, and, if you wish, allowing the object to be immutable. However, it still (partially) exposes the fact that the underlying object has a DOB field, so it's not the bee-all end-all.
    – user949300
    Commented May 19, 2015 at 17:10
  • 1
    There are many great answers in this thread, but this one particularly stands out to me because it's the only one mentioning the criterion to use to decide whether to hide or expose data: data should be hidden when it's likely to change. Change likelihood is information or entropy. This fundamental design principle, called information hiding, was discovered and published by David Parnas in 1972 in his landmark paper ‘On the Criteria To Be Used in Decomposing Systems into Modules’.
    – Géry Ogam
    Commented Aug 27 at 13:52
1

So for example, given a Customer class that keeps alot if information about the customer, e.g. Name, DOB, Tel, Address etc., how would one avoid getter/setters for getting and setting all those attributes? What 'Behavior' type method can one write to populate all that data?

I think this question is prickly because you are worried about behavior methods for populating data but I don't see any indication of what behavior the Customer class of objects is intended to encapsulate.

Don't confuse Customer as a class of objects with 'Customer' as a user/actor who performs different tasks using your software.

When you say given a Customer class that keeps alot if information about the customer then as far as behavior goes it looks like your Customer class has little distinguishing it from a rock. A Rock can have a color, you could give it a name, you could have a field for storing its current address but we don't expect any sort of intelligent behavior from a rock.

From the linked article about getters/setters being evil:

The OO design process centers on use cases: a user performs standalone tasks that have some useful outcome. (Logging on is not a use case because it lacks a useful outcome in the problem domain. Drawing a paycheck is a use case.) An OO system, then, implements the activities needed to play out the various scenarios that comprise a use case.

Without any behavior defined, referring to a rock as a Customer doesn't change the fact that it is just an object with some properties you'd like to track and it doesn't matter what tricks you want to play to get away from getters and setters. A rock doesn't care if it has a valid name and a rock wouldn't be expected to know whether an address is valid or not.

Your order system could associate a Rock with a purchase order and as long as that Rock has an address defined then some part of the system can make sure an item gets delivered to a rock.

In all of these cases the Rock is just a Data Object and will continue to be one until we define specific behaviors with useful outcomes instead of hypotheticals.


Try This:

When you avoid overloading the word 'Customer' with 2 potentially different meanings it should make things easier to conceptualize.

Does a Rock object place an Order or is that something that a human being does by clicking on UI elements to trigger actions in your system?

3
  • The customer is an actor that does many things, but also happens to have alot of info associated with it; does that justify creating 2 separate classes, 1 as an actor and 1 as a data object? Commented May 19, 2015 at 16:59
  • @IntelliData passing rich objects across layers is where things get tricky. If you are sending the object from a controller to a view, the view needs to understand the general contract of the object (JavaBeans standard for example). If you are sending the object across the wire, JaxB or the like needs to be able to reinstaitate it to a dumb object (because you didn't give them the full rich object). Rich objects are wonderful for manipulating data - they are poor for transferring state. Conversely, dumb objects are poor for manipulating data and ok for transferring state.
    – user40980
    Commented May 19, 2015 at 19:49
  • +1 It's not answering the question 100 %, but anyway it is an important hint that "customer" might be a too generic term
    – Eugene
    Commented Jan 14, 2022 at 23:45
1

The issue of having getters and setters can be a matter of the fact that a class may be used in the business logic in one way but you may also have helper classes to serialize / deserialize the data from a database or file or other persistent storage.

Due to the fact there are many ways to store / retrieve your data and you want to decouple the data objects from the way they are stored, the encapsulation can be "compromised" by either making these members public, or making them accessible through getters and setters which is almost as bad as making them public.

There are various ways around this. One way is to make the data available to a "friend". Although friendship is not inherited, this can be overcome by whatever serializer requesting the information from the friend, i.e. the base serializer "forwarding" the information.

Your class could have a generic "fromMetadata" or "toMetadata" method. From-metadata constructs an object so may well be a constructor. If it is dynamically typed language, metadata is pretty standard to such a language and probably is the primary way to construct such objects.

If your language is C++ specifically, one way around this is to have a public "struct" of data and then for your class to have an instance of this "struct" as a member and in fact all the data you are going to store / retrieve to be stored in it. You can then easily write "wrappers" to read/write your data in multiple formats.

If your language is C# or Java which don't have "structs" then you can do similarly but your struct is now a secondary class. There is no real concept of "ownership" of the data or const-ness so if you give out an instance of the class containing your data and it's all public, whatever gets hold can modify it. You could "clone" it although this can be expensive. Alternatively you could make this class have private data but use accessors. That gives users of your class a roundabout way to get to the data but it isn't the direct interface with your class and is really a detail in storing the data of the class which is a use-case too.

0

OOP is about encapsulating and hidding behavior inside objects. Objects are black boxes. This is a way to design thing. The asset is in many case one doesn't need to know the internal state of another component and is better to not have to know it. You can enforce that idea with mainly interfaces or inside an object with visibility and taking care only allowed verbs/action are available to caller.

This work well for some kind of problem. For example in user interfaces to modelize individual UI component. When you interract with a text box you are only interrested in setting the text, getting it or listening to text change event. You are typically not interrested as to where the cursor is, the font used to draw the text or how the keyboard is used. Encapsulation provide a lot here.

On the contrary when you call a network service, your provide an explicit input. There usually a grammar (like in JSON or XML) and all the option of calling the service have no reason to be hidden. The idea is that you can call the service the way you want and the data format is public and published.

In this case, or many other (like access to a database) you really work with shared data. As such there no reason to hide it, on the contrary you want to make it available. There can be concern of read/write access or datacheck consistency but at this core, the core concept if this is public.

For such design requirement where you want to avoid encapsulation and make things publics and in the clear, you want to avoid objects. What you really need are tuples, C structs or their equivalent, not objects.

But it also happen in languages like Java, the only things you can modelize is objects or arrays of objects. Objects themselve can hold a few natives types (int, float...) but that's all. But objects can also behave like a simple struct with just public fields and that all.

So if you modelize data you can be done with just public fields inside objects because you don't need more. You don't use encapsulation because you don't need it. This is done this way in many languages. In java, historically, a standard rose where with getter/setter you could at least have read/write control (by not adding setter for example) and that toolings and framework by using instrospection API would look for getter/setter methods and use it to autofill the content or display theses as modifiable fields in auto generated user interface.

There also the argument you could add some logic/checking in the setter method.

In reality there almost no justification for getter/setters as they are most often used to modelize pure data. Frameworks and developpers using your objects do expect the getter/setter do nothing more than setting/getting the fields anyway. You are effectively doing no more with getter/setter than what could be done with public fields.

But that's old habits and olds habits are difficult to remove... You could even be threatened by your collegues or teacher if you don't put getters/setter blindly everywhere if they lack the background to better understand what they are and what they are not.

You would likely need to change the language to get ride of all theses getters/setters boilerplate code. (Like C# or lisp). To me getters/setter are just another one billion dollar mistake...

5
  • 6
    C# properties do not really implement encapsulation any more than do getters and setters... Commented May 19, 2015 at 17:01
  • 1
    The advantage of [gs]etters is that you can do the things you usually don't (check values, notify observers), simulate non-existing fields etc., and mainly that you can change it later. With Lombok, the boilerplate is gone: @Getter @Setter class MutablePoint3D {private int x, y, z;}.
    – maaartinus
    Commented May 20, 2015 at 3:47
  • 1
    @maaartinus For sure [gs]etter can do anything as any other method can. This mean that any caller code should be aware any value they set can throw an exception, be changed or potentialy send notification of change... or whatever else. That more or less [gs]etter are not providing access to a field but doing arbitrar code. Commented May 20, 2015 at 7:06
  • @IntelliData C# properties allow to not write 1 useless single character of boilerplate and to not care of all this getter/setter stuff... This is already better achievement than project lombok. Also to me a POJO with just getters/setter is not here to provide encapsulation but rather to publish a data format that one is free to read or write as exchange with a service. Encapsulation is then the opposite of the design requirement. Commented May 20, 2015 at 7:17
  • I don't really think properties are really good. Sure, you save the prefix and the parentheses, i.e., 5 chars per call, but 1. they look like fields, which is confusing. 2. they're an additional thing for which you need support in reflection. No big deal, but no big advantage either (when compared to Java+Lombok; pure Java is at clear loss).
    – maaartinus
    Commented May 20, 2015 at 7:51
0

I add my 2 cents here mentioning SQL-speaking objects approach.

This approach is based on the notion of self-contained object. It has all resources it needs to implement its behavior. It doesn't need to be told how to do its job -- declarative request is enough. And an object definitely doesn't have to hold all its data as class properties. It really doesn't -- and shouldn't -- matter where they are got from.

Talking about an aggregate, immutability is not an issue as well. Say, you have a sequence of states that aggregate can hold: Aggregate root as saga It's totally fine to implement each state as standalone object. Probably you could go even further: have a chat with your domain expert. Chances are that he or she doesn't see this aggregate as some unified entity. Probably each state has it's own meaning, deserving it's own object.

Finally, I'd like to note that object finding process is very similar with system decomposition into subsystems. Both are based on behavior, not anything else.

Not the answer you're looking for? Browse other questions tagged or ask your own question.