학습목표
· 디자인 패턴이란?
· 어댑터 패턴
· 프록시 패턴
· 데코레이터 패턴
· 싱글턴 패턴
· 템플릿 메서드 패턴
· 팩터리 메서드 패턴
· 전략 패턴
· 템플릿 콜백 패턴
· 스프링이 사랑한 다른 패턴들
디자인 패턴이란?
· 프로그램을 작성하다 보면 비슷한 상황에 직면하게 되는 경우가 많은데,
그러한 상황에서 이전의 많은 개발자들이 고민하고 정제한 사실상의 표준 설계 패턴
· 실제 개발 현장에서 비즈니스 요구 사항을 프로그래밍으로 처리하면서
만들어진 다양한 해결책 중에서 많은 사람들이 인정한 베스트 프랙티스를 정리한 것
· 객체 지향을 도구로, 설계 원칙(SOLID)은 도구를 올바르게 사용하는 방법으로, 디자인 패턴은 레시피로 비유할 수 있음
· 스프링은 객체 지향의 특정과 설계 원칙을 극한까지 적용한 프레임워크로,
스프링을 공부하다보면 자엽스럽게 객체 지향 설계의 베스트 프랙티스인 디자인 패턴을 만날 수 있고,
이 때문에 스프링은 'OOP 프레임워크'라고 불림
어댑터 패턴
· 호출당하는 쪽의 메서드를 호출하는 쪽의 코드에 대응하도록 중간에 변환기를 통해 호출하는 패턴
- 객체를 속성으로 만들어서 참조하는 디자인 패턴(합성)
· SOLID의 개방 폐쇄 원칙(OCP)을 활용한 설계 패턴
· 어댑터: 변환기(converter)
- 변환기의 역할은 서로 다른 두 인터페이스 사이에 통신이 가능하게 하는 것 ex) 충전기 (핸드폰-전원 콘센트 사이 통신)
- 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해서 쓸 수 있게됨
- 인터페이스: 서로 다른 두 개 이상의 독립된 컴퓨터 시스템 구성 요소 간에 정보를 교환하는 공유 경계
· 사용예: 다양한 데이터베이스 시스템을 단일한 인터페이스로 조작할 수 있게 해주는 JDBC,
다양한 플랫폼의 프로그램을 단일한 자바 언어로 조작할 수 있도록 하는 JRE
▶ 예시 - 어댑터 패턴이 적용되지 않은 코드
public class ServiceA {
void runServiceA(){
System.out.println("ServiceA");
}
}
public class ServiceB {
void runServiceB(){
System.out.println("ServiceB");
}
}
public class ClientWithNoAdapter {
public static void main(String[] args){
ServiceA sa1 = new ServiceA();
ServiceB sb1 = new ServiceB();
sa1.runServiceA();
sb1.runServiceB();
}
}
▶ 예시 - 어댑터 패턴 적용
public class AdapterServiceA {
ServiceA sa1 = new ServiceA();
void runService() {
sa1.runServiceA();
}
}
public class AdapterServiceB {
ServiceB sb1 = new ServiceB();
void runService() {
sb1.runServiceB();
}
}
public class ClientWithAdapter {
public static void main(String[] args){
AdapterServiceA sa1 = new AdapterServiceA();
AdapterServiceB sb1 = new AdapterServiceB();
sa1.runService();
sb1.runService();
}
}
· ServiceA와 ServiceB의 메서드를 runService()라고 하는 같은 이름의 메서드로 호출해서 사용할 수 있게 해주는 변환기 예시
- 변환기들이 인터페이스를 구현하게 해서 더 개선할 수도 있음
· 이러한 방식을 오브젝트(객체) 어댑터 패턴이라함
- 클래스 어댑터 방식이 존재하지만, 다중상속을 이용하기 때문에 자바에서는 쓸 수 없음 (https://lalwr.blogspot.com/2016/03/adapter-vs-facade-vs-decorator.html)
프록시 패턴
· 제어 흐름을 조정하기 위한 목적으로 중간에 대리자를 두는 패턴
- 실제 서비스 메서드의 반환값에 가감하는 것을 목적으로 하지 않고,
제어의 흐름을 변경하거나 다른 로직을 수행하기 위해 사용
· 프록시: 대리자/대변인, 누군가를 대신해 그 역할을 수행
· 개방 폐쇄 원칙(OCP), 의존 역전 원칙(DIP)이 적용된 설계 패턴
▶ 예시 - 프록시 패턴이 적용되지 않은 코드
public class Service {
public String runSomething(){
return "서비스 짱!!!";
}
}
public class ClientWithNoProxy {
public static void main(String[] args){
// 프록시를 이용하지 않은 호출
Service service = new Service();
System.out.println(service.runSomething());
}
}
▶ 예시 - 프록시 패턴 적용
public interface IService {
String runSomething();
}
public class Service implements IService{
public String runSomething(){
return "서비스 짱!!!";
}
}
public class Proxy implements IService{
IService service1;
public String runSomething(){
System.out.println("호출에 대한 흐름 제어가 주목적, 반환 결과는 그대로 전달");
service1 = new Service();
return service1.runSomething();
}
}
public class ClientWithProxy {
public static void main(String[] args) {
// 프록시를 이용한 호출
IService proxy = new Proxy();
System.out.println(proxy.runSomething());
}
}
· 프록시 패턴의 중요 포인트:
- 대리자는 실제 서비스와 같은 이름의 메서드를 구현하며, 이를 위해 인터페이스를 사용
- 대리자는 실제 서비스에 대한 참조 변수를 갖음(합성)
- 대리자는 실제 서비스의 같은 이름을 가진 메서드를 호출하고, 그 값을 클라이언트에게 돌려줌
- 대리자는 실제 서비스의 메서드 호출 전후에 별도의 로직을 수행할 수 있음
· 서비스 객체가 들어갈 자리에 대리자 객체를 대신 투입해,
클라이언트 쪽에서는 실제 서비스 객체를 통해 메서드를 호출하고 반환값을 받는지 전혀 모르게 처리할 수 있음
데코레이터 패턴
· 원본에 장식을 더하는 패턴
· 프록시 패턴과 구현 방법이 같지만,
프록시 패턴은 클라이언트가 최종적으로 돌려 받는 반환값을 조작하지 않고 그래로 전달하는 반면,
데코레이터 패턴은 클라이언트가 받는 반환값에 장식을 덧입힘
▶ 예시 - 데코레이터 패턴을 적용한 코드
public interface IService {
String runSomething();
}
public class Service implements IService{
public String runSomething(){
return "서비스 짱!!!";
}
}
public class Decorator implements IService{
IService service;
public String runSomething(){
System.out.println("호출에 대한 장식이 주목적, 클라이언트에게 반환 결과에 장식을 더하여 전달");
service = new Service();
return "정말" + service.runSomething();
}
}
public class ClientWithDecorator {
public static void main(String[] args){
IService decorator = new Decorator();
System.out.println(decorator.runSomething());
}
}
· 데코레이터 패턴의 중요 포인트:
- 장식자는 실제 서비스와 같은 이름의 메서드를 구현하며, 이를 위해 인터페이스를 사용
- 장식자는 실제 서비스에 대한 참조 변수를 갖음(합성)
- 장식자는 실제 서비스의 같은 이름을 가진 메서드를 호출하고, 그 값에 장식을 더해 클라이언트에게 돌려줌
- 장식자는 실제 서비스의 메서드 호출 전후에 별도의 로직을 수행할 수 있음
싱글턴 패턴
· 오직 인스턴스를 하나만 만들고 그것을 계속 재사용하는 패턴
· 용도: 커넥션 풀, 스레드 풀, 디바이스 설정 객체 등 인스턴스를 여러 개 만들면 불필요한 자원을 사용하게 되고,
프로그램이 예상치 못한 결과를 낳을 수 있는 경우 사용함
▶ 예시 - 싱글턴 패턴을 적용한 코드
public class Singleton {
static Singleton singletonObject; //정적 참조 변수
private Singleton() {}; //private 생성자
//객체 반환 정적 메서드
public static Singleton getInstance(){
if(singletonObject == null){
singletonObject = new Singleton();
}
return singletonObject;
}
}
public class Client {
public static void main(String[] args){
//private 생성자이므로 new를 통해 인스턴스 생성 불가
//Singleton s = new Singleton();
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
Singleton s3 = Singleton.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
s1 = null;
s2 = null;
s3 = null;
}
}
· 싱글턴 패턴을 적용하기 위한 방법:
- new를 생성할 수 없도록 생성자에 private 접근 제어자를 지정
- 유일한 단일 객체를 반환할 수 있는 정적 메서드가 필요
- 유일한 단일 객체를 참조할 정적 참조 변수가 필요
· s1, s2, s3 참조 변수에 null을 입력하기전 T 메모리 스냅샷
· 단일 객체는 공유 객체로 사용되므로 속성을 갖지 않는 게 정석
- 단일 객체가 속성을 갖게 되면 하나의 참조 변수가 변경한 단일 객체의 속성이 다른 참조 변수에 영향을 미침
- 단, 읽기 전용 속성과 다른 단일 객체에 대한 참조 속성을 갖는 것은 예외
템플릿 메서드 패턴
· 상위 클래스의 템플릿(견본) 메서드에서 하위 클래스가 오버라이딩한 메서드를 호출하는 패턴
▶ 예시 - 템플릿 메서드 패턴이 적용되지 않은 코드: 강아지, 고양이와 재미있는 시간을 보내는 프로그램
public class Dog {
public void playWithOwner(){
System.out.println("귀염둥이 이리온...");
System.out.println("멍! 멍!");
System.out.println("꼬리 살랑 살랑~");
System.out.println("잘했어");
}
}
public class Cat {
public void playWithOwner(){
System.out.println("귀염둥이 이리온...");
System.out.println("야옹~ 야용~");
System.out.println("꼬리 살랑 살랑~");
System.out.println("잘했어");
}
}
▶ 예시 - 템플릿 메서드 패턴 적용
· 상속을 통해 중복되는 부분은 상위 클래스로, 달라지는 부분은 하위 클래스로 분할
public abstract class Animal {
//템플릿 메서드
public void playWithOwner(){
System.out.println("귀염둥이 이리온...");
play();
runSomething();
System.out.println("잘했어");
}
//추상 메서드
abstract void play();
//추상 메서드
//Hook(갈고리) 메서드
void runSomething(){
System.out.println("꼬리 살랑 살랑~");
}
}
public class Cat extends Animal{
@Override
//추상 메서드 오버라이딩
void play(){
System.out.println("야옹~ 야옹~");
}
@Override
// Hook(갈고리) 메서드 오버라이딩
void runSomething() {
System.out.println("야옹~ 야옹~ 꼬리 살랑 살랑~");
}
}
public class Dog extends Animal {
@Override
//추상 메서드 오버라이딩
void play() {
System.out.println("멍! 멍!");
}
@Override
// Hook(갈고리) 메서드 오버라이딩
void runSomething() {
System.out.println("멍! 멍! 꼬리 살랑 살랑~");
}
}
public class Driver {
public static void main(String[] args){
Animal bolt = new Dog();
Animal kitty = new Cat();
bolt.playWithOwner();
System.out.println();
System.out.println();
kitty.playWithOwner();
}
}
· 상위 클래스인 Animal에는 템플릿(공통 로직)을 제공하는 playWithOwner() 메서드,
하위 클래스에게 구현을 강제하는 play() 추상 메서드,
하위 클래스가 선택적으로 오버라이딩할 수 있는 훅(Hook) 메서드인 runSomething()가 존재
· 템플릿 메서드 클래스 다이어그램
· 클래스 다이어그램에서 보이듯이 의존 역전 원칙(DIP)를 활용하는 패턴
팩터리 메서드 패턴
· 하위 클래스에서 팩터리 메서드를 오버라이딩해서 객체를 반환하는 패턴
· 팩터리 메서드: 객체를 생성 반한하는 메서드
▶ 예시 - 팩터리 메서드 패턴을 적용한 코드: 강아지와 고양이가 각자 장난감을 가져오는 프로그램
public abstract class Animal {
//추상 팩터리 메서드
abstract AnimalToy getToy();
}
//팩터리 메서드가 생성할 객체의 상위 클래스
public abstract class AnimalToy {
abstract void identify();
}
public class Dog extends Animal{
//추상 팩터리 메서드 오버라이딩
@Override
AnimalToy getToy(){
return new DogToy();
}
}
//팩터리 메서드가 생성할 객체
public class DogToy extends AnimalToy{
public void identify(){
System.out.println("나는 테니스공! 강아지의 친구!");
}
}
public class Cat extends Animal{
//추상 팩터리 메서드 오버라이딩
@Override
AnimalToy getToy(){
return new CatToy();
}
}
//팩터리 메서드가 생성할 객체
public class CatToy extends AnimalToy{
@Override
public void identify(){
System.out.println("나는 캣타워! 고양이의 친구!");
}
}
public class Driver {
public static void main(String[] args){
//팩터리 메서드를 보유한 객체들 생성
Animal bolt = new Dog();
Animal kitty = new Cat();
//팩터리 메서드가 반환하는 객체들
AnimalToy boltBall = bolt.getToy();
AnimalToy kittyTower = kitty.getToy();
//팩터리 메서드가 반환한 객체들을 사용
boltBall.identify();
kittyTower.identify();
}
}
· 팩터리 메서드 클래스 다이어그램
· 클래스 다이어그램 위와 아래에 보이듯이 의존 역전 원칙(DIP)를 활용함
전략 패턴
· 클라이언트가 전략을 생성해 전략을 실행할 컨텍스트에 주입하는 패턴
· 템플릿 메서드 패턴과 유사하여 같은 문제의 해결책으로 상속을 이용하는 템플릿 패턴과
객체 주입을 통한 전략 패턴 중에서 선택/적용할 수 있음
- 단일 상속만 가능한 자바에서는 상속이라는 제한이 있는 템플릿 메서드 패턴보다 전략 패턴을 더 많이 활용
· 전략 패턴을 구성하는 세 요소
- 전략 메서드를 가진 전략 객체
- 전략 메서드를 사용하는 컨텍스트(전략 객체의 사용자/소비자)
- 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(제3자, 전략 객체의 공급자)
▶ 예시 - 전략 패턴을 적용한 코드
· 무기(전략)를 조달(생성)해서 군인(컨텍스트)에게 지급(주입)해 줄 보급 장교(클라이언트, 제3자)를 구현
public interface Strategy {
void runStrategy();
}
public class StrategyGun implements Strategy{
@Override
public void runStrategy(){
System.out.println("탕, 타당, 타다당");
}
}
public class StrategySword implements Strategy{
@Override
public void runStrategy(){
System.out.println("챙.. 채쟁챙 챙챙");
}
}
public class StrategyBow implements Strategy{
@Override
public void runStrategy(){
System.out.println("슝.. 쐐액.. 쉑, 최종 병기");
}
}
public class Soldier {
void runContext(Strategy strategy){
System.out.println("전투 시작");
strategy.runStrategy();
System.out.println("전투 종료");
}
}
public class Client {
public static void main(String[] args){
Strategy strategy = null;
Soldier rambo = new Soldier();
// 총을 람보에게 전달해서 전투를 수행하게 한다.
strategy = new StrategyGun();
rambo.runContext(strategy);
System.out.println();
// 검을 람보에게 전달해서 전투를 수행하게 한다.
strategy = new StrategySword();
rambo.runContext(strategy);
System.out.println();
// 활을 람보에게 전달해서 전투를 수행하게 한다.
strategy = new StrategyBow();
rambo.runContext(strategy);
}
}
· 전략 패턴의 클래스 다이어그램
TODO: 템플릿 콜백 패턴
▶ 예시 - 템플릿 콜백 패턴을 적용한 코드
public interface Strategy {
void runStrategy();
}
public class Soldier {
void runContext(final String weaponSound){
System.out.println("전투 시작");
executeWeapon(weaponSound);
System.out.println("전투 종료");
}
private Strategy executeWeapon(final String weaponSound){
return new Strategy() {
@Override
public void runStrategy() {
System.out.println(weaponSound);
}
};
}
}
public class Client {
public static void main(String[] args){
Soldier rambo = new Soldier();
rambo.runContext("총! 총초종총 총! 총!");
System.out.println();
rambo.runContext("칼! 카가갈 칼! 칼!");
System.out.println();
rambo.runContext("도끼! 독독..도도독 독끼!");
}
}
TODO: 스프링이 사랑한 다른 패턴들
'객체 지향 프로그래밍 > 스프링 입문을 위한 자바 객체 지향의 원리와 이해' 카테고리의 다른 글
[스프링 입문을 위한 자바 객체 지향의 원리와 이해] 객체 지향 설계 5원칙 - SOLID (0) | 2021.10.15 |
---|
댓글