Head First Design Patterns는 객체지향 프로그래밍과 디자인 패턴에 대한 입문서다. 이 책 또한 다른 전공 서적과 다르게 쉽게 읽히는 책이다. 그림과 예제 코드를 통해 설명되며, 이러한 방식으로 개념을 쉽게 이해할 수 있었다.  
  
"변하지 않는 사실은 계속 변화한다"라는 문장을 책에서 강조하는데, 현업에서 개발하다 보면 기획내용이나 사용자의 요구사항은 계속 변한다. 어제까지는 분명 A를 얘기했는데, 오늘 저녁에 갑자기 B나 C 또는 H로 요구사항이 바뀔 수 있다. 열심히 다 만들었더라도 요구사항이 추가되거나 변하면 새로운 것을 다시 만들어야 하는 상황이 흔한 것이다. 사실 요구사항의 변화는 SW의 본질이라고 할 수 있다. 그렇기에 변경에 용이한 아키텍쳐를 설계하고 개발하는 것이 개발자의 중요 역량이라고 생각한다. 그게 안 되면 IT로 밥 벌어먹으면 안 된다는 생각. 어쨌거나, 변경에 용이한 코드를 작성하기 위해서는 여러 가지 디자인 패턴이 있고, 예제를 패턴을 적용해 개선해 나가는 예시로 설명해준다.

책에서는 디자인 패턴의 개념과 각 패턴이 어떤 상황에서 사용되는지에 대해 설명한다. 디자인 패턴은 각각 객체 생성, 객체 구조, 인터페이스, 행위 등의 다양한 측면에서 소프트웨어 디자인을 개선하기 위한 것이다. 또한, 이 책은 디자인 패턴을 적용하는 과정에서 발생하는 문제와 해결 방법에 대해 다루며, 이를 통해 디자인 패턴을 적용하는 방법을 익힐 수 있도록 돕는다. 디자인 패턴은 사실 무궁무진하게 많다, java, c, javascript 언어에 따라서도 다른 패턴이 나온다. 이 책에서는 java 계열 실무에서 자주 사용하는 디자인 패턴을 집중해서 다뤄주고 있다. 책은 650여 페이지로 굉장히 두꺼운데, 그림이 대다수를 차지하고 있고, 독자의 가독성을 고려하여 쓴 책이라 쉽게 읽힌다. (글을 잘 쓰는 사람이 개발도 잘한다는 말을 반증하는 것 같다.)

나는 객체 지향을 조금 더 잘 활용해보고 싶은 개발자로 추상화, 캡슐화, 다형성, 상속을 어떻게 하면 더 잘 적용할 수 있을지를 이 책을 통해 심도있게 고민해보았다. 흔히들 코드 재사용을 막기 위해 상속을 사용하는데, 생각지 못한 상황에 반복된 코드를 짤 수밖에 없었던 적이 많다. 그런 상황에 대한 해법들도 이 책을 통해 얻었다.
Head First Design Patterns는 객체지향 프로그래밍과 디자인 패턴에 대한 이해를 쉽게 접근할 수 있도록 돕는다. 이 책으로 습득한 디자인 패턴을 사용하면 더 나은 소프트웨어 디자인을 구현할 수 있을 것으로 기대되고, 이는 개발자로서의 역량 향상에 큰 도움이 될 것 같다.

728x90
반응형

이론 적인 것은 잘 정리된 글이 많기 때문에 생략.
java로 int 형 stack과 Object형 stack을 구현한 코드.


int 형을 저장하는 스택

public class IntStack {
    // 스택용 배열
    private int[] stk;
    // 스택 용량
    private int capacity;
    // 스택 포인터
    private int ptr;

    // 실행 시 예외 : 스택이 비어있는 경우
    public class EmptyIntStackException extends RuntimeException {
        public EmptyIntStackException(){}
    }

    // 실행 시 예외 : 스택이 가득 찬 경우
    public class OverflowIntStackException extends RuntimeException {
        public OverflowIntStackException(){}
    }

    // 생성자
    public IntStack(int maxLen) {
        ptr = 0;
        capacity = maxLen;
        try {
            // 스택 본채용 배열 생성
            stk = new int[capacity];
        } catch (OutOfMemoryError e) {
            // 생성할 수 없음
            capacity = 0;
        }
    }

    // 스택에 X를 푸시
    public int push(int x) throws OverflowIntStackException {
        if (ptr >= capacity)
            // 스택이 가득 찬 경우
            throw new OverflowIntStackException();

        return stk[ptr++] = x;
    }

    // 스택에서 데이터를 팝 (꼭대기에 있는 데이터를 꺼냄)
    public int pop() throws EmptyIntStackException {
        if (ptr <= 0)
            // 스택이 비어 있음
            throw new EmptyIntStackException();

        return stk[--ptr];
    }

    // 스택에서 데이트를 피크 (꼭대기에 있는 데이터 조회)
    public int peek() throws EmptyIntStackException {
        if (ptr <= 0)
            // 스택이 비어 있음
            throw new EmptyIntStackException();

        return stk[ptr - 1];
    }

    // 스택을 비움
    public void clear() {
        // 모든 작업이 ptr 값으로 이루어 지므로 배열의 요소를 변경할 필요가 없음.
        ptr = 0;
    }

    // 스택에서 X 찾아 인덱스 반환, 없다면 -1 반환
    public int indexOf(int x) {
        // 뒤(꼭대기)에서 부터 선형 탐색
        for (int i = ptr - 1; i >= 0; i--) {
            if (stk[i] == x)
                // 검색 성공
                return i;
        }
        // 검색 실패
        return -1;
    }

    // 스택의 용량 반환
    public int getCapacity() {
        return this.capacity;
    }

    // 스택에 쌓여있는 데이터 개수 반환
    public int size() {
        return ptr;
    }

    // 스택이 비어있는지 확인
    public boolean isEmpty() {
        return ptr <= 0;
    }

    // 스택이 가득 찼는지 확인
    public boolean isFull() {
        return ptr >= capacity;
    }

    public void dump() {
        if (ptr <= 0)
            System.out.println("EMPTY STACK");
        else {
            for (int i = 0; i < ptr; i++)
                System.out.print(stk[i] + " ");

            System.out.println("");
        }
    }
}

Object 형을 저장하는 스택

// 임의의 객체형을 쌓을 수 있는 테네릭 스택 클래서 Stack<E> 작성
public class GenericStack<E> {
    // 스택용 배열
    private E[] stk;
    // 스택 용량
    private int capacity;
    // 스택 포인터
    private int ptr;

    // 실행 시 예외 : 스택이 비어있는 경우
    public static class EmptyGStackException extends RuntimeException {
        public EmptyGStackException(){}
    }

    // 실행 시 예외 : 스택이 가득 찬 경우
    public static class OverflowGStackException extends RuntimeException {
        public OverflowGStackException(){}
    }

    // 생성자
    public GenericStack(int maxLen) {
        ptr = 0;
        capacity = maxLen;
        try {
            // 스택 본채용 배열 생성
            stk = (E[])new Object[capacity];
        } catch (OutOfMemoryError e) {
            // 생성할 수 없음
            capacity = 0;
        }
    }

    // 스택에 X를 푸시
    public E push(E x) throws OverflowGStackException {
        if (ptr >= capacity)
            // 스택이 가득 찬 경우
            throw new OverflowGStackException();

        return stk[ptr++] = x;
    }

    // 스택에서 데이터를 팝 (꼭대기에 있는 데이터를 꺼냄)
    public E pop() throws EmptyGStackException {
        if (ptr <= 0)
            // 스택이 비어 있음
            throw new EmptyGStackException();

        return stk[--ptr];
    }

    // 스택에서 데이트를 피크 (꼭대기에 있는 데이터 조회)
    public E peek() throws EmptyGStackException {
        if (ptr <= 0)
            // 스택이 비어 있음
            throw new EmptyGStackException();

        return stk[ptr - 1];
    }

    // 스택을 비움
    public void clear() {
        // 모든 작업이 ptr 값으로 이루어 지므로 배열의 요소를 변경할 필요가 없음.
        ptr = 0;
    }

    // 스택에서 X 찾아 인덱스 반환, 없다면 -1 반환
    public int indexOf(Object x) {
        // 뒤(꼭대기)에서 부터 선형 탐색
        for (int i = ptr - 1; i >= 0; i--) {
            if (stk[i].equals(x))
                // 검색 성공
                return i;
        }
        // 검색 실패
        return -1;
    }

    // 스택의 용량 반환
    public int getCapacity() {
        return this.capacity;
    }

    // 스택에 쌓여있는 데이터 개수 반환
    public int size() {
        return ptr;
    }

    // 스택이 비어있는지 확인
    public boolean isEmpty() {
        return ptr <= 0;
    }

    // 스택이 가득 찼는지 확인
    public boolean isFull() {
        return ptr >= capacity;
    }

    public void dump() {
        if (ptr <= 0)
            System.out.println("EMPTY STACK");
        else {
            for (int i = 0; i < ptr; i++)
                System.out.print(stk[i] + " ");

            System.out.println("");
        }
    }
}
728x90
반응형

'Computer Science > DataStructure' 카테고리의 다른 글

[DS] Array 배열  (0) 2022.07.17

토비의 스프링 3.1 VOL.1

스프링 사용자로서 항상 읽어야봐야 한다라는 소리에 두권을 샀지만, 엄청 어렵다는 소문에 읽을 엄두를 못 내던 책이다. 한 권에 800 페이지가 넘는 굉장한 분량에 두번의 정독 시도를 헛탕쳤었다. [유튜브] "개발바닥" 채널에서 저자 이일민님의 인터뷰와, 그 분이 이제는 스프링 부트 인강을 내시면서 이 책을 다시 시도하였고. 이번에는 1독을 끝냈다.
이제 4년차에 들어서면서 읽어보니 '어느 정도 개발을 했고, 좋은 구조와 효율을 고민해본 사람이 봐야 깊은 뜻을 더 잘 이해할 수 있겠구나' 라고 느꼈다.

이 책은 객체지향적 설계, 리팩토링, 테스트주도개발의 중요성에 대해 꾸준히 강조하고 있다. 단순하게 스프링 프레임워크의 기능 / 개념에 대한 설명도 깊게 나와있지만, 이 책의 요점은 스프링이 왜 이렇게 발전했고, 어떤 패러다임을 녹여서 만든건지 엿볼 수 있도록 도와주는 책이다. 따라서 개발 철학에 더 중점을 두고 읽어야 하는 책이라고 생각 된다.

마틴 파울러와 켄트 백이 강조하던 내용이 녹아 있으며, 아래와 같은 조언을 책 전반에 걸쳐 꾸준히 강조하고 있다.

  • '고정된 작업 흐름을 갖고 있으면서 여기저기서 자주 반복되는 코드가 있다면, 중복되는 코드를 분리할 방법을 생각해보는 습관을 기르자.'
  • '비슷한 기능이 새로 필요할 때마다 앞에서 만든 코드를 복사해서 사용할 것인가? 물론 아니어야 한다. 한두 번까지는 어떻게 넘어간다고 해도, 세번 이상 반복된다면 본격적으로 코드를 개선할 시점이라고 생각해야 한다.'

인상 깊었던 문장

인상 깊었던 문장들은 다음과 같다. 다 읽어보면 떠오르는 것이 있다. 그렇다, 대부분의 레거시 프로젝트의 소스들은 아래에서 하지 말라는 대로 개발되어 있다.

  계층형 아키텍쳐
  관심, 책임, 성격, 변하는 이유와 방식이 서로 다른 것들을 분리함으로써 분리된 각 요소의 응집도는 높여주고 서로 결합도를 낮춰줬을 때의 장점과 유익이 무엇인지 살펴봤다. 성격이 다른 모듈이 강하게 결합되어 한데 모여 있으면 한 가지 이유로 변경이 일어날 때 그와 상관이 없는 요소도 함께 영향을 받게 된다. 따라서 불필요한 부분까지 변경이 일어나고 그로 인해 작업은 더뎌지고 오류가 발생할 가능성이 높아진다. 어느 부분을 수정해야할지를 파악하기도 쉽지 않다.
  따라서 인터페이스와 같은 유연한 경계를 만들어 두고 분리하거나 모아주는 작업이 필요하다.
또, 흔히 저지르는 실수 중의 하나는 프레젠테이션 계층의 오브젝트를 그대로 서비스 계층으로 전달하는 것이다.
서블릿의 HttpServletRequest나 HttpServletResponse, HttpSession 같은 타입을 서비스 계층 인터페이스 메소드의 파라미터 타입으로 사용하면 안 된다.
계층의 경계를 넘어갈 때는 반드시 특정 계층에 종속되니 않는 오브젝트 형태로 변환해줘야 한다.
스프링을 사용하면 이런 데이터 중심의 코드를 만들 수 있을 뿐만 아니라, 실제로 매우 흔하게 발견된다.
데이터와 업무 트랜잭션 중심의 개발에 익숙한 사람들이 많고 이런 아키텍쳐를 의도적으로 선호하는 개발자도 많기 때문이다.
개발자들끼리 서로 간선없이 자신에게 할당된 기능을 독립적으로 만드는 데도 편하다.
최소한의 공통 모듈 정도만 제공되는 것을 사용하고, 그 외의 기능은 단위 업무 또는 웹 화면 단위로 만들어 진다.

하지만 이런 개발 방식은 변화에 매우 취약하다.
객체지향의 장점이 별로 활용되지 못하는데다 각 계층의 코드가 긴밀하게 연결되어 있기 때문이다.
중복을 제거하기도 쉽지 않다.
업무 트랜잭션에 따라 필드 하나가 달라도 거의 비슷한 DAO 메소드를 새로 만들기도 한다.
또한 로직을 DB와 SQL에 많이 담으면 담을수록 점점 확장성이 떨어진다.
DB는 확장에 한계가 있을 뿐 아니라 확장한다 하더라도 매우 큰 비용이 든다.
잘 작성된 복잡한 SQL 하나가 수백 라인의 자바 코드가 필요한 비지니스 로직을 한번에 처리할 수도 있다.
하지만 과연 바람직한 것일까?
이런 복잡한 sql을 누구나 쉽게 이해하고 필요에 따라 유연하게 변경할 수 있을까?
또, 복잡한 sql을 처리하기 위해 제한된 자원인 DB에 큰 부담을 주는 게 과연 바람직한 일인지 생각해볼 필요가 있다.
데이터 중심 아키텍쳐의 특징은 계층 사이의 결합도가 높은 편이고 응집도는 떨어진다는 접이다.
화면을 중심으로 하는 업무 트랜잭션 단위로 코드가 모이기 때문에 처음엔 개발하기 편하지만 중복이 많아지기 쉽고 장기적으로 코드를 관리하고 발전시키기 힘들다는 단점이 있다.
스프링은 그 개발철학과 목표를 분명히 이해하고 사용해야 한다.
자바의 근본인 OOP 원리에 충실하게 개발할 수 있으며,
환경이나 규약에 의존적이지 않은 POJO를 이용한 애플리케이션 개발은
엔터프라이즈 시스템 개발의 복잡함이 주는 많은 문제를 해결할 수 있다.
POJO 방식의 개발을 돕기 위해 스프링은 IoC/DI, AOP, PSA와 같은 기능 기술을 프레임워크와 컨테이너라는 방식을 통해 제공한다.
728x90
반응형

문자열이 정해진 날짜 포맷에 맞는지 확인해야하는 로직이 종종 필요하다

문자열이 날짜 형식인지 유효성 검사하기

    /**
     * 입력받은 문자열이 날짜형식으로 변환이 가능한지 확인한다.
     *
     * @param str2cmp    날짜형식 변환 가능여부 확인 대상
     * @param dateFormat 비교할 날짜 형식
     * @return
     */
    public static boolean isDateFormat(String str2cmp, String dateFormat) {
        try {
            //  검증할 날짜 포맷 설정
            SimpleDateFormat dateFormatParser = new SimpleDateFormat(dateFormat);
            //  parse()에 잘못된 값이 들어오면 Exception을 리턴하도록 setLenient(false) 설정
            dateFormatParser.setLenient(false);
            // 대상 인자 검증
            dateFormatParser.parse(str2cmp);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public static boolean isDateFormat(String str2cmp) {
        return isDateFormat(str2cmp, "yyyyMMdd");
    }

setLenient( false );로 설정하면, 잘못된 형식의 데이터가 들어올 경우 Exception을 리턴하도록 설정함.

728x90
반응형

'Language > Java' 카테고리의 다른 글

[Java] 성능을 고려한 try-catch 문  (0) 2020.12.16
[Java] Map 반복문  (0) 2020.12.08
[JAVA] 구 버젼 설치  (0) 2020.11.02
[Java] ArrayList Sort  (0) 2020.05.22

egovframework의 EgovHttpSessionBindingListener 클래스와 EgovMultiLoginPreventor 클래스로 중복 로그인 방지를 구현하던 중,
java.lang.illegalargumentexception setattribute non-serializable attribute ***에러가 발생했다.

session에 저장하기 위해선 해당 클래스 Serializable 클래스를 상속해 줘야 하는데, VO는 이미 상속이 되어 있었다.

VO 말고도 EgovHttpSessionBindingListener, EgovMultiLoginPreventor역시 Serializable 클래스를 상속해줘야 한다.

egov도 버젼 코드에 실수가 존재하나보다.

 

EgovHttpSessionBindingListener.java

package egovframework.com.utl.slm;

import java.io.Serializable;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Class Name : EgovHttpSessionBindingListener.java
 * @Description : 중복 로그인 방지를 위해 사용자의 로그인 아이디와 세션을 제어하는 구현 클래스
 * @Modification Information
 *
 *               수정일 수정자 수정내용 ------- ------- ------------------- 2014.09.30 표준프레임워크 최초생성
 * @author YJ Kwon
 * @since 2014.09.30
 * @version 3.5
 */
public class EgovHttpSessionBindingListener implements HttpSessionBindingListener, Serializable {

  private static final long serialVersionUID = -6535453381287200501L;
  private static Logger logger = LoggerFactory.getLogger(EgovMultiLoginPreventor.class);

  /**
   * 사용자의 로그인 세션에 EgovHttpSessionBindingListener가 바인딩될 때 자동 호출되는 메소드로, 사용자 세션이 이미 존재하는지를 검사하여 하나의
   * 어플리케이션 내에서 하나의 세션만 유지되도록 한다
   */
  public void valueBound(HttpSessionBindingEvent event) {
    logger.error("\n\t!! VALUE BOUND !!");
    if (EgovMultiLoginPreventor.findByLoginId(event.getName())) {
      EgovMultiLoginPreventor.invalidateByLoginId(event.getName());
    }

    EgovMultiLoginPreventor.loginUsers.put(event.getName(), event.getSession());
  }


  /**
   * 
   * 로그아웃 혹은 세션타임아웃 설정에 따라 사용자 세션으로부터 EgovHttpSessionBindingListener가 제거될 때 자동 호출되는 메소드로, 사용자의 로그인
   * 아이디에 해당하는 세션을 ConcurrentHashMap에서 모두 제거한다
   */
  @Override
  public void valueUnbound(HttpSessionBindingEvent event) {
    logger.error("\n\t!! VALUE UNBOUND !!");
    EgovMultiLoginPreventor.loginUsers.remove(event.getName(), event.getSession());
  }

}

 

 

EgovMultiLoginPreventor.java

package egovframework.com.utl.slm;

import java.io.Serializable;
import java.util.Enumeration;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Class Name : EgovMultiLoginPreventor.java
 * @Description : 중복 로그인 방지를 위해 사용자의 로그인 아이디와 세션 아이디를 관리하는 구현 클래스
 * @Modification Information
 *
 *               수정일 수정자 수정내용 ------- ------- ------------------- 2014.09.30 표준프레임워크 최초생성
 * @author YJ Kwon
 * @since 2014.09.30
 * @version 3.5
 */
public class EgovMultiLoginPreventor implements Serializable {

  private static final long serialVersionUID = 4012190701164146206L;

  private static Logger logger = LoggerFactory.getLogger(EgovMultiLoginPreventor.class);

  public static ConcurrentHashMap<String, HttpSession> loginUsers =
      new ConcurrentHashMap<String, HttpSession>();

  /**
   * 사용자의 로그인 아이디로 생성된 세션이 있는지를 확인한다
   */
  public static boolean findByLoginId(String loginId) {
    logger.error("\n\t>> loginUsers id : {} <<", loginId);
    return loginUsers.containsKey(loginId);
  }


  /**
   * 사용자의 로그인 아이디로 이미 존재하는 세션을 무효화한다
   */
  public static void invalidateByLoginId(String loginId) {
    Enumeration<String> e = loginUsers.keys();

    while (e.hasMoreElements()) {
      String key = (String) e.nextElement();

      logger.error("\n\t>> session key : {} <<", e.toString());
      if (key.equals(loginId)) {
        logger.error("\n\t:: SESSION INVALIDATE {} <<", loginId);
        loginUsers.get(key).invalidate();
      }
    }
  }

}
728x90
반응형

화면을 수정했는데, 클라이언트의 브라우져에 캐시가 남아서 이전 화면이 나오는 경우가 있다.
직접 브라우져 설정 들어가서 캐시를 지워달라고 요청 할 수도 있지만, 그게 한두번이 아니게 되니 코드에서 캐시를 지울수 없는지 찾아보았다.
이럴수가...
크게 어렵지도 않지만, html한정으로 이게 꼭 필요한 화면이 아니면 아래 설정은 가급적 안 하는게 좋을 것 같다.

HTML 메타태그 이용하기

    <!-- 1990년 이후 이 페이지의 캐시를 만료시킴. -->
    <meta http-equiv="Expires" content="Mon, 06 Jan 1990 00:00:01 GMT" />

    <!-- 캐시를 바로 만료시킴. -->
    <meta http-equiv="Expires" content="-1" />

    <!-- 페이지 로드시마다 페이지를 캐싱하지 않음. (HTTP 1.0) -->
    <meta http-equiv="Pragma" content="no-cache" />

    <!-- 페이지 로드시마다 페이지를 캐싱하지 않음. (HTTP 1.1) -->
    <meta http-equiv="Cache-Control" content="no-cache" />

JSP, Java 사용시

자바 코드 지만 html과 같음.

    response.setHeader( "Pragma", "no-cache" );
    response.setDateHeader( "Expires", -1 );
    response.setHeader( "Cache-Control", "no-cache" );

JS, CSS 파일 캐시 사용 방지

js나 css등 정적파일도 종종 변경을 해도 캐시문제로 적용이 되지 않는 경우가 많다.
그럴 경우 파일의 url 뒤에 ?timestamp 형태를 넣어준다.

  <link rel="stylesheet" href="/css/common.css?20210512" />
  <script type="text/javascript" src="/js/common.js?20210512"></script>

728x90
반응형

'WEB > JSP, JSTL' 카테고리의 다른 글

[JSP, JSTL] fn function 정리 - 2  (0) 2020.12.11
[JSP, JSTL] fn function 정리  (0) 2020.11.30
[JSP, JSTL] forEach 잘 사용하기  (0) 2020.11.30

성능을 고려한 try-catch 문

try-catch 문을 사용하면 예외 처리는 쉽지만, 에러객체.printStackTrace() 로 출력한 stack 이 시스템의 성능을 저하시킬 수 있다.
따라서 에러객체에서 다음과 같이 필요한 정보만 뽑아서 출력하는게 시스템 성능에 좋다.
어떻게 보면 긴 에러 출력보다는 아래와 같이 필요한 부분만 뽑아 보는게 디버깅에 효율 적 인것 같기도 하고...

        try {
            // 예외가 발생할 수 있는 로직
        } catch (Exception e) {
            StackTraceElement[] ste = e.getStackTrace();
            String className  = ste[0].getClassName();
            String methodName = ste[0].getMethodName();
            int lineNumber = ste[0].getLineNumber();
            String fileName = ste[0].getFileName();
            logger.error(">>> ERROR ON {} <<<", fileName);
            logger.error("{} >> {}() : {}", className, methodName, lineNumber);
            logger.error("MSG : {}", e.getMessage());
            logger.error("CUZ : {}", e.getCause());
        }
728x90
반응형

'Language > Java' 카테고리의 다른 글

[Java] 문자열이 날짜 형식인지 확인하기  (0) 2021.10.18
[Java] Map 반복문  (0) 2020.12.08
[JAVA] 구 버젼 설치  (0) 2020.11.02
[Java] ArrayList Sort  (0) 2020.05.22

SiteMesh와 Tiles

이직 하고 프로젝트를 인계 받다 보니 SiteMesh라는 것을 사용하고 있었다.
도대체 이게 뭐신고 하니 apache tiles 와 같은 화면 구성을 위한 템플릿 프레임워크였다.

현재는 소멸 된 오픈 소스 프로젝트로(이마짚...) 2015년 이후로는 업데이트가 없다는 설명에 tiles와 무슨 차이가 있는건지 찾아보았다.

혹시 라도 SiteMesh 사용법을 찾는 사람이라면 여기서 뒤로가기를 누르시라...

 


 

Google Trends

SiteMesh가 과연 얼마나 자주 사용 되는 것인가가 궁금해서 Google Trends에 비교 검색 해 보았다.

지난 5년간 압도적으로 Tiles 검색이 더 많았다...

지역별 비교 분석을 보면 한국과 중국에서 많이 검색을 했단다...

"공공 SI 에서 많은 메뉴들을 간단한 설정으로 표출 하고 싶어서" 가 아닐까 싶다.

 


 

SiteMesh와 Tiles의 차이점

 

SiteMesh와 Tiles의 가장 큰 차이점은 패턴이다.
Tiles의 경우에는 Composite View 패턴을, SiteMesh의 경우에는 Decorator 패턴을 사용하고 있다.

아래 비교 내용는 해당 링크에서 가져왔다. http://tiles.apache.org/framework/tutorial/pattern.html

Composite View vs. Decorator

Tiles is a composite view framework: it allows to reuse page pieces across the application. But another approach to achieve the same result is using the Decorator pattern. For example, Sitemesh is based on the Decorator pattern.

Instead of creating a template and organizing the pieces together, the Decorator pattern (in this case) takes a simple HTML page, transforms it adding the missing pieces (in our example, adding header, footer and menu) and finally renders it.

Here you can find a comparison table between the two patterns.

Aspect Composite View Decorator
Reusability The different parts of the page (template and pieces) can be reused across the whole application. Each decorator can be reused, but the decoration itself can be applied to one page at a time.
Ease of configuration Each page must be defined explicitly. The decorator can be applied even to the entire application.
Runtime configuration The pages can be configured and organized at runtime Since one page is decorated at a time, this feature is not present.
Performances Low overhead for composition. The page to be decorated has to be parsed.



재사용성

Composite View Pattern의 Tiles의 경우에는 페이지의 모든 부분을 모두 재사용 가능하다.
반면, Decorator Pattern을 사용하는 SiteMesh는 각각의 데코레이터를 재사용 할수는 있지만, 데코레이션 자체는 한 페이지씩 적용할 수 있다고 한다.
데코레이터가 무엇인지 모르겠지만, 모든 부분을 재사용할 수 있는 Composite View Pattern이 Decorator Pattern 보다 나아보인다.

설정

각 페이지를 일일히 다 정의 해야하는 Tiles 보다는 SiteMesh가 더 쉽다고 한다.
근데 Tiles도 일일히까지는 설정 안해도 됬던 것으로 기억하는데,,

실행중 수정 여부

Composite View Pattern은 런타임중 각 페이지의 설정을 변경할 수 있고, Decorator Pattern 은 최초 한번에 적용 되기 때문에 런타임준 수정할 수 있는 기능은 제공하지 않는다.

성능

Composite View Pattern에 비해 매번 페이지를 파싱해야 하는 Decorator Pattern은 성능면에서는 당연히 떨어질 것이라 생각한다.

 


 

결론

이렇게 보니, Tiles가 trend인 이유도, 공공 SI에서 SiteMesh를 사용하는 이유도 이해가 간다.
간단한 프로그램에 복잡하지 않지만 페이지의 양이 많은 공공 지자체 SI라면 SiteMesh를 사용하는 합리적인 이유인것 같긴 하다만, 그래도 Tiles가 더 편한 것 같다는 생각은 변하지 않는다.
SiteMesh가 2015년 이후로 업데이트가 없었고, Tiles는 2016 이후로 retired 했다고 한다. (?)
그리고 비슷한 대안으로 Thymeleaf, Freemarker, mustach.java등이 있다고 한다. 그리고 Thymeleaf가 Tiles 만큼 사용율도 높다...

기술 트렌드... 참 사람 어쩌라는 건지 모르겠다.

728x90
반응형

Java 에서 Map 안의 요소 반복문으로 꺼내기.

package dev.jeaha.practice;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class MapIterationPractice {

  public static void main(String[] args) {
    Map<String, String> map = new HashMap<>();

    map.put("k00", "v00");
    map.put("k01", "v01");
    map.put("k02", "v02");
    map.put("k03", "v03");
    map.put("k04", "v04");
    map.put("k05", "v05");
    map.put("k06", "v06");
    map.put("k07", "v07");
    map.put("k08", "v08");
    map.put("k09", "v09");
    map.put("k10", "v10");

    System.out.println(">>>> SOLUTION 01-1 <<<<");
    /**
     * Generic 미사용.
     * Java 버젼이 낮을 경우 사용할 수 있는 방법.
     */
    Iterator entries = map.entrySet().iterator();
    while( entries.hasNext() ) {
      Map.Entry entry = (Map.Entry) entries.next();
      String key = (String) entry.getKey();
      String val = (String) entry.getValue();
      System.out.println( String.format("key : %s, value : %s", key, val ) );
    }


    System.out.println("\n\n>>>> SOLUTION 01-2 <<<<");
    /**
     * Generic, Entry 사용
     */
    Iterator<Map.Entry<String, String>> entriex = map.entrySet().iterator();
    while( entriex.hasNext() ) {
      Map.Entry<String, String> entry = entriex.next();
      System.out.println( String.format("key : %s, value : %s", entry.getKey(), entry.getValue() ) );
    }

    System.out.println("\n\n>>>> SOLUTION 01-3 <<<<");
    /**
     * Generic 사용, Entry 미사용
     */
    Iterator<String> keys = map.keySet().iterator();
    while( keys.hasNext() ) {
      String key = keys.next();
      System.out.println( String.format("key : %s, value : %s", key, map.get(key)) );
    }

    System.out.println("\n\n>>>> SOLUTION 02 <<<<");
    /**
     * Entry에 for-each loop 사용.
     * 가장 일반적이고 많은 경우에 사용됨.
     * 반복문 안에서 key 값과 value 값이 전부 필요할 때 사용.
     * Java 1.5 이상부터 사용 가능. NullPointerException 발생 가능.
     */
    for( Map.Entry<String, String> entry : map.entrySet() ) {
      System.out.println( String.format("key : %s, value : %s", entry.getKey(), entry.getValue() ) );
    }

    System.out.println("\n\n>>>> SOLUTION 03 <<<<");
    /**
     * entry 대신에 key 값이나 value 값만 필요할 때 사용.
     * entrySet을 사용할 때보다 약 10% 빠르다고 함.
     */
    for( String key : map.keySet() ) {
      System.out.println( String.format("key : %s", key ) );
    }
    for( String value : map.values() ) {
      System.out.println( String.format("value : %s", value ) );
    }

    System.out.println("\n\n>>>> SOLUTION 04 <<<<");
    /**
     * 코드상으로는 깔끔해 보이지만 비효율 적이고 성능면에서 좋지 않은 방법.
     * key 값을 이용해서 value 를 찾는 과정에서 시간 소모가 많음.
     * SOLUTION 02에 비해 20~200% 성능 저하 유발.
     */
    for( String key : map.keySet() ) {
      System.out.println( String.format("key : %s, value : %s", key, map.get(key) ) );
    }

    System.out.println("\n\n>>>> SOLUTION 05 <<<<");
    /**
     * stream을 사용한 forEach.
     */
    map.entrySet()
    .stream()
    .forEach(System.out::println);

    System.out.println("\n\n>>>> SOLUTION 06 <<<<");
    /**
     * forEach 및 Lambda를 사용한 반복.
     * forEach() 내에서 람다 표현식 사용.
     */
    map.forEach( (key, val) -> {
      System.out.println( String.format("key : %s, value : %s", key, val ) );
    });
  }
}
728x90
반응형

'Language > Java' 카테고리의 다른 글

[Java] 문자열이 날짜 형식인지 확인하기  (0) 2021.10.18
[Java] 성능을 고려한 try-catch 문  (0) 2020.12.16
[JAVA] 구 버젼 설치  (0) 2020.11.02
[Java] ArrayList Sort  (0) 2020.05.22

종종 특수한 이유로 Java 구 버젼을 설치해야 할 일이 있음.

오라클 공식 사이트에서는 1.7이하 버젼은 더 이상 다운로드를 지원하지 않지만.
잘 찾아보면 각 버젼별로 다운 받을 수 있음.

한참 헤매다 따로 링크를 저장해 두기로 하고 포스트를 작성함.

JDK 1.5
JDK 1.6
JDK 1.7
JDK 1.8

728x90
반응형

'Language > Java' 카테고리의 다른 글

[Java] 문자열이 날짜 형식인지 확인하기  (0) 2021.10.18
[Java] 성능을 고려한 try-catch 문  (0) 2020.12.16
[Java] Map 반복문  (0) 2020.12.08
[Java] ArrayList Sort  (0) 2020.05.22

+ Recent posts