问题
I was researching on getters/setters
, and the general idea is that they are evil and should be avoided. You should let the object do the work, and produce the result.
Reading Material:
Why getter and setter methods are evil
Are getters and setters poor design? Contradictory advice seen
Why use getters and setters?
With all that in mind, suppose I have a Book class
that looked like this:
publc final Book{
private final String title;
private final List<Authors> listofAuthors;
private final int numberofpages;
//constructor not included in example.
//getters for title, listofAuthors, and numberofpages
public int numberofpagesleft(int pagesRead){
//code to determine number of pages left
}
public boolean isWrittenBy(Author author){
//code to determine if book contains author
}
}
If I had a UI
(ex. JavaFX
, webpage
, etc..) and wanted my class to be flexible, would including the getters
for title
, listofAuthors
and numberofpages
for display purposes break encapsulation
?
Example:
//library is an List<Book> collection, and author is an object of the Author class.
for (Book book : library){
if(book.isWrittenBy(author){
System.out.println("Books in your collection written by " + author.toString() + " include:" );
System.out.println(book.getTitle());
}
}
or
for (Book book : library){
//returns a Collections.unmodifiableList().
if(book.getAuthors(){
//code to build a set of authors in my library
}
}
Questions:
Is calling
getTitle()
orgetAuthors()
in the loop breakingencapsulation
?If the answer to the above question is yes, how would I display the book if
isWrittenBy()
returnstrue
? and How would I gather all the authors in my library?
回答1:
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.
回答2:
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.
回答3:
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();
}
来源:https://stackoverflow.com/questions/46672320/getters-for-display