이 섹션의 다중 페이지 출력 화면임. 여기를 클릭하여 프린트.

이 페이지의 일반 화면으로 돌아가기.

프로그래밍 개발과 설계

1 - 개발과 설계 원칙

1.1 - MSA (Microservices Architecture) 마이크로소프트 아키텍처

MSA이란?

  • Micoroservices Architecture
  • 모든 시스템의 구성요소가 한 프로젝트에 통합되어 있는 Monolithic Architecture(모놀리식 아키텍처)의 한계점을 극복하고자 등장하였다.
  • 작고, 독립적으로 배포 가능한 각각의 기능을 수행하는 서비스로 구성된 프레임워크이다.
  • MSA는 1개의 시스템을 완전한 독립적으로 배포 가능한 각각의 서비스로 분할한다.
  • 각각의 서비스는 RESTful API를 통해 데이터를 주고 받으며, 1개의 큰 서비스를 구성한다.
  • 다른 기술 스택(개발 언어, 데이터베이스 등)이 사용 가능한 단일 사업 영역에 초점을 둔다.

MSA의 특징

  • API를 통해서만 상호작용 할 수 있다.
  • 서비스의 end-point(접근점)을 API 형태로 외부에 노출한다.
  • 실직적인 세부 사항은 모두 추상화 한다.
  • 내부의 구현 로직, 아키텍처와 프로그램 언어, 데이터베이스, 품질 유지 체계와 같은 기술적인 사항들은 서비스 API에 의해 철저하게 가려진다.

MSA의 장점

  • 각각의 서비스는 모듈화가 되어 있으며 이러한 모듈끼리는 RPC 또는 Message-driven API 등을 이용해서 통신한다.
  • 각각의 개별의 서비스 개발을 빠르게 하고, 유지 보수도 쉽게 할 수 있도록 한다.
  • 적절한 수준의 기술 스택을 다르게 가져갈 수 있다.
  • 서비스별로 독립적 배포가 가능하다.
    • 지속적인 배포(CD)도 모놀로식에 비해서 가볍게 할 수 있다.
  • 각각 서비스의 부하에 따라 개별적인 Scale-out이 가능하다.
    • 이는 메모리, CPU 적으로 상당부분이 이득이다.
  • 일부 서비스에 장애가 발생하여도 전체 서비스에 장애가 발생하지 않는다.
  • 각각의 서비스들은 다른 언어와 프레임워크로 구성될 수 있다.
  • 서비스의 확장이 용이하다.

MSA의 단점

  • 모놀리식에 비해 상대적으로 많이 복잡하다.
  • 서비스 간에 RESTful API로 통신하기 때문에 그에 대한 비용이 발생한다.
  • 서비스가 분리되어 있어, 테스팅이나 트랜잭션 처리 등이 어렵다.
    • 통신 장애와 서버의 부하 등이 있을 경우 어떻게 transaction을 유지해야 할지 결정하고 구현해야 한다.
  • 서비스 간에 호출이 연속적이기 때문에 디버깅 및 통합 테스트가 어렵다.
  • 실제 운영 환경에 대해서 배포하는 것이 쉽지 않다.

MSA 서버 구성

서비스 디스커버리 서버

  • Spring : Netflix Eureka

웹 서비스 클라이언트

  • Spring : Netfilx Feign

클라이언트 사이드 로드 밸런서

  • Spring : Netflix Ribbon

서킷 브레이커(Circuit Breaker)

  • Spring : Netflix Hystrix
  • 클라이언트에서 원격 서버로 전송한 요청의 실패율이 특정 임계치(threshold)를 넘어서면, 이를 서버가 문제가 있다고 판단하여 더 이상 무의미한 요청을 전송하지 않고 빠르게 에러를 발생키시는 방법(fail fast)이다.

API 게이트웨이(Gateway)

  • Spring : Netflix Zuul, Gateway
  • 리버스 프록시 서버

참고

1.2 - TDD(Test Driven Development) 테스트 주도 개발

TDD(Test Driven Development)란?

  • 테스트 주도 개발
  • 우선 테스트 코드를 작성하고 이를 통해서 테스트를 하여 제대로 동작하는지에 대한 피드백을 받아가면 진행하는 개발 방법이다.
  • 매주 짧은 개발 사이클의 반복에 의존하는 개발 프로세스로, 개발자는 우선 요구되는 기능에 대한 테스트케이스를 작성하고, 그에 맞는 코드를 작성하여 테스트를 통과한 후에 상황에 맞게 리팩토링하는 개발 방식을 의미한다.
  • 개발자는 테스트를 작성하기 위해 해당 기능의 요구사항을 확실히 이해해야 하기 때문에 개발 전에 요구 사하에 집중할 수 있도록 도와주지만, 테스트를 위한 진입 장벽과 작성해야 하는 코드의 증가는 단점으로 뽑한다.

Mock 이란?

실제 객체를 만들어 사용하기에는 비용이 크고, 객체간 의존성이 높아 구현하기 힘들 경우 만드는 가짜 객체를 말한다.

Mock 사용 예시

  • 테스트 작성을 위한 환경 구축
  • 테스트가 특정 경우나 순간에 의존적인 경우
  • 테스트 시간이 오래 걸리는 경우
  • 개인 PC나 서버의 성능 문제로 동작이 오래 걸릴 수 있는 경우

Mockito란?

  • 유닛 테스트를 위한 Java mocking framework이다.
  • 자바 단위테스트에서 모의(Mock) 객체를 지원해주는 테스트 프레임워크이다.
  • 즉, 단위 테스트를 하기 위해 Mock을 만들어주는 프레임워크이다.
  • Mock 객체 생성, Mock 객체 동작을 지정, 그리고 테스트 대상 로직이 제대로 수행되었는지 확인 가능하다.
  • 일반적으로 Spring과 같은 웹 어플리케이션을 개발한다고 하면, 여러 객체들 간의 의존성이 존재한다. 이러한 의존성은 단위 테스트를 작성하는 것을 어렵게 하는데, 이를 해결하기 위해 가짜 객체를 주입시켜주는 Mockito 라이브러리를 활용ㅎ할 수 있다.
  • Mockito를 활용함으로써 가짜 객체에 원하는 결과를 Stub하여 단위 테스트를 진행할 수 있다.

Mock 객체 의존성 주입

  • Mockito에서 Mock(가짜) 객체의 의존성 주입을 위해서 크게 3가지 어노테이션이 사용된다.
    • @Mock
      • Mock 객체를 만들어 반환해주는 어노테이션
    • @Spy
      • Stub하지 않은 메소드들은 원본 메소드 그대로 사용하는 어노테이션
    • @InjectMock
      • @Mock 또는 @Spy로 생성된 가짝 객체를 자동으로 주입시켜주는 어노테이션
      • 예를 들어 UserController에 대한 단위 테스트를 작성하고자 할때, UserService를 사용하고 있다면 @Mock 어노테이션을 통해 가짜 UserService를 만들고, @injectMocks을 통해 UserController에 이를 주입시킬 수 있다.

1.3 - DDD(Domain Driven Design) 도메인 주도 개발

DDD(Domain Driven Design)란?

  • 도메인 주도 설계
  • 정교한 객체 시스템을 제작하는데 도움이 되는 원칙과 패턴의 집합이다.
  • 비즈니스 Domin별로 나누어 설계하는 방식이다.
  • 실세계에서 사건이 발생하는 집합인 Domain(도메인)을 중심으로 설계하는 방법이다.
    • 옷 쇼핑몰을 예를 들면, 손님들의 주문하는 도메인, 점주들이 관리하는 도메인 등이 있을 수 있다.
    • 이런한 도메인들이 서로 상호 작용을 하며 설계하는 것이 도메인 주도 설계이다.
  • 도메인 주도 설계에서 도메인은 각각 분리되어 있는데, 이런한 관점에서 MSA(MicroService Architecture)를 적용하면 용이한 설계를 할 수 있다.
  • DDD에서는 같은 객체들이 존재할 수 있는데, 예를 들어 옷 구매자의 입장에서는 (name, price)와 같은 객체 정보를 담지만, 판매자 입장에서는 (madeTie, Size, madeCounty) 등이 있을 수 있다. 즉, 문맥에 따라 객체의 역할이 발뀔 수 있는 것이 DDD이다.

Ubiquitous Language (유비쿼터스 랭귀지)

도메인에서 사용하는 용어를 코드에 반영하지 않으면 그 코드는 개발자에게 코드의 의미를 해석해야 하는 부담을 준다. 코드의 가독성을 높여서 코드를 분석하고 이해하는 시간을 절약, 용어가 정의 될 때마다 용어 사전에 이를 기록하고 명확하게 정의 함으로써 추후 또는 다른 사람들도 공통된 언어를 사용할 수 있도록 한다.

도메인이란?

  • 일반적인 요구사항, 소프트웨어로 해결하고자 하는 문제의 영역이다.
    • 온라인 서점 시스템을 구현한다고 할때, 온라인 서점이 도메인이 된다.
  • 한 도메인은 다시 여러개의 하위 도메인으로 나뉠 수 있다.
    • 온라인 서점의 하위 도메인은 상품, 회원, 주문, 정산, 배송 등이 된다.
  • 모든 도메인마다 고정된 하위 도메인이 존재하는 것은 아니다. 상황에 따라 달라진다.
    • 대상이 기업인지, 사용자인지 등등
  • 특정 도메인을 위한 소프트웨어라고 해서 도메인이 제공해야 할 모든 기능을 구현하는 것은 아니다.
    • 외부 업체의 배송 시스템이나, 결재 시스템 같은 것을 이용하기도 한다.

도메인 모델

  • 특정 도메인을 개념적으로 표현한 것이다.
  • 실제 세계를 반영은 하지만, 실제 세계의 복사본이 아니며, 실제 대상을 더 잘 이해할 수 있도록 추상화되고 구체화하는 것이다.
  • 이를 사용하면 여러 관계자들이 동일한 모습으로 도메인을 이해하고, 도메인 지식을 공유하는데 도움이 된다.
    • 도메인을 이해하려면 도메인이 제공하는 기능과 주요 데이터 구성을 파악해야 한다.
    • 보통은 기능과 데이터를 함께 보여주는 클래스 다이어그램이 적합하다.
    • 꼭 UML만 사용해야 하는 것은 아니다. 도메인을 이해하는데 도움이 된다면 표현 방식이 무엇인지는 중요하지 않다.
  • 여러 하위 도메인을 하나의 다이어그램에 모델링하면 안된다.
    • 각 하위 도메인마다 별도로 모델을 만들어야 한다.
    • 모델의 각 구성 요소는 특정 도메인을 한정할 때 비로서 의미가 완전해지기 때문이다.
      • 카탈로그의 상품과 배송의 상품은 다르다.

Entity, VO

  • Entity
    • 테이블 모델, 고유 식별자를 가진다.
    • 고유 식별자(primary Key)를 바탕으로 객체의 정체성을 부여한다.
  • VO(Value Object)
    • 데이터 표현 모델 식별자를 가지고 있지 않고 불변 타입이다.
    • 상태(Attribute)를 바탕으로 객체의 정체성을 부여한다.

Java로 쉽게 설명하자면, equals HashCode를 id로만 하면 Entity이고 상태에 대한 모든 정보로 하면 VO 이다.

도메인 애그리거트(Aggregate)

  • 서로 관련 있는 도메인 모델들의 집합, 연관 객체의 묶음을 애그리거트라고 한다.

애그리거트 루트

  • 애그리거트에 속한 객체가 일관된 상태를 유지하려면 애그리거트 전체를 관리할 주체가 필요하다.
  • 루트 엔티티는 애그리거트의 대표 엔티티로, 애그리거트에 속한 엔티티는 루트 엔티티에 직접 혹은 간접적으로 속한다.
    • 애그리거트 루트의 핵심 역할은 애그리거트의 일관성이 깨지지 않도록 조율하는 것이다.
    • 애그리거트 루트는 애그리거트가 제공해야 할 도메인 기능을 제공한다.
      • 주문 애그리거트의 루트 엔티티 Order는 관련 기능을 구혀한 메소드를 제공한다.
        • 배송지 변경, 상품 변경 등.

도메인 모델 패턴

  • 일반적인 어플리케이션의 아키텍처는 4가지 영역(계층)으로 구성된다.

Presentation: 표현(UI) 영역

  • 사용자의 요청을 받아 응용 영역에 전달한다.
  • 스프링 MVC 프레임워크가 표현 영역을 위한 기술에 해당된다.
  • 웹 애플맄이션에서 표현 영역의 사용자는 웹 브라우저를 사용하는 사람일 수도 있고, REST API를 호출하는 외부 시스템일 수도 있다.

Application: 응용 영역

  • 처리 결과를 다시 사용자에게 보여주는 역할을 한다.
  • 업무 로직을 직업 구현하지 않으며, 도메인 계층을 조합해서 기능을 실행한다.
  • 주로 도메인과 Repository를 바탕으로 실제 서비스(API)를 제공하는 계층이다.

Domain: 도메인 영역

  • 시스템이 제공할 도메인의 규칙을 구현한다.
  • Entity, VO(Value Object)를 활용하여 도메인 로직이 진행되는 계층이다.

Intrastructure: 인프라스트럭처 영역

  • 영속성을 구현하거나 외부 시스템과의 연동을 처리한다.
  • 즉, 외부와의 통신 DB, NoSQL, Messaging 등을 담당하는 계층이다.

1.4 - SOLID 객체 지향 프로그래밍 및 설계의 5가지 기본 원칙

객체 지향 프로그래밍 및 설계의 5가지 기본 원칙이다.

  • SRP: 단일 책임 원칙 (Single Responsibility Principle)
  • OCP: 개방-폐쇄 원칙 (Open-Closed Principle)
  • LSP: 리스코프 치환 원칙 (Liskov Substitution Principle)
  • ISP: 인터페이스 분리 원칙 (Interface Segregation Principle)
  • DIP: 의존성 역전 원칙 (Dependency Inversion)

앞 알파벳만 따와서 이를 SOLID라도 한다.

SRP: 단일 책임 원칙 (Single Responsibility Principle)

  • 소프트웨어의 설계 부품(클래스, 함수, 모듈 등)은 단 하나의 책임만 가져야 한다.
  • 설계를 잘한 프로그램은 기본적으로 새로운 요구사항에 프로그램 변경에 영향을 받는 부분이 적다.
  • 즉, 응집도가 높도, 결합도는 낮은 프로그램을 뜻한다.
  • 만약 한 클래스가 수행할 있는 기능. 즉, 책임 많아진다면 클래스 내부의 함수끼리 강한 결합이 발생할 가능성이 높아진다. 이는 유지보수에 비용이 증가하게 되므로써 책임을 분리시킬 필요가 있다.
  • 클래스는 하나의 책임을 수행하고 여러개를 수행한다면 쪼갠다.

단일 책임 원칙을 위반한 예제

아아래 코드는 직원에 따라 업무에 대한 예제이다.

package com.devkuma.tutorial.solid.srp.bad;

public interface Employee {

    // 경리팀 업무
    void calculatePay();

    // 인사팀 업무
    void reportHours();

    // DB 관린자 업무
    void save();
}

위에 코드는 세 가지 메서드는 각각 다른 액터에 대한 책임을 지고 있으며, SRP를 위반하고 있다.
즉, 액터의 다른 코드는 분할되어야 한다.

단일 책임 원칙을 위반한 예제의 해결책

위에 예제에 대한 해결책은 아래와 같다.

공유 데이터 클래스

package com.devkuma.tutorial.solid.srp.good;

// 공유 데이터
public class Employee {
    private Integer id;
    private String name;
    private Integer salary;

    public Employee(Integer id, String name, Integer salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }
}

인사팀 업무 클래스

package com.devkuma.tutorial.solid.srp.good;

// 인사팀 업무
public abstract class HourReporter {

    private Employee employee;

    public HourReporter(Employee employee) {
        this.employee = employee;
    }

    abstract void reportHours();
}

경리팀 업무 클래스

package com.devkuma.tutorial.solid.srp.good;

// 경리팀 업무
public abstract class PayCalculator {

    private Employee employee;

    public PayCalculator(Employee employee) {
        this.employee = employee;
    }

    abstract void reportHours();
}

DB 관린자 업무 클래스

package com.devkuma.tutorial.solid.srp.good;

// DB 관린자 업무
public abstract class EmployeeSaver {

    private Employee employee;

    public EmployeeSaver(Employee employee) {
        this.employee = employee;
    }

    // 개발팀에서 사용한다.
    abstract void save();
}

공유 데이터는 한 곳에 모으고, 각 액터의 다른 함수는 각각에 업무에 맞는 클래스로 이동시킨다.

OCP: 개방-폐쇄 원칙 (Open-Closed Principle)

  • 기존 코드를 변경하지 않고(Closed) 기능을 수정하거나 추가할 수 있도록(Open) 설계를 해야 한다.
  • 소프트웨어는 확장(기능)에는 열려 있어야 하고, 주변 변화에는 닫혀 있어야 한다. 코드의 직접적인 수정은 없어야 한다.

개방-폐쇄 원칙을 위반한 예제

아래 코드는 직원들의 정보를 관리하는 예제 프로그램이다.

package com.devkuma.tutorial.solid.ocp.bad;

public class Employee {
    private String description;

    private String[] names;

    public Employee(String description, String[] names) {
        this.description = description;
        this.names = names;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    public void setNames(String[] names) {
        this.names = names;
    }

    public String[] getNames() {
        return names;
    }
}
package com.devkuma.tutorial.solid.ocp.bad;

public class EmployeePrinter {

    public void print(Employee employee) {
        System.out.println(employee.getDescription());
        for (String name : employee.getNames()) {
            System.out.println(name);
        }
    }
}
package com.devkuma.tutorial.solid.ocp.bad;

public class Main {

    public static void main(String[] args) {
        Employee employee = new Employee("직원 정보", new String[]{"devkuma", "araikuma", "kimkc"});
        new EmployeePrinter().print(employee);
    }
}

위에 예제에서는 Employee 객체의 names데이터 구조를 변경되면 EmployeePrinter 객체의 구현도 변경해야 한다.

개방-폐쇄 원칙을 위반한 예제의 해결책

위에 예제에 대한 해결책은 아래와 같다.

package com.devkuma.tutorial.solid.ocp.good;

public class Employee {
    private String description;

    private String[] names;

    public Employee(String description, String[] names) {
        this.description = description;
        this.names = names;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    public void setNames(String[] names) {
        this.names = names;
    }

    public String[] getNames() {
        return names;
    }

    public void printNames() {
        for (String name : names) {
            System.out.println(name);
        }
    }
}
package com.devkuma.tutorial.solid.ocp.good;

public class EmployeePrinter {

    public void print(Employee employee) {
        System.out.println(employee.getDescription());
        employee.printNames();
    }
}

위에 코드를 보면 Employee 객체의 names의 반복하는 메소드를 갖게 된다.
EmployeePrinter 객체에서는 Employee 객체의 변경이 있어도 인터페이스가 동일한면 변경할 필요 없이 확장 가능하게 되었다.

LSP: 리스코프 치환 원칙 (Liskov Substitution Principle)

  • 자식 클래스는 부모클래스에서 가능한 행위를 수행할 수 있어야 한다.
  • 특정 메소드가 상위 타입을 인자로 사용한다고 할 때, 그 타입의 하위 타입도 문제 없이 정상적으로 작동을 해야 한다는 것이다.

리스코프 치환 원칙 예제

아래 Animal는 상속을 위한 최상의 클래스이다.

package com.devkuma.tutorial.solid.lsp;

public class Animal {

    void run(Integer speed) {
        System.out.println("running at " + speed + " km/h");
    }
}

리스코프 치환 원칙을 따른 예제

아래의 Dog 객체는 LSP를 따른다고 할 수 있다.

package com.devkuma.tutorial.solid.lsp.bad;

import com.devkuma.tutorial.solid.liskovsubstitution.Animal;

public class Sloth extends Animal {

    void run(Integer speed) throws Exception {
        throw new Exception("Sorry, I'm too lazy to run");
    }
}

리스코프 치환 원칙을 따르지 않은 예제

아래의 Sloth 객체는 Animal으로 대체할 수 없기 때문에 LSP를 위반하고 있다고 할 수 있다.

package com.devkuma.tutorial.solid.lsp.good;

import com.devkuma.tutorial.solid.liskovsubstitution.Animal;

public class Dog extends Animal {

    void bark() {
        System.out.println("bow-wow");
    }

    void run(Integer speed) {
        System.out.println("running at " + speed + " km/h");
    }
}

ISP: 인터페이스 분리 원칙 (Interface Segregation Principle)

  • 한 클래스는 자신이 사용하지 않는 인터페이스를 구현하지 말아야 한다. 하나의 일반적인 인터페이스 보다는 여러 개의 구체적인 인터페이스가 낫다. (= 불필요한 종속성 제거)
  • 즉, 자신이 사용하지 않는 기능(인터페이스)에는 영향을 받지 말아야 한다는 의미이다.
  • 예를 들어, Print 인터페이스에 print, scan을 분리해야 한다.

인터페이스 분리 원칙 예제

아래 Animal는 구현을 위한 인터페이스이다.

package com.devkuma.tutorial.solid.isp.ex1;

public interface Animal {
    void run();

    void eat();

    void cry();
}

아래의 코드에서는 Dog 클래스는 Animal 인터페이스를 모든 메소드를 구현하고 있어서 문제 없다.

package com.devkuma.tutorial.solid.isp.ex1.good;

import com.devkuma.tutorial.solid.isp.Animal;

public class Dog implements Animal {
    @Override
    public void run() {
        System.out.println("run");
    }

    @Override
    public void eat() {
        System.out.println("eat");
    }

    @Override
    public void cry() {
        System.out.println("cry");
    }
}

그러나, Lizard 클래스의 cry 메서드에 대해서는 아무 처리가 없어 Animal 인터페이스에 불필요하게 의존하고 있어 인터페이스 분리의 원칙을 위반하고 있다고 할 수 있다.

package com.devkuma.tutorial.solid.isp.bad;

import com.devkuma.tutorial.solid.isp.ex1.Animal;

public class Lizard implements Animal {
    @Override
    public void run() {
        System.out.println("run");
    }

    @Override
    public void eat() {
        System.out.println("eat");
    }

    // cry 메서드에 대한 처리가 없다. Animal에 불필요하게 의존하고 있다.
    @Override
    public void cry() {
        // Don't call this method
    }
}

인터페이스 분리 원칙 해결책

해결책으로는 아래와 같이 공통 부분만을 따로 뽑아내서, 보다 세세한 인터페이스에 분리하는 것으로 불필요한 의존을 없애는 것이 가능하다.

아래 Animal는 구현을 위한 최상의 인터페이스이다.

package com.devkuma.tutorial.solid.isp.ex2;

public interface Animal {
    void run();

    void eat();
}

Mammal(포유류) 인터페이스에서는 대한 Animal 인터페이스를 상속 받아 cry 메소드를 새로 추가하였다.

package com.devkuma.tutorial.solid.isp.ex2;

interface Mammal extends Animal {

    void cry();
}

Reptile(파충류) 인터페이스에서는 대한 Animal 인터페이스를 상속 받아 따로 메소드를 만들지 않았다.

package com.devkuma.tutorial.solid.isp.ex2;

interface Reptile extends Animal {
}

Dog 클래스에서는 Mammal 인터페이스의 run, eat 메소드를 구현하였고, Mammal 인터페이스의 cry도 구현하였다.

package com.devkuma.tutorial.solid.isp.ex2;

public class Dog implements Mammal {
    @Override
    public void run() {
        System.out.println("run");
    }

    @Override
    public void eat() {
        System.out.println("eat");
    }

    @Override
    public void cry() {
        System.out.println("cry");
    }
}

Lizard 클래스에서는 Mammal 인터페이스의 run, eat 메소드를 구현하였다.

package com.devkuma.tutorial.solid.isp.ex2;

public class Lizard implements Reptile {
    @Override
    public void run() {
        System.out.println("run");
    }

    @Override
    public void eat() {
        System.out.println("eat");
    }
}

DIP: 의존성 역전 원칙 (Dependency Inversion)

  • 의존 관계를 맺을 때, 변화하기 쉬운것 보단 변화하기 어려운 것을 의존해야 한다는 원칙이다.
  • 상위 모듈은 하위 모듈에 의존해서는 안되며, 둘 다 추상에 의존해야 한다. (= 하위 모듈 변경에 상위 모듈이 영향을 받지 않도록 함)
  • 추상(Interfaces/Abstraction 클래스)은 구현의 상세(Class)에 의존하지 않아야 하고, 구현의 상세가 추상에 의존해야 한다.

의존성 역전 원칙의 예제

아래 HttpClient는 구현을 위한 상의 추상 클래스이다.

package com.devkuma.tutorial.solid.dip;

abstract public class HttpClient {

    public String get(String arg) {
        return arg;
    }
}

아래의 코드에서는 DataProvider(상위 모듈)가 CustomHTTPClient(하위 모듈)에 의존한 상태이며, DIP의 원칙에 반하고 있다고 할 수 있다.

package com.devkuma.tutorial.solid.dip.good;

import com.devkuma.tutorial.solid.dip.HttpClient;

public class CustomHttpClient extends HttpClient {

}
package com.devkuma.tutorial.solid.dip.good;

import com.devkuma.tutorial.solid.dip.HttpClient;

public class DataProvider {

    public HttpClient httpClient;

    public DataProvider(CustomHttpClient customHttpClient) {
        this.httpClient = customHttpClient;
    }

    public String getData() {
        return httpClient.get("URL");
    }
}

의존성 역전 원칙의 해결책

모듈이 추상(Abstract)에 의존하도록 한다.

package com.devkuma.tutorial.solid.dip.bad;

import com.devkuma.tutorial.solid.dip.HttpClient;
import com.devkuma.tutorial.solid.dip.good.DataFetchClient;

public class DataProvider {
    public HttpClient httpClient;

    public DataProvider(HttpClient httpClient) {
        this.httpClient = httpClient;
    }

    public String getData() {
        return httpClient.get("URL");
    }
}

참고

1.5 - BDD(Behavior Driven Development) 행위 주도 개발

BDD이란?

  • 행위 주도 개발
  • BDD(Behavior Driven Development)로 TDD를 근간으로 파생된 개발 방법이다.
  • TDD(Test Driven Development)에서 한발 더 나가아 테스트 케이스 자체가 요구사양이 되도록 하는 개발방법이다.

BDD 기본 패턴

BDD는 시나리오를 기반으로 테스트 케이스를 작성하며 함수 단위 테스트를 권장하지 않고, 시나리오는 개발자가 아닌 사람이 봐도 이해할 수 있는 정도의 레벨을 권장한다.

  • Feature : 테스트에 대상의 기능/책임을 명시한다.
  • Scenario : 테스트 목적에 대한 상황을 설명한다.
  • Given : 시나리오 진행에 필요한 값을 설정한다.
  • When : 시나리오를 진행하는데 필요한 조건을 명시한다.
  • Then : 시나리오를 완료했을 때 보장해야 하는 결과를 명시한다.

작성 예시

@Test
fun `aliases for behavior driven development`() {
    // given
    given(calculatorService.add(20.0, 10.0)).willReturn(30.0)

    // when
    val result = calculatorService.add(20.0, 10.0)

    //then
    Assert.assertThat(30.0, CoreMatchers.`is`(result))
}

1.6 - DTO, VO, Entity

DTO(Data Transfer Object) 란?

  • 데이터 전송(이동) 객체라는 의미를 가지고 있다.
  • 계층간 데이터 교환을 위한 객체(Java Beans)이다.
    • DB에서 데이터를 얻어 Service나 Controller 등으터 보낼 때 사용하는 객체를 말한다.
    • 로직을 갖고 있지 않는 순수한 데이터 객체이며, getter/setter 메서드만을 갖는다.

VO(Value Object) 란?

  • VO(Value Object)는 말 그대로 값 객체라는 의미를 가지고 있다.
  • 객체의 불변성(객체의 정보가 변경하지 않음)을 보장한다.
  • 서로 다른 이름을 갖는 VO 인스턴스더라도 모든 속성 값이 같다면 두 인스턴스는 같은 객체라고 할 수 있다.
    • 이를 위해 VO에는 Object 클래스의 equals()hashcode()를 오버라이딩해야 한다.
  • VO 내부에 선언된 속성(필드)의 모든 값들이 VO 객체마다 값이 같아야, 똑같은 객체라고 판별한다.
  • Getter와 Setter를 가질 수 있다
  • 테이블 내에 있는 속성 외에 추가적인 속성을 가질 수 있다.
  • 여러 테이블(A, B, C)에 대한 공통 속성을 모아서 만든 BaseVO 클래스를 상속받아서 사용할 수도 있다.

Entity 란?

  • 실제 DB의 테이블과 매핑되는 객체이다.
  • 데이터의 집합을 의미한다.
  • 저장되고, 관리되어야 하는 데이터이다.
  • 개념, 장소, 사건 등을 가리킨다.
  • 유형 또는 무형의 대상을 가리킨다.
  • ID를 통해 각각의 Entity를 구분하고 유일한 식별자를 갖고 있어야 한다.
  • ID, 회원번호 등…
  • 반드시 속성이 한개 이상 존재해야 한다.
    • 이름, 주소 등…
  • 영속적으로 존재하는 인스턴스의 집합이다.
  • 반드시 해당 업무에서 필요하고 관리하고자 하는 정보여야 한다.

정리

  • DTO는 계층(Layer)간 데이터 이동을 위해 사용되는 객체
  • VO는 값을 갖는 순수한 도메인
  • Entity는 이를 DB 테이블과 매핑하는 객체

계층간의 구분

1.7 - 컴퓨터 특수 기호 이름

기호 이름
! Exclamation Point (엑스클러메이션 포인트)
" Quotation Mark (쿼테이션 마크)
# Crosshatch (크로스해치), Sharp(샵), Pound Sign(파운드 사인)
$ Dollar Sign (달러사인)
% Percent Sign (퍼센트사인)
@ At Sign (앳 사인, 혹은 앳), Commercial At(커머셜 앳)
& Ampersand (앰퍼샌드)
' Apostrophe (어파스트로피)
* Asterisk (애스터리스크)
- Hyphen (하이픈), Dash (대시)
. Period (피리어드), Full Stop (풀스탑)
/ Slash (슬래시), Virgule (버귤)
Back Slash (백슬래시)
\ Won sign (원사인)
: Colon (콜론)
; Semicolon (세미콜론)
^ Circumflex (서컴플렉스), Caret (캐럿)
` Backtick(백틱), Grave (그레이브)
{ Left Brace (레프트 브레이스)
} Right Brace (라이트 브레이스)
[ Left Bracket (레프트 브래킷)
] Right Bracket (라이트 브래킷)
( Left Parenthesis (레프트 퍼렌씨시스)
) Right Parenthesis (라이트 퍼렌씨시스)
- Vertical Bar (버티컬바)
~ Tilde (틸드)
= Equal Sign (이퀄사인)
+ Plus Sign (플러스사인)
- Minus Sign (마이너스사인)
_ Underscore (언더스코어), Underline (언더라인)
< Less Than Sign (레스댄 사인), Left Angle Bracket(레프트 앵글브래킷)
> Greater Than Sign (그레이터댄 사인), Right Angle Bracket (라이트 앵글브래킷)

1.8 - Open Software License

라이선스란?

  • 넓은 의미에서의 라이선스

    • 면허, 면허증
    • 특정한 일을 할 수 있는 자격을 행정기관에서 허가 하는 일
  • 소프트웨어에서의 라이선스

    • 소프트웨어를 사용할 수 있는 권한 또는 사용을 허가한다는 내용을 담은 문서

Free Software License

  • 공짜가 아닌 자유(금전적인 측면이 아님)
  • 자유의 의미
    • 프로그램을 어떠한 목적을 위해서도 실행할 수 있는 자유
    • 프로그램의 작동 원리를 연구하고 이를 자신의 필요에 맞게 변경시킬 수 있는 자유
    • 이웃을 돕기 위해서 프로그램을 복제하고 배포할 수 있는 자유
    • 프로그램을 향상시키고 이를 공동체 전체의 이익을 위해서 다시 환원시킬 수 있는 자유

Free Software Foundation

  • 1985 10월 4일 리처드 스톨만이 세움
  • 소프트웨어의 자유로운 복사와 배포,개선을 촉진하기 위한 조직
  • 주요 활동
    • 자유소프트웨어 철학 및 유지관리
    • 저작권을 가진 자유 소프트웨어의 보호
  • http://www.fsf.org

라이선스 분류

  • GPL 호환 자유소프트웨어 라이선스
    • GPL, LGPL, Apache 2.0 등등…
  • GPL 비호환 자유소프트웨어 라이선스
    • Apache 1.0, BSD, MPL 등등…
  • NonFree 소프트웨어 라이선스
    • Code Project License, JSON 등등…

라이선스 종류

GPL (The GNU General Public License)

  • 자유소프트웨어재단에서 만든 라이선스로 GNU 프로젝트로 배포하는 소프트웨어에 적용하기 위하여 리처드스톨만이 만들었다.
  • 오픈 소스들 중에서 많이 알려져 있고 의무사항들도 다른 오픈 소스 라이선스에 비해 엄격한 편이다.
  • GPL프로그램은 어떤 목적으로, 어떤 형태로든 사용할 수 있지만 사용하거나 변경된 프로그램을 배포하는 경우 무조건 동일한 라이선스로 공개해야 하며. ”본 제품(SW)은 GPL 라이선스 하에 배포되는 SW인 ○○○ (사용한 GPL SW 이름)를 포함합니다”와 같은 문구를 매뉴얼 혹은 그에 준하는 매체에 포함시키고, GPL 전문을 첨부해야 한다.
  • 컴퓨터 프로그램을 어떠한 목적으로든지 사용할 수 있다. 다만 법으로 제한하는 행위는 할 수 없다.
  • 컴퓨터 프로그램의 실행 복사본은 언제나 프로그램의 소스 코드와 함께 판매하거나 소스코드를 무료로 배포해야 한다.
  • 변경된 소스프로그램의 소스코드를 용도에 따라 변경 할 수 있다.
  • 변경된 컴퓨터 프로그램 역시 프로그램의 소스코드를 반드시 공개 배포해야 한다.
  • 변경된 프로그램 역시 반드시 똑같은 라이선스를 취해야 한다. 즉, GPL 라이선스를 적용해야 한다.
항목 여부
저작권 보호
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무
명시적 특허권 행사 가능 여부 아니오
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부 아니오
라이선스 전파 여부

AGPL (Affero General Public License)

  • GPL을 기반을 만든 라이선스로 버전 1, 2는 Affero, 버전 3은 자유소프트웨어재단에 의해 개발됐다.
  • 이 라이선스는 수정한 소스코드를 서버에서만 사용하는 개발자가 그 프로그램을 배포하지 않을 경우 사용자는 소스코드를 가질 수가 없는 문제를 해결하기 위해 마련됐다.
  • 서버에서 프로그램을 실행해 다른 사용자들과 통신하면, 실행되고 있는 프로그램의 소스코드를 사용자들이 다운로드할 수 있게 해야 한다는 조항을 담고 있다.

LGPL (The Lesser GNU General Public License)

  • LGPL은 자유소프트웨어재단이 일부 라이브러리(Library)에 대하여 GPL보다 소스코드의 공개 정도를 다소 완화된 형태로 사용할 수 있도록 만든 라이선스이다. (GPL의 제약을 완화시킨 라이선스)
  • 상용 라이브러리와 동일한 기능을 제공하는 오픈 소스 라이브러리에 GPL과 같은 엄격한 라이선스를 적용하면 소스코드를 공개해야 하기 때문에 개발자들이 사용을 꺼려할 것이기 때문에 이를 막고 오픈 소스의 사용을 장려하기 위해 만들어 졌다.
  • LGPL이 적용된 라이브러리는 원 프로그램의 소스코드를 공개하지 않고 이에 사용된 오픈 소스의 소스코드만 공개하면 된다.
  • 원래는 한정된 라이브러리에만 적용하려는 의도로 Library GPL이라는 이름을 붙였으나, 모든 라이브러리에 적용된다는 오해를 사 Lesser GPL로 2.1버전에서 변경되었다.
  • LGPL 라이선스를 가지고 있는 라이브러리를 동적 링크할 경우 소스공개의 의무가 사라짐
  • LGPL 라이선스 라이브러리를 수정시에는 수정된 라이브러리 소스는 공개해야 한다.
  • 현재 GNU 재단에서 밀고 있다.
항목 여부
저작권 보호
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무
명시적 특허권 행사 가능 여부 아니오
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부
라이선스 전파 여부

The BSD(Berkely Software Distribution) License

  • BSD는 버클리의 캘리포니아 대학에서 배포하는 공개 SW라이선스로 SW의 소스코드를 공개하지 않아도 되는 대표적인 오픈 소스 SW의 라이선스 중 하나이다.
  • 이렇게 BSD의 라이선스의 허영범위가 넓은 이유는 BSD라이선스로 배포되는 프로젝트가 미국 정부에서 제공한 재원으로 운영되었기 때문이다. (미국 공공기관에서 지원. 미국인의 세금으로 만들어진 소프트웨어)
  • 즉, SW에 대한 대가를 미국 국민의 세금으로 미리 지불했기 때문에 사람들에게 그들이 원하는 방식으로 SW를 사용하거나 만들도록 허가된 것이다. (공공성을 강조)
  • 따라서 BSD라이선스의 소스코드를 이용하여 새로운 프로그램을 개발하여도 새로운 프로그램의 소스코드를 공개하지 않고 BSD가 아닌 다른 라이선스를 적용하여 판매할 수 있다.(소스를 자유롭게 이용, 배포 가능)
항목 여부
저작권 보호
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무 아니오
명시적 특허권 행사 가능 여부 아니오
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부
라이선스 전파 여부 아니오

MPL(Mozilla Public License)

  • Mozilla 재단에서 MS Explorer 점유율 상승으로 Netscape 소스 공개 결정하고, GPL의 제약과 BSD의 불안감에 의해 새로운 라이선스의 개발했다.
  • 프로그램의 자유로운 사용, 복제, 배포, 수정을 허용한다.
  • MPL은 Netscape브라우저의 소스코드를 공개하기 위해 개발된 라이선스로 공개하여야 할 소스코드의 범위를 좀 더 명확하게 정의하고 있다.
  • 즉, GPL에서는 링크되는 SW의 소스코드를 포함하여 공개하여야 할 소스코드의 범위가 모호하게 정의되어 있지만, MPL에서는 링크 등의 여부에 상관없이 원래의 소스코드가 아닌 새로운 파일에 작성된 소스코드에 대해서는 공개의 의무가 발생하지 않는다.
  • 따라서 MPL SW 그 자체는 어떻게 하든 공개를 해야 하지만 원래 소스코드에 없던 새로운 파일들은 공개할 의무가 발생하지 않는다.

The Apache License, Version 2.0

  • 아파치 소프트웨어 재단(Apache Software Foundation)에서 만든 규정이다.
  • 아파치 웹 서버를 포함한 아파치 재단의 모든 SW에 적용되는 라이선스로 BSD라이선스와 비슷하여 소스코드 공개 등의 의무가 발생하지 않는다.
  • 다만 “Apache"라는 이름에 대한 상표권을 침해하지 않아야 한다라는 조항이 명시적으로 들어가 있고, 특허권에 대한 내용이 포함되어 있다.
  • 아파치 라이선스 소스코드를 수정해 배포하는 경우 아파치 라이선스 버전 2.0을 꼭 포함시켜야 하며 아파치 재단에서 만든 소프트웨어임을 밝혀야 한다.
  • 누구나 해당소프트웨어에서 파생된 프로그램 제작가능, 저작권 양도, 배포 가능이다.
  • 소스 공개 의무 없음
항목 여부
저작권 보호
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무 아니오
명시적 특허권 행사 가능 여부
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부
라이선스 전파 여부 아니오

The MIT License

  • MIT는 미국 Massachusetts Institute of Technology에서 해당 대학 SW 공학도들을 돕기 위해 개발한 라이선스이다.
  • 라이선스와 저작권 관련 명시만 지켜주면 되는 라이선스로, BSD라이선스를 기초로 작성되었으며 소스코드를 수정하여 배포 시에도 반드시 오픈 소스로 배포해야 한다는 규정이 없어 GPL등의 엄격함을 피하려는 사용자들에게 인기가 많다.
항목 여부
저작권 보호
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무 아니오
명시적 특허권 행사 가능 여부 아니오
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부
라이선스 전파 여부 아니오

CPOL(The Code Project Open License)

항목 여부
저작권 보호
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무 아니오
명시적 특허권 행사 가능 여부
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부
라이선스 전파 여부 아니오

CDDL (The Common Development and Distribution License)

항목 여부
저작권 보호
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무
명시적 특허권 행사 가능 여부
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부
라이선스 전파 여부 아니오

Ms-PL (The Microsoft Public License)

항목 여부
저작권 보호
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무 아니오
명시적 특허권 행사 가능 여부
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부
라이선스 전파 여부 아니오

MPL 1.1 (The Mozilla Public License 1.1)

항목 여부
저작권 보호
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무
명시적 특허권 행사 가능 여부
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부
라이선스 전파 여부 아니오

CPL (The Common Public License Version 1.0)

항목 여부
저작권 보호
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무
명시적 특허권 행사 가능 여부
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부
라이선스 전파 여부 아니오

The Eclipse Public License 1.0

항목 여부
저작권 보호
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무
명시적 특허권 행사 가능 여부
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부
라이선스 전파 여부 아니오

The Creative Commons Attribution-ShareAlike 2.5 License

항목 여부
저작권 보호
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무 아니오
명시적 특허권 행사 가능 여부 아니오
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부 아니오
라이선스 전파 여부

The zlib/libpng License

항목 여부
저작권 보호
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무 아니오
명시적 특허권 행사 가능 여부 아니오
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부
라이선스 전파 여부 아니오

공개 커뮤니티에 대한 공헌 (라이선스 아님)

항목 여부
저작권 보호 아니오
상용 소프트웨어에서 사용 가능
버그 패치 및 기능 확장 제공의 의무 아니오
명시적 특허권 행사 가능 여부 아니오
사유 프로그램 (소스 비공개 프로그램)에서 사용 가능 여부
라이선스 전파 여부 아니오

출처

1.9 - 프레임워크(Framework)와 라이브러리(Library)의 차이점

프레임워크와 라이브러리는 혼용해서 사용하는 경우가 많은데 분명히 다른 의미이다.

먼저, 각각의 뜻을 알아보도록 하자.

프레임워크(Framework)

프레임워크를 말 그대로 뼈대 즉, 프로그램 구현에 있어서 큰 구조를 결정하고 흐름을 제어하는 역활을 한다. 개발자는 그 위에서 코드를 기술하고 프레임워크는 개발자가 기술한 코드를 흐름에 맞게 실행을 해준다. 대표적인 프레임워크로는 Spring framework, .NET framework 등이 있다.

마이크로소프트웨어 2006/8월호 130쪽 “프레임워크 활용전략"에서

GoF의 디자인 패턴으로 유명한 랄프 존슨(Ralph Johnson) 교수는 프레임워크를 “소프트웨어의 구체적인 부분에 해당하는 설계와 구현을 재사용이 가능하게끔 일련의 협업화된 형태로 클래스들을 제공하는 것"이라고 정의하였다. 프레임워크는 라이브러리와 달리 애플리케이션의 틀과 구조를 결정할 뿐 아니라, 그 위에 개발된 개발자의 코드를 제어한다. 프레임워크는 구체적이며 확장 가능한 기반 코드를 가지고 있으며, 설계자가 의도하는 여러 디자인 패턴의 집합으로 구성되어 있다.

라이브러리(Library)

라이브러리는 자주 사용되는 로직들의 모음 혹은 묶음이라로 할 수 있다. 자주 쓰는 메소드 및 함수들을 미리 구현해 놓고, 프로그램에 추가해서 사용하는 형태가 된다. 개발자는 미리 만들어 놓은 기능들을 사용하기 편하고 빠르게 개발을 진행할수 있게 된다. 대표적인 라이브러리로는 jQuery, Apache commons library 등이 있다.

프레임워크와 라이브러리의 차이점

프레임워크와 라이브러리는 큰 차이는 점은 제어 흐름의 주도권이 어디에 있는가에 있다.

프레임워크는 전체적인 흐름을 제어하고 있으며 개발자는 그 안에서 필요한 코드를 넣는 반면에 라이브러리는 개발자가 전체적인 흐름을 만들며 라이브러리를 사용하는 것이라 할 수 있다.

비유적인 표현으로는 자동자가 프레임이라면 거기에 속한 바퀴, 핸들 등은 라이브러리라라고 할 수 있다.

이렇듯 이미 구현된 프로그램에서 라이브러리를 비교적 쉬울 수 있으나, 프레임워크를 변경하는 것은 모든 기반을 다 흔드는 작업이 되어버린다.

토비의 스프링3. 1장 오브젝트와 의존관계 p.95에서

프레임워크도 제어의 역전 개념이 적용된 대표적인 기술이다. 프레임워크는 라이브러리의 다른 이름이 아니다. 프레임워크는 단지 미리 만들어 둔 반제품이나, 확장해서 사용할 수 있도록 준비된 추상 라이브러리의 집합이 아니다. 프레임워크가 어떤 것인지 이해하려면 라이브러리와 프레임워크가 어떻게 다른지 알아야 한다. 라이브러리를 사용하는 애플리케이션 코드는 애플리케이션 흐름을 직접 제어한다. 단지 동작하는 중에 필요한 기능이 있을 때 능동적으로 라이브러리를 사용할 뿐이다. 반면에 프레임워크는 거꾸로 애플리케이션 코드가 프레임워크에 의해 사용된다. 보통 프레임워크 위에 개발한 클래스를 등록해두고, 프레임워크가 흐름을 주도하는 중에 개발자가 만든 애플리케이션 코드를 사용하도록 만드는 방식이다. 최근에는 툴킨, 엔진, 라이브러리 등도 유행을 따라서 무작정 프레임워크라고 부르기도 하는데 이는 잘못된 것이다. 프레임워크에는 분명한 제어의 역전 개념이 적용되어 있어야 한다. 애플리케이션 코드는 프레임워크가 짜놓은 틀에서 수동적으로 동작해야 한다.

참조

2 - 디자인 패턴(Design Pattern)

Java 언어로 배우는 디자인 패턴 입문

2.1 - 디자인 패턴(Design Pattern) 개요

디자인 패턴 기본 개념

2.1.1 - 디자인 패턴(Design Pattern)의 개념과 종류

디자인 패턴이란?

소프트웨어를 설계할 때 특정 맥락에서 자주 발생하는 고질적인 문제들이 또 발생했을 때 재사용할 할 수 있는 훌륭한 해결책이다.
“바퀴를 다시 발명하지 마라(Don’t reinvent the wheel)” 이미 만들어져서 잘 되는 것을 처음부터 다시 만들 필요가 없다는 의미이다.

디자인 패턴의 종류

이미 알려진 디자인 패턴은 다음과 같이 23개로 나누어져 있다. 크게 생성(Creational), 구조(Structural), 행위(Behavioral) 3가지로 분류된다. 이는 GoF(Gang of Four) 디자인 패턴이라고 불리며, 에리히 감마(Erich Gamma), 리차드 헬름(Richard Helm), 랄프 존슨(Ralph Johnson), 존 블리시디스(John Vissides) 4명의 유명한 개발자들에 의해 고안되었다. 4명의 개발자는 ‘경험’이나 ‘내적인 축적’을 <디자인 패턴>이라는 형태로 정리하였다. 이 4명을 the Gang of Four 또는 GoF라고 부른다.

생성 패턴 (Creational Pattern)

객체 생성에 관련된 패턴으로, 객체의 생성과 조합을 캡슐화해 특정 객체가 생성되거나 변경되어도 프로그램 구조에 영향을 크게 받지 않도록 유연성을 제공한다.

  • 추상 팩토리 메서드 (Abstract Factory Methods) : 관련된 부품을 조립해서 제품을 만든다.
  • 팩토리 메서드 (Factory Methods) : 인스턴스 작성을 하위 클래스에게 맡긴다.
  • 빌더 (Builder) : 잡한 인스턴스를 조립한다.
  • 프로토타입 (Prototype) : 복사해서 인스턴스를 만든다.
  • 싱글톤 (Singleton) : 단 하나의 인스턴스.

구조 패턴 (Structural Pattern)

클래스나 객체를 조합해 더 큰 구조를 만드는 패턴으로 예를 들어 서로 다른 인터페이스를 지닌 2개의 객체를 묶어 단일 인터페이스를 제공하거나 객체들을 서로 묶어 새로운 기능을 제공하는 패턴이다.

  • 어댑터 (Adapter) : 한 꺼풀 덧씌워 재사용.
  • 브리지 (Bridge) : 기능의 계층과 구현의 계층을 분리한다.
  • 컴퍼지트 (Composite) : 그릇과 내용물의 동일시.
  • 데코레이터 (Decorator) : 장식과 내용물의 동일시.
  • 퍼사드 (Facade) : 간단한 창구.
  • 플라이웨이트 (Flyweight) : 동일한 것을 공유해서 낭비를 없앤다.
  • 프록시 (Proxy) : 필요해지면 만든다.

행위 패턴 (Behavioral Pattern)

객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴으로, 한 객체가 혼자 수행할 수 없는 작업을 여러 개의 객체로 어떻게 분배하는지, 또 그렇게 하면서도 객체 사이의 결합도를 최소화하는 것에 중점을 둔다.

  • 책임 연쇄 (Chain of Responsibility) : 책임 떠넘기기.
  • 커맨드 (Command) : 명령을 클래스로 만든다.
  • 인터프리터 (Interpreter) : 문법 규칙을 클래스로 표현한다.
  • 이터레이터 (Iterator) : 하나씩 세다.
  • 미디에이터 (Mediator) : 상대는 카운셀러 한사람뿐.
  • 메멘토 (Memento) : 상태를 보존한다.
  • 옵저버 (Observer) : 상태의 변화를 통지한다.
  • 스테이트 (State) : 상태를 클래스로서 표현한다.
  • 스트레티지 (Strategy) : 알고리즘을 모두 교체한다.
  • 템플릿 메서드 (Template Meothods) : 체적인 처리를 하위 클래스에게 맡긴다.
  • 비지터 (Visitor) : 구조 안을 돌아다니면서 일을 한다.

Java 예제 출처

참조

2.2 - 생성 패턴 (Creational Pattern)

객체 생성에 관련된 패턴

객체 생성에 관련된 패턴으로, 객체의 생성과 조합을 캡슐화해 특정 객체가 생성되거나 변경되어도 프로그램 구조에 영향을 크게 받지 않도록 유연성을 제공한다.

2.2.1 - Design Pattern | Abstract Factory Methods

Abstract Factory 패턴이란?

  • Abstract Factory는 추상 공장 이라는 의미한다.
  • 추상적인 것은 구체적으로 어떻게 구현되고 있는지에 대해서는 고려하지 않고, 인터페이스에만 주목하고 있는 상태이다.
  • Abstract Factory 패턴은 부품의 구체적인 구현에 주목하지 않고 인터페이스에 주목한다. 그리고 그 인터페이스만을 사용해 부품을 조립하여 제품에 정리하는 방식이다.
  • 관련 부품을 조합하여 제품 만든 것이다.
  • GoF 디자인 패턴에서는 생성과 관련된 디자인 패턴으로 분류된다.

Abstract Factory 패턴 예제 프로그램

즐겨찾기 목록을 HTML 형식으로 출력하는 예제 프로그램이다.

Class Diagram
Abstract Factory Pattern Class Diagram

1. Factory 클래스

추상 공장을 대표하는 클래스이다. Link, Tray, Page를 만든다.

Factory.java

package com.devkuma.designpattern.creational.abstractfactory.factory;

public abstract class Factory {

    public abstract Link createLink(String caption, String url);

    public abstract Tray createTray(String caption);

    public abstract Page createPage(String title);

    public static Factory getFactory(String classname) {
        Factory factory = null;
        try {
            factory = (Factory) Class.forName(classname).getDeclaredConstructor().newInstance();
        } catch (ClassNotFoundException e) {
            System.err.println("클래스 " + classname + "를 찾을 수 없습니다.");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return factory;
    }
}

2. Item 클래스

Link와 Tray를 통일적으로 취급하는 클래스이다.

Item.java

package com.devkuma.designpattern.creational.abstractfactory.factory;

public abstract class Item {
    protected String caption;

    public Item(String caption) {
        this.caption = caption;
    }

    public abstract String makeHTML();
}

추상 부품: HTML 링크를 나타내는 클래스이다.

Link.java

package com.devkuma.designpattern.creational.abstractfactory.factory;

public abstract class Link extends Item {
    protected String url;

    public Link(String caption, String url) {
        super(caption);
        this.url = url;
    }
}

4. Tray 클래스

추상 부품: Link와 Tray를 모은 클래스이다.

Tray.java

package com.devkuma.designpattern.creational.abstractfactory.factory;

import java.util.ArrayList;

public abstract class Tray extends Item {
    protected ArrayList tray = new ArrayList();

    public Tray(String caption) {
        super(caption);
    }

    public void add(Item item) {
        tray.add(item);
    }
}

5. Page 클래스

추상 파트: HTML 페이지를 나타내는 클래스이다.

Page.java

package com.devkuma.designpattern.creational.abstractfactory.factory;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;

public abstract class Page {
    protected String title;
    protected ArrayList content = new ArrayList();

    public Page(String title) {
        this.title = title;
    }

    public void add(Item item) {
        content.add(item);
    }

    public void output() {
        try {
            String filename = title + ".html";
            Writer writer = new FileWriter(filename);
            writer.write(this.makeHTML());
            writer.close();
            System.out.println(filename + "을 생성했습니다.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public abstract String makeHTML();
}

6. ListFactory 클래스

구체적인 공장을 나타내는 클래스이다. ListLink, ListTray, ListPage를 만든다.

ListFactory.java

package com.devkuma.designpattern.creational.abstractfactory.listfactory;

import com.devkuma.designpattern.creational.abstractfactory.factory.Factory;
import com.devkuma.designpattern.creational.abstractfactory.factory.Link;
import com.devkuma.designpattern.creational.abstractfactory.factory.Page;
import com.devkuma.designpattern.creational.abstractfactory.factory.Tray;

public class ListFactory extends Factory {
    public Link createLink(String caption, String url) {
        return new ListLink(caption, url);
    }

    public Tray createTray(String caption) {
        return new ListTray(caption);
    }

    public Page createPage(String title) {
        return new ListPage(title);
    }
}

구체적인 파트: HTML 링크를 나타내는 클래스이다.

ListLink.java

package com.devkuma.designpattern.creational.abstractfactory.listfactory;

import com.devkuma.designpattern.creational.abstractfactory.factory.Link;

public class ListLink extends Link {
    public ListLink(String caption, String url) {
        super(caption, url);
    }

    public String makeHTML() {
        return "  <li><a href=\"" + url + "\">" + caption + "</a></li>\n";
    }
}

8. ListTray 클래스

구체적인 부품:Link나 Tray를 모은 클래스이다.

ListTray.java

package com.devkuma.designpattern.creational.abstractfactory.listfactory;

import com.devkuma.designpattern.creational.abstractfactory.factory.Item;
import com.devkuma.designpattern.creational.abstractfactory.factory.Tray;

import java.util.Iterator;

public class ListTray extends Tray {
    public ListTray(String caption) {
        super(caption);
    }

    public String makeHTML() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<li>\n");
        buffer.append(caption + "\n");
        buffer.append("<ul>\n");
        Iterator it = tray.iterator();
        while (it.hasNext()) {
            Item item = (Item) it.next();
            buffer.append(item.makeHTML());
        }
        buffer.append("</ul>\n");
        buffer.append("</li>\n");
        return buffer.toString();
    }
}

9. ListPage 클래스

구체적인 파트: HTML 페이지를 나타내는 클래스이다.

ListPage.java

package com.devkuma.designpattern.creational.abstractfactory.listfactory;

import com.devkuma.designpattern.creational.abstractfactory.factory.Item;
import com.devkuma.designpattern.creational.abstractfactory.factory.Page;

import java.util.Iterator;

public class ListPage extends Page {
    public ListPage(String title) {
        super(title);
    }

    public String makeHTML() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<html><head><title>" + title + "</title></head>\n");
        buffer.append("<body>\n");
        buffer.append("<h1>" + title + "</h1>\n");
        buffer.append("<ul>\n");
        Iterator it = content.iterator();
        while (it.hasNext()) {
            Item item = (Item) it.next();
            buffer.append(item.makeHTML());
        }
        buffer.append("</ul>\n");
        buffer.append("</body></html>\n");
        return buffer.toString();
    }
}

10. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.creational.abstractfactory;

import com.devkuma.designpattern.creational.abstractfactory.factory.Factory;
import com.devkuma.designpattern.creational.abstractfactory.factory.Link;
import com.devkuma.designpattern.creational.abstractfactory.factory.Page;
import com.devkuma.designpattern.creational.abstractfactory.factory.Tray;

public class Main {
    public static void main(String[] args) {

        Factory factory = Factory.getFactory("com.devkuma.designpattern.creational.abstractfactory.listfactory.ListFactory");

        Link devkuma = factory.createLink("Devkuma", "https://www.devkuma.com//");
        Link araikuma = factory.createLink("araikuma", "https://araikuma.tistory.com/");

        Link naver = factory.createLink("Naver", "https://www.naver.com/");
        Link daum = factory.createLink("Daum", "https://www.daum.com/");
        Link google = factory.createLink("Google", "https://www.google.com/");

        Tray pgTray = factory.createTray("프로그래밍");
        pgTray.add(devkuma);
        pgTray.add(araikuma);

        Tray searchTray = factory.createTray("검색사이트");
        searchTray.add(naver);
        searchTray.add(daum);
        searchTray.add(google);

        Page page = factory.createPage("즐겨찾기");
        page.add(pgTray);
        page.add(searchTray);
        page.output();
    }
}

11. 실행 결과

프로그램으로 생성한 파일을 아래와 같다.

즐겨찾기.html

<html><head><title>즐겨찾기</title></head>
<body>
<h1>즐겨찾기</h1>
<ul>
<li>
프로그래밍
<ul>
  <li><a href="https://www.devkuma.com//">Devkuma</a></li>
  <li><a href="https://araikuma.tistory.com/">araikuma</a></li>
</ul>
</li>
<li>
검색사이트
<ul>
  <li><a href="https://www.naver.com/">Naver</a></li>
  <li><a href="https://www.daum.com/">Daum</a></li>
  <li><a href="https://www.google.com/">Google</a></li>
</ul>
</li>
</ul>
</body></html>

Abstract Factory 패턴의 장점

예를 들어, 예제 프로그램에 새로운 구체적인 공장을 추가하는 경우, Factory, Link, Tray, Page의 서브 클래스 만들어 각각의 추상 메소드를 구현하게 된다.
즉, factory 패키지의 클래스가 가지고 있는 추상 부분을 구체화해 가는 것만이 된다. 이 때 아무리 구체적인 공장을 추가해도 추상 공장을 수정할 필요가 없다.

2.2.2 - Design Pattern | Builder Pattern (빌더 패턴)

Builder 패턴이란?

  • Build라는 단어는 구조를 가진 큰 것을 빌드하는 의미이다.
  • 복잡한 구조를 가진 것을 만들 때 단번에 완성하기가 어렵다. 우선 전체를 구축하고 있는 각 부분을 만들어, 단계를 밟아 조립해 가게 된다.
  • 동일한 구축 공정, 다른 표현 객체을 생성한다.
  • 생성자에 들어갈 매개 변수가 많든 적든 차례차례 매개 변수를 받아들이고 모든 매개 변수를 받은 뒤에 이 변수들을 통합해서 한번에 사용을 한다.
  • Builder 패턴은 구조를 가진 인스턴스를 구축하는 방법이다.
  • GoF 디자인 패턴에서는 생성과 관련된 디자인 패턴 으로 분류된다.

Builder 패턴 예제 프로그램

문서를 일반 텍스트 및 HTML 형식으로 출력하는 예제 프로그램이다.

Class Diagram
Builder Pattern Class Diagram

1. Builder 클래스

문서를 구성하기 위한 메소드를 정의한 추상 클래스이다.

Builder.java

public abstract class Builder {
    public abstract void makeTitle(String title);
    public abstract void makeString(String str);
    public abstract void makeItems(String[] items);
    public abstract void close();
}

2. Guide 클래스

하나의 문서를 만드는 클래스이다.

Guide.java

package com.devkuma.designpattern.creational.builder;

public class Guide {

    private Builder builder;

    public Guide(Builder builder) {
        this.builder = builder;
    }

    public void construct() {
        builder.makeTitle("야유회에 대해서");
        builder.makeString("일시");
        builder.makeItems(new String[]{
                "2022/3/14 (월)",
                "11:00~",
        });
        builder.makeString("장소");
        builder.makeItems(new String[]{
                "xxx 캠핌장",
        });
        builder.makeString("준비물");
        builder.makeItems(new String[]{
                "회비",
                "고기",
                "음료수",
        });
        builder.close();
    }
}

3. TextBuilder 클래스

일반 텍스트로 문서를 만드는 클래스이다.

TextBuilder.java

package com.devkuma.designpattern.creational.builder;

public class TextBuilder extends Builder {

    private StringBuffer buffer = new StringBuffer();

    public void makeTitle(String title) {
        buffer.append("==============================\n");
        buffer.append("'" + title + "'\n");
        buffer.append("\n");
    }

    public void makeString(String str) {
        buffer.append("- " + str + "\n");
    }

    public void makeItems(String[] items) {
        for (int i = 0; i < items.length; i++) {
            buffer.append(" - " + items[i] + "\n");
        }
        buffer.append("\n");
    }

    public void close() {
        buffer.append("==============================\n");
    }

    public String getResult() {
        return buffer.toString();
    }
}

3. HTMLBuilder 클래스

HTML 파일로 문서를 작성하는 클래스이다.

HTMLBuilder.java

package com.devkuma.designpattern.creational.builder;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class HtmlBuilder extends Builder {

    private String filename;
    private PrintWriter writer;

    public void makeTitle(String title) {
        filename = title + ".html";
        try {
            writer = new PrintWriter(new FileWriter(filename));
        } catch (IOException e) {
            e.printStackTrace();
        }
        writer.println("<html><head><title>" + title + "</title></head><body>");
        writer.println("<h1>" + title + "</h1>");
    }

    public void makeString(String str) {
        writer.println("<p>" + str + "</p>");
    }

    public void makeItems(String[] items) {
        writer.println("<ul>");
        for (int i = 0; i < items.length; i++) {
            writer.println("<li>" + items[i] + "</li>");
        }
        writer.println("</ul>");
    }

    public void close() {
        writer.println("</body></html>");
        writer.close();
    }

    public String getResult() {
        return filename;
    }
}

5. Main 클래스

예제 프로그램을 실행하는 메인 클래스이다.

Main.java

package com.devkuma.designpattern.creational.builder;

public class Main {
    public static void main(String[] args) {

        if (args.length != 1) {
            System.exit(0);
        }
        if (args[0].equals("plain")) {
            TextBuilder textbuilder = new TextBuilder();
            Guide guide = new Guide(textbuilder);
            guide.construct();
            String result = textbuilder.getResult();
            System.out.println(result);
        } else if (args[0].equals("html")) {
            HtmlBuilder htmlbuilder = new HtmlBuilder();
            Guide guide = new Guide(htmlbuilder);
            guide.construct();
            String filename = htmlbuilder.getResult();
            System.out.println(filename + "이 작성되었습니다.");
        } else {
            System.exit(0);
        }
    }
}

6. 실행 결과

텍스트

==============================
'야유회에 대해서'

- 일시
 - 2022/3/14 (월)
 - 11:00~

- 장소
 - xxx 캠핌장

- 준비물
 - 회비
 - 고기
 - 음료수

==============================

HTML

<html><head><title>야유회에 대해서</title></head><body>
<h1>야유회에 대해서</h1>
<p>일시</p>
<ul>
<li>2022/3/14 (월)</li>
<li>11:00~</li>
</ul>
<p>장소</p>
<ul>
<li>xxx 캠핌장</li>
</ul>
<p>준비물</p>
<ul>
<li>회비</li>
<li>고기</li>
<li>음료수</li>
</ul>
</body></html>

7. Builder 패턴의 장점

예제 프로그램을 보면, Main 클래스는 문서의 구축 방법(Builder 클래스의 메소드)을 모른다. Main 클래스는 Guide 클래스의 construct 메소드를 호출하는 것만으로 문장을 구축할 수 있다.
그리고, Guide 클래스는 Builder 클래스를 사용해 문서를 작성을 하지만, Guide 클래스는 자신이 실제로 이용하고 있는 클래스가 무엇인가(TextBuilder인가 HtmlBuilder인가)를 모른다.
TextBuilder의 인스턴스를 Guide에 주어도 HTMLBuilder의 인스턴스를 Guide에 주어도 올바르게 기능하는 것은 Guide 클래스가 Builder 클래스의 구체적인 클래스를 모르기 때문이다.
모르기 때문에 교체가 가능한 교체가 가능하기 때문에 부품으로서의 가치가 높아진다.

2.2.3 - Design Pattern | Factory Method (팩토리 메소드)

Factory Method 패턴이란?

  • Factory라는 영어 단어는 공장 이라는 의미가 된다. 공장에서는 물건을 스펙에 맞게 생산하고 요구사항이 좀 달라지면 그에 맞게 조금 다른 물건도 생산을 하게 된다.
  • 원하는 스펙을 입력하면 그에 맞는 instance를 생성해주는 것이 팩토리 메서드 패턴이다.
  • Factory Method 패턴은 인스턴스의 만드는 방법을 슈퍼 클래스로 정해, 구체적인 생성 처리는 서브 클래스측에서 실시하는 방식이다.
  • 인스턴스를 생성하는 공장을 Tmplate Method 패턴으로 구성한 것이 Factory Method 패턴이 된다.
  • GoF 디자인 패턴에서는 생성과 관련된 디자인 패턴으로 분류된다.

Factory Method 패턴 예제 프로그램

ID 카드 공장에서 ID 카드를 만드는 프로그램이다.

Class Diagram
Factory Method Pattern Class Diagram

1. Product 클래스

Factory에서 생성되는 객체의 기본이 되는 클래스이다.

Product.java

package com.devkuma.designpattern.creational.factorymethod.framwork;

public abstract class Product {
    public abstract void use();
}

2. Factory 클래스

Factory의 기본이 되는 클래스이다. 인스턴스를 생성한다.

Factory.java

package com.devkuma.designpattern.creational.factorymethod.framwork;

public abstract class Factory {

    public final Product create(String owner) {
        Product product = createProduct(owner);
        registerProduct(product);
        return product;
    }

    protected abstract Product createProduct(String owner);
    protected abstract void registerProduct(Product product);
}

3. IDCard 클래스

Product 클래스로 정의된 메소드를 구현하는 구상 클래스입니다.

IDCard.java

package com.devkuma.designpattern.creational.factorymethod.idcard;

import com.devkuma.designpattern.creational.factorymethod.framwork.Product;

public class IdCard extends Product {

    private String owner;

    IdCard(String owner) {
        System.out.println(owner + "의 카드를 만듭니다.");
        this.owner = owner;
    }

    public void use() {
        System.out.println(owner + "의 카드를 사용합니다.");
    }

    public String getOwner() {
        return owner;
    }
}

4. IDCardFactory 클래스

Factory 클래스로 정의된 메소드를 구현하는 구상 클래스이다.

IDCardFactory.java

package com.devkuma.designpattern.creational.factorymethod.idcard;

import com.devkuma.designpattern.creational.factorymethod.framwork.Factory;
import com.devkuma.designpattern.creational.factorymethod.framwork.Product;

import java.util.ArrayList;

public class IdCardFactory extends Factory {

    private ArrayList<String> owners = new ArrayList();

    protected Product createProduct(String owner) {
        return new IdCard(owner);
    }

    protected void registerProduct(Product product) {
        IdCard icCard = (IdCard) product;
        String owner = icCard.getOwner();
        owners.add(owner);
    }

    public ArrayList<String> getOwners() {
        return owners;
    }
}

5. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.creational.factorymethod;

import com.devkuma.designpattern.creational.factorymethod.framwork.Factory;
import com.devkuma.designpattern.creational.factorymethod.framwork.Product;
import com.devkuma.designpattern.creational.factorymethod.idcard.IdCardFactory;

public class Main {
    public static void main(String[] args) {
        Factory factory = new IdCardFactory();
        Product card1 = factory.create("devkuma");
        Product card2 = factory.create("kimkc");
        Product card3 = factory.create("yunho");
        card1.use();
        card2.use();
        card3.use();
    }
}

6. 실행 결과

devkuma의 카드를 만듭니다.
kimkc의 카드를 만듭니다.
yunho의 카드를 만듭니다.
devkuma의 카드를 사용합니다.
kimkc의 카드를 사용합니다.
yunho의 카드를 사용합니다.

Factory Method 패턴의 장점

Factory/Product는 framework 패키지, IDCardFactory/IDCard는 idcard 패키지에 존재한다.
framework 패키지는 idcard 패키지를 가져오지 않는다. 즉, framework 패키지는 idcard 패키지에 의존하지 않는 형태이다.
완전히 다른 ‘제품’과 ‘공장’을 만들려는 경우 프레임워크 패키지의 내용을 수정할 필요가 없다.

2.2.4 - Design Pattern | Prototype Pattern (프로토타입 패턴)

Prototype 패턴이란?

  • Prototype이라는 영엉 단어는 원형이나 모범 이라는 의미가 된다.
  • Prototype 패턴은 new Xxx()으로 클래스로부터 인스턴스를 생성하는 것이 아니라, 인스턴스로부터 다른 인스턴스를 생성하는 방식이다. 즉, 복사하여 인스턴스 만드는 것이다.
  • 복제를 만드는 작업을 clone 이라고 한다.
  • GoF 디자인 패턴에서는 생성과 관련된 디자인 패턴으로 분류된다.

Prototype 패턴 예제 프로그램

입력한 문자열에 밑줄을 그거나 둘러싸는 예제 프로그램이다.

Class Diagram
Prototype Pattern Class Diagram

1. Product 인터페이스

복제 메소드를 정의하는 인터페이스이다. java의 Cloneable 인터페이스를 상속한다.

Product.java

package com.devkuma.designpattern.creational.prototype.framework;

import java.lang.Cloneable;

public interface Product extends Cloneable {
    void use(String s);

    Product createClone();
}

2. Manager 클래스

Product의 생성 지시나 관리를 실시하는 클래스이다.

Manager.java

package com.devkuma.designpattern.creational.prototype.framework;

import java.util.HashMap;

public class Manager {

    private HashMap showcase = new HashMap();

    public void register(String name, Product prototype) {
        showcase.put(name, prototype);
    }

    public Product create(String prototypeName) {
        Product p = (Product) showcase.get(prototypeName);
        return p.createClone();
    }
}

3. UnderlinePen 클래스

Product 인터페이스를 구현하는 클래스입니다. 당 클래스는 「문자에 밑줄을 긋는」클래스가 됩니다.

UnderlinePen.java

package com.devkuma.designpattern.creational.prototype.product;

import com.devkuma.designpattern.creational.prototype.framework.Product;

public class UnderlinePen implements Product {

    private char underlineChar;

    public UnderlinePen(char underlineChar) {
        this.underlineChar = underlineChar;
    }

    public void use(String s) {

        int length = s.getBytes().length;

        System.out.println(s);

        for (int i = 0; i < length; i++) {
            System.out.print(underlineChar);
        }

        System.out.println();
    }

    public Product createClone() {
        Product p = null;
        try {
            p = (Product) clone();
        } catch (CloneNotSupportedException e) {
            // 객체의 클래스가 Cloneable 인터페이스를 구현하고 있지 않은 경우에 Throw되는 예외
            e.printStackTrace();
        }
        return p;
    }
}

4. MessageBox 클래스

Product 인터페이스를 구현하는 클래스입니다. 당 클래스는 「문자를 둘러싼」클래스가 됩니다.

MessageBox.java

package com.devkuma.designpattern.creational.prototype.product;

import com.devkuma.designpattern.creational.prototype.framework.Product;

public class MessageBox implements Product {

    private char decoChar;

    public MessageBox(char decoChar) {
        this.decoChar = decoChar;
    }

    public void use(String s) {

        int length = s.getBytes().length;

        for (int i = 0; i < length + 2; i++) {
            System.out.print(decoChar);
        }

        System.out.println();
        System.out.println(decoChar + s + decoChar);

        for (int i = 0; i < length + 2; i++) {
            System.out.print(decoChar);
        }

        System.out.println();
    }

    public Product createClone() {
        Product p = null;
        try {
            p = (Product) clone();
        } catch (CloneNotSupportedException e) {
            // 객체의 클래스가 Cloneable 인터페이스를 구현하고 있지 않은 경우에 Throw되는 예외
            e.printStackTrace();
        }
        return p;
    }
}

5. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.creational.prototype;

import com.devkuma.designpattern.creational.prototype.framework.Manager;
import com.devkuma.designpattern.creational.prototype.framework.Product;
import com.devkuma.designpattern.creational.prototype.product.MessageBox;
import com.devkuma.designpattern.creational.prototype.product.UnderlinePen;

public class Main {
    public static void main(String[] args) {

        Manager manager = new Manager();
        UnderlinePen upen = new UnderlinePen('~');
        MessageBox mbox = new MessageBox('*');
        MessageBox pbox = new MessageBox('+');
        manager.register("strong message", upen);
        manager.register("warning box", mbox);
        manager.register("slash box", pbox);

        Product p1 = manager.create("strong message");
        p1.use("Hello world");
        System.out.println();
        Product p2 = manager.create("warning box");
        p2.use("Hello world");
        System.out.println();
        Product p3 = manager.create("slash box");
        p3.use("Hello world");
    }
}

6. 실행 결과

Hello world
~~~~~~~~~~~

*************
*Hello world*
*************

+++++++++++++
+Hello world+
+++++++++++++

Prototype 패턴의 장점

예제 프로그램에서는 비교적 단순한 경우였지만, 인스턴스를 생성하는데 있어서 복잡한 처리가 필요하거나 시간이 걸리는 처리가 필요할 수도 있다. 인스턴스를 생성할 때마다 new Xxx()로서는, 매우 비효율적이다.
Prototype을 만들고 복사하면 쉽게 인스턴스를 생성할 수 있다.

2.2.5 - Design Pattern | Singleton Pattern (싱글톤 패턴)

Singleton 패턴이란?

  • Singleton(싱글톤)이란 하나의 요소만 가진 집합 이라는 의미이다.
  • Singleton 패턴은 인스턴스가 하나만 존재하는지를 보증하는 방식이다.
  • Singleton 패턴은 프로그램 내에서 인스턴스를 한번만 생성하도록 하고 싶을 때 사용된다.
  • 전역 변수를 사용하지 않고 객체를 하나만 생성 하도록 하며, 생성된 객체를 어디에서든지 참조할 수 있도록 하는 패턴이다.
  • 프로그램의 전역에서 사용될 설정 정보 값은 Singleton 패턴을 이용해 한개만 생성해 정보를 공유 하는데도 필요하다.
  • 예를 들어, 시스템 설정을 표현한 클래스, 윈도우 시스템을 표현한 클래스 등을 들 수 있다.
  • GoF 디자인 패턴에서는 생성과 관련된 디자인 패턴으로 분류된다.

Singleton 패턴 예제 프로그램

싱글 톤 인스턴스를 생성하는 프로그램이다.

Class Diagram
Singleton Pattern Class Diagram

1. Singleton 클래스

유일한 인스턴스를 반환하는 클래스이다.
Singleton 클래스의 생성자는 private으로 지정한다. 이는 Singleton 클래스 밖에서 생성자를 호출하는 것을 못하게 하기 위함이다.

Singleton.java

package com.devkuma.designpattern.creational.singleton.ex1;

public class Singleton {

    private static Singleton singleton = new Singleton();

    private Singleton() {
        System.out.println("인스턴스를 생성하였습니다.");
    }

    public static Singleton getInstance() {
        return singleton;
    }
}

2. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.creational.singleton.ex1;

public class Main {
    public static void main(String[] args) {
        Singleton obj1 = Singleton.getInstance();
        Singleton obj2 = Singleton.getInstance();
        if (obj1 == obj2) {
            System.out.println("obj1와 obj2는 동일한 인스턴스입니다.");
        } else {
            System.out.println("obj1와 obj2는 동일한 인스턴스가 아닙니다.");
        }
    }
}

3. 실행 결과

인스턴스를 생성하였습니다.
obj1와 obj2는 동일한 인스턴스입니다.

Singleton 패턴의 장점

Singleton 패턴은 인스턴스 수에 제한을 두고 있다.
인스턴스가 여러 개 존재하면 인스턴스가 서로 영향을 미치고 뜻밖의 버그를 만들 수 있다.
그러나 인스턴스가 하나만 있다는 보장이 있다면 그 전제조건으로 프로그래밍할 수 있다.

synchronized를 사용한 Singleton 패턴

synchronized를 사용한 Singleton 패턴 프로그램 예제

여러 객체에서 동시에 접근을 할 수 있기에 synchronized를 선언을 해야 스레드로 부터 안전하다.

package com.devkuma.designpattern.creational.singleton.ex2;

public class SynchronizedSingleton1 {

    private static Singleton1 instance;

    /**
     * 접근할 수 없는 생성자
     */
    private SynchronizedSingleton1() {
    }

    public static synchronized Singleton1 getInstance() {
        if (instance == null) {
            instance = new Singleton1();
        }
        return instance;
    }

    public static void main(String[] args) {

        Singleton1 singleton1 = Singleton1.getInstance();
        Singleton1 singleton2 = Singleton1.getInstance();

        System.out.println(singleton1);
        System.out.println(singleton2);
    }
}

synchronized 성능 개선한 Singleton 패턴 프로그램 예제

동기화 때문에 성능관련 이슈가 발생할 수 있기 때문에 아래와 같은 코드를 작성해 최초 한번만 동기화하도록 해서 성능을 향상 시킬 수 있다.

  • volatile: 스레딩 환경에서 하나의 변수가 완벽하게 한번에 작동하도록 보장하는 키워드
package com.devkuma.designpattern.creational.singleton.ex2;

public class SynchronizedSingleton2 {

    private volatile static Singleton2 instance;

    /**
     * 접근할 수 없는 생성자
     */
    private SynchronizedSingleton2() {
    }

    public static Singleton2 getInstance() {
        if (instance == null) {
            // 처음 한번만 인스턴스가 생성된다.
            synchronized (Singleton2.class) {
                if (instance == null) {
                    instance = new Singleton2();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {

        Singleton2 singleton1 = Singleton2.getInstance();
        Singleton2 singleton2 = Singleton2.getInstance();

        System.out.println(singleton1);
        System.out.println(singleton2);
    }
}

2.3 - 구조 패턴 (Structural Pattern)

클래스나 객체를 조합해 더 큰 구조를 만드는 패턴

클래스나 객체를 조합해 더 큰 구조를 만드는 패턴으로 예를 들어 서로 다른 인터페이스를 지닌 2개의 객체를 묶어 단일 인터페이스를 제공하거나 객체들을 서로 묶어 새로운 기능을 제공하는 패턴이다.

2.3.1 - Design Pattern | Adapter Pattern (아답터 패턴)

Adapter 패턴이란?

  • Adapter라는 영어 단어는, 적합시키는 것이라는 의미가 된다.
  • Adapter 패턴은, 이미 제공되고 있는 것을 그대로 사용할 수 없는 것에 대해, 필요한 형태로 변환해 이용하기 위한 방식이다. 즉, 인터페이스의 변경하기 위한 방식이다.
  • 어댑터는 볼트 수가 맞지 않을 때 중간에 연결해주는 돼지코를 생각하면 된다.
  • 패턴도 양쪽에 서로 맞지 않은 인테페이스가 있다면 이 둘 간에 연결시켜줄 수 있는 Bridge를 만들어 주면 된다.
  • 상속을 이용한 방법과 위임을 이용한 방법이 있다.
    • 클래스에 의한 Adapter Pattern
      • 상속을 이용한 Adapter Pattern이다.
    • 인스턴스에 의한 Adapter Pattern
      • 위임을 사용한 Adapter Pattern이다.
  • 래퍼 패턴 이라고도 하다. 래퍼는 감싸는 것을 의미하다.
  • GoF의 디자인 패턴에서는 구조와 관련된 디자인 패턴으로 분류된다.

Adapter 패턴 예제 프로그램

학생의 이름과 나이를 표시하는 예제 프로그램이다.

1. 클래스 상속을 이용한 방법

Class Diagram
Adapter Pattern Class Diagram

1-1. Human 클래스

원래 제공된 클래스이다.

Human.java

package com.devkuma.designpattern.structural.adapter.ex1;

public class Human {

    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void printName() {
        System.out.println(name);
    }

    public void printAge() {
        System.out.println(age);
    }
}

1-2. Student 인터페이스

필요한 인터페이스이다.

Student.java

package com.devkuma.designpattern.structural.adapter.ex1;

public interface Student {
    void showName();

    void showAge();
}

1-3. HumanAdapter 클래스

Adapter 역할이 되는 클래스입니다.

HumanAdapter.java

package com.devkuma.designpattern.structural.adapter.ex1;

public class HumanAdapter extends Human implements Student {

    public HumanAdapter(String name, int age) {
        super(name, age);
    }

    public void showName() {
        printName();
    }

    public void showAge() {
        printAge();
    }
}

1-4. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.structural.adapter.ex1;

public class Main {
    public static void main(String[] args) {
        Student student = new HumanAdapter("devkuma", 25);
        student.showName();
        student.showAge();
    }
}

1-5. 실행 결과

devkuma
25

2. 위임을 이용한 방법

Adapter Pattern Class Diagram

2-1. Human 클래스

원래 제공된 클래스이다.

Human.java

package com.devkuma.designpattern.structural.adapter.ex2;

public class Human {

    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void printName() {
        System.out.println(name);
    }

    public void printAge() {
        System.out.println(age);
    }
}

2-2. Student 인터페이스

필요한 인터페이스이다.

Student.java

package com.devkuma.designpattern.structural.adapter.ex2;

public interface Student {
    void showName();

    void showAge();
}

2-3. HumanAdapter 클래스

Adapter 역할이 되는 클래스이다.

HumanAdapter.java

package com.devkuma.designpattern.structural.adapter.ex2;

public class HumanAdapter implements Student {

    private Human human;

    public HumanAdapter(String name, int age) {
        this.human = new Human("田中", 25);;
    }

    public void showName() {
        human.printName();
    }

    public void showAge() {
        human.printAge();
    }
}

2-4. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.structural.adapter.ex2;

public class Main {
    public static void main(String[] args) {
        Student student = new HumanAdapter("devkuma", 25);
        student.showName();
        student.showAge();
    }
}

2-5. 실행 결과

devkuma
25

Adapter 패턴의 장점

Adapter 패턴은 기존 클래스에 가죽을 씌우고 필요한 클래스를 만든다. 이 패턴을 사용하면 필요한 메소드 그룹을 신속하게 만들 수 있다.
만약 버그가 검출되었다고 해도, 기존의 클래스가 충분히 테스트되고 있다면, Adapter역의 클래스를 중점적으로 조사하면 좋게 되므로, 프로그램의 체크가 편해진다.
또한 Adapter 패턴을 사용하면 기존 클래스에 손을 추가하지 않고 기능을 구현할 수 있으므로 기존 클래스를 다시 테스트하는 번거로움을 줄일 수 있다.

2.3.2 - Design Pattern | Bridge Pattern (브릿지 패턴)

Bridge 패턴이란?

  • Bridge라는 영어 단어는 다리라는 의미이다.
  • 현실 세계의 다리가 강가를 양쪽으로 연결시키는 것처럼, Bridge 패턴도 2개의 장소를 연결하는 방식이다.
  • 구현과 추상을 분리. ‘기능 클래스 계층’과 ‘구현 클래스 계층’간에 다리(Bridge)를 놓는 역할을 하는 패턴이다.
  • Bridge 패턴이 교차하는 두 위치는 기능 클래스 계층구현 클래스 계층이 된다.
    • 기능 클래스의 계층 : 슈퍼 클래스로 기본적인 기능을 가지고 있어, 서브 클래스로 새로운 기능을 추가하는 경우의 계층이다.
    • 구현 클래스의 계층 : 슈퍼 클래스로 추상 메소드에 의해 인터페이스를 규정하고 있어, 서브 클래스에서 구상 메소드에 의해 그 인터페이스를 구현하는 경우의 계층이다.
  • GoF의 디자인 패턴에서는 구조와 관련된 디자인 패턴으로 분류된다.

Bridge 패턴 예제 프로그램

입력한 문자열을 지정 횟수 및 랜덤 횟수 표시하는 예제 프로그램이다.

Class Diagram
Bridge Pattern

1. Display 클래스

기능 클래스 계층이 되는 “표시"를 실시하는 클래스이다. 이 클래스가 가지는 impl 필드가 2개의 클래스 계층의 “다리"가 된다.

Display.java

package com.devkuma.designpattern.structural.bridge;

public class Display {

    private DisplayImpl impl;

    public Display(DisplayImpl impl) {
        this.impl = impl;
    }

    public void open() {
        impl.rawOpen();
    }

    public void print() {
        impl.rawPrint();
    }

    public void close() {
        impl.rawClose();
    }

    public final void display() {
        open();
        print();
        close();
    }
}

2. CountDisplay 클래스

기능 클래스 계층이 되는 “지정 횟수 표시"라고 하는 기능을 추가한 클래스이다.

CountDisplay.java

package com.devkuma.designpattern.structural.bridge;

public class CountDisplay extends Display {

    public CountDisplay(DisplayImpl impl) {
        super(impl);
    }

    public void multiDisplay(int times) {
        open();
        for (int i = 0; i < times; i++) {
            print();
        }
        close();
    }
}

3. RandomCountDisplay 클래스

기능 클래스 계층이 되는 “랜덤 횟수 표시"라는 기능을 추가한 클래스이다.

RandomCountDisplay.java

package com.devkuma.designpattern.structural.bridge;

import java.util.Random;

public class RandomCountDisplay extends CountDisplay {

    private Random random = new Random();

    public RandomCountDisplay(DisplayImpl impl) {
        super(impl);
    }

    public void randomDisplay(int times) {
        multiDisplay(random.nextInt(times));
    }
}

4. DisplayImpl 클래스

구현 클래스 계층이 되는 “표시"용의 메소드를 정의한 클래스이다.

DisplayImpl.java

public abstract class DisplayImpl {
    public abstract void rawOpen();
    public abstract void rawPrint();
    public abstract void rawClose();
}

5. StringDisplayImpl 클래스

구현 클래스 계층이 되는 “문자열을 사용해 표시"하는 클래스이다.

StringDisplayImpl.java

package com.devkuma.designpattern.structural.bridge;

public class StringDisplayImpl extends DisplayImpl {

    private String string;
    private int width;

    public StringDisplayImpl(String string) {
        this.string = string;
        this.width = string.getBytes().length;
    }

    public void rawOpen() {
        printLine();
    }

    public void rawPrint() {
        System.out.println("|" + string + "|");
    }

    public void rawClose() {
        printLine();
    }

    private void printLine() {
        System.out.print("+");
        for (int i = 0; i < width; i++) {
            System.out.print("-");
        }
        System.out.println("+");
    }
}

6. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.structural.bridge;

public class Main {

    public static void main(String[] args) {
        Display display = new Display(new StringDisplayImpl("Display Test"));
        CountDisplay countDisplay = new CountDisplay(new StringDisplayImpl("CountDisplay Test"));
        RandomCountDisplay randomCountDisplay = new RandomCountDisplay(new StringDisplayImpl("RandomCountDisplay Test"));
        display.display();
        countDisplay.multiDisplay(5);
        randomCountDisplay.randomDisplay(10);
    }
}

7. 실행 결과

+------------+
|Display Test|
+------------+
+-----------------+
|CountDisplay Test|
|CountDisplay Test|
|CountDisplay Test|
|CountDisplay Test|
|CountDisplay Test|
+-----------------+
+-----------------------+
|RandomCountDisplay Test|
|RandomCountDisplay Test|
|RandomCountDisplay Test|
|RandomCountDisplay Test|
|RandomCountDisplay Test|
|RandomCountDisplay Test|
+-----------------------+

장점

앞서 언급했듯이 브릿지 패턴의 특징은 기능 클래스의 계층구현 클래스의 계층을 구분한다는 것이다. 이 2개의 클래스 계층을 나누어 두면, 각각의 클래스 계층을 독립적으로 확장할 수 있다.
기능을 추가하려면 함수의 클래스 계층에 클래스를 추가한다. 이 때, 구현 클래스 계층은 전혀 수정할 필요는 없다. 게다가, 추가한 기능은 모든 구현으로 이용할 수 있게 된다.
예제 프로그램에서는, CountDisplay 클래스나 RandomCountDisplay 클래스를 추가하는 것이 기능 추가에 해당한다.
이와 같이 Bridge 패턴에서는 클래스의 확장을 전망하고 잘 할 수 있다.

2.3.3 - Design Pattern | Composite Pattern (컴포즈 패턴)

Composite 패턴이란?

  • Composite라는 단어는 혼합 또는 복합이라는 의미이다.
  • Composite 패턴은 용기와 내용물을 동일화하여 재귀적인 구조를 만드는 방식이다.
  • 디렉토리와 파일을 함께 디렉토리 항목으로 취급하기 위해 컨테이너와 내용물을 같은 종류의 것으로 취급하는 것이 편리 할 수 ​​있다. 예를 들어, 용기 안에는 내용물을 넣어도 되고, 한층 더 용기를 넣는 것도 좋다. 이런 식으로 재귀 구조를 만들 수 있다.
  • GoF의 디자인 패턴에서는 구조와 관련된 디자인 패턴으로 분류된다.

Composite 패턴 예제 프로그램

디렉토리, 파일 목록을 표시하는 예제 프로그램이다.

Class Diagram
Composite Pattern Class Diagram

1. Entry 클래스

File과 Directory의 기본이 되는 클래스이다.

Entry.java

package com.devkuma.designpattern.structural.composite;

public abstract class Entry {

    public abstract String getName();

    protected abstract void printList(String prefix);

    public void printList() {
        printList("");
    }
}

2. File 클래스

파일을 나타내는 클래스이다.

File.java

package com.devkuma.designpattern.structural.composite;

public class File extends Entry {

    private String name;

    public File(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    protected void printList(String prefix) {
        System.out.println(prefix + "/" + name);
    }
}

3. Directory 클래스

디렉토리를 나타내는 클래스이다.

Directory.java

package com.devkuma.designpattern.structural.composite;

import java.util.ArrayList;
import java.util.Iterator;

public class Directory extends Entry {

    private String name;
    private ArrayList<Entry> directory = new ArrayList();

    public Directory(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    public Entry add(Entry entry) {
        directory.add(entry);
        return this;
    }

    @Override
    protected void printList(String prefix) {
        System.out.println(prefix + "/" + name);
        Iterator<Entry> it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = it.next();
            entry.printList(prefix + "/" + name);
        }
    }
}

4. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.structural.composite;

public class Main {
    public static void main(String[] args) {

        Directory workspaceDir = new Directory("workspace");
        Directory compositeDir = new Directory("composite");
        Directory testDir1 = new Directory("test1");
        Directory testDir2 = new Directory("test2");
        workspaceDir.add(compositeDir);
        workspaceDir.add(testDir1);
        workspaceDir.add(testDir2);

        File directory = new File("Directory.java");
        File entity = new File("Entity.java");
        File file = new File("file.java");
        File main = new File("main.java");
        compositeDir.add(directory);
        compositeDir.add(entity);
        compositeDir.add(file);
        compositeDir.add(main);
        workspaceDir.printList();
    }
}

5. 실행 결과

/workspace
/workspace/composite
/workspace/composite/Directory.java
/workspace/composite/Entity.java
/workspace/composite/file.java
/workspace/composite/main.java
/workspace/test1
/workspace/test2

장점

모든 객체(File, Directory)는 공통의 추상 클래스를 가지고 있으므로, 클라이언트로에서 봤을 때, 어느 것이 File인가 Directory인지, 내용을 의식할 필요가 없고, 동일하게 취급할 수 있다.
또, 새로운 클래스(예: SymbolicLink)를 추가해도, 기본 클래스(Entry)의 인터페이스가 바뀌지 않으면, 클라이언트의 처리에는 영향을 주지 않는다.

2.3.4 - Design Pattern | Decorator Pattern (데코레이터 패턴)

Decorator 패턴이란?

  • Decorator라는 영어 단어는 장식하다(Decorate)라는 의미이다. 기존 내용에 무엇인가를 덧붙인다는 의미가 강하다.
  • Decorator 패턴은, 객체에 계속해서 데코레이션(장식)을 추가하는 방식이다.
  • 피자를 예를 들면, 기존 피자 위에 토핑을 추가한다고 생각하면 된다.
  • 스폰지 케이크에 대해 크림, 초콜릿, 딸기 … 등으로 장식 할 수 있도록 객체도 기능을 하나 하나 씌워 장식하는 방식이 된다.
  • GoF 디자인 패턴은 생성과 관련된 디자인 패턴으로 분류된다.

Decorator 패턴 예제 프로그램

입력한 문자열에 대해 테두리 등의 장식을 하는 예제 프로그램이다.

Class Diagram
Decorator Pattern Class Diagram

1. Display 클래스

캐릭터 라인 표시용의 추상 클래스이다.

Display.java

package com.devkuma.designpattern.structural.decorator;

public abstract class Display {

    // 열의 문자 수를 반환한다.
    public abstract int getColumns();

    // 행 수를 반환한다.
    public abstract int getRows();

    // 지정된 행의 문자열을 반환환다.
    public abstract String getRowText(int row);

    public void show() {
        for (int i = 0; i < getRows(); i++) {
            System.out.println(getRowText(i));
        }
    }
}

2. StringDisplay 클래스

1행만으로 이루어지는 캐릭터 라인 표시용의 클래스이다.

StringDisplay.java

package com.devkuma.designpattern.structural.decorator;

public class StringDisplay extends Display {

    private String string;

    public StringDisplay(String string) {
        this.string = string;
    }

    public int getColumns() {

        return string.getBytes().length;
    }

    public int getRows() {
        return 1;
    }

    public String getRowText(int row) {
        return (row == 0) ? string : null;
    }
}

3. Border 클래스

장식 테두리를 나타내는 추상 클래스이다.

Border.java

package com.devkuma.designpattern.structural.decorator;

public abstract class Border extends Display {

    protected Display display;

    protected Border(Display display) {
        this.display = display;
    }
}

4. SideBorder 클래스

좌우에 장식 테두리를 붙이는 클래스이다.

SideBorder.java

package com.devkuma.designpattern.structural.decorator;

public class SideBorder extends Border {

    public SideBorder(Display display) {
        super(display);
    }

    public int getColumns() {
        // 문자수는 내용의 양측에 장식한 문자 수를 더한 수
        return 1 + display.getColumns() + 1;
    }

    public int getRows() {
        // 행 수는 내용의 행 수와 동일
        return display.getRows();
    }

    public String getRowText(int row) {
        return "*" + display.getRowText(row) + "*";
    }
}

5. FullBorder 클래스

상하 좌우에 장식 테두리를 붙이는 클래스이다.

FullBorder.java

package com.devkuma.designpattern.structural.decorator;

public class FullBorder extends Border {

    public FullBorder(Display display) {
        super(display);
    }

    public int getColumns() {
        // 문자수는 내용의 양측에 좌우의 장식한 문자 수를 더한 수
        return 1 + display.getColumns() + 1;
    }

    public int getRows() {
        // 행수는 내용의 행수에 상하의 장식한 문자 수을 더한 수
        return 1 + display.getRows() + 1;
    }

    public String getRowText(int row) {
        if (row == 0) {
            // 상단 테두리
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else if (row == display.getRows() + 1) {
            // 하단 테두리
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else {
            // 그밖에
            return "|" + display.getRowText(row - 1) + "|";
        }
    }

    private String makeLine(char ch, int count) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < count; i++) {
            buf.append(ch);
        }
        return buf.toString();
    }
}

6. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.structural.decorator;

public class Main {

    public static void main(String[] args) {
        Display display1 = new StringDisplay("Hello world");
        display1.show();
        System.out.println("");

        Display display2 = new SideBorder(display1);
        display2.show();
        System.out.println("");

        Display display3 = new FullBorder(display2);
        display3.show();
        System.out.println("");

        Display display4 =
                new FullBorder(
                        new SideBorder(
                                new FullBorder(
                                        new StringDisplay("Hello world"))));
        display4.show();
    }
}

7. 실행 결과

Hello world

*Hello world*

+-------------+
|*Hello world*|
+-------------+

+---------------+
|*+-----------+*|
|*|Hello world|*|
|*+-----------+*|
+---------------+

Decorator 패턴 장점

Decorator 패턴에서는, 장식 테두리(Border)도 내용(StringDisplay)도 공통의 인터페이스를 가지고 있다. 인터페이스는 공통이지만, 감싸면 감을수록 기능이 추가되어 간다. 그 때, 감싸는 쪽을 수정할 필요는 없다. 감싸는 것을 변경하지 않고 기능을 추가 할 수 있다.

2.3.5 - Design Pattern | Facade Pattern (퍼사드 패턴)

Facade 패턴이란?

  • Facade라는 영어 단어는 정면이라는 의미이다.
  • “건물의 정면"을 의미하는 단어로 어떤 소프트웨어의 다른 커다른 코드 부분에 대하여 간략화된 인터페이스를 제공해 주는 디자인 패턴을 의미한다.
  • 큰 프로그램을 사용해 처리를 실시하려면, 관련되어 있는 많은 클래스를 적절히 제어해야 한다. 그 처리를 실시하기 위한 창구를 만들어 두면, 많은 클래스를 개별적으로 제어하지 않아도 된다.
  • Facade 패턴은 복잡한 시스템에 대한 간단한 창구를 준비하는 방식이다.
  • Facade 객체는 복잡한 소프트웨어 바깥쪽의 코드가 라이브러리의 안쪽 코드에 의존하는 일을 감소시켜 주고, 복잡한 소프트웨를 사용할 수 있게 간단한 인터페이스를 제공해 준다.
  • GoF의 디자인 패턴에서는 구조와 관련된 디자인 패턴으로 분류된다.

Facade 패턴 예제 프로그램

사용자의 웹 페이지를 만드는 예제 프로그램이다.

Class Diagram
Facade Pattern Class Diagram

1. PageMaker 클래스

이메일 주소에서 사용자의 웹 페이지를 만드는 클래스이다. 이 클래스는 Facade가 된다.

PageMaker.java

package com.devkuma.designpattern.structural.facade.pagemaker;

import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;

public class PageMaker {

    private PageMaker() {}

    public static void makeWelcomePage(String mailaddr, String filename) {
        try {
            Properties mailProp = Database.getProperties("maildata");
            String username = mailProp.getProperty(mailaddr);
            HtmlWriter writer = new HtmlWriter(new FileWriter(filename));
            writer.title("Welcome to " + username + "'s page!");
            writer.paragraph(username + "의 페이지에 어서오세요.");
            writer.paragraph("문의 사항을 메일을 보내주세요.");
            writer.mailto(mailaddr, username);
            writer.close();
            System.out.println(filename + " is created for " + mailaddr + " (" + username + ")");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2. HtmlWriter 클래스

HTML 파일을 작성하는 클래스입니다.

HtmlWriter.java

package com.devkuma.designpattern.structural.facade.pagemaker;

import java.io.IOException;
import java.io.Writer;

public class HtmlWriter {

    private Writer writer;

    public HtmlWriter(Writer writer) {
        this.writer = writer;
    }

    public void title(String title) throws IOException {
        writer.write("<html>");
        writer.write("<head>");
        writer.write("<title>" + title + "</title>");
        writer.write("</head>");
        writer.write("<body>\n");
        writer.write("<h1>" + title + "</h1>\n");
    }

    public void paragraph(String msg) throws IOException {
        writer.write("<p>" + msg + "</p>\n");
    }

    public void link(String href, String caption) throws IOException {
        paragraph("<a href=\"" + href + "\">" + caption + "</a>");
    }

    public void mailto(String mailaddr, String username) throws IOException {
        link("mailto:" + mailaddr, username);
    }

    public void close() throws IOException {
        writer.write("</body>");
        writer.write("</html>\n");
        writer.close();
    }
}

3. Database 클래스

이메일 주소에서 사용자 이름을 얻는 클래스이다.

Database.java

package com.devkuma.designpattern.structural.facade.pagemaker;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class Database {

    private Database() {
    }

    public static Properties getProperties(String dbname) {
        ClassLoader loader = Database.class.getClassLoader();
        String file = loader.getResource("facade/pagemaker/" + dbname + ".txt").getFile();
        Properties prop = new Properties();
        try {
            prop.load(new FileInputStream(file));
        } catch (IOException e) {
            System.out.println("Warning: " + file + " is not found.");
        }
        return prop;
    }
}

4. maildata 데이터베이스

데이터베이스 파일이다.

maildata.txt

devkuma@devkuma.com=dekuma
ariakuma@devkuma.com=ariakuma
kimkc@devkuma.com=kim

5. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.structural.facade;

import com.devkuma.designpattern.structural.facade.pagemaker.PageMaker;

public class Main {
    public static void main(String[] args) {
        PageMaker.makeWelcomePage("devkuma@devkuma.com", "welcome.html");
    }
}

6. 실행 결과

<html><head><title>Welcome to devkuma's page!</title></head><body>
<h1>Welcome to devkuma's page!</h1>
<p>devkuma의 페이지에 어서오세요.</p>
<p>문의 사항을 메일을 보내주세요.</p>
<p><a href="mailto:devkuma@devkuma.com">devkuma</a></p>
</body></html>

Facade 패턴의 장점

클래스나 메소드가 많이 있으면, 프로그래머는 어느 것을 사용해야 할지 헤매거나, 호출 순서에 주의해야 한다. 주의해야 한다는 것은 그만큼 실수하기 쉽다는 것이다.
Facade 패턴을 사용하면 인터페이스를 줄이고 복잡한 것을 단순히 보여줄 수 있다.
인터페이스의 수가 적다는 것은 외부와의 결합이 희소하다는 표현도 할 수 있다. 즉, 완만한 결합이 되어, 패키지를 부품으로서 재이용하기 쉽게 해준다.

2.3.6 - Design Pattern | Decorator Flyweight (플라이트 패턴)

Flyweight 패턴이란?

  • Flyweight라는 영어 단어는 “플라이급” 이라는 의미가 된다. 권투에서 가장 무게가 가벼운 계급을 나타낸다.
  • Flyweight 패턴의 무게는 메모리 사용량이다.
  • Flyweight 패턴은 인스턴스를 가능한 한 많이 공유하여 메모리 사용량을 줄이는 방법이다.
  • GoF의 디자인 패턴에서는 구조와 관련된 디자인 패턴으로 분류된다.

Flyweight 패턴 예제 프로그램

파일에서 큰 문자의 텍스트를 읽고 그것을 표시하는 프로그램이다.

Class Diagram
Flyweight Pattern Class Diagram

1. BigChar 클래스

큰 문자를 나타내는 클래스이다.

BigChar.java

package com.devkuma.designpattern.structural.flyweight;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BigChar {

    // 큰 문자를 나타내는 문자열 ('#', '.', '\n'열)
    private String fontData;

    public BigChar(char charName) {
        try {
            ClassLoader loader = BigChar.class.getClassLoader();
            String file = loader.getResource("flyweight/big" + charName + ".txt").getFile();
            BufferedReader reader = new BufferedReader(new FileReader(file));
            String line;
            StringBuffer buf = new StringBuffer();
            while ((line = reader.readLine()) != null) {
                buf.append(line);
                buf.append("\n");
            }
            reader.close();
            this.fontData = buf.toString();
        } catch (IOException e) {
            this.fontData = charName + "?";
        }
    }

    // 큰 문자 표시을 표시한다.
    public void print() {
        System.out.print(fontData);
    }
}

2. BigCharFactory 클래스

BigChar의 인스턴스를 공유하면서 생성하는 클래스이다.

BigCharFactory.java

package com.devkuma.designpattern.structural.flyweight;

import java.util.HashMap;

public class BigCharFactory {

    // 이미 만든 BigChar 인스턴스 관리
    private HashMap pool = new HashMap();

    private static BigCharFactory singleton = new BigCharFactory();

    private BigCharFactory() {
    }

    // 하나뿐인 인스턴스를 반환한다.
    public static BigCharFactory getInstance() {
        return singleton;
    }

    // BigChar의 인스턴스 생성 (공유)
    public synchronized BigChar getBigChar(char charName) {
        BigChar bigChar = (BigChar) pool.get("" + charName);
        if (bigChar == null) {
            bigChar = new BigChar(charName);
            pool.put("" + charName, bigChar);
        }
        return bigChar;
    }
}

3. BigString 클래스

BigChar를 모아서 만든 “큰 문자열"을 나타내는 클래스입니다.

BigString.java

package com.devkuma.designpattern.structural.flyweight;

public class BigString {

    private BigChar[] bigChars;

    public BigString(String string) {
        bigChars = new BigChar[string.length()];
        BigCharFactory factory = BigCharFactory.getInstance();
        for (int i = 0; i < bigChars.length; i++) {
            bigChars[i] = factory.getBigChar(string.charAt(i));
        }
    }

    public void print() {
        for (int i = 0; i < bigChars.length; i++) {
            bigChars[i].print();
        }
    }
}

4. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.structural.flyweight;

public class Main {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println("Usage: java Main digits");
            System.out.println("Example: java Main 1234");
            System.exit(0);
        }
        BigString bs = new BigString(args[0]);
        bs.print();
    }
}

5. 데이터

아래 txt 파일을 참조한다.
/java-design-pattern-tutorial/src/resources/com/devkuma/designpattern/flyweight

6. 실행 결과

아래 결과값은 프로그램애 인자값으로 0329를 넣고 실행시킨 결과이다.

....######......
..##......##....
..##......##....
..##......##....
..##......##....
..##......##....
....######......
................
....######......
..##......##....
..........##....
......####......
..........##....
..##......##....
....######......
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
....######......
..##......##....
..##......##....
....########....
..........##....
..##......##....
....######......
................

Flyweight 패턴의 장점

인스턴스를 공유하면 매번 new를 할 필요가 없어져서 메모리 사용량이 적다. 보다 일반적으로 말하면 인스턴스를 공유하면 인스턴스를 생성하는데 필요한 리소스의 양을 줄일 수 있다. 리소스는 컴퓨터의 리소스이며 메모리는 리소스 유형이다.
시간도 자원 유형이다. 인스턴스를 new를 할 때마다 일정 시간이 걸린다면 Flyweight 패턴을 사용하여 인스턴스를 공유하면 인스턴스를 new를 하는 수 만큼 줄일 수 있다. 이렇게 함으로써 프로그램 속도를 향상 시킬 수 있다.

2.3.7 - Design Pattern | Proxy Flyweight (프록시 패턴)

Proxy 패턴이란?

  • Proxy라는 영어 단어는 대리인, 대변인이라는 의미이다.
  • 기존 요소를 대신하는 방식이다.
  • 객체 지향에서는 “자신"도 “대리인"도 객체가 된다.
  • Proxy 패턴은 바쁘고 일할 수 없는 객체 자신 대신에 객체 대리인이 일부의 일을 해내는 방식이다.
  • Proxy 패턴에 중요한 것은 흐름제어만 할 뿐 결과값을 조작하거나 변경시키면 안된다.
  • 어떤 객체에 접근하기 위해 대리인을 사용한다.
  • GoF의 디자인 패턴에서는 구조와 관련된 디자인 패턴으로 분류된다.

Proxy 패턴 예제 프로그램

화면에 문자를 표시하는 “이름을 붙이는 프린터"의 예제 프로그램이다.

Class Diagram
Proxy Pattern Class Diagram

1. Printable 인터페이스

Printer와 PrinterProxy 공통 인터페이스이다.

Printable.java

package com.devkuma.designpattern.structural.proxy;

public interface Printable {
    // 이름 설정
    void setPrinterName(String name);

    // 이름 반환
    String getPrinterName();

    // 문자열 표시(print out)
    void print(String string);
}

2. Printer 클래스

이름을 붙이는 프린터를 나타내는 클래스이다. (자신)

Printer.java

package com.devkuma.designpattern.structural.proxy;

public class Printer implements Printable {

    private String name;

    public Printer() {
        heavyJob("Printer의 인스턴스를 생성중");
    }

    public Printer(String name) {
        this.name = name;
        heavyJob("Printer의 인스턴스 (" + name + ")를 생성중");
    }

    public void setPrinterName(String name) {
        this.name = name;
    }

    public String getPrinterName() {
        return name;
    }

    public void print(String string) {
        System.out.println("=== " + name + " ===");
        System.out.println(string);
    }

    private void heavyJob(String msg) {
        System.out.print(msg);
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.print(".");
        }
        System.out.println("완료");
    }
}

3. PrinterProxy 클래스

이름 첨부의 프린터를 나타내는 클래스이다. (대리인)

PrinterProxy.java

package com.devkuma.designpattern.structural.proxy;

public class PrinterProxy implements Printable {

    private String name;
    private Printer real;

    public PrinterProxy() {
    }

    public PrinterProxy(String name) {
        this.name = name;
    }

    public synchronized void setPrinterName(String name) {
        if (real != null) {
            real.setPrinterName(name);
        }
        this.name = name;
    }

    public String getPrinterName() {
        return name;
    }

    public void print(String string) {
        realize();
        real.print(string);
    }

    private synchronized void realize() {
        if (real == null) {
            real = new Printer(name);
        }
    }
}

4. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.structural.proxy;

public class Main {
    public static void main(String[] args) {
        Printable p = new PrinterProxy("Alice");
        System.out.println("현재 이름은 " + p.getPrinterName() + " 입니다.");
        p.setPrinterName("Bob");
        System.out.println("현재 이름은 " + p.getPrinterName() + " 입니다.");
        p.print("Hello, world.");
    }
}

5. 실행 결과

현재 이름은 Alice 입니다.
현재 이름은 Bob 입니다.
Printer의 인스턴스 (Bob)를 생성중.....완료
=== Bob ===
Hello, world.

Proxy 패턴의 장점

Proxy 패턴에서는 Proxy가 대리인이 되어 가능한 한 처리를 어깨 대체한다.
예제 프로그램에서는, Proxy역할을 사용하는 것으로, 실제로 print할 때까지, 무거운 처리(인스턴스 생성)를 지연시킬 수 있었다.
예를 들면, 초기화에 시간이 걸리는 기능이 많이 존재하는 시스템의 경우, 기동 시점에서는 이용하지 않는 기능까지 전부 초기화해 버리면, 어플리케이션의 기동에 시간이 걸려 버리게 된다.
PrinterProxy 클래스와 Printer 클래스의 2개로 나누지 않고, Printer 클래스 안에 처음부터 지연 기능을 넣어 둘 수도 있지만, 클래스를 나누는 것으로 프로그램의 부품화가 진행되어 개별적으로 기능을 더 할 수 있는 거다.

2.4 - 행위 패턴 (Behavioral Pattern)

객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴

객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴으로, 한 객체가 혼자 수행할 수 없는 작업을 여러 개의 객체로 어떻게 분배하는지, 또 그렇게 하면서도 객체 사이의 결합도를 최소화하는 것에 중점을 둔다.

2.4.1 - Design Pattern | Chain of Responsibility (책임 연쇄)

Chain of Responsibility 패턴이란?

  • Chain이라는 영어 단어는 연쇄, Responsibility라는 영어 단어는 책임, 즉 Chain of Responsibility는 책임의 연쇄라는 의미가 된다. 실제로는 회전하는 구조라고 생각하는 것이 이해하기 쉬울 것이다.
  • Chain of Responsibility 패턴은, 여러개의 객체를 체인으로 연결하여 그 객체의 체인을 순차적으로 걸어 가면서 최종적인 객체를 결정하는 방식이다.
  • 어떤 사람에게 요구 사항이 온다고 하였을 때, 그 사람이 바로 그것을 처리할 수 있으면 처리한다. 처리할 수 없다면 그 요구를 ‘다음 사람’으로 전달한다. 이후 계속적으로 반복하는 것이 Chain of Responsibility 패턴이다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴으로 분류된다.

Chain of Responsibility 패턴 예제 프로그램

입력한 문제를 어느 하나의 지원으로 해결하는 프로그램이다.

Class Diagram
Chain of Responsibility Pattern Class Diagram

1. Trouble 클래스

발생한 문제를 나타내는 클래스이다.

Trouble.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

public class Trouble {

    // 트러블 번호
    private int number;

    public Trouble(int number) {
        this.number = number;
    }

    public int getNumber() {
        return number;
    }

    public String toString() {
        return "[Trouble " + number + "]";
    }
}

2. Support 클래스

문제를 해결하는 추상 클래스이다.

Support.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

public abstract class Support {

    // 이 트러블 해결자의 이름
    private String name;
    // 다음 차례로 넘기는 곳
    private Support next;

    public Support(String name) {
        this.name = name;
    }

    public Support setNext(Support next) {
        this.next = next;
        return next;
    }

    // 문제 해결 절차
    public void support(Trouble trouble) {
        if (resolve(trouble)) {
            done(trouble);
        } else if (next != null) {
            next.support(trouble);
        } else {
            fail(trouble);
        }
    }

    public String toString() {
        return "[" + name + "]";
    }

    // 해결 방법
    protected abstract boolean resolve(Trouble trouble);

    // 해결
    protected void done(Trouble trouble) {
        System.out.println(trouble + " is resolved by " + this + ".");
    }

    // 미해결
    protected void fail(Trouble trouble) {
        System.out.println(trouble + " cannot be resolved.");
    }
}

3. NoSupport 클래스

트러블을 해결하는 구상 클래스이다. 항상 처리하지 않는다.

NoSupport.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

public class NoSupport extends Support {

    public NoSupport(String name) {
        super(name);
    }

    protected boolean resolve(Trouble trouble) {
        return false;
    }
}

4. LimitSupport 클래스

트러블을 해결하는 구상 클래스이다. 지정한 번호 미만의 문제를 해결할 수 있다.

LimitSupport.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

public class LimitSupport extends Support {

    private int limit;

    public LimitSupport(String name, int limit) {
        super(name);
        this.limit = limit;
    }

    // limit 미만이면 해결 가능
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() < limit) {
            return true;
        } else {
            return false;
        }
    }
}

5. OddSupport 클래스

트러블을 해결하는 구상 클래스이다. 홀수 번호 문제를 해결할 수 있다.

OddSupport.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

public class OddSupport extends Support {

    public OddSupport(String name) {
        super(name);
    }

    // 홀수 번호라면 해결 가능
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() % 2 == 1) {
            return true;
        } else {
            return false;
        }
    }
}

6. SpecialSupport 클래스

트러블을 해결하는 구상 클래스이다. 특정 번호의 문제를 해결할 수 있다.

SpecialSupport.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

public class SpecialSupport extends Support {

    private int number;

    public SpecialSupport(String name, int number) {
        super(name);
        this.number = number;
    }

    // number와 같으면 해결 가능
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() == number) {
            return true;
        } else {
            return false;
        }
    }
}

7. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

public class Main {
    public static void main(String[] args) {

        Support alice = new NoSupport("Alice");
        Support bob = new LimitSupport("Bob", 100);
        Support charlie = new SpecialSupport("Charlie", 429);
        Support diana = new LimitSupport("Diana", 200);
        Support elmo = new OddSupport("Elmo");

        alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo);

        // 다양한 문제 발생
        for (int i = 0; i < 500; i += 33) {
            alice.support(new Trouble(i));
        }
    }
}

8. 실행 결과

[Trouble 0] is resolved by [Bob].
[Trouble 33] is resolved by [Bob].
[Trouble 66] is resolved by [Bob].
[Trouble 99] is resolved by [Bob].
[Trouble 132] is resolved by [Diana].
[Trouble 165] is resolved by [Diana].
[Trouble 198] is resolved by [Diana].
[Trouble 231] is resolved by [Elmo].
[Trouble 264] cannot be resolved.
[Trouble 297] is resolved by [Elmo].
[Trouble 330] cannot be resolved.
[Trouble 363] is resolved by [Elmo].
[Trouble 396] cannot be resolved.
[Trouble 429] is resolved by [Charlie].
[Trouble 462] cannot be resolved.
[Trouble 495] is resolved by [Elmo].

Chain of Responsibility 패턴의 장점 및 주의점

Chain of Responsibility 패턴의 포인트는 “요구를 내는 사람(Main)“과 “요구를 처리하는 사람(Support)“을 완만하게 연결시키는 것에 있다. “요구를 내는 사람(Main)“은 최초의 사람에게 요구를 내면, 나머지는 체인 안을 그 요구가 흘러 가게 되어, 적절한 사람에 의해 요구가 처리된다.
Chain of Responsibility 패턴을 사용하지 않으면 누군가가 “이 요청은 이 사람이 처리하는 것"이라는 정보를 가지고 있어야 한다. 그 정보를 “요구를 내는 사람(Main)“이 갖게 해버리면, 부품으로서의 독립성이 손상된다.
주의점으로서는 Chain of Responsibility 패턴은 유연성이 높아지만, 돌려보내는 만큰 처리는 느려진다. 처리 속도가 매우 중요한 경우에는 Chain of Responsibility 패턴을 사용하지 않는 것이 좋다.

2.4.2 - Design Pattern | Command Pattern (커맨드 패턴)

Command 패턴이란?

  • Command라는 영어 단어는 명령이라는 의미이다.
  • Command 패턴은 명령을 나타내는 클래스의 인스턴스를 하나로 표현하는 방법이다.
  • 명령의 이력을 관리하고 싶을 때는, 그 인스턴스의 집합을 관리하면 된다. 명령 모음을 저장하면 동일한 명령을 실행하거나 여러 명령을 함께 새 명령으로 재사용할 수 있다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴 으로 분류된다.

Command 패턴 예제 프로그램

간단한 그림 그리기 프로그램이다.

Class Diagram
Command Pattern Class Diagram

1. Command 인터페이스

명령을 표현하는 인터페이스이다.

Command.java

package com.devkuma.designpattern.behavioral.command.command;

public interface Command {
    void execute();
}

2. MacroCommand 클래스

“여러 명령을 정리한 명령"을 표현하는 클래스이다.

MacroCommand.java

package com.devkuma.designpattern.behavioral.command.command;

import java.util.Iterator;
import java.util.Stack;

public class MacroCommand implements Command {

    // 명령 모음
    private Stack commands = new Stack();

    public void execute() {
        Iterator it = commands.iterator();
        while (it.hasNext()) {
            ((Command) it.next()).execute();
        }
    }

    // 추가
    public void append(Command cmd) {
        if (cmd != this) {
            commands.push(cmd);
        }
    }

    // 마지막 명령 삭제
    public void undo() {
        if (!commands.empty()) {
            commands.pop();
        }
    }

    // 전부 삭제
    public void clear() {
        commands.clear();
    }
}

3. Drawable 인터페이스

“그리기 대상"을 표현하는 인터페이스이다.

Drawable.java

package com.devkuma.designpattern.behavioral.command.drawer;

public interface Drawable {
    void draw(int x, int y);
}

4. DrawCanvas 클래스

“그리기 대상"을 구현한 클래스이다.

DrawCanvas.java

package com.devkuma.designpattern.behavioral.command.drawer;

import com.devkuma.designpattern.behavioral.command.command.MacroCommand;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;

public class DrawCanvas extends Canvas implements Drawable {

    // 그리기 색상
    private Color color = Color.red;
    // 그리는 점의 반경
    private int radius = 6;
    // 이력
    private MacroCommand history;

    public DrawCanvas(int width, int height, MacroCommand history) {
        setSize(width, height);
        setBackground(Color.white);
        this.history = history;
    }

    // 전체 이력 다시 그리기
    public void paint(Graphics g) {
        history.execute();
    }

    // 그리기
    public void draw(int x, int y) {
        Graphics g = getGraphics();
        g.setColor(color);
        g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
    }
}

5. DrawCommand 클래스

“점의 그리기 명령"을 표현하는 클래스이다.

DrawCommand.java

package com.devkuma.designpattern.behavioral.command.drawer;

import com.devkuma.designpattern.behavioral.command.command.Command;

import java.awt.Point;

public class DrawCommand implements Command {

    // 그리기 대상
    protected Drawable drawable;
    // 그리기 위치
    private Point position;

    public DrawCommand(Drawable drawable, Point position) {
        this.drawable = drawable;
        this.position = position;
    }

    public void execute() {
        drawable.draw(position.x, position.y);
    }
}

6. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.command;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;

import com.devkuma.designpattern.behavioral.command.command.Command;
import com.devkuma.designpattern.behavioral.command.command.MacroCommand;
import com.devkuma.designpattern.behavioral.command.drawer.DrawCanvas;
import com.devkuma.designpattern.behavioral.command.drawer.DrawCommand;


public class Main extends JFrame implements ActionListener, MouseMotionListener, WindowListener {

    // 그리기 이력
    private MacroCommand history = new MacroCommand();
    // 그리기 영역
    private DrawCanvas canvas = new DrawCanvas(400, 400, history);
    // 실행 취소 버튼
    private JButton undoButton = new JButton("undo");
    // 지우기 버튼
    private JButton clearButton = new JButton("clear");

    public Main(String title) {
        super(title);

        this.addWindowListener(this);
        canvas.addMouseMotionListener(this);
        undoButton.addActionListener(this);
        clearButton.addActionListener(this);

        Box buttonBox = new Box(BoxLayout.X_AXIS);
        buttonBox.add(undoButton);
        buttonBox.add(clearButton);
        Box mainBox = new Box(BoxLayout.Y_AXIS);
        mainBox.add(buttonBox);
        mainBox.add(canvas);
        getContentPane().add(mainBox);

        pack();
        setVisible(true);
    }

    // ActionListener 용
    public void actionPerformed(ActionEvent e) {
        Object source = e.getSource();
        if (source == undoButton) {
            history.undo();
            canvas.repaint();
        } else if (source == clearButton) {
            history.clear();
            canvas.repaint();
        }
    }

    // MouseMotionListener用
    public void mouseMoved(MouseEvent e) {
    }

    public void mouseDragged(MouseEvent e) {
        Command cmd = new DrawCommand(canvas, e.getPoint());
        history.append(cmd);
        cmd.execute();
    }

    // WindowListener 용
    public void windowClosing(WindowEvent e) {
    }

    public void windowActivated(WindowEvent e) {
    }

    public void windowClosed(WindowEvent e) {
    }

    public void windowDeactivated(WindowEvent e) {
    }

    public void windowDeiconified(WindowEvent e) {
    }

    public void windowIconified(WindowEvent e) {
    }

    public void windowOpened(WindowEvent e) {
    }

    public static void main(String[] args) {
        new Main("Command Pattern Sample");
    }
}

7. 실행 결과

Command Pattern Result

Command 패턴의 장점

“명령"을 오브젝트로서 표현하는 것으로 명령의 이력을 취하거나 명령의 재실행을 실시할 수가 있다.
또, 새로운 “명령"을 추가하고 싶은 경우는 Command 인터페이스를 구현한 클래스를 작성하면 되는 것만이므로, 기능 확장이 실시하기 쉬워진다.

2.4.3 - Design Pattern | Interpreter Pattern (인터프리터 패턴)

Interpreter 패턴이란?

  • Interpreter라는 영어 단어는 통역이라는 의미이다.
  • Interpreter 패턴은, 어떠한 형식으로 쓰여진 파일의 내용을 “통역"의 역할을 하는 프로그램으로 해석 및 표현하는 방식이다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴으로 분류된다.

Interpreter 패턴 예제 프로그램

텍스트 파일에 쓰여진 언어를 구문 분석하는 프로그램이다.

Class Diagram
Interpreter Pattern Class Diagram

구문 분석 대상 텍스트로 사용하는 언어의 문법에서는 BNF 표기법을 사용한다.

<program> ::= program <command list>
<command list> ::= <command>* end
<command> ::= <repeat command> | <primitive command>
<repeat command> ::= repeat <number> <command list>
<primitive command> ::= go | right | left
  • <program> : 토큰 program 뒤에 커멘드의 열이 계속된다는 것을 의미한다.
  • <command list> : 이 0개 이상 반복한 뒤 토큰 end 가 계속된다는 것을 의미한다.
  • <command> : 반복 명령 또는 기본 명령 중 하나가 된다. ( | 는 or 를 나타낸다.)
  • <repeat command> : 토큰 repeat 의 후에 반복 회수가 계속되고, 커멘드의 열이 계속된다는 것을 의미한다.
  • <primitive command> : go 또는 right 또는 left 을 의미한다.

1. Context 클래스

구문 분석을 위한 전후 관계를 나타내는 클래스이다.

Context.java

package com.devkuma.designpattern.behavioral.interpreter;

import java.util.StringTokenizer;

public class Context {

    private StringTokenizer tokenizer;
    private String currentToken;

    public Context(String text) {
        tokenizer = new StringTokenizer(text);
        nextToken();
    }

    public String nextToken() {
        if (tokenizer.hasMoreTokens()) {
            currentToken = tokenizer.nextToken();
        } else {
            currentToken = null;
        }
        return currentToken;
    }

    public String currentToken() {
        return currentToken;
    }

    public void skipToken(String token) throws Exception {
        if (!token.equals(currentToken)) {
            throw new Exception("Warning: " + token + " is expected, but " + currentToken + " is found.");
        }
        nextToken();
    }

    public int currentNumber() throws Exception {
        int number = 0;
        try {
            number = Integer.parseInt(currentToken);
        } catch (NumberFormatException e) {
            throw new Exception("Warning: " + e);
        }
        return number;
    }
}

2. Node 클래스

구문 트리의 “노드"가 되는 클래스이다.

Node.java

package com.devkuma.designpattern.behavioral.interpreter;

public abstract class Node {
    public abstract void parse(Context context) throws Exception;
}

3. ProgramNode 클래스

<program> ::= program <command list>에 대응하는 클래스입니다. Node의 구현이다.

ProgramNode.java

package com.devkuma.designpattern.behavioral.interpreter;

// <program> ::= program <command list>
public class ProgramNode extends Node {

    private Node commandListNode;

    @Override
    public void parse(Context context) throws Exception {
        context.skipToken("program");
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

    public String toString() {
        return "[program " + commandListNode + "]";
    }
}

4. CommandNode 클래스

<command> ::= <repeat command> | <primitive command>에 대응하는 클래스이다. Node의 구현이다.

CommandNode.java

// <command> ::= <repeat command> | <primitive command>
public class CommandNode extends Node {

    private Node node;
    
    @Override
    public void parse(Context context) throws Exception {
        if (context.currentToken().equals("repeat")) {
            node = new RepeatCommandNode();
            node.parse(context);
        } else {
            node = new PrimitiveCommandNode();
            node.parse(context);
        }
    }

    public String toString() {
        return node.toString();
    }
}

5. RepeatCommandNode 클래스

<repeat command> ::= repeat <number> <command list>에 대응하는 클래스이다. Node의 구현이다.

RepeatCommandNode.java

package com.devkuma.designpattern.behavioral.interpreter;

// <command> ::= <repeat command> | <primitive command>
public class CommandNode extends Node {

    private Node node;

    @Override
    public void parse(Context context) throws Exception {
        if (context.currentToken().equals("repeat")) {
            node = new RepeatCommandNode();
            node.parse(context);
        } else {
            node = new PrimitiveCommandNode();
            node.parse(context);
        }
    }

    public String toString() {
        return node.toString();
    }
}

6. CommandListNode 클래스

<command list> ::= <command>* end에 대응하는 클래스이다. Node의 구현이다.

CommandListNode.java

package com.devkuma.designpattern.behavioral.interpreter;

import java.util.ArrayList;
import java.util.List;

// <command list> ::= <command>* end
public class CommandListNode extends Node {

    private List<Node> list = new ArrayList<Node>();

    @Override
    public void parse(Context context) throws Exception {
        while (true) {
            if (context.currentToken() == null) {
                throw new Exception("Missing 'end'");
            } else if (context.currentToken().equals("end")) {
                context.skipToken("end");
                break;
            } else {
                Node commandNode = new CommandNode();
                commandNode.parse(context);
                list.add(commandNode);
            }
        }
    }

    public String toString() {
        return list.toString();
    }
}

7. PrimitiveCommandNode 클래스

<primitive command> ::= go | right | left에 대응하는 클래스이다. Node의 구현이다.

PrimitiveCommandNode.java

package com.devkuma.designpattern.behavioral.interpreter;

// <primitive command> ::= go | right | left
public class PrimitiveCommandNode extends Node {

    private String name;

    @Override
    public void parse(Context context) throws Exception {
        name = context.currentToken();
        context.skipToken(name);
        if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {
            throw new Exception(name + " is undefined");
        }
    }

    public String toString() {
        return name;
    }
}

8. 구문 분석 대상 텍스트

구문 분석 대상이 되는 텍스트이다.

program.txt

program end
program go end
program go right go right go right go right end
program repeat 4 go right end end
program repeat 4 repeat 3 go right go left end right end end

9. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.interpreter;

import java.io.BufferedReader;
import java.io.FileReader;

public class Main {
    public static void main(String[] args) {
        ClassLoader loader = Main.class.getClassLoader();
        String file = loader.getResource("interpreter/program.txt").getFile();

        try {
            BufferedReader reader = new BufferedReader(new FileReader(file));
            String text;
            while ((text = reader.readLine()) != null) {
                System.out.println("text = \"" + text + "\"");
                Node node = new ProgramNode();
                node.parse(new Context(text));
                System.out.println("node = " + node);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

10. 실행 결과

text = "program end"
node = [program []]
text = "program go end"
node = [program [go]]
text = "program go right go right go right go right end"
node = [program [go, right, go, right, go, right, go, right]]
text = "program repeat 4 go right end end"
node = [program [[repeat 4 [go, right]]]]
text = "program repeat 4 repeat 3 go right go left end right end end"
node = [program [[repeat 4 [[repeat 3 [go, right, go, left]], right]]]]

장점

Interpreter 패턴을 활용하면 규칙을 추가하고 변경할 수 있다.
인터프리터 패턴의 특징 중 하나는 하나의 규칙을 하나의 클래스로 표현하는 것이다. 즉, 새로운 규칙을 추가하는 경우는 Node 클래스의 서브 클래스를 추가하는 것만으로 좋아진다.
또, 규칙을 수정하는 경우도 Node 클래스의 서브 클래스만 수정하면 된다.

2.4.4 - Design Pattern | Iterator Pattern (이터레이터 패턴)

Iterator 패턴이란?

  • Iterate라는 영어 단어는 무언가를 반복한다는 의미이다. Iterator는 반복자라는 의미이다.
  • Iterator 패턴은, 집합체의 요소에 대해, 차례로 액세스 하는 처리를 실시하기 위한 방식이다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴으로 분류된다.

어떠한 종류의 데이터 집합이 있다. 이 데이터를 조작, 검색 등을 하는 기능이 포함 되어있다.
이 중 검색 기능과 다른 기능이 뒤섞여 개발하면 클래스간의 결합도가 증가되어 코드가 보기가 어려워진다.
Iterator 패턴을 이용해 검색 기능을 재사용 가능 하게 만들어 보자.

Iterator 패턴 예제 프로그램

클래스(교실)에 학생을 넣어 학생의 이름을 차례로 표시하는 프로그램을 만들어 보겠다.

Class Diagram
Iterator Pattern

1. Iterator 인터페이스

요소를 순차적으로 스캔하는 인터페이스이다.

Iterator.java

package com.devkuma.designpattern.behavioral.iterator;

public interface Iterator {
    boolean hasNext();
    Object next();
}

2. Aggregate 인터페이스

Iterator를 만드는 인터페이스이다. 샘플에서는 “집합체"라고 하고 있습니다.

Aggregate.java

package com.devkuma.designpattern.behavioral.iterator;

public interface Aggregate {
    Iterator iterator();
}

3. Student 클래스

집합체의 요소가 되는 클래스입니다. 샘플에서는 ‘학생’이라고 합니다.

Student.java

package com.devkuma.designpattern.behavioral.iterator;

public class Student {

    private String name;

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

4. ClassRoom 클래스

Aggregate가 선언한 인터페이스를 구현하는 클래스이다. 샘플에서는 “교실"이라고 하고 있다.

ClassRoom.java

package com.devkuma.designpattern.behavioral.iterator;

public class ClassRoom implements Aggregate {

    private Student[] students;
    private int last = 0;

    public ClassRoom(int maxsize) {
        this.students = new Student[maxsize];
    }

    public Student getStudentAt(int index) {
        return students[index];
    }

    public void appendStudent(Student student) {
        this.students[last] = student;
        last++;
    }

    public int getLength() {
        return last;
    }

    public Iterator iterator() {
        return new ClassRoomIterator(this);
    }
}

5. ClassRoomIterator 클래스

Iterator가 정한 인터페이스를 구현하는 클래스이다.

ClassRoomIterator.java

package com.devkuma.designpattern.behavioral.iterator;

public class ClassRoomIterator implements Iterator {

    private ClassRoom classRoom;
    private int index;

    public ClassRoomIterator(ClassRoom classRoom) {
        this.classRoom = classRoom;
        this.index = 0;
    }

    public boolean hasNext() {
        if (index < classRoom.getLength()) {
            return true;
        } else {
            return false;
        }
    }

    public Object next() {
        Student student = classRoom.getStudentAt(index);
        index++;
        return student;
    }
}

6. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.iterator;

public class Main {
    public static void main(String[] args) {

        ClassRoom classRoom = new ClassRoom(4);
        classRoom.appendStudent(new Student("devkuma"));
        classRoom.appendStudent(new Student("kimkc"));
        classRoom.appendStudent(new Student("yunho"));
        classRoom.appendStudent(new Student("etkim"));

        Iterator iterator= classRoom.iterator();

        while (iterator.hasNext()) {
            Student student = (Student)iterator.next();
            System.out.println(student.getName());
        }
    }
}

7. 실행 결과

devkuma
kimkc
yunho
etkim

Iterator 패턴의 장점

Iterator 패턴의 장점은 구현과 분리되어 계산을 할 수 있다는 것 입니다. 디자인 패턴은 클래스를 부품으로 사용할 수 있게 해, 재이용성을 촉진하는 것입니다. 샘플 프로그램의 Main 클래스 로 사용되고 있는 Iterator 메소드는, hasNext(), next() 만이 됩니다. 즉, ClassRoom 클래스 에 의존하지 않는 구현이 되어 있어, 배열의 사이즈등을 신경쓰지 않아도 됩니다.

2.4.5 - Design Pattern | Mediator Pattern (미디에이터 패턴)

Mediator 패턴이란?

  • Mediator라는 영어 단어는 중개자라는 의미이다.
  • 맴버가 10명 있었다고 할 때에 서로 지시하고 있으면, 작업은 큰 혼란스러워진다. 그럴 때는 입장이 다른 중개자가 있으면 멤버는 중개자에만 보고하고, 멤버에의 지시는 중개자만으로부터 받도록 하면 된다.
  • Mediator 패턴은 중개자을 통해서 행동을 일으키게 하는 방식이다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴으로 분류된다.

Mediator 패턴 예제 프로그램

로그인 다이얼로그를 표시하여 텍스트나 버튼의 유효/무효 상태를 제어하는 ​​프로그램이다.

Class Diagram
Mediator Pattern Class Diagram

1. Mediator 인터페이스

상담역이 되는 인터페이스이다.

Mediator.java

public interface Mediator {
    void createColleagues();
    void colleagueChanged();
}

2. Colleague 인터페이스

멤버가 되는 인터페이스이다.

Colleague.java

public interface Colleague {
    public abstract void setMediator(Mediator mediator);
    public abstract void setColleagueEnabled(boolean enabled);
}

3. ColleagueButton 클래스

버튼을 나타내는 클래스이다. Colleague 인터페이스를 구현한다.

ColleagueButton.java

package com.devkuma.designpattern.behavioral.mediator;

import java.awt.Button;

public class ColleagueButton extends Button implements Colleague {

    private Mediator mediator;

    public ColleagueButton(String caption) {
        super(caption);
    }

    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    public void setColleagueEnabled(boolean enabled) {
        // Mediator에서 활성화/비활성화를 지시함
        setEnabled(enabled);
    }
}

4. ColleagueCheckbox 클래스

체크 박스 (여기에서는 라디오 버튼)를 나타내는 클래스이다. Colleague 인터페이스를 구현한다.

ColleagueCheckbox.java

package com.devkuma.designpattern.behavioral.mediator;

import java.awt.Checkbox;
import java.awt.CheckboxGroup;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

public class ColleagueCheckbox extends Checkbox implements ItemListener, Colleague {

    private Mediator mediator;

    public ColleagueCheckbox(String caption, CheckboxGroup group, boolean state) {
        super(caption, group, state);
    }

    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    public void setColleagueEnabled(boolean enabled) {
        // Mediator에서 활성화/비활성화를 지시함
        setEnabled(enabled);
    }

    public void itemStateChanged(ItemEvent e) {
        // 상태가 변경되면 Mediator에 알림
        mediator.colleagueChanged();
    }
}

5. ColleagueTextField 클래스

텍스트 박스를 나타내는 클래스이다. Colleague 인터페이스를 구현한다.

ColleagueTextField.java

package com.devkuma.designpattern.behavioral.mediator;

import java.awt.Color;
import java.awt.TextField;
import java.awt.event.TextEvent;
import java.awt.event.TextListener;

public class ColleagueTextField extends TextField implements TextListener, Colleague {

    private Mediator mediator;

    public ColleagueTextField(String text, int columns) {
        super(text, columns);
    }

    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    public void setColleagueEnabled(boolean enabled) {
        // Mediator에서 활성화/비활성화를 지시함
        setEnabled(enabled);
        setBackground(enabled ? Color.white : Color.lightGray);
    }

    public void textValueChanged(TextEvent e) {
        // 문자열이 변경되면 Mediator에 알림
        mediator.colleagueChanged();
    }
}

6. LoginFrame 클래스

로그인 다이얼로그를 나타내는 클래스이다. Mediator 인터페이스를 구현한다.

LoginFrame.java

package com.devkuma.designpattern.behavioral.mediator;

import java.awt.CheckboxGroup;
import java.awt.Color;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Label;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class LoginFrame extends Frame implements ActionListener, Mediator {

    private ColleagueCheckbox checkGuest;
    private ColleagueCheckbox checkLogin;
    private ColleagueTextField textUser;
    private ColleagueTextField textPass;
    private ColleagueButton buttonOk;
    private ColleagueButton buttonCancel;

    public LoginFrame(String title) {
        super(title);
        setBackground(Color.lightGray);
        setLayout(new GridLayout(4, 2));

        createColleagues();
        add(checkGuest);
        add(checkLogin);
        add(new Label("Username:"));
        add(textUser);
        add(new Label("Password:"));
        add(textPass);
        add(buttonOk);
        add(buttonCancel);
        colleagueChanged();

        pack();
        setVisible(true);
    }

    public void createColleagues() {

        CheckboxGroup g = new CheckboxGroup();
        checkGuest = new ColleagueCheckbox("Guest", g, true);
        checkLogin = new ColleagueCheckbox("Login", g, false);
        textUser = new ColleagueTextField("", 10);
        textPass = new ColleagueTextField("", 10);
        textPass.setEchoChar('*');
        buttonOk = new ColleagueButton("OK");
        buttonCancel = new ColleagueButton("Cancel");

        checkGuest.setMediator(this);
        checkLogin.setMediator(this);
        textUser.setMediator(this);
        textPass.setMediator(this);
        buttonOk.setMediator(this);
        buttonCancel.setMediator(this);

        checkGuest.addItemListener(checkGuest);
        checkLogin.addItemListener(checkLogin);
        textUser.addTextListener(textUser);
        textPass.addTextListener(textPass);
        buttonOk.addActionListener(this);
        buttonCancel.addActionListener(this);
    }

    // Colleage로부터의 통지로 각 Colleage의 유효/무효를 판정한다.
    public void colleagueChanged() {
        if (checkGuest.getState()) {
            // Guest mode
            textUser.setColleagueEnabled(false);
            textPass.setColleagueEnabled(false);
            buttonOk.setColleagueEnabled(true);
        } else {
            // Login mode
            textUser.setColleagueEnabled(true);
            userpassChanged();
        }
    }

    private void userpassChanged() {
        if (textUser.getText().length() > 0) {
            textPass.setColleagueEnabled(true);
            if (textPass.getText().length() > 0) {
                buttonOk.setColleagueEnabled(true);
            } else {
                buttonOk.setColleagueEnabled(false);
            }
        } else {
            textPass.setColleagueEnabled(false);
            buttonOk.setColleagueEnabled(false);
        }
    }

    public void actionPerformed(ActionEvent e) {
        System.out.println(e.toString());
        System.exit(0);
    }
}

7. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.mediator;

public class Main {
    static public void main(String args[]) {
        new LoginFrame("Mediator Sample");
    }
}

8. 실행 결과

Mediator Pattern Result

장점

표시의 유효/무효에 관한 로직은 복잡하게 되지만, LoginFrame 클래스에 중개되고 있다.
표시에 관한 사양을 변경하거나, 버그를 발견했을 경우는 LoginFrame 클래스만 수정하거나 디버그하면 된다.
로직이 ColleagueButton, ColleagueCheckbox, ColleagueTextField에 분산되면 사용하거나 디버깅하거나 수정하는 것이 어렵다.

2.4.6 - Design Pattern | Memento Pattern (메멘토 패턴)

Memento 패턴이란?

  • Memento 라는 영어 단어는 기념품, 모양, 추억의 종이라는 의미이다.
  • 객체 지향 프로그램에서의 실행 취소(Undo)를 하려면, 인스턴스가 가지고 있는 정보를 저장해야 한다.
  • 인스턴스를 복원하려면 인스턴스 내부의 정보에 자유롭게 액세스할 수 있어야 한다. 그러나, 부주의하게 액세스를 허가해 버리면, 그 클래스의 내부 구조에 의존한 코드가 되어 버린다. 이를 캡슐화의 파괴라고 한다.
  • Memento 패턴은 인스턴스의 상태를 나타내는 역할을 도입하여, 캡슐화의 파괴에 일으키지 않고 상태(이전의 인스턴스)를 저장/복원을 수행하는 방식이다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴으로 분류된다.

Memento 패턴 예제 프로그램

던지 주사위의 수에 따라 소지금과 소지품(과일)을 변화시키는 프로그램이다.
상황에 따라 저장 및 제거를 수행한다.

Class Diagram
Memento Pattern Class Diagram

1. Memento 클래스

Game의 상태를 나타내는 클래스이다.

Memento.java

package com.devkuma.designpattern.behavioral.memento.game;

import java.util.ArrayList;

public class Memento {

    int money;
    ArrayList<String> fruits;

    public int getMoney() {
        return money;
    }

    Memento(int money) {
        this.money = money;
        this.fruits = new ArrayList();
    }

    void addFruit(String fruit) {
        fruits.add(fruit);
    }

    ArrayList<String> getFruits() {
        return (ArrayList<String>) fruits.clone();
    }
}

위에 코드에서는 변수나 함수에 모두 public, private와 등 접근 제안자를 선언하지 않았다. 이는 game 이외의 패키지에서는 내부를 변경할 수 없다는 것을 뜻한다.

2. Gamer 클래스

Game을 하는 주인공의 클래스이다. Memento의 인스턴스를 만든다.

Gamer.java

package com.devkuma.designpattern.behavioral.memento.game;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

public class Gamer {

    private int money;
    private ArrayList<String> fruits = new ArrayList();
    private Random random = new Random();
    private static String[] fruitNames = {
            "사과", "포도", "바나나", "귤",
    };

    public Gamer(int money) {
        this.money = money;
    }

    public int getMoney() {
        return money;
    }

    // 베팅...게임 진행한다.
    public void bet() {
        // 주사위를 던진다.
        int dice = random.nextInt(6) + 1;

        if (dice == 1) {
            // 1인 경우, 소지금 증가한다.
            money += 100;
            System.out.println("소지금이 증가하였습니다.");
        } else if (dice == 2) {
            // 2인 경우, 소지금이 절반이 된다.
            money /= 2;
            System.out.println("소지금이 절반이 되었습니다.");
        } else if (dice == 6) {
            // 6인 경우, 과일을 받는다.
            String fruit = getFruit();
            System.out.println("과일(" + fruit + ")을 받았습니다.");
            fruits.add(fruit);
        } else {
            // 그외인 경우, 아무 일도 일어나지 않는다.
            System.out.println("아무 일도 일어나지 않았다.");
        }
    }

    // 스냅샷 생성: 현재 상황을 저장한 객체를 생성하고 반환한다.
    public Memento createMemento() {
        Memento memento = new Memento(money);
        Iterator it = fruits.iterator();
        while (it.hasNext()) {
            String fruit = (String) it.next();
            if (fruit.startsWith("맛있는")) {
                // 맛있는 과일만 저장한다.
                memento.addFruit(fruit);
            }
        }
        return memento;
    }

    // 실행 취소(Undo)을 실행: 저장했던 객체를 전달받아 이전 상태로 되돌린다.
    public void restoreMemento(Memento memento) {
        this.money = memento.money;
        this.fruits = memento.getFruits();
    }

    public String toString() {
        return "[money = " + money + ", fruits = " + fruits + "]";
    }

    private String getFruit() {
        String prefix = "";
        if (random.nextBoolean()) {
            prefix = "맛있는 ";
        }
        return prefix + fruitNames[random.nextInt(fruitNames.length)];
    }
}

3. Main 클래스

메인 처리를 실행하는 클래스이다. 게임을 진행시킨다. 또한 Memento의 인스턴스를 저장하고 필요에 따라 Gamer의 상태를 복원한다.

Main.java

package com.devkuma.designpattern.behavioral.memento;

import com.devkuma.designpattern.behavioral.memento.game.Gamer;
import com.devkuma.designpattern.behavioral.memento.game.Memento;

public class Main {

    public static void main(String[] args) {
        // 첫 소지금은 100이다.
        Gamer gamer = new Gamer(100);
        // 첫번째 상태를 저장한다.
        Memento memento = gamer.createMemento();

        for (int i = 0; i < 10; i++) {
            System.out.println("==== " + i);
            System.out.println("현재 상태:" + gamer);

            // 게임을 진행한다.
            gamer.bet();

            System.out.println("소지금은 " + gamer.getMoney() + "원이 되었습니다.");

            if (gamer.getMoney() > memento.getMoney()) {
                System.out.println("    (많이 증가했으므로 현재 상태를 저장하자)");
                memento = gamer.createMemento();
            } else if (gamer.getMoney() < memento.getMoney() / 2) {
                System.out.println("    (많이 줄어들었으므로 이전 상태로 복원하자)");
                gamer.restoreMemento(memento);
            }
        }
    }
}

4. 실행 결과

==== 0
현재 상태:[money = 100, fruits = []]
소지금이 증가하였습니다.
소지금은 200원이 되었습니다.
    (많이 증가했으므로 현재 상태를 저장하자)
==== 1
현재 상태:[money = 200, fruits = []]
소지금이 절반이 되었습니다.
소지금은 100원이 되었습니다.
==== 2
현재 상태:[money = 100, fruits = []]
아무 일도 일어나지 않았다.
소지금은 100원이 되었습니다.
==== 3
현재 상태:[money = 100, fruits = []]
아무 일도 일어나지 않았다.
소지금은 100원이 되었습니다.
==== 4
현재 상태:[money = 100, fruits = []]
아무 일도 일어나지 않았다.
소지금은 100원이 되었습니다.
==== 5
현재 상태:[money = 100, fruits = []]
아무 일도 일어나지 않았다.
소지금은 100원이 되었습니다.
==== 6
현재 상태:[money = 100, fruits = []]
과일(맛있는 귤)을 받았습니다.
소지금은 100원이 되었습니다.
==== 7
현재 상태:[money = 100, fruits = [맛있는 귤]]
과일(맛있는 사과)을 받았습니다.
소지금은 100원이 되었습니다.
==== 8
현재 상태:[money = 100, fruits = [맛있는 귤, 맛있는 사과]]
과일(사과)을 받았습니다.
소지금은 100원이 되었습니다.
==== 9
현재 상태:[money = 100, fruits = [맛있는 귤, 맛있는 사과, 사과]]
아무 일도 일어나지 않았다.
소지금은 100원이 되었습니다.

Memento 패턴의 장점

Memento 패턴을 사용하면 실행 취소(Undo), 다시 실행(Redo), 작업 이력 작성, 현재 상태 저장 등을 할 수 있다.
실행 취소(Undo)를 하고 싶다면, Gamer 클래스에 그 기능을 만들어 넣으면 좋을까 하는 의문도 나올 수 있을 거라 생각된다.
Main 클래스에서는, “어느 타이밍에 스냅샷을 찍을까”, “언제 실행 취소를 할지"를 결정하여 Memento를 보관 유지하는 일을 실행한다.
한편, Gamer 클래스에서는 Memento를 만드는 작업과 주어진 Memento를 사용해 자신의 상태를 되돌리는 일을 실행한다.
Main 클래스와 Gamer 클래스에서는 이와 같이 역할 분담을 하고 있는 것을 알 수 있다. 이렇게 역할 분담을 하게 되면,

  • 여러 단계를 취소하도록 변경하고 싶다.
  • 실행 취소뿐만 아니라 현재 상태를 파일에 저장하고 싶다. 와 같은 수정 사항을 반영하고 하고 싶을 때에도 Gamer를 변경할 필요는 없어진다.

2.4.7 - Design Pattern | Observer Pattern (옵저버 패턴)

Observer 패턴이란?

  • Observer라는 영어 단어는 관찰자라는 의미이다.
  • Observer 패턴은 관찰 대상의 상태가 변할 때 관찰자에게 통지되는 방식이다.
  • Observer 패턴은 상태 변화에 따른 처리를 기술할 때 유효하다.
  • 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다.
  • 주로 분산 이벤트 핸들링 시스템을 구현하는데 사용된다. 발행/구독 모델로 알려져 있기도 하다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴으로 분류된다.

Observer 패턴 예제 프로그램

무작위로 숫자를 생성하고 그 변화에 따라 “숫자” 및 “*“를 표시하는 프로그램이다.

Class Diagram
Observer Pattern Class Diagram

1. Observer 인터페이스

관찰자를 나타내는 인터페이스이다.

Observer.java

package com.devkuma.designpattern.behavioral.observer;

public interface Observer {
    void update(NumberGenerator generator);
}

2. DigitObserver 클래스

수치를 숫자로 그대로 표현하는 클래스이다. 이 클래스는 Observer 인터페이스를 구현한다.

DigitObserver.java

package com.devkuma.designpattern.behavioral.observer;

public class DigitObserver implements Observer {

    public void update(NumberGenerator generator) {
        System.out.println("DigitObserver: " + generator.getNumber());
    }
}

3. GraphObserver 클래스

수치를 간단한 그래프로 표현하는 클래스이다. 이 클래스는 Observer 인터페이스를 구현한다.

GraphObserver.java

package com.devkuma.designpattern.behavioral.observer;

public class GraphObserver implements Observer {

    public void update(NumberGenerator generator) {
        System.out.print("GraphObserver: ");
        int count = generator.getNumber();
        for (int i = 0; i < count; i++) {
            System.out.print("*");
        }
        System.out.println("");
    }
}

4. NumberGenerator 클래스

숫자를 생성하는 객체를 나타내는 추상 클래스이다.

NumberGenerator.java

package com.devkuma.designpattern.behavioral.observer;

import java.util.ArrayList;
import java.util.Iterator;

public abstract class NumberGenerator {

    private ArrayList<Observer> observers = new ArrayList();

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void deleteObserver(Observer observer) {
        observers.remove(observer);
    }

    public void notifyObservers() {
        Iterator it = observers.iterator();
        while (it.hasNext()) {
            Observer o = (Observer) it.next();
            o.update(this);
        }
    }

    public abstract int getNumber();

    public abstract void execute();
}

5. RandomNumberGenerator 클래스

무작위로 숫자를 생성하는 클래스이다.

RandomNumberGenerator.java

package com.devkuma.designpattern.behavioral.observer;

import java.util.Random;

public class RandomNumberGenerator extends NumberGenerator {

    private Random random = new Random();
    private int number;

    public int getNumber() {
        return number;
    }

    public void execute() {
        for (int i = 0; i < 10; i++) {
            number = random.nextInt(50);
            notifyObservers();
        }
    }
}

6. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.observer;

public class Main {
    public static void main(String[] args) {
        NumberGenerator generator = new RandomNumberGenerator();
        Observer observer1 = new DigitObserver();
        Observer observer2 = new GraphObserver();
        generator.addObserver(observer1);
        generator.addObserver(observer2);
        generator.execute();
    }
}

7. 실행 결과

DigitObserver: 42
GraphObserver: ******************************************
DigitObserver: 19
GraphObserver: *******************
DigitObserver: 47
GraphObserver: ***********************************************
DigitObserver: 32
GraphObserver: ********************************
DigitObserver: 18
GraphObserver: ******************
DigitObserver: 34
GraphObserver: **********************************
DigitObserver: 23
GraphObserver: ***********************
DigitObserver: 9
GraphObserver: *********
DigitObserver: 17
GraphObserver: *****************
DigitObserver: 24
GraphObserver: ************************

Observer 패턴의 장점

Observer 패턴에서는 상태를 가지고 있는 RandomNumberGenerator 클래스와 상태 변화를 전달받는 클래스(DigitObserver, GraphObserver)가 등장한다. 그리고 그 2개의 역할을 이어지고 있는 것이 Observer 인터페이스와 NumberGenerator 클래스이다.
RandomNumberGenerator 클래스는 자신이 현재 감시하고 있는 것이 DigitObserver 인스턴스인지 GraphObserver 인스턴스인지를 모른다. 다만, observers 필드에 저장된 인스턴스가 Observer 인터페이스를 상속한다는 것을 알고 있으며 update 메서드를 호출할 수 있다.

한편, DigitObserver 클래스, GraphObserver 클래스는 자신을 관찰하고 있는 것이 RandomNumberGenerator 인스턴스인지 다른 XxxNumberGenerator 인스턴스인지는 모른다. 다만, NumberGenerator의 서브 클래스의 인스턴스이며, getNumber 메소드를 가지고 있는 것은 알고 있다.

  • 추상 클래스나 인터페이스를 사용하여 구상(Concrete) 클래스로부터 추상 메소드를 떼어낸다.
  • 인수로 인스턴스를 전달 할 경우나 필드로 인스턴스를 보관 유지할 경우에는 추상 클래스나 인터페이스의 형태로 해둔다. 이렇게 함으로써 구상 클래스의 부분을 정확하게 교환할 수 있게 된다.

2.4.8 - Design Pattern | State Pattern (스테이트 패턴)

State 패턴이란?

  • State라는 영어 단어는 상태라는 의미이다.
  • State 패턴은 상태를 클래스로 표현하고 클래스를 전환하여 “상태 변경"을 나타내는 방법이다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴으로 분류된다.

State 패턴의 예제 프로그램

낮, 밤의 상태에 따라 버튼의 동작을 바꾸는 금고 관리 프로그램이다.

Class Diagram
State Pattern Class Diagram

1. Context 인터페이스

금고의 상태 변화를 관리하여 보안 센터와의 연락을 하는 인터페이스이다.

Context.java

package com.devkuma.designpattern.behavioral.state;

public interface Context {
    // 시간 설정
    void setClock(int hour);

    // 상태 변화
    void changeState(State state);

    // 보안센터 보안요원 호출
    void callSecurityCenter(String msg);

    // 보안 센터 기록
    void recordLog(String msg);
}

2. SafeFrame 클래스

Context 인터페이스를 구현하는 클래스이다. 버튼이나 화면 표시 등의 UI를 가진다.

SafeFrame.java

package com.devkuma.designpattern.behavioral.state;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Panel;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class SafeFrame extends Frame implements ActionListener, Context {

    private TextField textClock = new TextField(60);
    private TextArea textScreen = new TextArea(10, 60);
    private Button buttonUse = new Button("금고 사용");
    private Button buttonAlarm = new Button("비상벨");
    private Button buttonPhone = new Button("일반 통화");
    private Button buttonExit = new Button("종료");

    private State state = DayState.getInstance();

    public SafeFrame(String title) {
        super(title);

        setBackground(Color.lightGray);
        setLayout(new BorderLayout());
        add(textClock, BorderLayout.NORTH);
        textClock.setEditable(false);
        add(textScreen, BorderLayout.CENTER);
        textScreen.setEditable(false);

        Panel panel = new Panel();
        panel.add(buttonUse);
        panel.add(buttonAlarm);
        panel.add(buttonPhone);
        panel.add(buttonExit);
        add(panel, BorderLayout.SOUTH);

        pack();
        setVisible(true);

        buttonUse.addActionListener(this);
        buttonAlarm.addActionListener(this);
        buttonPhone.addActionListener(this);
        buttonExit.addActionListener(this);
    }

    // 버튼을 누르는 이벤트가 발생하면 여기 메소드가 실행된다.
    public void actionPerformed(ActionEvent e) {
        System.out.println(e.toString());
        if (e.getSource() == buttonUse) {
            // 금고 사용 버튼
            state.doUse(this);
        } else if (e.getSource() == buttonAlarm) {
            // 비상벨 버튼
            state.doAlarm(this);
        } else if (e.getSource() == buttonPhone) {
            // 일반 통화 버튼
            state.doPhone(this);
        } else if (e.getSource() == buttonExit) {
            // 종료 버튼
            System.exit(0);
        } else {
            System.out.println("?");
        }
    }

    // 시간 설정
    public void setClock(int hour) {
        String clockstring = "현재 시간은 ";
        if (hour < 10) {
            clockstring += "0" + hour + ":00";
        } else {
            clockstring += hour + ":00";
        }
        System.out.println(clockstring);
        textClock.setText(clockstring);
        state.doClock(this, hour);
    }

    // 상태 변화
    public void changeState(State state) {
        System.out.println(this.state + "에서 " + state + "로 상태가 변경되었습니다.");
        this.state = state;
    }

    // 경비센터 경비원 호출
    public void callSecurityCenter(String msg) {
        textScreen.append("call! " + msg + "\n");
    }

    // 보안 센터 기록
    public void recordLog(String msg) {
        textScreen.append("record... " + msg + "\n");
    }
}

3. State 인터페이스

금고의 상태를 나타내는 인터페이스이다.

State.java

package com.devkuma.designpattern.behavioral.state;

public interface State {
    // 시간 설정
    void doClock(Context context, int hour);

    // 금고 사용
    void doUse(Context context);

    // 비상벨
    void doAlarm(Context context);

    // 일반 통화
    void doPhone(Context context);
}

4. DayState 클래스

State 인터페이스를 구현하는 클래스이다. 낮 상태를 나타낸다.

DayState.java

package com.devkuma.designpattern.behavioral.state;

public class DayState implements State {
    private static DayState singleton = new DayState();

    private DayState() {
    }

    public static State getInstance() {
        return singleton;
    }

    @Override
    public void doClock(Context context, int hour) {
        if (hour < 9 || 17 <= hour) {
            context.changeState(NightState.getInstance());
        }
    }

    @Override
    public void doUse(Context context) {
        context.recordLog("금고사용(낮)");
    }

    @Override
    public void doAlarm(Context context) {
        context.callSecurityCenter("비상 벨(낮)");
    }

    @Override
    public void doPhone(Context context) {
        context.callSecurityCenter("일반 통화(낮)");
    }

    @Override
    public String toString() {
        return "[낮]";
    }
}

5. NightState 클래스

State 인터페이스를 구현하는 클래스이다. 야간 상태를 나타낸다.

NightState.java

package com.devkuma.designpattern.behavioral.state;

public class NightState implements State {
    private static NightState singleton = new NightState();

    private NightState() {
    }

    public static State getInstance() {
        return singleton;
    }

    @Override
    public void doClock(Context context, int hour) {
        if (9 <= hour && hour < 17) {
            context.changeState(DayState.getInstance());
        }
    }

    @Override
    public void doUse(Context context) {
        context.callSecurityCenter("비상: 야간 금고 사용!");
    }

    @Override
    public void doAlarm(Context context) {
        context.callSecurityCenter("비상 벨(야간)");
    }

    @Override
    public void doPhone(Context context) {
        context.recordLog("야간 통화 녹음");
    }

    @Override
    public String toString() {
        return "[야간]";
    }
}

6. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.state;

public class Main {

    public static void main(String[] args) {
        SafeFrame frame = new SafeFrame("State Sample");
        while (true) {
            for (int hour = 0; hour < 24; hour++) {
                frame.setClock(hour);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

7. 실행 결과

State Pattern Class Result

State 패턴의 장점

분할하고 통합하라는 방침은 프로그래밍에 자주 등장한다.
State 패턴에서는 상태를 클래스로 표현한다. 개별의 구체적인 상태를 별개의 클래스로서 표현하여 분할을 실행하고 있다. DayState를 구현하고 있는 동안 프로그래머는 다른 클래스(NightState)를 의식할 필요는 없어진다. 예제 프로그램에서는 상태가 2개 밖에 없지만, 상태가 더 늘어났을 경우에 State 패턴은 강점을 더 발휘한다.

2.4.9 - Design Pattern | Strategy Pattern (전략 패턴)

Strategy 패턴이란?

  • Strategy라는 영어 단어는 전략이라는 의미이다. 프로그래밍 경우에는 알고리즘 이라고 생각하면 이해하기 쉬울 것이다.
  • 프로그램은 문제를 해결하기 위해 작성된다. 문제를 해결하기 위해 특정 알고리즘이 구현되었다. Strategy 패턴은 알고리즘을 구현한 부분을 몰래 교환할 수 있는 방식이다.
  • 시스템이 유연하게 변경되고 확장될 수 있도록 한다.
  • 사용자(Client)는 자신에게 맞는 전략(Strategy)을 취사선택하여 로직을 수행할 수 있게하는 방법이다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴으로 분류된다.

예제 프로그램

가위바위보를 하는 프로그램이다. 무작위로 손을 내는 전략과 바위만의 손을 내는 전략이 있다.

Class Diagram
Strategy Pattern Class Diagram

1. Hand 클래스

가위바위보의 손을 나타내는 클래스이다.

Hand.java

package com.devkuma.designpattern.behavioral.strategy;

public class Hand {

    public static final int HAND_VALUE_ROCK = 0; // 바위
    public static final int HAND_VALUE_SCISSORS = 1; // 가위
    public static final int HAND_VALUE_PAPER = 2; // 보

    public static final Hand[] hand = {
            new Hand(HAND_VALUE_ROCK),
            new Hand(HAND_VALUE_SCISSORS),
            new Hand(HAND_VALUE_PAPER),
    };

    private int handValue;

    private Hand(int handValue) {
        this.handValue = handValue;
    }

    public static Hand getHand(int handValue) {
        return hand[handValue];
    }

    public boolean isStrongerThan(Hand hand) {
        // this가 hand보다 강했을 때 true
        return fight(hand) == 1;
    }

    private int fight(Hand hand) {
        if (this == hand) {
            // 무승부
            return 0;
        } else if ((this.handValue + 1) % 3 == hand.handValue) {
            // this의 승리
            return 1;
        } else {
            // hand의 승리
            return -1;
        }
    }
}

2. Player 클래스

가위바위보를 하는 플레이어를 나타내는 클래스이다.

Player.java

package com.devkuma.designpattern.behavioral.strategy;

public class Player {

    private String name;
    private Strategy strategy;
    private int winCount;
    private int loseCount;
    private int gameCount;

    public Player(String name, Strategy strategy) {
        this.name = name;
        this.strategy = strategy;
    }

    public String getName() {
        return name;
    }

    public Hand nextHand() {
        return strategy.nextHand();
    }

    public void win() {
        winCount++;
        gameCount++;
    }

    public void lose() {
        loseCount++;
        gameCount++;
    }

    public void even() {
        gameCount++;
    }

    public String toString() {
        return "[" + name + "] " + gameCount + " games, " + winCount + " win, " + loseCount + " lose";
    }
}

3. Strategy 인터페이스

가위바위보의 “전략"을 나타내는 인터페이스이다.

Strategy.java

package com.devkuma.designpattern.behavioral.strategy;

public interface Strategy {
    Hand nextHand();
}

4. RandomStrategy 클래스

무작위로 내는 전략을 나타내는 클래스이다.

RandomStrategy.java

package com.devkuma.designpattern.behavioral.strategy;

import java.util.Random;

public class RandomStrategy implements Strategy {

    public Hand nextHand() {
        Random random = new Random();
        return Hand.getHand(random.nextInt(3));
    }
}

5. RockStrategy 클래스

바위만 내는 전략을 나타내는 클래스이다.

GuuStrategy.java

package com.devkuma.designpattern.behavioral.strategy;

public class RockStrategy implements Strategy {

    public Hand nextHand() {
        return Hand.getHand(Hand.HAND_VALUE_ROCK);
    }
}

6. Main 클래스

메인 처리를 실시하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.strategy;

public class Main {

    public static void main(String[] args) {

        Player player1 = new Player("kimkc", new RandomStrategy());
        Player player2 = new Player("yunho", new RockStrategy());
        for (int i = 0; i < 5; i++) {
            Hand nextHand1 = player1.nextHand();
            Hand nextHand2 = player2.nextHand();
            if (nextHand1.isStrongerThan(nextHand2)) {
                System.out.println("Winner: " + player1.getName());
                player1.win();
                player2.lose();
            } else if (nextHand2.isStrongerThan(nextHand1)) {
                System.out.println("Winner: " + player2.getName());
                player1.lose();
                player2.win();
            } else {
                System.out.println("Even...");
                player1.even();
                player2.even();
            }
        }

        System.out.println("----- Total result -----");
        System.out.println(player1);
        System.out.println(player2);
    }
}

7. 실행 결과

Winner: kimkc
Winner: yunho
Even...
Even...
Winner: yunho
----- Total result -----
[kimkc] 5 gemes, 1 win, 2 lose
[yunho] 5 gemes, 2 win, 1 lose

Strategy 패턴의 장점

Strategy 패턴에서는 알고리즘의 부분을 다른 부분과 의식적으로 분리한다. 그리고 알고리즘과의 인터페이스의 부분만을 규정하여 위양에 의해 알고리즘을 이용한다.
이것은 프로그램을 복잡하게 만드는 것처럼 보이지만 그렇지 않는다. 예를 들어, 알고리즘을 개선하고 더 빨리 만들고 싶다면 Strategy 패턴을 사용하고 있다면 Strategy 역할의 인터페이스를 변경하지 않도록 알고리즘을 추가하고 수정해야 한다. 위양이라는 완만한 연결을 사용하고 있기 때문에 알고리즘을 준비로 전환할 수 있다.
그리고, 게임 프로그램등에서는 유저의 선택에 맞추어 난이도를 바꾸거나 하는데도 사용할 수 있다.

2.4.10 - Design Pattern | Template Method Pattern (템플릿 메소드 패턴)

Template Method 패턴이란?

  • 템플릿은 일정한 문자나 모형을 새겨 만든 주형이다. Template를 보면 어떤 문자를 쓸 수 있는지는 알 수 있지만, 실제로 어떤 문자가 되는지는 구체적인 필기 도구가 정해지지 않으면 모른다.
  • Template Method 패턴은, 슈퍼 클래스로 처리의 틀을 정해서 서브 클래스로 그 구체적 내용을 정하는 방식이다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴으로 분류된다.

Template Method 패턴의 예제 프로그램

몬스터의 이름, 공격력, 수비력을 표시하는 예제 프로그램이다.

Class Diagram
Iterator Pattern

1. AbstractMonster 클래스

템플릿이 되는 클래스이다.

AbstractMonster.java

package com.devkuma.designpattern.behavioral.template;

public abstract class AbstractMonster {

    public String name;

    public abstract int getAttack();

    public abstract int getDefense();

    public final void showInfo() {
        System.out.print("이름 : ");
        System.out.println(name);
        System.out.print("공격력 : ");
        System.out.println(getAttack());
        System.out.print("수비력 : ");
        System.out.println(getDefense());
        System.out.println();
    }
}

2. Slime 클래스

AbstractMonster 클래스로 정의된 메서드를 구현하는 클래스이다.

Slime.java

package com.devkuma.designpattern.behavioral.template;

public class Slime extends AbstractMonster {

    public Slime(String name) {
        this.name = name;
    }

    public int getAttack() {
        return 15;
    }

    public int getDefense() {
        return 10;
    }
}

3. Dragon 클래스

AbstractMonster 클래스로 정의된 메서드를 구현하는 클래스이다.

Dragon.java

package com.devkuma.designpattern.behavioral.template;

public class Dragon extends AbstractMonster {

    public Dragon(String name) {
        this.name = name;
    }

    public int getAttack() {
        return 60;
    }

    public int getDefense() {
        return 45;
    }
}

4. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.template;

public class Main {
    public static void main(String[] args) {
        AbstractMonster slime = new Slime("슬라임");
        AbstractMonster dragon = new Dragon("드래곤");
        slime.showInfo();
        dragon.showInfo();
    }
}

5. 실행 결과

이름 : 슬라임
공격력 : 15
수비력 : 10

이름 : 드래곤
공격력 : 60
수비력 : 45

Template Method 패턴의 장점

Template Method 패턴에서는 슈퍼 클래스의 템플릿 메소드로 알고리즘이 작성되어 있으므로, 서브 클래스측에서는 알고리즘을 일일이 작성할 필요가 없어진다.
예를 들면, Template Method 패턴을 사용하지 않고, 비슷한 클래스, Class1, Class2, Class3…를 작성되어 있는 경우는 Class1에 버그가 발견되면, 그 버그를 Class2, Class3…에서도 수정해야만 한다.
Template Method 패턴으로 작성되어 있다면, 템플릿 메소드에 버그가 발견된 경우에서도 템플릿 메소드만 수정하면 된다.

2.4.11 - Design Pattern | Visitor Pattern (비지터 패턴)

Visitor 패턴이란?

  • Visitor라는 영어 단어는 방문자라는 의미이다.
  • Visitor 패턴은 데이터 구조와 처리를 분리하는 방법이다.
  • 데이터 구조를 둘러싼 방문자 클래스를 준비하고 방문자 클래스에 처리를 맡긴다. 그러고 새로운 처리를 추가하고 싶을 때는 새로운 방문자를 만들면 된다. 그리고 데이터 구조에서는 방문자를 받아들이면 된다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴으로 분류된다.

Visitor 패턴의 예제 프로그램

디렉토리, 파일 목록을 표시하는 예제 프로그램이다.

Class Diagram
Visitor Pattern Class Diagram

1. Element 인터페이스

Visitor 클래스의 인스턴스를 받아들이는 데이터 구조를 나타내는 인터페이스이다.

Element.java

package com.devkuma.designpattern.behavioral.visitor;

public interface Element {
    void accept(Visitor visitor);
}

2. Entry 클래스

File이나 Directory의 기본이 되는 클래스이다. Element 인터페이스를 구현한다.

Entry.java

package com.devkuma.designpattern.behavioral.visitor;

public abstract class Entry implements Element {

    public abstract String getName();

    public String toString() {
        return getName();
    }
}

3. File 클래스

파일을 나타내는 클래스이다. Visitor의 수락 역할을 한다.

File.java

package com.devkuma.designpattern.behavioral.visitor;

public class File extends Entry {

    private String name;

    public File(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

4. Directory 클래스

디렉토리를 나타내는 클래스이다. Visitor의 수락 역할을 한다.

Directory.java

package com.devkuma.designpattern.behavioral.visitor;

import java.util.ArrayList;
import java.util.Iterator;

public class Directory extends Entry {

    private String name;
    private ArrayList<Entry> dir = new ArrayList();

    public Directory(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public Entry add(Entry entry) {
        dir.add(entry);
        return this;
    }

    public Iterator<Entry> iterator() {
        return dir.iterator();
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

5. Visitor 클래스

파일이나 디렉토리를 방문하는 방문자를 나타내는 추상 클래스이다.

Visitor.java

package com.devkuma.designpattern.behavioral.visitor;

public abstract class Visitor {
    public abstract void visit(File file);

    public abstract void visit(Directory directory);
}

6. ListVisitor 클래스

파일이나 디렉토리의 일람을 표시하는 클래스이다.

ListVisitor.java

package com.devkuma.designpattern.behavioral.visitor;

import java.util.Iterator;

public class ListVisitor extends Visitor {

    // 현재 위치한 디렉토리명
    private String currentDir = "";

    // 파일을 방문했을 때 호출
    public void visit(File file) {
        System.out.println(currentDir + "/" + file);
    }

    // 디렉토리를 방문했을 때 호출
    public void visit(Directory directory) {
        System.out.println(currentDir + "/" + directory);
        String saveDir = currentDir;
        currentDir = currentDir + "/" + directory.getName();
        Iterator<Entry> it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = it.next();
            entry.accept(this);
        }
        currentDir = saveDir;
    }
}

7. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.visitor;

public class Main {
    public static void main(String[] args) {

        Directory workspaceDir = new Directory("workspace");
        Directory compositeDir = new Directory("Visitor");
        Directory testDir1 = new Directory("test1");
        Directory testDir2 = new Directory("test2");
        workspaceDir.add(compositeDir);
        workspaceDir.add(testDir1);
        workspaceDir.add(testDir2);

        File element = new File("Element.java");
        File entity = new File("Entity.java");
        File file = new File("File.java");
        File directory = new File("Directory.java");
        File visitor = new File("Visitor.java");
        File listVisitor = new File("ListVisitor.java");
        File main = new File("Main.java");
        compositeDir.add(element);
        compositeDir.add(entity);
        compositeDir.add(file);
        compositeDir.add(directory);
        compositeDir.add(visitor);
        compositeDir.add(listVisitor);
        compositeDir.add(main);

        workspaceDir.accept(new ListVisitor());
    }
}

8. 실행 결과

/workspace
/workspace/Visitor
/workspace/Visitor/Element.java
/workspace/Visitor/Entity.java
/workspace/Visitor/File.java
/workspace/Visitor/Directory.java
/workspace/Visitor/Visitor.java
/workspace/Visitor/ListVisitor.java
/workspace/Visitor/Main.java
/workspace/test1
/workspace/test2

Visitor 패턴의 장점

Visitor 패턴은 처리를 복잡하게 하고 있는 것만으로, “반복의 처리가 필요하다면 데이터 구조안에 루프 처리를 쓰면 좋은 것은 아닐까?“라고 느낀다. 방문자 패턴의 목적은 데이터 구조와 처리를 분리하는 것이다. 데이터 구조는 요소를 집합으로 정리하거나 요소간을 연결해 주는 것이다. 그 구조를 유지해 두는 것과 그 구조를 기초로 한 처리를 쓰는 것은 다른 것이다.
방문자 역할(ListVisitor)은 허용 역할(File, Directory)과는 독립적으로 개발할 수 있다. 즉, Visitor 패턴은, 받아들이게 되는(File, Directory) 클래스의 부품으로서의 독립성을 높이고 있는 것이다. 만약, 처리의 내용을 File 클래스나 Directory 클래스의 메소드로서 구현하게 된다면, 새로운 처리를 추가해 기능 확장할 때마다 File 클래스나 Directory 클래스를 수정해야만 한다.

3 - 데이터 구조 (Data Structure)

데이터 구조으로 알아보는 프로그래밍. 백준 알고리즘

3.1 - 데이터구조 | 스택 (Stack)

스택(Stack)의 개념

  • 스택(stack)은 쌓아놓은 더미를 말한다.
  • 한 쪽 끝에서만 자료를 넣고 뺄 수 있는 후입선출(LIFO:Last In First Out) 형식의 자료 구조이다.
    • 가장 최근에 들어온 데이터가 가장 먼저 나간다.

스택의 구조

  • 스택 상단: top
  • 스택 하단: 불필요
  • 요소, 항목
  • 삽입/삭제 연산

Stack

스택(Stack)의 연산

스택(Stack)는 LIFO(Last In First Out) 를 따른다. 즉, 가장 최근에 스택에 추가한 항목이 가장 먼저 제거될 항목이다.

  • push(x): 주어진 요소 x를 스택의 맨 위에 추가한다.
  • pop(): 스택이 비어있지 않으면 맨 위에 있는 요소를 삭제하고 반환한다.
  • isEmpty(): 스택이 비어있으면 참(true)을 아니면 거짓(false)을 반환한다.
  • peek(): 스택이 비어있지 않으면 맨 위에 있는 요소를 삭제하지 않고 반환한다.
  • isFull(): 스택이 가득 차 있으면 참(true)을 아니면 거짓(false)을 반환한다.
  • size(): 스택내의 모든 요소들의 개수를 반환한다.
  • display(): 스택내의 모든 요소들의 출력한다.

스택(Stack)의 용도

  • 함수 호출
  • 재귀 알고리즘을 사용하는 경우 스택이 유용하다.
    • 재귀 알고리즘
      • 재귀적으로 함수를 호출해야 하는 경우에 임시 데이터를 스택에 넣어준다.
      • 재귀함수를 빠져 나와 퇴각 검색(backtrack)을 할 때는 스택에 넣어 두었던 임시 데이터를 빼 줘야 한다.
      • 스택은 이런 일련의 행위를 직관적으로 가능하게 해 준다.
      • 또한 스택은 재귀 알고리즘을 반복적 형태(iterative)를 통해서 구현할 수 있게 해준다.
  • 웹 브라우저 방문기록 (뒤로가기)
  • 실행 취소 (undo)
  • 역순 문자열 만들기
  • 수식의 괄호 검사 (연산자 우선순위 표현을 위한 괄호 검사)
    • Ex) 올바른 괄호 문자열(VPS, Valid Parenthesis String) 판단하기
  • 계산기(후위 표기법 계산)

스택(Stack)의 사용 사례

참조

4 - 알고리즘 프로그래밍 (Algorithm Programming)

알고리즘으로 알아보는 프로그래밍. 기본 알고리즘, 백준 알고리즘

4.1 - 일반 알고리즘

4.1.1 - 알고리즘 프로그래밍 (Algorithm Programming) 소개

알고리즘

  • 자료구조에 쌓인 데이터를 활용해 어떤 문제를 해결하기 위한 여러 동작들의 모임이다.
  • 문제를 해결하는 절차를 말한다.
  • 입력, 출력, 명백성, 유효성, 효과성을 만족해야 한다.
  • 알고리즘은 논리이며 수학이고, 실질적인 개발에 적용되는 기초적인 아이디어이다.

알고리즘 강좌

알고리즘 문제 사이트

4.1.2 - 빅오(Big O) 표기법 | 시간 복잡도, 공간 복잡도

시간 복잡도, 공간 복잡도

시간 복잡도

소스 코드 로직의 실행 시간(Execution Time)을 예측해 얼마나 효율적인 코드인가를 나타내는 개념이다. 실행 시간은 연산(Operation)에 비례해 길어진다. CPU사용량과 관련 있다.

공간 복잡도

코드가 얼마나 메모리 공간을 효율적으로 사용하는지에 대한 개념이다. 정적 배열이나 해시 테이블 처럼 공간을 미리 확보하는 자료구조에 자주 등장하는 개념이다. RAM 사용량과 관련 있다.

소스 코드 로직에 먼저 동작이 되어야 하겠지만, 동작뿐 아니라 시공간 복잡도를 계산하여 얼마나 효율적인 코드인지 여부가 판가름 된다.

빅오(Big O) 표기법이란

빅오(Big-O)는 시공간 복잡도를 수학적으로 표시하는 대표적인 방법이다. 단, 코드의 실제 러닝 타임을 표시하는 것이 아니며, 인풋 데이터 증가율에 따른 알고리즘의 성능을 (논리적으로) 예측하기 위해 사용한다. 빅오 표기법에는 다음 2가지 규칙이 있다.

가장 높은 차수만 남긴다.

O(n² + 2n) -> O(n²)

계수 및 상수는 과감하게 버린다.

O(4n + 5) -> O(n)
<