
퍼사드 패턴은 복잡한 서브 시스템에 대한 의존성을 단순화하고, 클라이언트가 사용하기 쉬운 하나의 인터페이스(퍼사드)를 제공한다.
클라이언트는 퍼사드 인터페이스를 통해 서브 시스템과 상호작용하며, 내부 구현에 대해 알 필요가 없다.

public class Client {
public static void main(String[] args) {
String to = "keesun@whiteship.me";
String from = "whiteship@whiteship.me";
String host = "127.0.0.1";
Properties properties = System.getProperties();
properties.setProperty("mail.smtp.host", host);
Session session = Session.getDefaultInstance(properties);
try {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject("Test Mail from Java Program");
message.setText("message");
Transport.send(message);
} catch (MessagingException e) {
e.printStackTrace();
}
}
}
구조
1. 직접 서브 시스템 사용:
2. 문제점
구조
1. 퍼사드 클래스 도입:
2. 클라이언트 코드 간소화:
public class EmailSender {
private EmailSettings emailSettings;
public EmailSender(EmailSettings emailSettings) {
this.emailSettings = emailSettings;
}
/**
* 이메일 보내는 메소드
* @param emailMessage
*/
public void sendEmail(EmailMessage emailMessage) {
Properties properties = System.getProperties();
properties.setProperty("mail.smtp.host", emailSettings.getHost());
Session session = Session.getDefaultInstance(properties);
try {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(emailMessage.getFrom()));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(emailMessage.getTo()));
message.addRecipient(Message.RecipientType.CC, new InternetAddress(emailMessage.getCc()));
message.setSubject(emailMessage.getSubject());
message.setText(emailMessage.getText());
Transport.send(message);
} catch (MessagingException e) {
e.printStackTrace();
}
}
}
public class EmailSettings {
private String host;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
}
public class EmailMessage {
private String from;
private String to;
private String cc;
private String bcc;
private String subject;
private String text;
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getCc() {
return cc;
}
public void setCc(String cc) {
this.cc = cc;
}
public String getBcc() {
return bcc;
}
public void setBcc(String bcc) {
this.bcc = bcc;
}
}
public class Client {
public static void main(String[] args) {
EmailSettings emailSettings = new EmailSettings();
emailSettings.setHost("127.0.0.1");
EmailSender emailSender = new EmailSender(emailSettings);
EmailMessage emailMessage = new EmailMessage();
emailMessage.setFrom("keesun");
emailMessage.setTo("whiteship");
emailMessage.setCc("일남");
emailMessage.setSubject("오징어게임");
emailMessage.setText("밖은 더 지옥이더라고..");
emailSender.sendEmail(emailMessage);
}
}

개선점
1. 의존성 관리:
2. 코드 간소화:
3. 유지보수성 증가:
4. 역할 분리:
장점
1. 의존성 단순화:
2. 코드 재사용성 증가:
3. 유지보수 용이:
4. 코드 간소화:
단점
1. 퍼사드 클래스에 과도한 책임:
2. 추가 계층으로 인한 성능 저하 가능성:
Controller:
JdbcTemplate:
RestTemplate:
자주 변하는 속성(또는 외적인 속성, extrinsit)과 변하지 않는 속성(또는 내적인 속성, intrinsit)을 분리하고 재사용하여 메모리 사용을 줄일 수 있다.

1. 구조
Character 클래스:
value), 색상(color), 폰트(fontFamily, fontSize) 속성을 가진 클래스.Character 객체는 각각의 속성을 독립적으로 가짐.public class Character {
private char value;
private String color;
private String fontFamily;
private int fontSize;
public Character(char value, String color, String fontFamily, int fontSize) {
this.value = value;
this.color = color;
this.fontFamily = fontFamily;
this.fontSize = fontSize;
}
}
Client 클래스:
Character) 객체를 독립적으로 생성하며, 동일한 속성을 반복적으로 사용.public class Client {
public static void main(String[] args) {
Character c1 = new Character('h', "white", "Nanum", 12);
Character c2 = new Character('e', "white", "Nanum", 12);
Character c3 = new Character('l', "white", "Nanum", 12);
Character c4 = new Character('l', "white", "Nanum", 12);
Character c5 = new Character('o', "white", "Nanum", 12);
}
}
2. 문제점
fontFamily, fontSize)를 사용하는 객체를 반복적으로 생성하므로, 메모리를 불필요하게 소비.Nanum:12 폰트를 사용하는 Character 객체마다 동일한 폰트 정보를 중복 저장.1. 구조
Character 클래스:
fontFamily와 fontSize를 분리하고, 대신 Font 객체를 사용.Font 객체는 변하지 않는 속성(내적 속성)을 캡슐화.public class Character {
private char value;
private String color; // 외적 속성
private Font font; // 내적 속성 (재사용 가능)
public Character(char value, String color, Font font) {
this.value = value;
this.color = color;
this.font = font;
}
}
Font 클래스:
fontFamily와 fontSize를 캡슐화.public final class Font {
final String family;
final int size;
public Font(String family, int size) {
this.family = family;
this.size = size;
}
public String getFamily() {
return family;
}
public int getSize() {
return size;
}
}
FontFactory 클래스:
Font 객체를 생성하고, 캐시(Map)에서 동일한 Font 객체를 재사용.getFont() 메서드를 통해 Font 객체를 반환.public class FontFactory {
private Map<String, Font> cache = new HashMap<>();
public Font getFont(String font) {
if (cache.containsKey(font)) {
return cache.get(font);
} else {
String[] split = font.split(":");
Font newFont = new Font(split[0], Integer.parseInt(split[1]));
cache.put(font, newFont);
return newFont;
}
}
}
Client 클래스:
FontFactory를 사용하여 동일한 Font 객체를 재사용.Character 객체는 Font를 참조하며, 색상(color)과 문자(value)만 개별적으로 저장.public class Client {
public static void main(String[] args) {
FontFactory fontFactory = new FontFactory();
Character c1 = new Character('h', "white", fontFactory.getFont("nanum:12"));
Character c2 = new Character('e', "white", fontFactory.getFont("nanum:12"));
Character c3 = new Character('l', "white", fontFactory.getFont("nanum:12"));
}
}
다이어 그램

2. 개선점
중복 제거:
Font 객체는 FontFactory에서 캐싱되므로, 동일한 폰트를 사용하는 객체는 메모리를 공유.내적/외적 속성 분리:
Font)은 변하지 않으므로 재사용.color, value)만 개별적으로 유지.유지보수 용이:
Font 객체만 수정하면 됨.장점
메모리 사용 감소:
객체 생성 비용 감소:
내적/외적 속성의 명확한 분리:
단점
코드 복잡성 증가:
FontFactory)가 필요.캐싱 관리 필요:
Integer.valueOf(int):
Integer는 -128부터 127까지의 정수 객체를 캐싱하여 재사용.Integer 객체를 반복적으로 생성하지 않음.Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println(a == b); // true (같은 객체 참조)문자열 풀(String Pool):
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true (같은 객체 참조)1) 프록시(Proxy) 패턴은 특정 객체에 대한 대리 객체를 두어, 접근을 제어하거나 부가적인 작업을 수행할 수 있도록 하는 디자인 패턴이다.
2) 클라이언트는 실제 객체가 아닌 프록시 객체를 통해 간접적으로 실제 객체에 접근한다.
3) 초기화 지연, 접근 제어, 로깅, 캐싱 등 다양하게 응용해 사용 할 수 있다.

1. 구조
Client 클래스:
GameService 객체를 직접 생성하고, startGame() 메서드를 호출한다.public class Client {
public static void main(String[] args) throws InterruptedException {
GameService gameService = new GameService();
gameService.startGame();
}
}
GameService 클래스:
startGame())을 담당하는 단순한 클래스.public class GameService {
public void startGame() {
System.out.println("이 자리에 오신 여러분을 진심으로 환영합니다.");
}
}
2. 문제점
GameService의 startGame() 메서드에 부가적인 작업(예: 로깅, 성능 측정)을 추가하려면 기존 코드를 수정해야 함.GameService 객체에 직접 접근하며, 객체 생성 로직이나 접근 제어를 추가하기 어렵다.1. 구조
GameService 인터페이스:
GameService와 그 구현체(DefaultGameService, GameServiceProxy)의 공통 인터페이스.public interface GameService {
void startGame();
}
DefaultGameService:
GameService의 실제 구현체.GameService와 동일하게 startGame() 메서드를 구현.public class DefaultGameService implements GameService {
@Override
public void startGame() {
System.out.println("이 자리에 오신 여러분을 진심으로 환영합니다.");
}
}
GameServiceProxy:
DefaultGameService)에 대한 접근을 제어.public class GameServiceProxy implements GameService {
private GameService gameService;
@Override
public void startGame() {
long before = System.currentTimeMillis();
if (this.gameService == null) {
this.gameService = new DefaultGameService();
}
gameService.startGame();
System.out.println(System.currentTimeMillis() - before);
}
}
Client 클래스:
GameServiceProxy 객체를 사용하여 startGame() 메서드를 호출.DefaultGameService)를 직접 알 필요가 없음.public class Client {
public static void main(String[] args) {
GameService gameService = new GameServiceProxy();
gameService.startGame();
}
}

2. 개선점
확장성 증가:
DefaultGameService)는 수정하지 않아도 됨.실제 객체에 대한 간접 접근:
지연 초기화(Lazy Initialization):
DefaultGameService)는 필요할 때만 생성(if (this.gameService == null)).부가 작업 수행:
System.currentTimeMillis())과 같은 추가 작업을 수행.장점
부가 작업 처리:
확장성:
지연 초기화(Lazy Initialization):
접근 제어:
단점
추가 클래스 필요:
오버헤드 증가:
java.lang.reflect.Proxy를 사용하여 런타임에 프록시 객체를 동적으로 생성.InvocationHandler를 통해 모든 메서드 호출을 가로채고 부가 작업을 처리.프록시 객체 생성:
Proxy.newProxyInstance()를 사용하여 GameService 인터페이스를 구현한 프록시 객체를 생성.InvocationHandler는 메서드 호출을 가로채어 실행 흐름을 제어.부가 작업:
System.out.println("O"), "ㅁ" 출력).클라이언트 실행 흐름:
getGameServiceProxy() 메서드를 통해 프록시 객체를 생성하고 startGame() 메서드를 호출.DefaultGameService)의 메서드를 실행.import java.lang.reflect.Proxy;
public class ProxyInJava {
public static void main(String[] args) {
ProxyInJava proxyInJava = new ProxyInJava();
proxyInJava.dynamicProxy();
}
private void dynamicProxy() {
GameService gameServiceProxy = getGameServiceProxy(new DefaultGameService());
gameServiceProxy.startGame();
}
private GameService getGameServiceProxy(GameService target) {
return (GameService) Proxy.newProxyInstance(
this.getClass().getClassLoader(),
new Class[]{GameService.class},
(proxy, method, args) -> {
System.out.println("O"); // 호출 전 작업
method.invoke(target, args); // 실제 메서드 호출
System.out.println("ㅁ"); // 호출 후 작업
return null;
});
}
}
// GameService 인터페이스
public interface GameService {
void startGame();
}
// DefaultGameService (실제 객체)
public class DefaultGameService implements GameService {
@Override
public void startGame() {
System.out.println("게임이 시작되었습니다!");
}
}
O
게임이 시작되었습니다!
ㅁ
프록시 생성:
부가 작업 처리:
Aspect 클래스:
@Aspect와 @Around를 사용하여 AOP 설정.@Aspect
@Component
public class PerfAspect {
@Around("bean(gameService)")
public void timestamp(ProceedingJoinPoint point) throws Throwable {
long before = System.currentTimeMillis(); // 실행 전 작업
point.proceed(); // 실제 메서드 실행
System.out.println(System.currentTimeMillis() - before); // 실행 후 작업
}
}
스프링 빈 (Target 클래스):
GameService 인터페이스를 구현한 실제 객체.@Component("gameService")
public class DefaultGameService implements GameService {
@Override
public void startGame() {
System.out.println("게임이 시작되었습니다!");
}
}
출력 결과:
게임이 시작되었습니다!
5 (실행 시간 출력)