[Design Pattern] 구조(Structural) 패턴

ASk·2022년 4월 18일
0

GoF Design Pattern

목록 보기
2/3
post-thumbnail

06. 어댑터 (Adapter) 패턴

  • 기존 코드를 클라이언트가 사용하는 인터페이스의 구현체로 바꿔주는 패턴
  • 클라이언트가 사용하는 인터페이스를 따르지 않는 기존 코드를 재사용할 수 있게 해준다.
  • 객체 어댑터(Composition 사용) 와 클래스 어댑터(Inheritance 사용) 로 나뉜다.

구현 방법

// Target
interface Target {
  void operation();
}

// Adaptee
public class Adaptee {
  
  public void specificOperation() {
    // do something...
  }
  
}

// Object Adapter
public class ObjectAdapter implements Target {

  private final Adaptee adaptee;

  public ObjectAdapter(Adaptee adaptee) {
    this.adaptee = adaptee;
  }

  @Override
  public void operation() {
    adaptee.specificOperation();
  }
  
}

// Class Adapter
public class ClassAdapter extends Adaptee implements Target {

  @Override
  public void operation() {
    super.specificOperation();
  }

}
public static void main(String[] args) {
  Target targetObjectAdapter = new ObjectAdapter(new Adaptee());
  Target targetClassAdapter = new ClassAdapter();

  targetObjectAdapter.operation();
  targetClassAdapter.operation();
}

장단점

장점

  • 기존 코드를 변경하지 않고 원하는 인터페이스 구현체를 만들어 재사용할 수 있다.
  • 기존 코드가 하던 일과 특정 인터페이스 구현체로 변환하는 작업을 각기 다른 클래스로 분리하여 관리할 수 있다.

단점

  • 새 클래스가 생겨 복잡도가 증가할 수 있다.
    경우에 따라서는 기존 코드가 해당 인터페이스(Target)를 구현하도록 수정하는 것이 좋은 선택이 될 수도 있다.

07. 브릿지 (Bridge) 패턴

  • 추상적인 것과 구체적인 것을 분리하여 연결하는 패턴
  • Composition 사용
  • 하나의 계층 구조일 때 보다 각기 나누었을 때 독립적인 계층 구조로 발전 시킬 수 있다.
  • 구현부에서 추상층을 분리하여 각자 독립적으로 변형이 가능하고 확장이 가능하도록 한다.

구현 방법

// Abstraction
public class Shape {

  private final Color color;
  private final String name;

  public Shape(Color color, String name) {
    this.color = color;
    this.name = name;
  }

  public void printInfo() {
    System.out.printf("%s %s\n", color.getValue(), this.name);
  }
  
}

// Refined Abstraction 1
public class Triangle extends Shape {

  public Triangle(Color color) {
    super(color, "triangle");
  }

  // 기능 추가...
  
}

// Refined Abstraction 1
public class Pentagon extends Shape {

  public Triangle(Color color) {
    super(color, "pentagon");
  }

  // 기능 추가...

}
// Implementation
public interface Color {
  String getValue();
}

// Concrete Implementation 1
public class Green implements Color {
  public void getValue(){
    return "green";
  }
}

// Concrete Implementation 2
public class Red implements Color {
  public void getValue(){
    return "red";
  }
}
public static void main(String[] args) {
  Shape triangle = new Triangle(new Red());
  triangle.printInfo();

  Shape pentagon = new Pentagon(new Green());
  pentagon.printInfo();
}

장단점

장점

  • 추상적인 코드를 구체적인 코드 변경 없이도 독립적으로 확장할 수 있다. (OCP)
  • 추상적인 코드과 구체적인 코드를 분리할 수 있다. (SRP)

단점

  • 계층 구조가 늘어나 복잡도가 증가할 수 있다.

08. 컴포지트 (Composite) 패턴

  • 그룹 전체와 개별 객체를 동일하게 처리할 수 있는 패턴.
  • 클라이언트 입장에서는 ‘전체’나 ‘부분’이나 모두 동일한 컴포넌트로 인식할 수는 계층 구조 를 만든다. (Part-Whole Hierarchy)

구현 방법

// Component
public interface Validator {
  void validate(String input);
}

// Leaf 1
public class EmptyValidator implements Validator {
  
  @Override
  public void validate(String input) {
    if (isEmpty(input)) {
      throw new IllegalArgumentException("input must not be empty");
    }
  }

  private boolean isEmpty(String input) {
    return input == null || input.isEmpty();
  }
}

// Leaf 2
public class InputPatternValidator implements Validator {
  
  private static final Pattern PATTERN = Pattern.compile("^\\d+ [-+*/] \\d+$");

  @Override
  public void validate(String input) {
    if (!PATTERN.matcher(input).matches()) {
      throw new IllegalArgumentException("invalid input pattern");
    }
  }
}

// Composite
public class CompositeValidator implements Validator {

  private final List<Validator> validators;

  public CompositeValidator(Validator... validators) {
    this.validators = Arrays.asList(validators);
  }

  @Override
  public void validate(String input) {
    validators.forEach(validator -> validator.validate(input));
  }
}
public static void main(String[] args) {
  Validator validator =  new CompositeValidator(new EmptyValidator(), new InputPatternValidator());
  validator.validate("10 + 25");
}

장단점

장점

  • 복잡한 트리 구조를 편리하게 사용할 수 있다.
  • 다형성과 재귀를 활용할 수 있다.
  • 클라이언트 코드를 변경하지 않고 새로운 엘리먼트 타입을 추가할 수 있다.

단점

  • 트리를 만들어야 하기 때문에 (공통된 인터페이스를 정의해야 하기 때문에) 지나치게 일반화 해야 하는 경우도 생길 수 있다.

09. 데코레이터 (Decorator) 패턴

  • 기존 코드를 변경하지 않고 부가 기능을 추가하는 패턴
  • 상속이 아닌 위임(Delegation)을 사용해서 보다 유연하게(런타임에) 부가 기능을 추가하는 것도 가능하다.
  • 위임이란 어떤 행위를 위임 관계에 있는 객체에게 넘겨서 처리하는 것을 의미한다. Composition + Forwarding 이라고 보면 될 것 같다.
  • Decorator 의 경우 Wrapper 라고도 한다.

구현 방법

// Component
interface ChristmasTree {
  String decorate();
}

// ConcreteComponent
public class DefaultChristmasTree implements ChristmasTree {
  
  @Override
  public String decorate() {
    return "Christmas tree";
  }
  
}
// Decorator
public class TreeDecorator implements ChristmasTree {
  
  private final ChristmasTree christmasTree;

  public TreeDecorator(ChristmasTree christmasTree) {
    this.christmasTree = christmasTree;
  }

  @Override
  public String decorate() {
    return christmasTree.decorate();
  }
  
}

// Concrete Decorator 1
public class LightsTreeDecorator extends TreeDecorator {

  public LightsTreeDecorator(ChristmasTree christmasTree) {
    super(christmasTree);
  }
  
  @Override
  public String decorate() {
    return super.decorate() + addLights();
  }

  private String addLights() {
    return " with Lights";
  }
  
}

// Concrete Decorator 2
public class FlowersTreeDecorator extends TreeDecorator {

  public FlowersTreeDecorator(ChristmasTree christmasTree) {
    super(christmasTree);
  }

  @Override
  public String decorate() {
    return super.decorate() + addFlowers();
  }

  private String addFlowers() {
    return " with Flowers";
  }
  
}
public static void main(String[] args) {
  ChristmasTree tree = new FlowersTreeDecorator(new LightsTreeDecorator(new DefaultChristmasTree()));

  // Christmas tree with Lights with Flowers 
  System.out.println("tree: " + tree.decorate()); 
}

장단점

장점

  • 새로운 클래스를 만들지 않고 기존 기능을 조합할 수 있다.
  • 컴파일 타임이 아닌 런타임에 동적으로 기능을 변경할 수 있다.

단점

  • 데코레이터를 조합하는 코드가 복잡할 수 있다.

사용하는곳

  • InputStream, OutputStream, Reader, Writer 의 생성자를 활용한 Wrapper
  • java.util.Collections 가 제공하는 메서드들 활용한 Wrapper(checked 로 시작하거나 synchronized 로 시작하는 메서드)
  • javax.servlet.http.HttpServletRequest/ResponseWrapper

10. 퍼사드 (Facade) 패턴

  • 복잡한 서브 시스템 의존성을 최소화하는 방법.
  • 클라이언트가 사용해야 하는 복잡한 서브 시스템 의존성을 간단한 인터페이스로 추상화 할 수 있다.
  • 클린코드에서 말하는 깨끗한 경계와 비슷한 맥락으로 보인다.
  • Facade 는 "건물의 정면"을 의미하는 단어이다.

구현 방법

// Facade
public class GoOffice {

  public void goToWork() {
    Wash wash = new Wash();
    Breakfast breakfast = new Breakfast();
    Move move = new Move();

    wash.brushTeeth();
    wash.shower();
    breakfast.eat();
    breakfast.water();
    move.bus();
  }
  
}

// Sub System 1
public class Wash {
  
  public void brushTeeth() {
    System.out.println("Brush my teeth");
  }

  public void shower() {
    System.out.println("Take a shower");
  }
  
}

// Sub System 2
public class Breakfast {
  
  public void eat() {
    System.out.println("Have breakfast");
  }

  public void water() {
    System.out.println("Drink water");
  }
  
}

// Sub System 3
public class Move {
  
  public void bus() {
    System.out.println("Take the bus");
  }
  
}
public static void main(String[] args) {
  GoOffice goOffice = new GoOffice();
  goOffice.goToWork();
}

장단점

장점

  • 서브 시스템에 대한 의존성을 한곳으로 모을 수 있다.

단점

  • 퍼사드 클래스가 서브 시스템에 대한 모든 의존성을 가지게 된다.

사용하는곳

  • SLF4J (Simple Logging Facade for Java)

11. 플라이웨이트 (Flyweight) 패턴

  • 객체를 가볍게 만들어 메모리 사용을 줄이는 패턴.
  • 자주 변하는 속성(또는 외적인 속성, extrinsic)과 변하지 않는 속성(또는 내적인 속성, intrinsic)을 분리하고 재사용하여 메모리 사용을 줄일 수 있다.
  • 재사용되는 객체는 공유되기 때문에 Immutable 해야 한다.
  • 공통의 요소를 공유하여 메모리를 절약하므로 캐싱으로 볼 수 있다.

구현 방법

// Flyweight
public final class Font {

  private final String family;
  private 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;
  }
  
}

// Flyweight Factory
public class FontFactory {

  private static final Map<String, Font> CACHE = new HashMap<>();

  public static Font getFont(String font) {
    Font result = CACHE.get(font);
    if (result == null) {
      String[] split = font.split(":");
      result = new Font(split[0], Integer.parseInt(split[1]));
      cache.put(font, result);
    }
    
    return result;
  }
  
}
public static void main(String[] args) {
    Font font1 = FontFactory.getFont("Arial:12");
    Font font2 = FontFactory.getFont("Arial:12");

    assert font1 == font2;
}

장단점

장점

  • 애플리케이션에서 사용하는 메모리를 줄일 수 있다.

단점

  • 코드의 복잡도가 증가한다.

사용하는곳

  • Integer.valueOf(int), -128 ~ 127 까지 캐싱한다.

12. 프록시 (Proxy) 패턴

  • 특정 객체에 대한 접근을 제어하거나 기능을 추가할 수 있는 패턴.
  • 초기화 지연, 접근 제어, 로깅, 캐싱 등 다양하게 응용해 사용 할 수 있다.

구현 방법

// Subject
public interface OrderService {
  void order();
}

// RealSubject
public class DefaultOrderService implements OrderService {

  @Override
  public void order() {
    System.out.println("주문하기...");
  }
  
}

// Proxy
public class OrderServiceProxy implements OrderService {

  private OrderService orderService;

  @Override
  public void startGame() {
    long before = System.currentTimeMillis();
    
    if (this.orderService == null) {
      this.orderService = new DefaultOrderService();
    }

    orderService.order();

    System.out.println(System.currentTimeMillis() - before);
  }
  
}
public static void main(String[] args) {
  OrderService orderService = new OrderServiceProxy();
  orderService.order();
}

장단점

장점

  • 기존 코드를 변경하지 않고 새로운 기능을 추가할 수 있다.
  • 기존 코드가 해야 하는 일만 유지할 수 있다.
  • 기능 추가 및 초기화 지연 등으로 다양하게 활용할 수 있다.

단점

  • 코드의 복잡도가 증가한다.

사용하는곳

  • Java 다이나믹 프록시, java.lang.reflect.Proxy
  • Spring AOP

데코레이터 패턴 vs 프록시 패턴

  • 데코레이터 패턴은 Real Class 의 기능에 다른 기능을 추가(확장) 하는것이 목적이다.
  • 프록시 패턴은 Real Class 의 접근에 대한 제어가 목적이다.
profile
Backend Developer

0개의 댓글