Getters for Display

送分小仙女□ 提交于 2019-12-04 14:10:54

There is a difference between attributes of an object, and implementation details of an object.

  • A book has a title - that is not an implementation detail.
  • A book has authors - that is not an implementation detail.
  • How authors of a book are stored - this can be an implementation detail.

Getters are not evil, but you must use them carefully, since they can expose implementation details which restrict changes to your implementation.

class Book {
    private ArrayList<Author> authors = new ArrayList<>();

    public ArrayList<Author> getAuthors() { return authors; }
}

The above is bad, for two reasons. First, it returns a modifiable collection to the caller, allowing the caller to change the collection. Second, it promises the caller that the returned collection is an ArrayList.

Book book = ...;
ArrayList<Author> authors = book.getAuthors();

We fixing the first problem by wrapping the collection:

class Book {
    private ArrayList<Author> authors = new ArrayList<>();

    public List<Author> getAuthors() {
        return Collection.unmodifiableList(authors);
    }
}

Book book = ...;
List<Author> authors = book.getAuthors();

Now the caller can't modify the collection, but they are still promised the collection is a List. If we find we want to store our authors as a Set (because an author doesn't author a book more than once), we cannot simply change the internal storage.

class Book {
    private HashSet<Author> authors = new HashSet<>();

    public List<Author> getAuthors() {
        return Collection.unmodifiableList(authors); // ERROR - authors not a list
    }
}

We would have to collect the authors into a new List, or change the signature of the getAuthors(), but that would impact the callers who expected a List to be returned.

Instead, the code should simply return a Collection. This does not expose the implementation detail of the storage of authors.

class Book {
    public Collection<Author> getAuthors() {
        return Collection.unmodifiableCollection(authors);
    }
}

Note: it could be that the authors are ordered, with the primary author listed first. In that case, you may want to return a List to promise the caller that the data is, in fact, ordered. In that case, the List is not an implementation detail, but rather part of the interface contract.

So, does getTitle() break encapsulation? No. Absolutely not.

Does getAuthors() break encapsulation? Depends what it returns, and what you are promising. If it returns a Collection, then no. If it returns a List and you are not promising ordered results, then yes.

There is a school of thought that claims that an abundance of get/set methods shows a careless object-oriented design. You ask how you would display your "book" data if your Book object didn't have the appropriate getXXX methods. The answer, presumably, is you tell your book to display itself:

MyUserInterface interface=...;
...
book.renderToUserInterface (interface);

I think there is some force in the arguments again over-using get/set methods -- I would argue that a good design makes programmatic classes good models of the real-world things they represent, and real-world things generally aren't just repositories of discrete data items that can be set and queried. I think we do have a tendency to think of classes (in Java and other languages) as nothing more than pimped-up data structures, and they can be more than that.

However, the idea that get/set methods are "evil" just goes too far, in my view. Dogmatic avoidance of these methods is unlikely to be helpful, particularly when a lot of what goes on in most OO programs is really supporting infrastructure around the classes that model domain objects. Moreover, "making the object do the work" makes most sense when considering a complete OO model for an application. At the level of the design of an individual object, for use in a general purpose library, for example, I think we have to be a bit pragmatic.

I seem to recall that "make the object do the work" was a hot idea about ten years ago, but I'm not convinced that is such a big deal any more.

This is just auxiliary comment on some different, but related (and sometimes advanced) topics, and is not a direct answer to the question (which is already well answered in other answers). Perhaps the info here is not relevant for here or your question, if so, just ignore it :-)

On Immutability, i.e., Just Not Adding Setters

Getters are usually fine as the caller can't change the object, but unnecessary setters can make it more difficult to reason about an object, especially when writing concurrent code.

When you have setters, the source of the object's change can come from a wider area, another class and even another thread.

See Immutable Objects in the Java tutorials and a synchronized sample with setters which is just plain more complicated and potentially error prone vs an immutable version without setters.

The JavaFX Color class is an example of an immutable class in the JDK.

Generally, when I make a class, I usually try to put all of the inputs into constructor parameters, make the class final, not add setters and make all the members of the class final. You can't always do that, but when you can it seems to make programming easier to me.

Note, setters are often useful and necessary, so don't worry about adding them in if you need them, immutability is just a sometimes desirable attribute and not a rule.

On bridging the data model and the display

If you are really interested in different patterns and conventions of getting data from the object to display, then read Martin Fowler's GUI Architectures as it gives a very good overview of this.

Usually, you want a separation of concerns from the data and domain objects and the UI. For small apps, you don't need that separation and it is just more overhead. But, as soon as you start developing larger apps, it usually becomes a lot better organized if you separate the UI from the domain model completely. Usually, observables help to do this, often coupled with a design pattern like MVC, MVP or MVVM.

On JavaFX Properties and Binding to the Display

If using JavaFX, as you mention in your question, properties and binding provide an observable interface so a change in your domain object can be reflected automatically in your UI. You can define the properties directly in your data and domain classes. You can define your UI using something like FXML and an FXML Controller. In the controller you can bind properties of the UI (e.g. the text string of a Label) to properties in your data and domain objects. That way, if you internally change the value of a property in your domain, e.g. the number of books in the library, you can expose that number as a property which is automatically updated in the UI whenever a new book is added (without you having to explicitly tell the UI to update the label when the book is added).

For example in your LibraryController, you might have:

@FXML
Label numberOfBooksInLibraryLabel;
Library library = new Library();

...

numberOfBooksInLibraryLabel.textProperty().bind(
    library.books().sizeProperty().asString()
);     

On JavaFX Properties and Encapsulation

JavaFX has some conventions around JavaFX properties which are a bit more complex and subtle than plain Java without JavaFX. To help with encapsulation and JavaFX properties, consider using FXCollections.unmodifiableObservableList() and read only properties.

private final ReadOnlyDoubleWrapper size = new ReadOnlyDoubleWrapper();

public final double getSize() {
    return size.get();
}

public final ReadOnlyDoubleProperty sizeProperty() {
    return size.getReadOnlyProperty();
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!