CS_Step5 데코레이터 패턴(Decorator Pattern)

장선웅·2022년 7월 16일
1

데코레이터 패턴?

  1. 기본 기능에 추가할 수 있는 기능의 종류가 많은 경우 각 추가 기능을 Decorator클래스로 정의한 뒤, 필요한 Decorator 객체를 조합함으로써 추가 기능의 조합을 설계하는 패턴.
  2. 기본 기능에 추가할 수 있는 많은 종류의 부가 기능에서 파생되는 다양한 조합을 동적으로 구현할 수 있는 패턴.

간단히 말하면, 객체의 결합을 통해 기능을 동적으로 유연하게 확장 할 수 있게 해주는 패턴.


1. 데코레이터 패턴은 왜 사용할까?

예를 들면, 당신은 지금 샌드위치를 만들고 있다고 가정하자. 그러기 위해서는 기본적으로 빵(Bread)이 필요하다. 그리고 토핑으로 치즈(Cheese), 햄(Ham)이 들어간다고 하자.

//Sandwich 클래스 생성
public class Sandwich {
	//Bread추가 메서드
    public void make(){
    	System.out.println("Bread 생성");
    }
}
//토핑으로 Cheese만 들어간 Sandwich 클래스(Sandwich클래스 상속)
public class SandwichWithCheese extends Sandwich{
	//SandwichWithCheese를 만드는 메서드
    public void make(){
    	//상속받은 Sandwich클래스에 있는 make 불러오기
        super.make();
        //Sandwich에 Cheese넣는 매서드 호출
        addCheese();
    }
    //Sandwuch에 Cheese를 넣는 메서드
    public void addCheese(){
    	System.out.println(" + Cheese");
    }
}
//토핑으로 Ham만 들어간 Sandwich 클래스(Sandwich클래스 상속)
public class SandwichWithHam extends Sandwich{
	//SandwichWithHam를 만드는 메서드
    public void make(){
    	//상속받은 Sandwich클래스에 있는 make 불러오기
        super.make();
        //Sandwich에 Ham넣는 매서드 호출
        addHam();
    }
    //Sandwich에 Ham을 넣는 메서드
    public void addHam(){
    	System.out.println(" + Ham");
    }
}
//Sandwich를 만드는 클래스(실제 코드가 실행되는 main클래스)
public class makerSandwich{
	public static void main(String args[]) {
    	//Sandwich 클래스 호출(Bread를 추가하는 메서드를 갖는 클래스 호출)
        Sandwich sandwich = new Sandwich();
        //Sandwich 클래스에 있는 Bread를 추가하는 메서드 호출
        sandwich.make();
        System.out.println("====구분선====");
        //Cheese만 들어가는 SandwichWithCheese클래스 호출
        SandwichWithCheese sandwichWithcheese = new SandwichWithCheese();
        //Cheese만 들어가는 SandwichWithCheese를 만드는 메서드 호출
        sandwichWithcheese.make();
        System.out.println("====구분선====");
         //Ham만 들어가는 SandwichWithHam클래스 호출
        SandwichWithHam sandwichWithham = new SandwichWithHam();
        //Ham만 들어가는 SandwichWithHam를 만드는 메서드 호출
        sandwichWithham.make();
    }
}
//결과값
Bread 생성
====구분선====
Bread 생성
 + Cheese
====구분선====
 Bread 생성
 + Ham

그런데 이런 상황에서 Cheese와 Ham이 모두 들어간 Sandwich를 만든다고 해보자.

//토핑으로 Cheese,Ham이 들어간 Sandwich 클래스(Sandwich클래스 상속)
public class SandwichWithCheeseAndHam extends Sandwich{
	//SandwichWithCheeseAndHam를 만드는 메서드
    public void make(){
    	//상속받은 Sandwich클래스에 있는 make 불러오기
        super.make();
        //Sandwich에 Cheese,Ham을 넣는 매서드 호출
        addHam();
    }
    //Sandwich에 Cheese와 Ham을 넣는 메서드
    public void addCheeseAndHam(){
    	System.out.println(" + Cheese + Ham");
    }
}
//Sandwich를 만드는 클래스(실제 코드가 실행되는 main클래스)
public class sandwichMaker{
	public static void main(String args[]) {
    	//Sandwich 클래스 호출(Bread를 추가하는 메서드를 갖는 클래스 호출)
        Sandwich sandwich = new Sandwich();
        //Sandwich 클래스에 있는 Bread를 추가하는 메서드 호출
        sandwich.make();
        System.out.println("====구분선====");
        //Cheese만 들어가는 SandwichWithCheese클래스 호출
        SandwichWithCheese sandwichWithcheese = new SandwichWithCheese();
        //Cheese만 들어가는 SandwichWithCheese를 만드는 메서드 호출
        sandwichWithcheese.make();
        System.out.println("====구분선====");
         //Ham만 들어가는 SandwichWithHam클래스 호출
        SandwichWithHam sandwichWithham = new SandwichWithHam();
        //Ham만 들어가는 SandwichWithHam를 만드는 메서드 호출
        sandwichWithham.make();
        System.out.println("====구분선====");
        //Cheese와 햄이 들어가는 SandwichWithCheeseAndHam클래스 호출
        SandwichWithcheeseAndham sandwichWithham = new SandwichWithCheeseAndHam();
        //Ham만 들어가는 SandwichWithCheeseAndHam를 만드는 메서드 호출
        SandwichWithCheeseAndHam.make();
    }
}
//결과값
Bread 생성
====구분선====
Bread 생성
 + Cheese
====구분선====
 Bread 생성
 + Ham
 ====구분선====
 Bread 생성
 + Chees + Ham

이처럼 코드가 길어지고 클래스의 개수가 많아지게 된다. 또 토핑의 종류가 양배추, 양파등 10개가 더 추가 되고 그들에 대한 조합이 많아지게되면 클래스는 수도 없이 많아질 것이다. 이를 해결 하기위해 위처럼 서브 클래스를 만드는 방식이 아닌, 데커레이터 패턴을 적용하여 이를 해결해보도록 하자.


2. 데코레이터 패턴 구현 방법

1) Sandwich 추상 클래스 정의

Cheese가 들어간 Sandwich, Ham이 들어간 Sandwich등 여러 샌드위치를 만드는 것을 캡슐화 하기 위함이다.

//Sandwich 추상 클래스 정의
public abstract class {
	//재료를 추가하는 추상 메서드
    public abstract void make();
}

2) 재료를 추가하는 AddDecorator 클래스 생성

AddDecorator 클래스는 샌드위치에 재료를 추가하는 것이므로 Sandwich를 상속받도록 한다.

//AddDecorator 클래스 구현(Sandwich 클래스 상속)
public class AddDecorator extends Sandwich{
	//Sandwich 변수 생성
    private Sandwich sandwich;
    //AddDecorator생성자
    public AddDecorator(Sandwich sandwich) {
    	this.sandwich = sandwich;
   	}
    //재료를 추가하는 추상 메서드 정의
    public void make(){
    	sandwich.make();
    }
}

3) 빵을 추가하기 위한 Bread클래스 구현

빵은 재료 추가와는 다르게 기본적으로 있어야 하므로 Decorator로 지정하지 않는다.

public class Bread extends Sandwich{
	//추상 클래스 Sandwich를 상속받았기 때문에 안에 있는 추상 메서드 정의(Bread 추가)
    public void make(){
    	System.out.println("Bread 추가");
    }
}

4) Cheese와 Ham을 추가하기 위한 각각의 Decorator 클래스 구현

//CheeseDecorator 클래스
public class CheeseDecorator extends AddDecorator{
	//CheeseDecorator 생성자
    public CheeseDecorator(Sandwich sandwich) {
    	super(sandwich);
    }
    //Cheese가 들어간 Sandwich 만드는 메서드 정의
    public void make(){
    	super.make();
        //Cheese를 추가하는 메서드 호출
        addCheese();
    }
    //Cheese를 추가하는 메서드 정의
    public void addCheese{
    	System.out.println(" + Cheese");
    }
}
//HamDecorator 클래스
public class HamDecorator extends AddDecorator{
	//HamDecorator 생성자
    public HamDecorator(Sandwich sandwich) {
    	super(sandwich);
    }
    //Ham 들어간 Sandwich 만드는 메서드 정의
    public void make(){
    	super.make();
        //Cheese를 추가하는 메서드 호출
        addHam();
    }
    //Ham 추가하는 메서드 정의
    public void addHam{
    	System.out.println(" + Ham");
    }
}

4. Sandwich를 실제롤 만드는 클래스 구현(main 클래스 구현)

//Sandwich를 만드는 클래스(실제 코드가 실행되는 main클래스)
public class sandwichMaker{
	public static void main(String args[]) {
    	//Cheese Sandwich
        Sandwich sandwichWithcheese = new CheeseDecorator(new Bread());
        //sandwichWithcheese 만들기
        sandwichWithcheese.make();
        System.out.println("====구분선====");
        //Ham Sandwich
        Sandwich sandwichWithcheese = new HamDecorator(new Bread());
        //sandwichWithham 만들기
        sandwichWithham.make();
        System.out.println("====구분선====");
        //Cheese와 Ham이 들어간 Sandwich
        Sandwich sandwichWithcheeseAndham = new CheeseDecorator(new HamDecorator(new Bread()));
        //sandwichWithcheeseAndham 만들기
        sandwichWithcheeseAndham.make();
    }
}
//결과값
 Bread 생성
 + Cheese
 ====구분선====
 Bread 생성
 + Ham
 ====구분선====
 Bread 생성
 + Chees + Ham

이처럼 데커레이터 객체를 생성할 때, 생성자로 다시 데커레이터를 생성하고, 최종적으로 Bread 객체를 생성하면 된다. 재료가 더 늘어난다고 해도 이처럼 계속 데커레이터 객체를 생성함으로써 샌드위치를 만들 수 있게 된다.


3. 데코레이터 패턴의 장/단점

장점

  1. 기존 코드를 수정하지 않고도 데로레이터 패턴을 통해 행동을 확장할 수 있다
  2. 구성과 위임을 통해 실행중에 새로운 행동을 추가할 수 있다.

단점

  1. 의미없는 객체들이 너무 많이 추가될 수 있다.
  2. 데코레이터를 너무 많이 사용하면 코드가 너무 복잡해질 수 있다.

4. 상속(Extends)와 구현(Implements)의 차이

  • 상속(Extends)
    1. 자식이 부모의 특징을 받아 사용한다는 의미이다.
    2. 코드의 재사용성을 높이고 코드의 중복성을 최소화 할 수 있다
    3. 상속 받은 클래스(자식 클래스)는 원하는 행위와 특징을 추가, 확장할 수 있다.(=> 재사용성을 높인다.)
  • 구현(Implements)
    1. 특정 인터페이스를 클래스에서 구현한다는 의미이다.
    2. 인터페이스에서 정의한 메스드에 강제력이 생긴다.
    3. 인터페이스를 구현한 클래스에서는 인터페이스의 메서드를 재정의해서 구현해야한다.

1. 클래스에서 클래스에 다중 상속이 불가능하다.

2. 인터페이스에서 인터페이스로 다중 구현이 가능하다.

3. 클래스에서 인터페이스를 다중 구현 가능하다.


4. 데코레이터 패턴을 이용한 자바의 I/O 클래스(Input / Out)

1) InputStream - 추상 클래스를 이용한 데코레이터 패턴을 바탕으로 만들어짐.
2) FileInputStream - 추상 클래스를 이용한 데코레이터 패턴을 바탕으로 만들어짐.
3) BufferedInputStream - 구현(Implements 즉, 인터페이스)을 이용한 데코레이터 페턴을 바탕으로 만들어짐.
4) LineNumberInputStream - 구현(Implements 즉, 인터페이스)을 이용한 데코레이터 페턴을 바탕으로 만들어짐.

profile
개발을 꿈꾸는 초짜

0개의 댓글