[λμμΈν¨ν΄] μ΄λν° ν¨ν΄(Adapter Pattern)
νκ·Έ: Design Pattern, Java, Refactoring
μΉ΄ν κ³ λ¦¬: Design Pattern
μ΄λν° ν¨ν΄
μΆμ²: https://refactoring.guru/ko/design-patterns/adapter
μ μ
κΈ°μ‘΄ μ½λλ₯Ό ν΄λΌμ΄μΈνΈκ° μ¬μ©νλ μΈν°νμ΄μ€μ ꡬνμ²΄λ‘ λ°κΏμ£Όλ ν¨ν΄
- ν΄λΌμ΄μΈνΈκ° μ¬μ©νλ μΈν°νμ΄μ€λ₯Ό λ°λ₯΄μ§ μλ κΈ°μ‘΄ μ½λλ₯Ό μ¬μ¬μ©ν μ μκ² ν΄μ€λ€.
μ£Όμ κ΅¬μ± μμ
ν΄λΌμ΄μΈνΈλ Target
μΈν°νμ΄μ€μλ§ μμ‘΄νλ€. κ·Έλ¦¬κ³ κΈ°μ‘΄μ μ¬μ©νλ μ½λ(Adaptee
)μ Target
μ¬μ΄μ Adapter
λΌλ ꡬνμ²΄κ° μλ€. μ¬κΈ°μ Adapter
μ μν μ κΈ°μ‘΄μ μ¬μ©νλ μ½λλ₯Ό ν΄λΌμ΄μΈνΈκ° μμ‘΄νλ νμ
μ κ°μ²΄λ‘ λ³νμμΌ μ£Όλ μν μ νλ€.
μ΄λν° ν¨ν΄ μ μ© μ
μ΄λν° ν¨ν΄ μ μ© μ ν¨ν€μ§ ꡬ쑰
μ μ© μ μ½λ보기
LoginHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class LoginHandler {
UserDetailsService userDetailsService;
public LoginHandler(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public String login(String username, String password) {
UserDetails userDetails = userDetailsService.loadUser(username);
if (userDetails.getPassword().equals(password)) {
return userDetails.getUsername();
} else {
throw new IllegalArgumentException();
}
}
}
UserDetails
1
2
3
4
5
6
7
8
public interface UserDetails {
String getUsername();
String getPassword();
}
UserDetailsService
1
2
3
4
5
public interface UserDetailsService {
UserDetails loadUser(String username);
}
Account
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
public class Account {
private String name;
private String password;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
AccountService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class AccountService {
public Account findAccountByUsername(String username) {
Account account = new Account();
account.setName(username);
account.setPassword(username);
account.setEmail(username);
return account;
}
public void createNewAccount(Account account) {
}
public void updateAccount(Account account) {
}
}
μ½λλ₯Ό κ°λ΅νκ² μ΄ν΄λ³΄λ©΄, UserDetails
μΈν°νμ΄μ€λ μ μ μ μ΄λ¦κ³Ό λΉλ°λ²νΈλ₯Ό κ°μ Έμ€λ κΈ°λ₯μ νκ³ , UserDetailsService
λ μ μ μ μ΄λ¦μ λ°μ κ·Έ μ μ μ 보λ₯Ό λ°ννλ μν μ νκ³ μλ€.
κΈ°μ‘΄μλ AccountService
λ₯Ό ν΅ν΄ μ μ μ 보λ₯Ό κ°μ Έμλλ°, μ±μ κ°μ νλ κ³Όμ μμ μλ‘μ΄ λ°©μμΌλ‘(LoginHandler
) νΈμΆν΄μΌ νλ μν©μ λμΈ κ²μ΄λ€.
μλ₯Ό λ€μ΄ security ν¨ν€μ§(μΈλΆ λΌμ΄λΈλ¬λ¦¬λΌκ³ κ°μ )λ₯Ό μ°λ¦¬ νλ‘μ νΈμ ν΅ν©νμ¬ μ°κ³ μ νλ€.
μ΄μ λΆν° ν΄λΌμ΄μΈνΈλ UserDetailService
λ₯Ό ν΅ν΄ λ°μ΄ν°λ₯Ό κ°μ ΈμμΌ νλ€κ³ νλ©΄ κΈ°μ‘΄μ μ½λλ₯Ό λ³κ²½νμ§ μκ³ μ΄λ»κ² μλ‘μ΄ λ°©μκ³Ό ν΅ν©ν μ μμκΉ?
κ·Έλ¬κΈ° μν΄ λ¨Όμ μλ‘ νΈνλμ§ μλ AccountService
μ UserDetailsService
λ₯Ό μ°κ²°ν΄μΌ νκ³ , Account
μ UserDetails
λ₯Ό μ°κ²°ν΄μΌ νλ€.
μ΄λν° ν¨ν΄ μ μ© νκΈ°
κΈ°μ‘΄μ μ½λ(Adaptee) λ₯Ό Target νμ μ λ§κ² λ³νν΄μ£Όλ ν΄λμ€(Adapter)λ₯Ό μ μν΄μΌ νλ€.
Adapter ν΄λμ€λ Adaptee λ₯Ό ν¬ν¨νλλ‘ λ§λ λ€.
μ μ© ν μ½λ보기
AccountUserDetailsService
UserDetails
ꡬν체μμ κΈ°μ‘΄μ μ°λ AccountService
κ°μ²΄λ₯Ό μ¬μ©νλλ‘ νλ€.
μ΄ κ°μ²΄λ κΈ°μ‘΄ μλΉμ€μ λ°νκ°μ Target μΈν°νμ΄μ€ κ·κ²©μ λ§μΆ° λ°ν νλ μν μ νλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
public class AccountUserDetailsService implements UserDetailsService {
private AccountService accountService;
public AccountUserDetailsService(AccountService accountService) {
this.accountService = accountService;
}
@Override
public UserDetails loadUser(String username) {
return new AccountUserDetails(accountService.findAccountByUsername(username));
}
}
AccountUserDetails
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AccountUserDetails implements UserDetails {
private Account account;
public AccountUserDetails(Account account) {
this.account = account;
}
@Override
public String getUsername() {
return account.getName();
}
@Override
public String getPassword() {
return account.getPassword();
}
}
Client
1
2
3
4
5
6
7
8
9
10
public class App {
public static void main(String[] args) {
AccountService accountService = new AccountService();
UserDetailsService userDetailsService = new AccountUserDetailsService(accountService);
LoginHandler loginHandler = new LoginHandler(userDetailsService);
String login = loginHandler.login("keesun", "keesun");
System.out.println(login);
}
}
Adaptee λ₯Ό νΈμΆν΄ λμ¨ κ²°κ³Όλ₯Ό ν΄λΌμ΄μΈνΈκ° μνλ νμ μΈ Target νμ μΌλ‘ λ°κΏμ£Όλ μν μ νλκ²μ΄ Adapter λ€.
μ₯μ κ³Ό λ¨μ
- μ₯μ
- κΈ°μ‘΄ μ½λλ₯Ό λ³κ²½νμ§ μκ³ μνλ μΈν°νμ΄μ€ ꡬν체λ₯Ό λ§λ€μ΄ μ¬μ¬μ© κ°λ₯. (OCP)
- κΈ°μ‘΄ μ½λκ° νλ μΌκ³Ό νΉμ μΈν°νμ΄μ€ ꡬνμ²΄λ‘ λ³ννλ μμ μ κ°κΈ° λ€λ₯Έ ν΄λμ€λ‘ λΆλ¦¬νμ¬ κ΄λ¦¬ν μ μλ€. (SRP)
- λ¨μ
- μ ν΄λμ€κ° μ겨 볡μ‘λκ° μ¦κ°ν μ μλ€.
μλ°μμ μ°Ύμ보λ μ΄λν° ν¨ν΄
Arrays.asList()
λ°°μ΄ νν(...args
ννμ κ°λ³μΈμ ν¬ν¨)λ₯Ό νλΌλ―Έν°λ‘ λ°μ List νμ
μ λ°ννλ€.
1
List<String> strings = Arrays.asList("a", "b", "c");
Enumeration()
1
2
3
4
// list to enumeration
Enumeration<String> enumeration = Collections.enumeration(strings);
// enumeration to list
ArrayList<String> list = Collections.list(enumeration);
io package
1
2
3
4
5
6
7
8
9
10
// io
try(InputStream is = new FileInputStream("input.txt");
InputStreamReader isr = new InputStreamReader(is);
BufferedReader reader = new BufferedReader(isr)) {
while(reader.ready()) {
System.out.println(reader.readLine());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
μ€νλ§μμ μ°Ύμ보λ μ΄λν° ν¨ν΄
HandlerAdapter
Spring MVC μλ DispatcherServlet
μ΄λΌλ νλ‘ νΈ μ»¨νΈλ‘€λ¬ μν μ νλ νΈλ€λ¬ ν΄λμ€κ° μλ€.
λ΄λΆ λ©μλ μ€ κ° μν©μ λ§λ νΈλ€λ¬λ₯Ό μ ννλ λ©μλκ° μλλ° μ΄ λΆλΆμ μ΄λν° ν¨ν΄μ΄ μ μ©λμ΄ μλ€.
doDispatch λ©μλ μ΄ν΄λ³΄κΈ°
DispatcherServlet - doDispatch
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
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
// μ΅κ·Ό μμ²μ λ§λ μ μ ν μ΄λν°λ₯Ό κ°μ Έμ¨λ€.
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// μ΄λν°λ₯Ό μ€νν΄ Target νμ
μΌλ‘ λ³ννλ€.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
Exception ex = var20;
dispatchException = ex;
} catch (Throwable var21) {
Throwable err = var21;
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
Exception ex = var22;
this.triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Throwable var23) {
Throwable err = var23;
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
ν΄λΉ λ©μλ μ€κ°μ 보면
1
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
μ΄λ° μ½λκ° μλλ°, ν΄λΉ μμ²μ λ§λ νΈλ€λ¬λ₯Ό μ²λ¦¬ν μ μλ μ΄λν°λ₯Ό κ°μ Έμ€λ μ½λλ€.
κ·Έ ν μ ν©ν μ΄λν°λ₯Ό ν΅ν΄ ModelAndView
κ°μ²΄λ₯Ό λ°ννλ μ½λκ° μλ€.
1
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
μ΄λν°μ handle
λ©μλμ κ²°κ³Όλ‘ Target νμ
(ModelAndView) λ₯Ό λ°ννλ€.
λ€μν μ’
λ₯μ νΈλ€λ¬μ λν΄ μΌκ΄λ λ°©μμΌλ‘ ModelAndView
λ₯Ό λ°νν΄ μ€λ€.
ν΄λΉ λ©μλλ₯Ό λ€μ΄κ°λ³΄λ©΄ μ΄λν° μ© μΈν°νμ΄μ€κ° μλ€.
1
2
3
4
5
6
7
8
public interface HandlerAdapter {
boolean supports(Object var1);
@Nullable
ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
long getLastModified(HttpServletRequest var1, Object var2);
}
κ·Έλ¦¬κ³ κ·Έ μ€ μμ²μ λν΄ @RequestMapping μ΄λ
Έν
μ΄μ
μ μ²λ¦¬νλ νΈλ€λ¬μΈ RequestMappingHandlerAdapter
μ΄ μλ€λ κ²μ μ μ μλ€.
λ€μν μ’ λ₯μ μμ²μ μ²λ¦¬νλ νΈλ€λ¬κ° μλ€.
μ΄λν° ν¨ν΄ vs. λ°μ½λ μ΄ν° ν¨ν΄
μλλ μ΄λν° ν¨ν΄κ³Ό λ°μ½λ μ΄ν° ν¨ν΄μ κ°λ΅νκ² λΉκ΅ν κ²μ΄λ€.
μ°¨μ΄μ λΉκ΅ ν΄λ¦
μ΄λν° ν¨ν΄ (Adapter Pattern)
μ μ: μ΄λν° ν¨ν΄μ νΈνλμ§ μλ μΈν°νμ΄μ€λ₯Ό κ°μ§ ν΄λμ€λ₯Ό νΈνλλλ‘ λ³νν΄μ£Όλ ν¨ν΄. μ£Όλ‘ κΈ°μ‘΄ ν΄λμ€λ₯Ό μ¬μ¬μ©νλ©΄μλ μΈν°νμ΄μ€κ° λ§μ§ μμ λ μ¬μ©νλ€.
λͺ©μ : κΈ°μ‘΄ ν΄λμ€μ μΈν°νμ΄μ€λ₯Ό μνλ μΈν°νμ΄μ€λ‘ λ³ννμ¬ μλ‘ νΈνλμ§ μλ μΈν°νμ΄μ€λ₯Ό κ°μ§ ν΄λμ€λ€μ΄ ν¨κ» λμν μ μκ² νλ€.
ꡬ쑰:
- νκΉ μΈν°νμ΄μ€(Target Interface): ν΄λΌμ΄μΈνΈκ° μ¬μ©νκ³ μ νλ μΈν°νμ΄μ€.
- μ΄λν°(Adapter): νκΉ μΈν°νμ΄μ€λ₯Ό ꡬννκ³ μ΄λν°(Adaptee)μ λ©μλλ₯Ό νΈμΆνμ¬ λ³νμ μν.
- μ΄λν°(Adaptee): μ΄λν°μ μν΄ λ³νλλ κΈ°μ‘΄ ν΄λμ€.
λ°μ½λ μ΄ν° ν¨ν΄ (Decorator Pattern)
μ μ: λ°μ½λ μ΄ν° ν¨ν΄μ κ°μ²΄μ λμ μΌλ‘ μλ‘μ΄ νλμ μΆκ°ν μ μκ² ν΄μ£Όλ ν¨ν΄. μμμ ν΅ν΄ κΈ°λ₯μ νμ₯νλ λμ λ°μ½λ μ΄ν° κ°μ²΄λ₯Ό ν΅ν΄ κΈ°λ₯μ μΆκ°νλ€.
λͺ©μ : κ°μ²΄μ κΈ°λ₯μ λμ μΌλ‘ νμ₯ν μ μλλ‘ νλ©°, μμλ³΄λ€ μ μ°ν κΈ°λ₯ νμ₯μ μ 곡νλ€.
ꡬ쑰:
- μ»΄ν¬λνΈ(Component): κΈ°λ³Έ μΈν°νμ΄μ€λ‘, λ°μ½λ μ΄ν°μ ꡬ체 μ»΄ν¬λνΈκ° ꡬννλ€.
- ꡬ체 μ»΄ν¬λνΈ(Concrete Component): κΈ°λ³Έ κΈ°λ₯μ ꡬννλ ν΄λμ€.
- λ°μ½λ μ΄ν°(Decorator): μ»΄ν¬λνΈ μΈν°νμ΄μ€λ₯Ό ꡬννλ©°, μ»΄ν¬λνΈ κ°μ²΄λ₯Ό ν¬ν¨νλ€. μΆκ°μ μΈ κΈ°λ₯μ μ μν μ μλ€.
- ꡬ체 λ°μ½λ μ΄ν°(Concrete Decorator): λ°μ½λ μ΄ν° ν΄λμ€λ₯Ό νμ₯νμ¬ κ΅¬μ²΄μ μΈ κΈ°λ₯μ μΆκ°νλ€.
μ΄λν° ν¨ν΄μ 곡λΆνλ©΄μ λ°μ½λ μ΄ν° ν¨ν΄κ³Ό λΉμ·νλ€λ λλμ λ°μλ€. ν©μ±μ΄λΌλ λ°©μ(λ©€λ²λ³μ)μΌλ‘ ν΄λμ€λ₯Ό νμ₯νκ³ μκΈ° λλ¬Έμ κ·Έλ κ² λκΌλ κ² κ°λ€.
보λ μκ°μ λ°λΌ μ΄λν°ν¨ν΄μ΄ λ μλ μκ³ , λ°μ½λ μ΄ν°ν¨ν΄μ΄ λ μλ μλ€. λͺ©μ μ λ°λΌ λΆλ₯λ₯Ό ν΄λ³΄μλ©΄, κΈ°μ‘΄ μ½λμμ νΈνμ λͺ©μ μ΄ μλ€λ©΄ μ΄λν°ν¨ν΄μΌλ‘ λ³Ό μ μκ³ , κΈ°μ‘΄μ½λμ κΈ°λ₯μ νμ₯νλ€λ κ°λ μΌλ‘ λ³Έλ€λ©΄ λ°μ½λ μ΄ν° ν¨ν΄μΌλ‘ λ³Ό μ μλ€.
μλ₯Ό λ€μ΄, μμμ μμλ‘ λ AccountUserDetailsService
λ AccountUserDetails
μ κ°μ μ΄λν°λ₯Ό μλ‘ λ§λ€μ§ μκ³ κΈ°μ‘΄μ ν΄λμ€μ Target νμ
μ ꡬννλ λ°©ν₯μΌλ‘ κ°λ€λ©΄ μλ‘μ΄ κΈ°λ₯μ μΆκ°νλ κ°λ
μΌλ‘ λ³Ό μλ μλ κ²μ΄λ€.
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
public class AccountService implements UserDetailService{
public Account findAccountByUsername(String username) {
Account account = new Account();
account.setName(username);
account.setPassword(username);
account.setEmail(username);
return account;
}
public void createNewAccount(Account account) {
}
public void updateAccount(Account account) {
}
// μλ‘κ² μΆκ°λ κΈ°λ₯
@Override
public UserDetail loadUser(String username) {
return findAccountByUsername(username);
}
}
κ°κ°μ λ°©λ²μ μ₯λ¨μ μ΄ μμ΄ νλ‘μ νΈμ μν©μ κ³ λ €ν΄ μ ννλ©΄ λ κ² κ°λ€.
λμμΈν¨ν΄μ λν μκ°
μ¬μ€ λμμΈν¨ν΄ μ΄λΌλ κ²μ νΉμ ν μν©μ ν¨μ¨μ μΌλ‘ ν΄κ²°νκΈ° μν λ§ κ·Έλλ‘ μΌμ’ μ ν¨ν΄λ€μ λΆκ³ΌνκΈ° λλ¬Έμ ν¨ν΄ μ체μ 맀λͺ°λκΈ° 보λ€λ κ° ν¨ν΄μ νΉμ§μ μ΄ν΄ν ν, μ§κΈ λμ μν©μ λ§λ μ μ ν ν¨ν΄μ μ νν μ μλ μμΌλ₯Ό κ°λ κ²μ΄ λ μ€μνλ€κ³ μκ°νλ€. κ·Έλ¬κΈ° μν΄μ λ€μν μμ λ₯Ό μ νλ©΄μ μ ν΄λΉ ν¨ν΄μ μ¬μ©νμκΉμ λν΄ μ€μ€λ‘ μΆ©λΆν κ³ λ―Όν΄λ³΄λ μκ°μ κ°μ ΈμΌ ν κ² κ°λ€.
λκΈλ¨κΈ°κΈ°