프로그램은 점점 커지는 경향이 있고 많은 클래스가 만들어져 서로 관계를 맺으면서 복잡하게 됨
클래스를 사용할 경우에는 클래스 간의 관계를 정확히 이해하고 정확한 순서대로 메소드를 호출할 필요가 있음
커다란 프로그램을 사용해서 처리를 실행하려면 상호 관련된 많은 클래스를 적절하게 제어해야함, 그 처리를 실행하기 위한 창구를 준비해 두는게 좋음
많은 클래스를 개별적으로 제어하지 않아도 그 창구에 대해서만 요구하면 역할이 끝나는데 이러한 창구가 Facade 패턴이 해 줌
Facade 패턴은 복잡하게 얽혀 있는 것을 정리해서 높은 레벌의 인터페이스를 제공함
Facade 역할은 시스템의 외부에 대해서는 단순한 인터페이스를 보여줌, 또한 시스템의 내부에 있는 각 클래스의 역할이나 의존관계를 생각해서 정확한 순서로 클래스를 이용함
Database : 메일 주소에서 사용자 이름을 얻는 클래스
HtmlWriter : HTML 파일을 작성하는 클래스
PageMaker : 메일 주소에서 사용자의 웹 페이지를 작성하는 클래스
package pagemaker;
import java.io.*;
import java.util.*;
public class Database {
private Database() { // new에서 인스턴스를 생성시키지 않기 위한 private 선언
}
public static Properties getProperties(String dbname) {
// 데이터베이스 이름에서 Properties를 얻음
String filename = dbname + ".txt";
Properties prop = new Properties();
try {
prop.load(new FileInputStream(filename));
} catch (IOException e) {
System.out.println("Warning: " + filename + " is not found.");
}
return prop;
}
}
데이터베이스 이름을 지정하고 그것에 대응하는 Properties를 작성하는 클래스
getProperties라는 static 메소드를 매개로 Properties의 인스턴스를 얻음
package pagemaker;
import java.io.*;
public class HtmlWriter {
private Writer writer;
public HtmlWriter(Writer writer) { // 생성자
this.writer = writer;
}
public void title(String title) throws IOException { // 타이틀 출력
writer.write("<html>");
writer.write("<head>");
writer.write("<title>" + title + "</title>");
writer.write("</head>");
writer.write("<body>\n");
writer.write("<h1>" + title + "</h1>\n");
}
public void paragraph(String msg) throws IOException { // 단락 출력
writer.write("<p>" + msg + "</p>\n");
}
public void link(String href, String caption) throws IOException { // 링크 출력
paragraph("<a href=\"" + href + "\">" + caption + "</a>");
}
public void mailto(String mailaddr, String username) throws IOException { // 메일 주소 출력
link("mailto:" + mailaddr, username);
}
public void close() throws IOException { // 닫는다.
writer.write("</body>");
writer.write("</html>\n");
writer.close();
}
}
인스턴스 작성 시에 Writer를 제공하고 그 Writer에 대해서 HTML을 출력함
각각 메소드는 각기 맞게 출력을 함
package pagemaker;
import java.io.*;
import java.util.*;
public class PageMaker {
private PageMaker() { // 인스턴스를 만들지 않기 때문에 private 선언함
}
public static void makeWelcomePage(String mailaddr, String filename) {
try {
Properties mailprop = Database.getProperties("maildata");
String username = mailprop.getProperty(mailaddr);
HtmlWriter writer = new HtmlWriter(new FileWriter(filenmae));
writer.title("Welcome to " + username + " 's page");
writer.paragraph(username + "의 페이지에 오신 걸 환영합니다.");
writer.paragraph("메일을 기다리고 있습니다.");
writer.mailto(mailaddr, username);
writer.close();
System.out.println(filename + " is created for " + mailaddr + " (" + username + ")");
} catch(IOException e) {
e.printStackTrace();
}
}
PageMaker 클래스는 Database 클래스와 HtmlWriter 클래스를 조합해서 지정한 사용자의 웹 페이지를 작성하기 위한 것
이 클래스에서 정의되어 있는 public인 메소드는 makeWelcomePage뿐임, 이 메소드에 메일 주소와 출력 파일 이름을 지정만하면 웹 페이지가 작성됨
HtmlWriter 클래스의 메소드를 호출하는 곳은 이 PageMaker 클래스가 혼자 인수해서, 외부에 대해서는 단 하나의 makeWelcomePage 메소드만을 보이고 있음
import pagemaker.PageMaker;
public class Main {
public static void main(String[] args) {
PageMaker.makeWelcomePage("youngjin@youngjin.com" , "welcome.html");
}
}
Facade 역할은 시스템을 구성하고 있는 그 밖의 많은 역할에 대해 단순한 창구가 됨
높은 레벨에서 단순한 인터페이스(API)를 시스템 외부에 제공함
시스템을 구성하는 다른 많은 역할은 각각의 임무를 실행하지만 Facade 역할에 대해서는 신경쓰지 않음
Facade 역할에서 호출되는 임무를 실행하지만, 다른 역할이 Facade 역할을 호출하는 일은 없음
Facade는 복잡한 것을 단순하게 보여줌, 이 복잡한 것은 내부에서 실행되고 있는 많은 클래스의 관계나 사용법을 의미하는데 Facade는 그 복잡한 것을 의식하지 않도록 해 줌
핵심은 인터페이스(API)를 적게 하는 일임, 클래스나 메소드가 많이 보이면 무엇을 사용하면 좋을지 망설이게 되고, 호출하는 순서에도 주의해야함
그리고 인터페이스(API)의 수가 적다는 것은 외부와의 결합이 소원하다고 표현할 수 있음, 이는 약한 결합, 유연한 결합으로 패키지를 부품으로써 재이용하기 쉽게 해 줌
서버 통신에서 쓰는 Retrofit 라이브러리가 Facade 패턴을 활용했다고 볼 수 있음
대표적으로 RESTful API를 통신하기 위해서 Retrofit을 사용할 때 이 Retrofit을 활용해서 HTTP 요청을 단순히 interface 호출만을 통해서 쓸 수 있음
사용자는 내부적으로 이 interface가 어떻게 호출되고 쓰이는지 알 필요가 없이 단순하게 해당 메소드를 불러오고 Dto로 데이터를 보내고 Callback을 받아서 처리하기만 하면 됨, 이는 안드로이드에서 Activity 상에서 영향을 주지 않고 API 적용만 변경을 해서 응용할 수 있음
Retrofit을 REST API 통신을 통해 로그인을 구현하는 예시
우선 Retrofit 사용을 위한 기본 설정을 아래와 같이 싱글톤으로 만듬
package com.example.fitin_v1.remote.singleton;
import com.example.fitin_v1.remote.api.SignUp;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitBuilder {
// 기본 Retrofit 세팅 기준 URL을 가지고
public static Retrofit getRetrofit() {
return new Retrofit.Builder()
.baseUrl("http://10.0.2.2:8080")
.addConverterFactory(GsonConverterFactory.create())
.build();
}
}
그리고 로그인을 위한 Dto 모델을 만듬 각각 Request 하는 부분과 Response로 받는 부분임
이는 로그인을 하면 토큰을 받는 로그인 로직임
package com.example.fitin_v1.remote.api;
import com.google.gson.annotations.SerializedName;
public class AccountLoginDto {
@SerializedName("email")
private String email;
@SerializedName("password")
private String password;
public AccountLoginDto(String email, String password) {
this.email = email;
this.password = password;
}
}
package com.example.fitin_v1.remote.api;
public class TokenDto {
private String grantType;
private String accessToken;
private String refreshToken;
private Long accessTokenExpiresIn;
public String getAccessToken() {
return accessToken;
}
public String getRefreshToken() {
return refreshToken;
}
}
package com.example.fitin_v1.remote.api;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
public interface SingIn {
@POST("/auth/login")
Call<TokenDto> getSingIn(@Body AccountLoginDto accountLoginDto);
}
그리고 로그인을 할 경우 아주 간단하게 인터페이스를 사용해서 Retrofit을 create 해주기 위해서 로그인하는 Activity에서 아래와 같이 한 줄로 씀
여기서 Facade 패턴의 장점이 나옴, Client에선 그 내부적인 것을 모른 상태로 로그인에 대해서 아주 간단하게 정의함
SingIn signIn = RetrofitBuilder.getRetrofit().create(SingIn.class);
singIn
만 사용해서 로그인을 보냄binding.btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
email = binding.etEmail.getText().toString();
password = binding.etPassword.getText().toString();
AccountLoginDto account = new AccountLoginDto(email, password);
// signIn만을 가지고 로그인 처리 완료
Call<TokenDto> call = signIn.getSingIn(account);
call.enqueue(new Callback<TokenDto>() {
@Override
public void onResponse(Call<TokenDto> call, Response<TokenDto> response) {
if (!response.isSuccessful()) {
Log.e("연결이 비정상적 : ", "error code : " + response.code());
return;
} else {
Log.e("액세스 토큰 ", response.body().getAccessToken());
Log.e("리프레시 토큰 ", response.body().getRefreshToken());
}
}
@Override
public void onFailure(Call<TokenDto> call, Throwable t) {
}
});
}
});
다른 코드가 있어서 복잡해 보이지만 사실상 Activity에선 signIn 한 줄로 정의된 것에서 인터페이스만 사용하고 Dto만 넘기고 다른 과정이나 모든 것들은 모름
이런 부분들이 Retrofit이 Facade 패턴을 잘 활용된 것이라고 볼 수 있음