[Gof 패턴] 데코레이터 패턴, 퍼사드 패턴

600g (Kim Dong Geun)·2021년 6월 13일
0

데코레이터 패턴

  • 의도 : 객체에 동적으로 새로운 책임을 추가할 수 있게 합니다. 기능을 추가하려면, 서브클래스를 생성하는 것보다 융통성 있는 방법을 제공.
  • 동기
    • 전체 클래스에 새로운 기능을 추가할 필요는 없지만 개별적인 객체에 새로운 책임을 추가할 필요 한 경우가 존재
    • 일반적인 방법은 상속 을 이용하면 해결
    • 그러나 상속을 이용하면 정적인 방식(Compile time)으로 새로운 기능의 추가를 필요
    • 장식자 패턴(Decorator Pattern) 을 사용하면 Runtime 시간에 새로운 기능을 추가하면서 장식자의 존재는 감출수 있음
    • Decorator 패턴은 자신이 둘러싼 구성요소로 전달되는 요청을 중간에 가로채서 해당 구성요소에 전달
      • (Proxy 패턴과 유사하다 라는 생각이 듬)
  • 활용성
    • 동적으로 또한 투명하게 = 다른 객체에 영향을 주지 않고 개개의 객체에 새로운 책임을 추가하기 위해 사용
    • 제거될 수 있는 책임에 대해 사용
    • 실제 상속으로 서브클래스를 계속 만드는 방법이 실질적이지 못할때 사용
      • 너무 많은 수의 독립된 확장이 가능할 대 모든 조합을 지원하기 위해 이를 상속으로 해결하면 클래스 수가 많아짐.
  • 구조

데코레이터 패턴 (Decorator Pattern) - 기계인간 John Grib

  • Component : 동적으로 추가할 서비스를 가질 가능성이 있는 객체들에 대한 인터페이스
  • ConcreteComponent : 추가적인 서비스가 실제로 정의되어야할 필요가 있는 객체
  • Decorator : Component 객체에 대한 참조자를 관리하면서 Component에 정의된 인터페이스를 만족하도록 인터페이스를 정의
  • ConcreteDecorator : Component에 새롭게 추가할 서비스를 실제로 구현하는 클래스
  • 협력방법
    1. Decorator는 자신의 Component 객체 쪽으로 요청을 전달.
    2. 요청 전달 및 전달 후에 자신만의 추가 연산을 선택적으로 수행.
  • 장점

    • 단순한 상속보다 설계의 융통성을 증가

      장식자 패턴은 객체에 새로운 행동을 추가할 수 있는 가장 효과적 방법, 장식자를 사용하면 장식자를 객체와 연결하거나 분리하는 작업을 통해 새로운 책임을 추가하거나 삭제하는 일이 런타임에 가능해짐.

      반면, 상속 에서는 정적으로 새로운 클래스를 추가해야만 추가적인 행동을 정의할 수 있음.

    • 클래스 계통의 상부측 클래스에 많은 기능이 누적되는 상황을 피할 수 있음.

      장식자 패턴은 책임 추가 작업에서 필요한 비용만 그때 지불하는 방법을 제공. 지금 예상하지 못한 특성들을 한꺼번에 다 개발하기 위해 고민하고 노력하기 보다는 발견하지 못하고 누락된 서비스들을 Decorator 객체를 통해 지속적으로 추가

  • 단점

    • 장식자와 해당 그 장식자의 구성요소가 동일한 것은 아님

      장식자는 사용자에게 일관된 인터페이스를 제공하는 껍데기. 그러므로 객체 식별자 관점에서 구성요소와 이를 둘러싼 Decorator 객체가 동일한 식별자를 가질 필요가 없음

    • 장식자를 사용함으로써 작은 규모의 객체들이 많이 생김

      장식자 패턴을 사용하는 설계에서는 규모가 작은 객체들의 수가 많아지는데, 이 객체들이 서로 다른 점은 상호작용하는 방법에 있지, 클래스가 다르거나 변수에 정의된 값이 다른 것은 아님.

  • 구현법

    1. 인터페이스 일치시키기 : Decorator 객체의 인터페이스는 반드시 자신을 둘러싼 구성요소의 인터페이스를 만족, 따라서 ConcreteDecorator 클래스는 동일한 부모클래스 상속

    2. 추상 클래스로 정의되는 Decorator 클래스 생략하기 : 간혹 추상 클래스인 Decorator 클래스를 정의할 필요가 없을때도 있습니다. 이때는 Decorator 클래스에 정의할 책임이 한가지 밖에 존재하지 않음. 이것은 새로운 클래스들을 설계할 때 발생하기 보다는 기존에 존재하는 클래스 계통에 자주 일어남, 따라서 구성요소에게 요청을 전달하는 Decorator 클래스의 책임을 ConcreteDecorator와 합칠수 있음.

    3. Component 클래스는 가벼운 무게를 유지하기 : 인터페이스를 만족하는지 확인하려면 구성요소와 Decorator 모두 동일한 부모 클래스인 Component 클래스를 상속받아야함. 가볍게 정의한다는 의미는 연산에 해당하는 인터페이스만을 정의하고 무언가 저장할 수 있는 변수는 정의하지 말라는 의미

    4. 객체의 겉포장을 변경할 것인가, 속을 변경할 것인가? : 흔히 장식이란 행동을 변경할 수 있도록 객체에 외장을 입힌 것으로 알고 있는데, 이렇게 겉만 바꾸는 것이 아니라 내부도 변경할 수 있는데, 내부를 변경하는 대표적인 예가 뒤에서 살펴볼 전략 패턴.

      Component 클래스가 무겁다면 전략패턴

      그 반대라면 Decorator 패턴을 사용할 수 있을 것 같다.

  • 예시
  abstract public class Beverage {
  	String description = "제목 없음";
  	
  	public String getDescription() {
  		return description;
  	}
  	
  	public abstract double cost();
  }
  
  //에스프레소
  public class Espresso extends Beverage{
  
  	public Espresso() {
  		description = "에스프레소";
  	}
  
  	@Override
  	public double cost() {
  		return 1.99;
  	}
  }
  
  // 하우스 블렌드
  public class HouseBlend extends Beverage {
  
  	public HouseBlend() {
  		description = "하우스 블렌드 커피";
  	}
  
  	@Override
  	public double cost() {
  		return .89;
  	}
  }
//decorator들
public abstract class CondimentDecorator extends Beverage {
	public abstract String getDescription();
}

//실제 mocha가 Beveage를 들고 있기 때문에 위 커피종류에 해당하는 모든 기능을 다 사용할 수 있다.
//결국 이부분이 Adapter 패턴이랑 유사한거잖아.
//다만 관점의 차이가 있을 뿐 (같은 뿌리에서 기능을 추가하느냐 : 데코)
//다른 클래스에서 해당 클래스를 호환하기 위해 사용 Adapter
public class Mocha extends CondimentDecorator{

	Beverage beverage;

	public Mocha(Beverage beverage) {
		this.beverage = beverage;
	}

	@Override
	public double cost() {
		return .20 + beverage.cost();
	}

	@Override
	public String getDescription() {
		return beverage.getDescription() + ", 모카";
	}
}
  • 관련패턴
    • Adapter 패턴과 관련되어 있음. Decorator는 어쩌면 일종의 적응자 패턴
    • 하지만 그차이는 Deco는 객체의 책임, 행동까지 추가하여 변화 시켜준다는 점을 기억하자.

퍼사드 패턴

  • 의도 : 한 서브시스템 내의 인터페이스 집합에 대한 획일화된 하나의 인터페이스를 제공하는 패턴으로, 서브시스템을 사용하기 쉽도록 상위 수준의 인터페이스를 정의

  • 동기

    • 시스템을 서브시스템으로 구조화하면 복잡성을 줄이는데 큰 도움이 됨.
    • 서브시스템들 사이의 의사소통 및 종속성을 최소화함
      • 퍼사드 객체에서만 전체적인 클래스에 대한 객체를 알고 나머지는 모름
  • 활용성

    • 복잡한 서브시스템에 대한 단순한 인터페이스 제공이 필요
    • 추상 개념에 대한 구현 클래스와 사용자 사이에 너무많은 종속성이 존재할때
    • 서브시스템을 계층화시킬때, 퍼사드 패턴을 사용하여 각 서브시스템의 계층에 대한 접근점을 제공
      • 서브시스템이 다른 서브시스템에 종속적이라 하더라도, 각자가 제공하는 퍼사드를 통해서만 대활르 진행하게 함으로써 서브시스템 간의 종속성을 줄일 수 있음.
  • 구조

  • 퍼사드 : 단순하고 일관된 통합 인터페이스를 제공, 서브 시스템을 구성하는 어떤 클래스가 어떤 요청을 처리해야 하는지 알고 있으며, 사용자의 요청을 해당 서브시스템 객체에 전달
  • 서브시스템 클래스들 : 서브 시스템의 기능을 구현하고 Facade 객체로 할당된 작업을 실제로 처리하지만 Facade에 대한 아무런 정보가 없음.
    • 즉 Facade는 서브시스템을 알고있지만, 서브시스템들은 Facade에 대한 정보를 모름
  • 장점
    • 서브시스템의 구성요소를 보호할 수 있음.
      • 이로써 사용자가 다루어야할 객체의 수가 줄어들며, 서브시스템을 쉽게 사용할 수 있음
    • 서브시스템과 사용자 코드 간의 결합도를 더욱 약하게 만듬.
      • 서브시스템 내 정의된 요소들은 강하게 결합될 수 있음. 서브시스템과 사용자 간의 결합이 약하면, 서브시스템 내의 요소를 다양화하는 작업을 원활하게 할 수 있음.
public class Light {
    public void on() {
        System.out.println("조명 킵니다.");
    }

    public void movieMode() {
        System.out.println("조명을 영화관 모드로 변경합니다.");
    }
}

public class Screen {
    public void down() {
        System.out.println("스크린을 내립니다.");
    }

    public void up() {
        System.out.println("스크린을 올립니다.");
    }
}

public class Projector {
    public void on() {
        System.out.println("Projector를 킵니다.");
    }


    public void off() {
        System.out.println("Projector를 끕니다.");
    }

    public void movieMode() {
        System.out.println("Projector를 영화관모드로 설정합니다.");
    }
}

public class DvdPlayer {
    public void on() {
        System.out.println("DvdPlayer를 킵니다.");
    }

    public void play(String movieName) {
        System.out.println(movieName + "를 시작합니다.");
    }

    public void off() {
        System.out.println("DvcPlayer를 끕니다.");
    }
}

//퍼사드 객체
public class HomeTheaterFacade {
    Light light;
    Projector projector;
    Screen screen;
    DvdPlayer dvdPlayer;

    public HomeTheaterFacade(Light light, Projector projector,
                             Screen screen, DvdPlayer dvdPlayer) {
        this.light = light;
        this.projector = projector;
        this.screen = screen;
        this.dvdPlayer = dvdPlayer;
    }

    public void watchMovie(String movieName) {
        System.out.println("===============WATCH MOIVE===============");
        light.movieMode();
        screen.down();
        projector.on();
        projector.movieMode();
        dvdPlayer.on();
        dvdPlayer.play(movieName);
    }

    public void endMovie() {
        System.out.println("===============END MOIVE===============");
        light.on();
        screen.up();
        projector.off();
        dvdPlayer.off();
    }
}
  • java의 slf4j(simple logging facade 4(for) java) 에서도 facade 패턴을 사용하여 client는 facade 패턴만 알면 내부적인 기능, 절차에 대해 알지 못해도 로깅에 대한 기능을 사용할 수 있게끔 구현해놨다.
    • 즉 사용자는 logging 권한체크, 로깅기록, 파일시스템 관리, 어떤 메소드에서 발생했는지(리플렉션) 등에 신경쓰지 않아도 단순히 logger.info 와 같이 slf4j가 제공하는 메소드 만으로 로그 기능을 쉽게 사용할 수 있다.

아마 이점이 가장 매력적인 것 같다.

profile
수동적인 과신과 행운이 아닌, 능동적인 노력과 치열함

0개의 댓글