-
Notifications
You must be signed in to change notification settings - Fork 68
/
chapter11-class_and_encapsulation.jsh
169 lines (142 loc) · 5.9 KB
/
chapter11-class_and_encapsulation.jsh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// To starts, run jshell --enable-preview which is a program able to interpret Java syntax
// then cut and paste the following lines to see how it works
// To exit jshell type /exit
// # Class and encapsulation
// Let's say i want to create a library of books,
// so we need a record Book and a record Library that stores the books has a list
record Book(String title, String author) { }
record Library(List<Book> books) { }
// and use it that way
var book = new Book("DaVinci Code", "Dan Brown");
var books = new ArrayList<Book>();
books.add(book);
var library = new Library(books);
System.out.println(library);
// The problem with a Library declared like this in that the library is not really
// in control of the books inside itself, one can write
books.add(new Book("Effective Java", "Joshua Bloch"));
System.out.println(library);
// The result is surprising, you can add books in the library without calling
// a method of the library which make the code hard to debug because changing
// an object has an effect to another object.
// ## Encapsulation principle
// In a pure functional language, the language doesn't allow you to
// do side effect. In an OO language, if you want to survive, the idea is
// to limit the functions that can do side effects to the instance methods.
// This idea is named the encapsulation principle and is sum up by this sentence
// > The only way to change the value of an object is to use one of the methods of this object.
// In Java, the way to ensure the encapsulation principle is to do information hiding,
// i.e. to separate the __public__ API part (what the user code can use) from the __private__
// implementation part (how the class is implemented).
// This separation is done by using a special syntax named __class__ that allows
// to precisely control of the visibility of its members.
// ## Class
// A class defines
// - private fields that is like a record component but not visible by the user code
// - a public constructor (Library), that guarantee that any objects will be correctly initialized
// - public and private instance and static methods
// ### Unmodifiable class
class Library {
private final List<Book> books;
public Library(List<Book> books) {
this.books = List.copyOf(books);
}
public String toString() {
return "Library " + books.toString();
}
}
var library = new Library(books);
System.out.println(library);
// Now changing the list of books has no effect on the library
// because the field `books` and the argument of the constructor `books` are different references
books.remove(new Book("DaVinci Code", "Dan Brown"));
System.out.println(library);
// You can notice that the constructor has no return type, it's because it's always void.
// The field 'books' is declared final which means must be initialized
// in the constructor (and not changed afterward) so we are sure that in toString(),
// the field 'books' has been initialized.
// Unlike a record, the method equals()/hashCode() and toString() are not provided and has
// to be hand written. We will see how to implement them later.
// ### Modifiable class
// The code above is an unmodifiable implementation of Library.
// We can also write a mutable version with the caveat that using it
// as element of a list or a map is not recommended.
class ModifiableLibrary {
private final ArrayList<Book> books;
public ModifiableLibrary() {
books = new ArrayList<>();
}
public void add(Book book) {
Objects.requireNonNull(book);
books.add(book);
}
public String toString() {
return "ModifiableLibrary " + books.toString();
}
}
var library = new ModifiableLibrary();
library.add(new Book("DaVinci Code", "Dan Brown"));
System.out.println(library);
library.add(new Book("Effective Java", "Joshua Bloch"));
System.out.println(library);
// ### Modifiable class and accessors
// An error sometime seen is to add a method to get the content of the library
// and forget that it may expose the private list of books
class ModifiableLibrary {
private final ArrayList<Book> books;
public ModifiableLibrary() {
books = new ArrayList<>();
}
public void add(Book book) {
Objects.requireNonNull(book);
books.add(book);
}
public List<Book> getBooks() {
return books;
}
public String toString() {
return "ModifiableLibrary " + books.toString();
}
}
// The following code breaks the encapsulation because you can
// modify the library without calling a method of the Library
// (`add()` is called on the List<Book> not on the Library)
var library = new ModifiableLibrary();
var books = library.getBooks();
books.add(new Book("DaVinci Code", "Dan Brown"));
// One solution is to return a copy, or better a non modifiable view
// of the internal list of books
class ModifiableLibrary {
private final ArrayList<Book> books;
public ModifiableLibrary() {
books = new ArrayList<>();
}
public void add(Book book) {
books.add(book);
}
public List<Book> getBooks() {
return Collections.unmodifiableList(books);
}
public String toString() {
return "ModifiableLibrary " + books.toString();
}
}
var library = new ModifiableLibrary();
var books = library.getBooks();
books.add(new Book("DaVinci Code", "Dan Brown"));
// The best solution being to not have a method `getBook()` at all,
// the less code you write the less bug you have.
// So please don't write getters and setters unless you really need them.
// ## Record constructor
// Records also provides ways to customize the code to respect the
// encapsulation principle
// Here, we only need to change the canonical constructor
record Library(List<Book> books) {
public Library(List<Book> books) {
this.books = List.copyOf(books);
}
}
// To summarize, a class is a general mechanism to describe how things
// are implemented and make a separation between what is publicly visible
// and what is privately implemented to make the code working.
// A record is a special case when there is no separation, everything is public.