Эта статья является частью нашего курса Академии под названием « Шаблоны проектирования Java» .
В этом курсе вы изучите огромное количество шаблонов проектирования и увидите, как они реализуются и используются в Java. Вы поймете причины, почему шаблоны так важны, и узнаете, когда и как применять каждый из них. Проверьте это здесь !
Содержание
Образец Строителя
В общем, детали построения объекта, такие как создание и инициализация компонентов, составляющих объект, хранятся внутри объекта, часто как часть его конструктора. Этот тип дизайна тесно связывает процесс строительства объекта с компонентами, составляющими объект. Этот подход подходит, если строящийся объект прост, а процесс создания объекта определен и всегда дает одно и то же представление объекта.
Однако этот дизайн может быть неэффективным, когда создаваемый объект является сложным, и последовательность этапов, составляющих процесс создания объекта, может быть реализована различными способами, создавая, таким образом, разные представления объекта. Поскольку различные реализации процесса построения хранятся внутри объекта, объект может стать громоздким (раздутие конструкции) и менее модульным. Впоследствии добавление новой реализации или внесение изменений в существующую реализацию требует внесения изменений в существующий код.
Используя шаблон Builder, процесс создания такого объекта может быть разработан более эффективно. Паттерн Builder предлагает перенести логику построения из класса объекта в отдельный класс, называемый классом конструктора. Таких классов-строителей может быть несколько, каждый с разными реализациями для серии шагов по созданию объекта. Каждая реализация компоновщика приводит к различному представлению объекта.
Чтобы проиллюстрировать использование Pattern Builder, давайте поможем автомобильной компании, которая показывает своим клиентам различные автомобили, используя графическую модель. У компании есть графический инструмент, который отображает автомобиль на экране. Требование инструмента — предоставить ему объект автомобиля. Объект автомобиля должен содержать технические характеристики автомобиля. Графический инструмент использует эти спецификации для отображения автомобиля. Компания классифицировала свои автомобили по различным категориям, таким как седан или спортивный автомобиль. Существует только один объект автомобиля, и наша задача — создать объект автомобиля в соответствии с классификацией. Например, для автомобиля «Седан» должен быть построен автомобильный объект в соответствии со спецификацией седана или, если требуется спортивный автомобиль, должен быть построен автомобильный объект в соответствии со спецификацией спортивного автомобиля. В настоящее время Компания хочет использовать только эти два типа автомобилей, но в будущем ей могут потребоваться и другие типы автомобилей.
Мы создадим двух разных производителей, по одному для каждой классификации, т. Е. Для седанов и спортивных автомобилей. Эти два строителя помогут нам построить автомобильный объект в соответствии с его спецификацией. Но перед этим давайте обсудим некоторые детали шаблона Builder.
2. Что такое шаблон строителя
Назначение Pattern Builder состоит в том, чтобы отделить построение сложного объекта от его представления, чтобы один и тот же процесс построения мог создавать разные представления. Этот тип разделения уменьшает размер объекта. Дизайн оказывается более модульным с каждой реализацией, содержащейся в другом объекте компоновщика. Добавление новой реализации (т. Е. Добавление нового компоновщика) становится проще. Процесс строительства объекта становится независимым от компонентов, составляющих объект. Это обеспечивает больший контроль над процессом строительства объекта.
С точки зрения реализации, каждый из различных этапов процесса конструирования может быть объявлен как метод общего интерфейса, который должен быть реализован различными конструкторами бетона.
Клиентский объект может создать экземпляр конкретного компоновщика и вызвать набор методов, необходимых для создания различных частей конечного объекта. Этот подход требует, чтобы каждый клиентский объект был осведомлен о логике построения. Всякий раз, когда логика построения претерпевает изменения, все объекты клиента должны быть изменены соответствующим образом.
Шаблон Builder вводит другой уровень разделения, который решает эту проблему. Вместо того, чтобы клиентские объекты вызывали различные методы построителя напрямую, шаблон Builder предлагает использовать выделенный объект, называемый Director, который отвечает за вызов различных методов построителя, необходимых для построения конечного объекта. Различные клиентские объекты могут использовать объект Director для создания требуемого объекта. Как только объект сконструирован, клиентский объект может напрямую запросить у построителя полностью сконструированный объект. Чтобы облегчить этот процесс, в общем интерфейсе Builder может быть объявлен новый метод getObject
который будет реализован различными конструкторами бетона.
Новый дизайн устраняет необходимость для клиентского объекта иметь дело с методами, составляющими процесс конструирования объекта, и инкапсулирует детали того, как объект создается из клиента.
строитель
- Определяет абстрактный интерфейс для создания частей объекта Product.
ConcreteBuilder
- Создает и собирает части продукта путем реализации интерфейса Builder.
- Определяет и отслеживает создаваемое представление.
- Предоставляет интерфейс для получения продукта.
директор
- Создает объект с использованием интерфейса Builder.
Товар
- Представляет сложный объект на стадии строительства. ConcreteBuilder создает внутреннее представление продукта и определяет процесс его сборки.
- Включает в себя классы, которые определяют составные части, в том числе интерфейсы для сборки частей в конечный результат.
3. Реализация шаблона Builder
Удар — это класс « Car
» (Product), который содержит некоторые важные компоненты автомобиля, которые требуются для создания полного объекта car
.
01
02
03
04
05
06
07
08
09
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
|
package com.javacodegeeks.patterns.builderpattern; public class Car { private String bodyStyle; private String power; private String engine; private String breaks; private String seats; private String windows; private String fuelType; private String carType; public Car (String carType){ this .carType = carType; } public String getBodyStyle() { return bodyStyle; } public void setBodyStyle(String bodyStyle) { this .bodyStyle = bodyStyle; } public String getPower() { return power; } public void setPower(String power) { this .power = power; } public String getEngine() { return engine; } public void setEngine(String engine) { this .engine = engine; } public String getBreaks() { return breaks; } public void setBreaks(String breaks) { this .breaks = breaks; } public String getSeats() { return seats; } public void setSeats(String seats) { this .seats = seats; } public String getWindows() { return windows; } public void setWindows(String windows) { this .windows = windows; } public String getFuelType() { return fuelType; } public void setFuelType(String fuelType) { this .fuelType = fuelType; } @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append( "--------------" +carType+ "--------------------- \\n" ); sb.append( " Body: " ); sb.append(bodyStyle); sb.append( "\\n Power: " ); sb.append(power); sb.append( "\\n Engine: " ); sb.append(engine); sb.append( "\\n Breaks: " ); sb.append(breaks); sb.append( "\\n Seats: " ); sb.append(seats); sb.append( "\\n Windows: " ); sb.append(windows); sb.append( "\\n Fuel Type: " ); sb.append(fuelType); return sb.toString(); } } |
CarBuilder
— это интерфейс построителя, содержащий набор общих методов, используемых для построения объекта car
и его компонентов.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
package com.javacodegeeks.patterns.builderpattern; public interface CarBuilder { public void buildBodyStyle(); public void buildPower(); public void buildEngine(); public void buildBreaks(); public void buildSeats(); public void buildWindows(); public void buildFuelType(); public Car getCar(); } |
Метод getCar
используется для возврата окончательного объекта car клиенту после его создания.
Давайте рассмотрим две реализации интерфейса CarBuilder
, по одной для каждого типа автомобиля, т. CarBuilder
Для седана и спортивного автомобиля.
01
02
03
04
05
06
07
08
09
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
|
package com.javacodegeeks.patterns.builderpattern; public class SedanCarBuilder implements CarBuilder{ private final Car car = new Car( "SEDAN" ); @Override public void buildBodyStyle() { car.setBodyStyle( "External dimensions: overall length (inches): 202.9, " + "overall width (inches): 76.2, overall height (inches): 60.7, wheelbase (inches): 112.9," + " front track (inches): 65.3, rear track (inches): 65.5 and curb to curb turning circle (feet): 39.5" ); } @Override public void buildPower(){ car.setPower( "285 hp @ 6,500 rpm; 253 ft lb of torque @ 4,000 rpm" ); } @Override public void buildEngine() { car.setEngine( "3.5L Duramax V 6 DOHC" ); } @Override public void buildBreaks() { car.setBreaks( "Four-wheel disc brakes: two ventilated. Electronic brake distribution" ); } @Override public void buildSeats() { car.setSeats( "Front seat center armrest.Rear seat center armrest.Split-folding rear seats" ); } @Override public void buildWindows() { car.setWindows( "Laminated side windows.Fixed rear window with defroster" ); } @Override public void buildFuelType() { car.setFuelType( "Gasoline 19 MPG city, 29 MPG highway, 23 MPG combined and 437 mi. range" ); } @Override public Car getCar(){ return car; } } |
01
02
03
04
05
06
07
08
09
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
|
package com.javacodegeeks.patterns.builderpattern; public class SportsCarBuilder implements CarBuilder{ private final Car car = new Car( "SPORTS" ); @Override public void buildBodyStyle() { car.setBodyStyle( "External dimensions: overall length (inches): 192.3," + " overall width (inches): 75.5, overall height (inches): 54.2, wheelbase (inches): 112.3," + " front track (inches): 63.7, rear track (inches): 64.1 and curb to curb turning circle (feet): 37.7" ); } @Override public void buildPower(){ car.setPower( "323 hp @ 6,800 rpm; 278 ft lb of torque @ 4,800 rpm" ); } @Override public void buildEngine() { car.setEngine( "3.6L V 6 DOHC and variable valve timing" ); } @Override public void buildBreaks() { car.setBreaks( "Four-wheel disc brakes: two ventilated. Electronic brake distribution. StabiliTrak stability control" ); } @Override public void buildSeats() { car.setSeats( "Driver sports front seat with one power adjustments manual height, front passenger seat sports front seat with one power adjustments" ); } @Override public void buildWindows() { car.setWindows( "Front windows with one-touch on two windows" ); } @Override public void buildFuelType() { car.setFuelType( "Gasoline 17 MPG city, 28 MPG highway, 20 MPG combined and 380 mi. range" ); } @Override public Car getCar(){ return car; } } |
Вышеупомянутые два строителя создают и конструируют продукт, то есть car
объект в соответствии с требуемой спецификацией.
Теперь давайте проверим Builder.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
package com.javacodegeeks.patterns.builderpattern; public class TestBuilderPattern { public static void main(String[] args) { CarBuilder carBuilder = new SedanCarBuilder(); CarDirector director = new CarDirector(carBuilder); director.build(); Car car = carBuilder.getCar(); System.out.println(car); carBuilder = new SportsCarBuilder(); director = new CarDirector(carBuilder); director.build(); car = carBuilder.getCar(); System.out.println(car); } } |
Приведенный выше код приведет к следующему выводу.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
--------------SEDAN--------------------- Body: External dimensions: overall length (inches): 202.9 , overall width (inches): 76.2 , overall height (inches): 60.7 , wheelbase (inches): 112.9 , front track (inches): 65.3 , rear track (inches): 65.5 and curb to curb turning circle (feet): 39.5 Power: 285 hp @ 6 , 500 rpm; 253 ft lb of torque @ 4 , 000 rpm Engine: 3 .5L Duramax V 6 DOHC Breaks: Four-wheel disc brakes: two ventilated. Electronic brake distribution Seats: Front seat center armrest.Rear seat center armrest.Split-folding rear seats Windows: Laminated side windows.Fixed rear window with defroster Fuel Type: Gasoline 19 MPG city, 29 MPG highway, 23 MPG combined and 437 mi. range --------------SPORTS--------------------- Body: External dimensions: overall length (inches): 192.3 , overall width (inches): 75.5 , overall height (inches): 54.2 , wheelbase (inches): 112.3 , front track (inches): 63.7 , rear track (inches): 64.1 and curb to curb turning circle (feet): 37.7 Power: 323 hp @ 6 , 800 rpm; 278 ft lb of torque @ 4 , 800 rpm Engine: 3 .6L V 6 DOHC and variable valve timing Breaks: Four-wheel disc brakes: two ventilated. Electronic brake distribution. StabiliTrak stability control Seats: Driver sports front seat with one power adjustments manual height, front passenger seat sports front seat with one power adjustments Windows: Front windows with one-touch on two windows Fuel Type: Gasoline 17 MPG city, 28 MPG highway, 20 MPG combined and 380 mi. range |
В вышеприведенном классе мы сначала создали SedanCarBuilder
и CarDirector
. Затем мы просим CarDirector
построить car
для нас в соответствии с переданным ему builder
. Затем, наконец, мы напрямую просим builder
предоставить нам созданный объект car
.
Мы сделали то же самое для SportsCarBuilder
который возвращает объект car
соответствии со спецификацией спортивного автомобиля.
Подход к использованию шаблона Builder достаточно гибок, чтобы добавить любой новый тип автомобиля в будущем без изменения какого-либо существующего кода. Все, что нам нужно, — это создать нового строителя в соответствии со спецификацией нового автомобиля и предоставить его директору для сборки.
4. Еще одна форма паттерна строителя
Существует другая форма паттерна строителя, отличная от того, что мы видели до сих пор. Иногда существует объект с длинным списком свойств, и большинство из этих свойств являются необязательными. Рассмотрим онлайн-форму, которую необходимо заполнить, чтобы стать участником сайта. Вам необходимо заполнить все обязательные поля, но вы можете пропустить необязательные поля, или иногда может показаться полезным заполнить некоторые из необязательных полей.
Пожалуйста, проверьте приведенный ниже класс Form
который содержит длинный список свойств, а некоторые свойства являются необязательными. Обязательно lastName
в форме firstName
, lastName
, userName
и password
но все остальные поля являются необязательными.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package com.javacodegeeks.patterns.builderpattern; import java.util.Date; public class Form { private String firstName; private String lastName; private String userName; private String password; private String address; private Date dob; private String email; private String backupEmail; private String spouseName; private String city; private String state; private String country; private String language; private String passwordHint; private String secuirtyQuestion; private String securityAnswer; } |
Вопрос в том, какой конструктор мы должны написать для такого класса? Хорошо писать конструктор с длинным списком параметров не очень хороший выбор, это может расстроить клиента, особенно если важных полей всего несколько. Это может увеличить область ошибки; клиент может случайно указать значение в неправильном поле.
Другой способ — использовать телескопические конструкторы, в которых вы предоставляете конструктору только обязательные параметры, другой — с одним необязательным параметром, третий — с двумя необязательными параметрами и т. Д., Что завершается конструктором со всеми необязательными параметрами.
Телескопический конструктор может выглядеть следующим образом.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public Form(String firstName,String lastName){ this (firstName,lastName, null , null ); } public Form(String firstName,String lastName,String userName,String password){ this (firstName,lastName,userName,password, null , null ); } public Form(String firstName,String lastName,String userName,String password,String address,Date dob){ this (firstName,lastName,userName,password,address,dob, null , null ); } public Form(String firstName,String lastName,String userName,String password,String address,Date dob,String email,String backupEmail){ … } |
Когда вы хотите создать экземпляр, вы используете конструктор с кратчайшим списком параметров, содержащим все параметры, которые вы хотите установить.
Телескопический конструктор работает, но при наличии множества параметров написать клиентский код сложно, а прочитать его еще сложнее. Читатель задается вопросом, что означают все эти значения, и должен тщательно рассчитать параметры, чтобы выяснить это. Длинные последовательности идентично типизированных параметров могут вызывать незначительные ошибки. Если клиент случайно отменит два таких параметра, компилятор не будет жаловаться, но программа будет плохо себя вести во время выполнения.
Второй альтернативой, когда вы сталкиваетесь со многими параметрами конструктора, является шаблон JavaBeans, в котором вы вызываете конструктор без параметров для создания объекта, а затем вызываете методы установки для установки каждого обязательного параметра и каждого необязательного интересующего параметра.
К сожалению, у шаблона JavaBeans есть свои серьезные недостатки. Поскольку конструкция разделена на несколько вызовов, JavaBean может находиться в несогласованном состоянии в процессе создания. Класс не имеет возможности обеспечения согласованности, просто проверяя правильность параметров конструктора. Попытка использовать объект, когда он находится в несогласованном состоянии, может привести к сбоям, которые далеки от кода, содержащего ошибку, и, следовательно, трудны для отладки.
Существует третья альтернатива, которая сочетает в себе безопасность шаблона телескопического конструктора с удобочитаемостью шаблона JavaBeans. Это форма шаблона Строителя. Вместо того чтобы создавать нужный объект напрямую, клиент вызывает конструктор со всеми необходимыми параметрами и получает объект конструктора. Затем клиент вызывает сеттер-подобные методы на объекте построителя, чтобы установить каждый необязательный интересующий параметр. Наконец, клиент вызывает метод сборки без параметров для создания объекта.
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
|
package com.javacodegeeks.patterns.builderpattern; import java.util.Date; public class Form { private String firstName; private String lastName; private String userName; private String password; private String address; private Date dob; private String email; private String backupEmail; private String spouseName; private String city; private String state; private String country; private String language; private String passwordHint; private String securityQuestion; private String securityAnswer; public static class FormBuilder { private String firstName; private String lastName; private String userName; private String password; private String address; private Date dob; private String email; private String backupEmail; private String spouseName; private String city; private String state; private String country; private String language; private String passwordHint; private String securityQuestion; private String securityAnswer; public FormBuilder(String firstName, String lastName, String userName, String password){ this .firstName = firstName; this .lastName = lastName; this .userName = userName; this .password = password; } public FormBuilder address(String address){ this .address = address; return this ; } public FormBuilder dob(Date dob){ this .dob = dob; return this ; } public FormBuilder email(String email){ this .email = email; return this ; } public FormBuilder backupEmail(String backupEmail){ this .backupEmail = backupEmail; return this ; } public FormBuilder spouseName(String spouseName){ this .spouseName = spouseName; return this ; } public FormBuilder city(String city){ this .city = city; return this ; } public FormBuilder state(String state){ this .state = state; return this ; } public FormBuilder country(String country){ this .country = country; return this ; } public FormBuilder language(String language){ this .language = language; return this ; } public FormBuilder passwordHint(String passwordHint){ this .passwordHint = passwordHint; return this ; } public FormBuilder securityQuestion(String securityQuestion){ this .securityQuestion = securityQuestion; return this ; } public FormBuilder securityAnswer(String securityAnswer){ this .securityAnswer = securityAnswer; return this ; } public Form build(){ return new Form( this ); } } private Form(FormBuilder formBuilder){ this .firstName = formBuilder.firstName; this .lastName = formBuilder.lastName; this .userName = formBuilder.userName; this .password = formBuilder.password; this .address = formBuilder.address; this .dob = formBuilder.dob; this .email = formBuilder.email; this .backupEmail = formBuilder.backupEmail; this .spouseName = formBuilder.spouseName; this .city = formBuilder.city; this .state = formBuilder.state; this .country = formBuilder.country; this .language = formBuilder.language; this .passwordHint = formBuilder.passwordHint; this .securityQuestion = formBuilder.securityQuestion; this .securityAnswer = formBuilder.securityAnswer; } @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append( " First Name: " ); sb.append(firstName); sb.append( "\\n Last Name: " ); sb.append(lastName); sb.append( "\\n User Name: " ); sb.append(userName); sb.append( "\\n Password: " ); sb.append(password); if ( this .address!= null ){ sb.append( "\\n Address: " ); sb.append(address); } if ( this .dob!= null ){ sb.append( "\\n DOB: " ); sb.append(dob); } if ( this .email!= null ){ sb.append( "\\n Email: " ); sb.append(email); } if ( this .backupEmail!= null ){ sb.append( "\\n Backup Email: " ); sb.append(backupEmail); } if ( this .spouseName!= null ){ sb.append( "\\n Spouse Name: " ); sb.append(spouseName); } if ( this .city!= null ){ sb.append( "\\n City: " ); sb.append(city); } if ( this .state!= null ){ sb.append( "\\n State: " ); sb.append(state); } if ( this .country!= null ){ sb.append( "\\n Country: " ); sb.append(country); } if ( this .language!= null ){ sb.append( "\\n Language: " ); sb.append(language); } if ( this .passwordHint!= null ){ sb.append( "\\n Password Hint: " ); sb.append(passwordHint); } if ( this .securityQuestion!= null ){ sb.append( "\\n Security Question: " ); sb.append(securityQuestion); } if ( this .securityAnswer!= null ){ sb.append( "\\n Security Answer: " ); sb.append(securityAnswer); } return sb.toString(); } public static void main(String[] args) { Form form = new Form.FormBuilder( "Dave" , "Carter" , "DavCarter" , "DAvCaEr123" ).passwordHint( "MyName" ).city( "NY" ).language( "English" ).build(); System.out.println(form); } } |
Приведенный выше код даст следующий вывод:
1
2
3
4
5
6
7
|
First Name: Dave Last Name: Carter User Name: DavCarter Password: DAvCaEr123 City: NY Language: English Password Hint: MyName |
Как вы можете ясно видеть, теперь клиенту нужно только предоставить обязательные поля и поля, которые важны для него. Чтобы создать объект формы сейчас, нам нужно вызвать конструктор FormBuilder
который принимает обязательные поля, а затем нам нужно вызвать для него набор необходимых методов и, наконец, метод build
чтобы получить объект формы.
5. Когда использовать шаблон Builder
Используйте шаблон Builder, когда
- Алгоритм создания сложного объекта должен быть независимым от частей, составляющих объект, и способа их сборки.
- Процесс строительства должен позволять разные представления для объекта, который построен.
6. Образец Строителя в JDK
-
java.lang.StringBuilder#append()
(несинхронизированный) -
java.lang.StringBuffer#append()
(синхронизировано) -
java.nio.ByteBuffer#put()
(также для CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer и DoubleBuffer) -
javax.swing.GroupLayout.Group#addComponent()
- Все реализации
java.lang.Appendable
7. Загрузите исходный код
Это был урок по Образцу Строителя. Вы можете скачать исходный код здесь: Builder Pattern Project