컬렉션 프레임 워크는 데이터 군을 저장하는 클래스를 표준화한 프레임워크로, 데이터를 저장하는 자료구조와 알고리즘을 클래스로 구현해놓은것입니다.
제네릭과 다형성을 이용한 객체지향 설계로 표준화 되어있어 사용법을 익히기도 편리하며, 재사용성이 높은 코드를 작성할 수 있다는 장점이 있습니다.

image

컬렉션 프레임워크 인터페이스

컬렉션 프레임 워크에는 대표적으로 다음의 3가지 인터페이스가 존재합니다.

  • List
  • Set
  • Map

ListSet을 구현한 컬렉션 클래스들은 공통점이 많아 공통된 부분을 Collection이라는 새로운 인터페이스로 정의하여 놓았습니다. 반면 Map을 구현한 클래스들은 전혀 다른 형태로 컬렉션을 다루기 때문에 Collection을 상속하지 않습니다.

Interface 특징 구현 클래스
List 순서유지 O, 중복 허용 ArrayList, LinkedList, Stack, Vector
Set 순서유지 X, 중복 불가 HashSet, TreeSet
Map key-value데이터 집합, 키 중복 X, 값 중복 O HashMap, TreeMap, HashTable, Properties

컬렉션 프레임 워크의 구현클래스들은 인터페이스의 이름을 포함하는 명명법을 따르지만 Vector, Stack, HashTable, Properties와 같은 클래스들은 컬렉션 프레임워크 이전부터 존재하였던 것이기 때문에 컬렉션 프레임워크의 명명법을 따르지 않습니다.

자바 컬렉션 프레임워크를 사용할 때 List, Set, Map과 같은 인터페이스가 아닌 ArrayList, Vector, HashMap, HashTable과 같은 구현체로 선언할 때가 있는데 이는 객체의 결합도를 매우 강하게 유지하여 비효율적이며, 추상화에 의존해야한다는 SOLID의 DIP원칙을 위반합니다.

import java.util.*;

class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();   // Bad usecase
        List<Integer> list2 = new ArrayList<>();        // Good usecase
    }
}

Vector, HashTable은 Backward Compatibility를 위해 존재하는것으로 가능하면 사용하지 않는것을 권장합니다.

Collection

CollectionListSet의 조상 인터페이스로 컬렉션 클래스에 저장된 데이터를 조회하고 추가 삭제하는등의 기본적인 메서드들을 정의하고 있습니다.

Type & Method Description
boolean add(E e) 객체 e를 컬렉션에 추가한다.
boolean addAll(Collection<? extends E> c 객체 c의 모든 요소를 컬렉션에 추가한다.
void clear() 컬렉션의 모든 요소를 삭제한다.
boolean contains(Object o) 컬렉션이 객체 o를 포함하고 있는지 확인합니다.
boolean containsAll(Collection<?> c) 컬렉션 c의 모든 요소를 컬렉션이 포함하고있는지 확인합니다.
boolean isEmpty() 컬렉션이 비어있는지 확인합니다.
int size() 컬렉션의 크기를 반환합니다.
boolean remove(Object o) 객체 o를 컬렉션에서 찾고, 있다면 삭제합니다.
boolean removeAll(Collection<?> c) 컬렉션 c에 존재하는 요소를 컬렉션에서 찾아 모두 삭제합니다.
Object[] toArray() 컬렉션을 배열로 변환합니다.
int hashCode() 컬렉션의 해시값을 반환합니다
equals(Object o) 컬렉션 내부의 값을 비교한다.
Iterator<E> iterator() 컬렉션의 이터레이터를 반환합니다.

contains의 경우 구현에 따라 시간복잡도가 달라집니다. ArrayList의 경우 O(n)시간에 확인하며, HashMap의 경우 O(log n)의 시간에 확인합니다.

equals의 경우 내부의 값을 비교하는데, 값의 순서가 달라도 false를 반환합니다. 따라서 값의 포함 여부를 확인하고 싶으면 contains를 사용하면 됩니다.

import java.util.*;

class Main {
    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>();
        List<Integer> list2 = new ArrayList<>();
        List<Integer> list3 = new ArrayList<>();

        for(int i = 0; i < 10; i++) {
            list1.add(i);
            list2.add(i);
        }

        for(int i = 0; i < 8; i++) {
            list3.add(i);
        }

        list3.add(9);
        list3.add(8);

        System.out.println(list1.equals(list2));
        System.out.println(list1.equals(list3));
    }
}

List Interface

List인터페이스는 중복을 허용하고 저장순서가 유지되는 자료구조를 가집니다. Collection인터페이스를 상속합니다.

순서가 유지되는 만큼 인덱스로 데이터에 접근할 수 있는 메서드들이 존재합니다.

Type & Method Description
void add(int index, E element) index위치에 요소를 삽입합니다.
void addAll(int index,Collection c) index위치에 컬렉션c의 데이터들을 모두 삽입합니다.
int indexOf(Object o) 객체 o의 index를 반환합니다.
boolean remove(int index) index위치의 요소를 삭제합니다.
E set(int index, E element) index 위치의 요소를 element로 대체하고 반환합니다
void sort(Comparator c) 비교자 c를 통해 컬렉션을 정렬합니다
List<E> subList(int fromIndex, int toIndex) fromIndex부터 toIndex에 있는 요소를 반환합니다

image

다음의 구현 클래스를 가집니다.

  • ArrayList
  • LinkedList
  • Vector
  • Stack

ArrayList vs LinkedList

ArrayList의 경우 저장공간이 부족하면, 현재사이즈 * 2만큼의 새로운 저장공간을 확보한 후 데이터를 추가합니다. 반면, LinkedList의 경우 저장공간을 따로 확보하는것이 아니라 새로운 노드를 생성하여 연결하는 방식입니다. 따라서 충분한 공간을 확보해 준다면 순차적인 추가/삭제는 ArrayList가 더 빠릅니다.

반면 중간의 데이터를 추가/삭제하는 경우 ArrayList는 모든 요소를 재배치해야 하기 때문에 LinkedList에 비해 속도가 느립니다.

ArrayList의 경우 사용하지 않는 공간도 할당 될 수 있기 때문에 메모리 사용이 비효율적이라는 단점이 있으며, LinkedList의 경우 각 요소들이 연결되어있는 형태이기 때문에 데이터가 많을수록 읽어오는 시간이 길어진다는 단점이 있습니다.

Stack

image

Stack클래스는 Vector클래스를 상속하며, Last In First Out의 자료구조입니다.

Type & Method Description
boolean empty() 스택이 비어있는지 확인합니다
E peek() Top의 값을 반환합니다
E pop() Top의 값을 pop합니다
E push(E item) Top에 데이터 요소를 추가합니다
int search(Object o) 가장 아래에서부터 객체의 위치를 찾아 반환합니다
import java.util.Stack;

class Main {
    public static void main(String[] args) {
        Stack<String> animals= new Stack<>();

        animals.push("Dog");
        animals.push("Horse");
        animals.push("Cat");
        System.out.println("Stack: " + animals);

        // Search an element
        int position = animals.search("Horse");
        System.out.println("Position of Horse: " + position);
    }
}
Stack: [Dog, Horse, Cat]
Position of Horse: 2

Queue Interface

Queue인터페이스는 Collection인터페이스를 상속하며 First In First Out의 자료구조입니다.

Type & Method Description
boolean add(E e) e 요소를 tail에 추가합니다
Object peek() head의 값을 반환합니다
Object poll() head의 값을 반환하고 삭제합니다
boolean offer(Object o) 객체o를 저장하며, 성공여부를 반환합니다
Object element() peek과 같은 역할이나, 큐가 비어있을 경우 NoSuchElementException을 발생시킵니다
Object remove() poll과 같은 역할이나, 큐가 비어있을 경우 NoSuchElementException을 발생시킵니다

구현체는 다음과 같습니다.

  • ArrayDequeue
  • LinkedList
  • PriorityQueue
    • 저장한 순서에 관계없이 우선순위가 높은 것부터 꺼내는 형태로, 힙 자료구조 형태로 저장합니다.

이외에도 Queue인터페이스를 상속하는 다음과 같은 인터페이스들이 있습니다

  • Deque
    • 기존의 큐와 달리 앞뒤로 삽입 삭제를 할 수 있습니다.
  • BlockingQueue
  • BlockingDeque

Set Interface

List인터페이스와 동일하게 Set인터페이스도 Collection인터페이스를 상속합니다. 하지만 중복 요소를 허용하지 않으며, 저장순서 또한 유지되지 않습니다.

  • HashSet
    • HashSet은 Set인터페이스를 구현한 가장 대표적인 컬렉션입니다. addaddAll메서드를 통해 새로운 요소를 추가하며, 중복된 요소를 허용하지 않기 위해 equals를 호출하여 중복된 요소가 컬렉션 내에 존재하는지 확인합니다.
    • HashSet은 저장된 순서를 유지하지 않기 때문에, 저장한 순서를 유지하고자 한다면, LinkedHashSet을 사용하여 저장순서를 유지할 수 있습니다.
  • TreeSet
    • 이진 탐색 트리의 일종인 레드-블랙트리로 구현된 컬렉션 클래스 입니다.

Map Interface

Map은 key-value의 형태로 요소를 저장하는 인터페이스입니다. 중복된 key를 허용하지 않고, 각 키는 하나의 값에 대응됩니다.

Type & Method Description
void clear() 모든 mapping을 삭제합니다
boolean containsKey(Object key) 해당하는 key가 존재하는지 확인합니다
boolean containsValue(Object value) 해당하는 value가 존재하는지 확인합니다
Set<Map.Entry<K,V>> enrtySet 저장된 key-value를 key-value형태의 Set으로 반환합니다
Set keySet() 저장된 key들의 set을 반환합니다.
V put(K key, V value) key-value를 추가합니다
void putAll(Map<K,V> m) map의 모든 key-value를 추가합니다
V remove(Object key) 키값에 대응되는 key-value가 존재할 경우 삭제합니다
V get(Object key) 키값에 대응되는 값을 반환합니다
int size() key-value쌍의 개수를 반환합니다.
Collection values() map의 모든 value를 반환합니다.

Map은 다음의 5개 구현 클래스를 가집니다

  • HashMap
    • 해싱을 사용하여 많은 양의 데이터를 검색하는데 좋은 성능을 보입니다.
  • EnumMap
  • LinkedHashMap
  • WeakHashMap
  • TreeMap
    • 이진 탐색트리의 형태로 탐색과 정렬에 적합한 클래스입니다.

Map은 다음과 같이 3개의 확장 인터페이스를 가집니다.

  • SortedMap
  • NavigableMap
  • ConcurrentMap

Reference

[Java] 추상클래스와 인터페이스

Goal

  • 추상클래스와 인터페이스에 대한 이해

추상클래스(Abstract Class)

추상클래스는 구현부가 없어 인스턴스를 생성할 수 없고, 상속을 통한 자손클래스로만 구현이 가능합니다. 이러한 추상클래스의 존재이유는 객체지향 설계의 추상 개념과 맞닿아 있습니다.

추상 개념은 구체적인 사물들의 공통적인 특징을 파악해서 이를 하나의 개념으로 나타내는것을 말합니다. 예를들어 구, 사면체, 육면체 등과 같은 도형은 입체라는 공통적인 특징을 통해 입체도형이라는 개념으로 나타낼 수 있습니다. 하지만, 이러한 도형의 겉넓이, 부피를 구하는 구체적인 행동을 구현하고자 한다면, 도형의 형태에 따라 겉넓이와 부피를 구하는 방식이 다르기 때문에 입체도형의 입장에서는 이러한 행동을 구현하기 어렵습니다.

이렇게 각각의 도형의 상위 클래스의 입장에서 하위클래스의 구체적인 행동내용을 정의 할 수 없기 때문에 추상클래스를 사용합니다.

abstract class Figure {
    abstract int getSurfaceArea();  // 추상 메서드
    abstract int getVolume();       // 추상 메서드
}

class Ball extends Figure {
    int getSurfaceArea() { ... };   // 추상 메서드 구현
    int getVolume() { ... };        // 추상 메서드 구현
}

class Tetrahedron extends Figure {
    int getSurfaceArea() { ... };   // 추상 메서드 구현
    int getVolume() { ... };        // 추상 메서드 구현

}

추상화는 구체화와 반대되는 의미로 이해할 수 있습니다. 상속 계층도에서 상위에 위치할 수록 추상화 정도가 심해져 공통요소만 남게 되며, 하위에 위치할 수록 구체화 정도가 심해져 세분화 된다고 할 수 있습니다.

인터페이스(Interface)

추상클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 합니다. 자바는 단일 상속만을 지원하기 때문에 추상클래스를 통한 설계 방식은 새로운 타입을 정의하는데 큰 제약을 받게 됩니다.

abstract class TV {
    void show();
}

abstract class VCR {
    void scanTape();
}

class TVCR extends TV,VCR { // 이와 같은 다중 상속은 허용하지 않습니다.
    ...
}

반면 인터페이스의 경우 선언한 추상메서드를 모두 정의하고 일반 규약만 잘 지킨다면 어떤 클래스를 상속했든 같은 타입으로 취급되어 다중 상속이 가능합니다.

interface TV {
    void show();
}

interface VCR {
    void scanTape();
}

interface TVCR extends TV, VCR {

}

이러한 다중 상속의 특성 때문에 인터페이스로는 계층구조가 없는 타입 프레임 워크를 만들 수 있습니다.

다음 코드에서 가수(Singer)와 작곡가(Songwriter)는 어느 쪽이 상위 개념이라 명확하게 구분하기 어렵습니다. 이때 작곡도 하는 가수를 구현하고자 하는 경우 가수에 작곡가의 코드를 구현해도 문제되지 않지만, 다중 상속을 통해 제 3의 인터페이스를 정의할 수도 있습니다.

public interface Singer {
    AudioClip sing(Song s);
}

public interface Songwriter {
    Song compose(int chartPosition);
}

public interface SingerSongwriter extends Singer, Songwriter {
    AudioClip strum();
    void actSensivitve();
}

인터페이스는 추상클래스보다 추상화 정도가 높아 일반 메서드나 멤버변수를 구성원으로 가질 수 없습니다. 오직 추상 메서드와 상수만을 허용합니다.

  • 인터페이스 작성시 모든 멤버변수는 public static final이어야 하며, 이를 생략할 수 있다.
  • 모든 메서드는 public abstract이어야 하며, 이를 생략할 수 있다.

추상클래스가 상속을 통해 추상 메서드를 완성하는 것 처럼, 인터페이스도 추상메서드를 구현하는 클래스를 작성해야 합니다. 다만 클래스는 확장한다는 의미의 키워드 extends를 사용하지만, 인터페이스는 구현한다는 의미의 implements를 사용합니다.


interface TV {
    void show();
}

interface VCR {
    void scanTape();
}

interface TVCR extends TV, VCR {

}

class LGTV implements TVCR {
    public void show() { ... }
    public void scanTape() { ... }
}

Reference

  • Java의 정석
  • Effective Java

'Language' 카테고리의 다른 글

[Java] orElse 와 orElseGet의 차이점  (0) 2022.08.25
[Java] StringBuilder vs StringBuffer  (0) 2022.08.22
[Java] 객체지향 프로그래밍 - 2  (0) 2022.08.22
[Java] 객체지향 프로그래밍 - 1  (0) 2022.08.22
[Java] Collection Framework  (0) 2022.08.22

What is FPGA?

  • Field Programmable은 장치를 언제 어디서나 프로그래밍 할 수 있음을 뜻합니다.
  • Gate Array는 논리 게이트의 규칙적인 배열을 의미합니다. FPGA에서는 논리게이트들의 interconnect로 기능을 구현할 수 있습니다.

FPGA vs ASIC

  • Reporgrammable
    • design 또는 prototype을 만들고 검증할때 계속 업그레이드가 가능합니다.
  • Multiple bitstreams
    • 앞선 Reprogrammable의 연장선으로 ASIC은 개발이 완료되어 출시되면 하나의 기능만 가능하지만, 다양한 bit streams을 올릴 수 있음.
  • Cost of Bug fix
    • HW에서의 bug fix가 가능합니다.

무엇보다도 FPGA는 수많은 logic block을 서로 연결하여 대규모 병렬, 실시간 처리를 이루어낼 수 있다는 점에서 큰 장점을 가집니다.

What is ASIC?

응용 프로그램별 집적 회로 또는 ASIC는 다양한 시장의 특정 응용 분야를 위해 설계 및 구현되는 IC입니다. ASIC는 기업이 BOM(Bill of Material) 비용을 절감하고 성능 요구사항을 개선하고자 할 때 매우 비용 효율적입니다. 일반적으로 ASIC를 reversing하는 것은 매우 어렵습니다. 장치를 개발하는 데 사용되는 독점적인 전자 부품 때문입니다. 회사에서 자체 설계에 개별 또는 표준 IC를 사용하는 경우 경쟁업체가 유사 제품을 개발하는 것이 더 쉽습니다. ASIC는 전체 제품 수명 동안 일관된 논리 기능을 가져야 하는 제품에 가장 적합합니다.

Architecture

Introductino to FPGA Resource

img

FPGA는 logic block, I/O cells, interconnection resources로 구성되어 있습니다.

  • CLB(Configurable Logic Block)
    • CLB는 FPGA의 기본적인 logic 리소스로, routing으로 통해 연결되어 복잡한 logic function을 수행할 수 있습니다.
    • Flip-Flop : FPGA에서의 가장 작은 storage resource로 각 CLB의 flip-flop은 clock cycle사이에서의 논리적인 상태를 저장하는데 사용되는 binary register입니다.
    • Look-up Table(LUT) : 모든 combination function을 수정할 수 있으며, 입력과 출력의 정의를 table로 작성합니다. 즉, 입력값 조합에 대해 원하는 출력 값을 포함하는 작은 메모리라 볼 수 있습니다.
    • Multiplexer : 두 개 이상의 입력 중에서 선택한 입력을 반환하는 회로입니다.
  • I/O block FPGA 외부의 소자와 통신을 위해 IOB 사용
  • Routing(Promgrammable Interconnection)
    • CLB, IOB와 같은 FPGA내 function component의 입력과 출력 사이의 신호 path를 프로그래밍 할 수 있는 네트워크입니다.
    • Vertical Routing Channel
    • Horizontal Routing Channel

'Infra' 카테고리의 다른 글

[RocksDB] Introduction  (0) 2022.08.22
[RocksDB] RocksDB Install  (0) 2022.08.22
[FPGA]Introduction of FPGA acceleration  (0) 2022.08.22
[CUDA]Programming Model  (0) 2022.08.22
[Kafka] Kafka 커맨드라인  (0) 2022.08.22

CPU vs FPGA

보통 CPU 벤치마킹은 실행 시간과 실행 속도를 명시하지만 FPGA 성능은 데이터 처리량을 기준으로 하기 때문에 CPU와 FPGA를 단순 비교하기는 어렵습니다. 예를 들어 CPU에는 각각 2.4GHz로 실행되는 4개의 코어가 존재하지만, FPGA는 62MHz로 상대적으로 속도 측면에서 CPU에 비해 상당히 느리게작동하는것처럼 보입니다. 하지만 FPGA는 병렬처리 특성을 이용하여 throughput을 크게 향상시킵니다.

image

먼저 FPGA가 얼마나 빨리 실행될 수 있는가에 대한 문제보다는 FPGA가 처리할 수 있는 데이터 전송 속도에 집중해야 합니다. 위의 이미지를 데이터 흐름의 예로 보면 속도는 방정식 의 일부 변수에 불과하다는 점을 알 수 있습니다. 데이터는 FPGA로 들어오고, 병렬 처리되도록 분할되며, 데이터는 다양한 기능(논리 블록을 사용하여 설정)으로 공급됩니다. 그런 다음 FPGA는 처리된 데이터를 출력합니다. 이러한 작업은 실시간으로 일어납니다.

  1. 데이터는 카메라 출력 속도로 수신됩니다(62MHz).
  2. 8개의 병렬 프로세스로 분할되므로 데이터 전송 속도는 496MOPS(Million operations per second)입니다.
  3. 여러 처리 단계를 거칩니다. (2480 MOPS)
  4. 병렬이 제거되고 62MHz의 일정한 속도로 메모리에 출력됩니다.

'Infra' 카테고리의 다른 글

[RocksDB] RocksDB Install  (0) 2022.08.22
[FPGA]Introduction of FPGA  (0) 2022.08.22
[CUDA]Programming Model  (0) 2022.08.22
[Kafka] Kafka 커맨드라인  (0) 2022.08.22
[Kafka] EC2에 Kafka서버 만들기  (0) 2022.08.22

2.1 Kernels

// Kernel definition 
__global__ void VecAdd(float* A, float* B, float* C) {
    int i = threadIdx.x; 
    C[i] = A[i] + B[i]; 
} 

int main() {
    ... 
    // Kernel invocation with N threads 
    VecAdd<<<1, N>>>(A, B, C);
    ... 
}

CUDA에서는 C++의 함수를 지원하는데 이를 kernel이라 합니다.

kernel 은 __ global __ 이라는 specifier를 통해 정의하며 선언시 다음과 같은 형식을 따릅니다.

__global__ type fucntionName(arg1,arg2,...)

상기 코드의 main함수에서 VecAdd()라는 이름의 kernel을 사용하는것을 확인 하실수 있는데 C++의 일반적인 함수들과 달리 <<<..., ...>>> 와 같은 꺽쇠 세번안에 두개의 인자가 들어갑니다.

꺽쇠안의 첫번째 인자에는 CUDA block의 개수를 두번째 인자에는 block당 CUDA thread의 개수가 들어갑니다.

2.2 Thread Hierarchy

CPU의 thread와 마찬가지로 CUDA의 thread는 작업기본 단위입니다. CUDA에서의 thread는 Block단위로 묶이고, 다시 이 Block은 Grid단위로 묶입니다.

위의 그림과 같이 CUDA의 grid과 block은 1차원, 2차원 또는 3차원 세가지 구조로 정의 될수 있습니다.

다음은 행렬A,B의 합을 행렬C에 저장하는 예제 코드입니다.

// Kernel definition 
__global__ void MatAdd(float A[N][N], float B[N][N], float C[N][N]) { 
    int i = threadIdx.x;
    int j = threadIdx.y;
    C[i][j] = A[i][j] + B[i][j]; 
} 
int main() { 
    ... 
    // Kernel invocation with one block of N * N * 1 threads 
    int numBlocks = 1; 
    dim3 threadsPerBlock(N, N);
    MatAdd<<<numBlocks, threadsPerBlock>>(A, B, C);
    …
}

CUDA에서는 사전정의된 변수를 통해 Block과 Thread에 접근할 수 있습니다.

threadIdx.~ : block안의 thread에서 해당 thread의 인덱스를 가리킵니다.

blockIdX.~ : gird안의 block에서 해당 block의 인덱스를 가리킵니다.

blockDim.~ : block안의 thread의 개수를 가리킵니다.

이에 따라 2차원의 block을 사용할때 blockIdx.x * blockDim.x + threadIdx.x를 통해 특정 thread를 가리킬수 있습니다.

다음은 2차원의 block을 사용하여 앞선 코드와 동일한 동작을 하는 예제 코드입니다.

// Kernel definition
__global__ void MatAdd(float A[N][N], float B[N][N], float C[N][N]){
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    int j = blockIdx.y * blockDim.y + threadIdx.y;
    if (i < N && j < N)
        C[i][j] = A[i][j] + B[i][j];
}

int main(){
    ...
    // Kernel invocation
    dim3 threadsPerBlock(16, 16);
    dim3 numBlocks(N / threadsPerBlock.x, N / threadsPerBlock.y);
    MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);
    ...
}

MatAdd kernel에서 변수 i, j를 통해 thread에 접근하는 인덱스를 정의하였습니다.

main함수에서는 1차원의 block을 사용할때와 달리 numBlocks의 자료형을 dim3로 정의된것을 확인하실수 있습니다. dim3은 unsigned int 형의 구조체로 x,y,z 3개의 구조체 변수를 가집니다.

Dimension Block of size Thread ID of a thread of index
1 Dx(thread per block) x
2 Dx , Dy x + y Dx
3 Dx , Dy , Dz x + y Dx + z Dx Dy

2.3 Memory Hierarchy

img

CUDA에서 각 thread는 local메모리를 가지고 각 Thread Block은 block과 똑같은 life time을 갖고, block내의 모든 thread가 접근할 수 있는 shared memory를 가집니다. 또 모든 thread는 어느grid, block에 속하는지 관계없이 Global memory에 접근할 수 있습니다.

'Infra' 카테고리의 다른 글

[FPGA]Introduction of FPGA  (0) 2022.08.22
[FPGA]Introduction of FPGA acceleration  (0) 2022.08.22
[Kafka] Kafka 커맨드라인  (0) 2022.08.22
[Kafka] EC2에 Kafka서버 만들기  (0) 2022.08.22
WSL에서 mysql사용환경 만들기  (0) 2022.08.22

Goal

  • Facade 패턴에대한 이해
  • SRP원칙에 대한 이해

SRP : 단일 책임 원칙(Single responsibility principle)

객체는 변화해야 할 단 한가지 이유만을 가져야 합니다.

쉽게 생각하면 변경이 있을때 파급 효과가 적다면, 즉 수정해야 할 코드가 적다면 단일 책임 원칙을 잘 따른것이라 볼 수 있습니다.

다음과 같이 SRP위반 사례를 예시로 SRP를 지켜야 하는 이유에 대해 알아보겠습니다.

Problem 1 : Accidental duplication

다음은 급여 애플리케이션의 Employee 클래스입니다.

Employ 클래스에는 calculatePay(), reportHours(), save() 메서드가 존재하는데 각각은 회계팀, 인사팀, 전산팀이 주로 사용합니다.

개발자가 이 세 메서드를 Employee 단일 클래스에 배치하여 서로 결합되었습니다. 이로인해 각 엑터의 결정이 다른 엑터가 의존하는 부분에 영향을 줄 수 있게 됩니다.

class Employee {
    int hour;

    // Actor : 회계팀
    public int calculatePay() { 
        ...
        regularHour();
        ... 
    }

    // Actor : 인사팀
    public void reportHours() { 
        ...
        regularHour();
        ... 
    }

    // Actor : 전산팀
    public void save() { ... }

    // 초과 업무 시간을 제외한 업무시간 계산
    private int regularHour() { ... }
}

calculatePay()reportHours()는 초과 업무 시간을 제외한 업무시간을 계산하는 regularHour()를 공유합니다. 회계팀에서 초과 업무 시간을 제외한 업무시간 계산 방식을 변경하고자 regularHour()를 수정하게 되면 기존의 방식을 그대로 사용하던 인사팀은 이에 영향을 받게 되고, reportHours()는 기존의 방식대로 동작하지 못하게 됩니다.

이러한 문제는 서로 다른 액터가 의존하는 코드를 너무가까이(단일 클래스)에 배치했기 때문에 발생합니다.

Problem 2 : Merge

인사팀에서는 reportHours()를 전산팀에서는 Employee의 스키마를 수정하기로 동시에 결정합니다. 각 팀의 수정사항이 반영될 때 충돌 즉, 병합이 발생하게 됩니다.

Solve

이러한 문제를 해결 하기 위해서는 데이터를 가지고 있는 EmployeeData클래스와 각 메서드를 가지고 있는 클래스로 분리하고 메서드를 가진 각 클래스들이 EmployeeData클래스를 공유하도록 하면 됩니다. 하지만 이경우 개발자가 메서드를 가진 세 클래스를 인스턴스화 하고 추적해야한다는게 단점입니다.

이경우 퍼사드 패턴(Facade Pattern)을 적용하여 클래스의 생성하여 인스턴스의 생성을 위임할 수 있습니다. 퍼사드 패턴은 서브 클래스간의 통합 인터페이스를 제공하는 역할로 클라이언트는 퍼사드 객체에서 제공하는 메서드를 호출함으로써 복잡한 서브클래스 사용을 간편하게 할 수 있습니다.

class EmployeeData {
    //data(공유자원)
    int hour;

    private EmployeeData() {
        this.hour = 0;
    }

    private static class LazyHolder {
        static final EmployeeData EMPLOYEE_DATA = new EmployeeData();
    }

    public static EmployeeData getInstance() {
        return LazyHolder.EMPLOYEE_DATA;
    }
}

class PayCalculator {
    final EmployeeData data = EmployeeData.getInstance();

    void calculatePay() {
        // 예제 코드
        data.hour += 1;
    }
}

class HourReporter {
    final EmployeeData data = EmployeeData.getInstance();

    void reportHours() {
        // 예제 코드
        data.hour += 1;
    }
}

class EmployeeSaver {
    final EmployeeData data = EmployeeData.getInstance();

    void save() {
        // 예제 코드
        data.hour += 1;
        System.out.println(data.hour);
    }
}

class EmployeeFacade {
    private final PayCalculator payCalculator;
    private final HourReporter hourReporter;
    private final EmployeeSaver employeeSaver;

    public EmployeeFacade() {
        payCalculator = new PayCalculator();
        hourReporter = new HourReporter();
        employeeSaver = new EmployeeSaver();
    }

    public void calculatePay() {
        payCalculator.calculatePay();
    }

    public void reportHour() {
        hourReporter.reportHours();
    }

    public void save() {
        employeeSaver.save();
    }
}

public class Facade {
    public static void main(String[] args) {
        EmployeeFacade employeeFacade = new EmployeeFacade();

        employeeFacade.calculatePay();
        employeeFacade.reportHour();
        employeeFacade.save();
    }
}

Singleton pattern을 적용하여 공유자원인 EmployeeData에 대한 thread-safety를 확보하였고, EmployeeFacade 클래스를 통해 객체의 생성을 위임하여 개발자가 메서드를 가진 세 클래스에 대한 인스턴스화를 하지 않아도 되도록 설계를 하였습니다.

다만 이러한 Facade 패턴을 관련 없는 메소드를 엮기 위해 사용할 경우 God-object가 될 수 있으므로 성능향상(관련없는 객체도)과 유지보수성(관련있는 객체만)의 Trade-off를 잘 고려하여 사용하여야 합니다.

Reference

'CS' 카테고리의 다른 글

[Network] 웹소켓(Websocket)  (0) 2022.10.09
[OS] 데드락의 탐지와 해결방법  (1) 2022.10.03
[Design Pattern] Singleton Pattern  (0) 2022.08.22
[OS] Scheduler  (0) 2022.08.22
[OS] RAM disk vs Disk 성능비교  (0) 2022.08.22

+ Recent posts