Design Pattern In Practice

Y3S Study Group : https://github.com/y3s-study

Park Young Jun

2018. 09. 04

Strategy Pattern

List.sort(Comparator comparator)

  • 리스트를 정렬한다
  • 구체적인 정렬 알고리즘(전략)은 Comparator 인터페이스를 구현하여 주입한다

List.sort(Comparator comparator)

public class NaturalOrder implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
}

Comparator를 구현한 별도의 정렬 클래스 NaturalOrder 구현

10
15
73
99
100
List<Integer> numbers = Arrays.asList(100, 10, 15, 73, 99);
numbers.sort(new NaturalOrder());
numbers.forEach(System.out::println);

List.sort(Comparator comparator)

List<Integer> numbers = Arrays.asList(100, 10, 15, 73, 99);
    
numbers.sort(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
});

numbers.forEach(System.out::println);

익명 클래스로 전략(Comparator)을 구현하여 주입

10
15
73
99
100

List.sort(Comparator comparator)

numbers.sort(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
});

Java 8 에서는 람다식도 가능

numbers.sort((o1, o2) -> o1 - o2);
numbers.sort(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2 - o1;
    }
});
numbers.sort((o1, o2) -> o2 - o1);

List.sort(Comparator comparator)

numbers.sort(Comparator.naturalOrder());

JAVA 8에서 제공하는 기본 정렬 전략 주입

numbers.sort(Comparator.reverseOrder());

Singleton Pattern

java.lang.Runtime

  • JVM이 작동하는 시스템과의 인터페이스를 제공

  • 시스템의 메모리, 프로세서 정보 등을 확인할 수 있음

java.lang.Runtime

Runtime runtime = Runtime.getRuntime();

System.out.println("총 메모리" + runtime.maxMemory());
System.out.println("여유 메모리" + runtime.freeMemory());
System.out.println("코어 수" + runtime.availableProcessors());
총 메모리 : 1908932608
여유 메모리 : 126248240
코어 수 : 4

java.lang.Runtime

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}

    // 생략 ...
}

Observer Pattern

Swing/AWT

  • Java GUI 라이브러리
  • UI 이벤트 처리 과정을 옵저버 패턴을 사용해 구현

Swing/AWT

ActionListener actionListener = new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
	System.out.println("Button clicked");
    }
};
		
JButton button = new JButton("Click me");
button.addActionListener(actionListener);
  • 버튼 클릭 이벤트가 발생했을 때 실행될 코드를 ActionListener를 통해 구현
  • 구현한 ActionListener를 button.addActionListener() 메서드로 등록
  • 이벤트(액션)이 발생하면 등록된 리스너들의 actionPerformed() 메서드를 호출
/**
 * Adds an <code>ActionListener</code> to the button.
 * @param l the <code>ActionListener</code> to be added
 */
public void addActionListener(ActionListener l) {
   listenerList.add(ActionListener.class, l);
}

Swing/AWT

Observer Pattern과 Swing / AWT 비교

Observer Pattern Swing / AWT
Observer ActionListener
Subject JButton
Subject.registerObserver() JButton.addActionListener()
Observer.update() ActionListener.actionPerformed()
Subject.notifyObservers() JButton.fireActionPerformed()

Swing/AWT

    protected void fireActionPerformed(ActionEvent event) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        ActionEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length-2; i>=0; i-=2) {
            if (listeners[i]==ActionListener.class) {
                // Lazily create the event:
                if (e == null) {
                      String actionCommand = event.getActionCommand();
                      if(actionCommand == null) {
                         actionCommand = getActionCommand();
                      }
                      e = new ActionEvent(AbstractButton.this,
                                          ActionEvent.ACTION_PERFORMED,
                                          actionCommand,
                                          event.getWhen(),
                                          event.getModifiers());
                }
                ((ActionListener)listeners[i+1]).actionPerformed(e);
            }
        }
    }

Decorator Pattern

java.io Package

  • InputStream, OutputStream, Reader, Writer 모두 Decorator 패턴으로 작성됨

java.io.Reader

File file = new File("data.txt");
		
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
    String line = br.readLine();

    while (line != null) {
        System.out.println(line);
	line = br.readLine();
    }
}
  • Reader : 문자기반 입력 스트림의 최상위 추상 클래스
  • FileReader : 문자 파일을 한 문자(2byte) 씩 읽을 수 있는 기능 제공
  • BufferedReader : 문자 입력 스트림을 효율적으로 읽을 수 있도록 버퍼링을 제공

Template Method Pattern

spring configuration

  • Java 코드를 이용한 스프링 설정시 Template Method Pattern이 사용됨
  • ex) Spring Security
  • 공통/필수 설정은 상위 추상클래스에서 구현하고, 바뀌는 부분은 하위 클래스에서 구현

Spring Security - WebSecurityConfigurerAdapter

  • WebSecurityConfigurerAdapter는 스프링이 제공하는 공통 추상 클래스
  • 추상 클래스 상속 후 설정하고자 하는 메서드를 오버라이드하여 구현
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
	    .antMatchers("/").permitAll()
	    .antMatchers("/admin/**").hasRole("ADMIN")
	    .anyRequest().authenticated();
    }
}

Factory Method Pattern

Long, Integer, BigDecimal, ...

  • new 키워드 대신 valueOf() 정적 팩토리 메서드 제공
  • 항상 새 객체를 생성하는 대신 캐시 전략 사용

LocalDate, LocalDateTime

  • new 키워드 대신 now() 팩토리 메서드 제공으로 가독성 향상

Long, Integer, BigDecimal, ...

Integer one = Integer.valueOf("1");
Long two = Long.valueOf("2");
BigDecimal three = BigDecimal.valueOf(3);
  • new 대신 valueOf() 팩토리 메서드로 생성

Long, Integer, BigDecimal, ...

public static Integer valueOf(String s) throws NumberFormatException {
    return Integer.valueOf(parseInt(s, 10));
}

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    
    return new Integer(i);
}
  • -128 ~ 127 사이의 수는 캐싱된 객체를 리턴.
  • -128 ~ 127 사이의 수는 '==' 연산자를 이용한 비교가 가능하다!

Long, Integer, BigDecimal, ...

public static BigDecimal valueOf(long val) {
    if (val >= 0 && val < zeroThroughTen.length)
        return zeroThroughTen[(int)val];
    else if (val != INFLATED)
        return new BigDecimal(null, val, 0, 0);
    return new BigDecimal(INFLATED_BIGINT, val, 0, 0);
}
  • 0 부터 10까지는 캐싱된 BigDecimal 객체 리턴

LocalDate, LocalDateTime

LocalDateTime localDateTime = LocalDateTime.now();
LocalDate localDate = LocalDate.now();
  • new 대신 now() 정적 팩토리 메서드를 제공하여 정확한 의미 전달 및 가독성 향상

Builder Pattern

Guava Cache

  • 캐시를 생성할 때 빌더 패턴을 활용

Guava?

  • https://github.com/google/guava
  • 구글에서 만든 자바 오픈소스 라이브러리
  • 캐시, 문자열, 컬렉션 등 자바 프로젝트에서 공통적으로 필요한 요소를 제공

Guava LoadingCache

  • 외부 데이터를 로컬 메모리에 캐시할 때 주로 사용
  • 캐시 미스가 발생하면 자동으로 데이터를 로드
  • 간단한 코드를 통해  캐시 크기, 캐시 시간, 데이터 로딩 방법, 데이터 Refresh 전략을 제어 가능

Guava LocalCache

public class CacheService {
    private static LoadingCache<String, String> localCache = CacheBuilder.newBuilder()
        .maximumSize(100)
	.expireAfterWrite(10, TimeUnit.MINUTES)
	.expireAfterAccess(5, TimeUnit.MINUTES)
	.build(new CacheLoader<String, String>() {
	    @Override
	    public String load(String key) {
		return getRemoteData(key);
	    }
        });
	
    public String getData(String id) {
        try {
            return localCache.get(id);
        } catch (ExecutionException e) {
	    return null;
	}
    }

    private static String getRemoteData(String id) {
        return "remote data";
    }
}

Builder 패턴 쉽게 적용하기

  • Lombok(롬복)
  • https://projectlombok.org/
  • 어노테이션을 사용하여 Builder 코드를 자동 생성
  • Builder 뿐 아니라 Getter, Setter, 생성자 등을 자동 생성 할 수 있다.

Builder 패턴 쉽게 적용하기

import lombok.Builder;

@Builder
public class Person {
    private String name;
    private int age;
    private int tall;
    private int weight;
}
public static void main(String[] args) {
    Person p = Person.builder()
	.age(29)
	.weight(100)
	.tall(180)
	.name("man")
	.build();
}

Thank you!

Design Pattern In Practice

By Young Jun Park (박영준)

Design Pattern In Practice

디자인 패턴의 실제 활용 사례를 소개합니다.

  • 219