Facade 패턴

gang_shik·2022년 3월 17일
0
post-thumbnail

Facade 패턴

  • 프로그램은 점점 커지는 경향이 있고 많은 클래스가 만들어져 서로 관계를 맺으면서 복잡하게 됨

  • 클래스를 사용할 경우에는 클래스 간의 관계를 정확히 이해하고 정확한 순서대로 메소드를 호출할 필요가 있음

  • 커다란 프로그램을 사용해서 처리를 실행하려면 상호 관련된 많은 클래스를 적절하게 제어해야함, 그 처리를 실행하기 위한 창구를 준비해 두는게 좋음

  • 많은 클래스를 개별적으로 제어하지 않아도 그 창구에 대해서만 요구하면 역할이 끝나는데 이러한 창구가 Facade 패턴이 해 줌

  • Facade 패턴은 복잡하게 얽혀 있는 것을 정리해서 높은 레벌의 인터페이스를 제공

  • Facade 역할은 시스템의 외부에 대해서는 단순한 인터페이스를 보여줌, 또한 시스템의 내부에 있는 각 클래스의 역할이나 의존관계를 생각해서 정확한 순서로 클래스를 이용함


예제 프로그램

  • Database : 메일 주소에서 사용자 이름을 얻는 클래스

  • HtmlWriter : HTML 파일을 작성하는 클래스

  • PageMaker : 메일 주소에서 사용자의 웹 페이지를 작성하는 클래스

Database 클래스

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의 인스턴스를 얻음

HtmlWriter 클래스

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을 출력함

  • 각각 메소드는 각기 맞게 출력을 함

PageMaker 클래스

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 메소드만을 보이고 있음

Main 클래스

import pagemaker.PageMaker;

public class Main {
		public static void main(String[] args) {
				PageMaker.makeWelcomePage("youngjin@youngjin.com" , "welcome.html");
		}
}
  • Main 클래스에서 pagemaker 패키지의 PageMaker 클래스를 이용함, 내용은 단 위와 같이 단 하나임

정리

Facade의 역할

  • Facade 역할은 시스템을 구성하고 있는 그 밖의 많은 역할에 대해 단순한 창구가 됨

  • 높은 레벨에서 단순한 인터페이스(API)를 시스템 외부에 제공

시스템을 구성하고 있는 그 밖의 많은 역할

  • 시스템을 구성하는 다른 많은 역할은 각각의 임무를 실행하지만 Facade 역할에 대해서는 신경쓰지 않음

  • Facade 역할에서 호출되는 임무를 실행하지만, 다른 역할이 Facade 역할을 호출하는 일은 없음

Client의 역할

  • 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;
    }
}
  • 그리고 결정적으로 Client 즉, Activity 내에서 쓸 인터페이스를 정의함
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);
  • 그리고 버튼을 누르면 로그인하고 토큰 값을 받아와야 하는데 여기서도 마찬가지임, Dto로 보낼 값을 제외하고선 Call로 위에서 한 줄로 정의한 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 패턴을 잘 활용된 것이라고 볼 수 있음

profile
측정할 수 없으면 관리할 수 없고, 관리할 수 없으면 개선시킬 수도 없다

0개의 댓글