본문 바로가기

프로그래밍/JAVA

디자인 패턴 몇가지 정리

 

 

디자인 패턴을 쓰는 이유?

 

 

여러 프로그래머들의 경험과 지혜가 모여 문제를 해결하는 데 도움이 되도록 발전된 디자인이기 때문.

 

그리고 어떤 문제에 대해 해결책을 논할 때 디자인 패턴이 간결한 용어모음을 제공한다는 점 때문.

 

하지만 디자인 패턴을 잘못 쓰면 애플리케이션이 쓸 데 없이 복잡해질 수 있고, 비효율적으로 구현되버려 버그가 생기거나 성능이 나빠질 수도 있다. 

 

————————

 

 

싱글톤

 

 

인스턴스가 최대 한개를 넘지 않도록 한다

한번만 생성되었으므로 메모리 방지를 막을 수 있고, 다른 객체들이 자원 공유를 하기 좋다.

캐시, DBCP, 로그 객체 등에서 싱글톤 패턴을 사용한다.

 

다른 인스턴스들간에 결합도가 높아져 수정이 어렵고 테스트하기 어려워진다.

동기화 주의해야 한다.

#

 

1.

public class Logger {

private static final Logger instance = new Logger();

    

    private Logger(){}

    

    public static Logger getInstance() { return instance; }

    ...

   

}

 위 코드의 문제점은 인스턴스가 여러 개 생길 수 있다는 점이다. 

 

 

public class See {

 

public static void main(String[] args) {

    Logger l1 = Logger.getInstance();

        Logger l2 = null;

        

        try {

        Constructor[] constructors = Logger.class.getDeclaredConstructors();

            for (Constructor c : constructors) {

            // 문제의 코드!

            c.setAccessible(true);

                l2 = (Logger) c.newInstance();

                break;

            }

        } catch (Exception e) {

        

        }

        

        System.out.println("l1.hashCode(): " + l1.hashCode());

        System.out.println("l2.hashCode(): " + l2.hashCode());

    }

}

hashCode값이 다르게 나오는데, 오브젝트가 2개란 소리다. 

 

public class Test {

public static void main(String[] args) {

    Logger l1 = Logger.getInstance();

        Logger l2 = (Logger).l1.clone();

    

    }

}

만약 Cloneable 인터페이스를 가진 싱글턴 클래스였다면 위 코드로 인해 여러개의 객체가 생성될 것이다.

#

 

 

2. 

지연 초기화 하는 방식으로 수정

public class Logger {

 

private static Logger instance = null;

    

    private Logger(){}

    

    public syncronized static Logger getInstance() {

    if (instance == null) {

        instance = new Logger();

        } 

        

        return instance;

    }

}

thread-safe를 위해 syncronized 키워드를 사용하였다. 

하지만 이 방식은 getInstance 호출시점마다 동기화로 인해 성능적인 면에서 단점이 있다. 

#

 

 

3.

정적 지연 초기화

public class Logger {

private Logger() {}

    

private static class LoggerHolder {

public static final Logger instance = new Logger();

}

 

public static Logger getInstance() { return LoggerHolder.instance; }

}

 인스턴스를 정적으로 초기화 하는 내부 클래스를 지연 로딩하는 방법.

Logger가 JVM에 로드되면서 초기화 과정을 거칠때 LoggerHolder는 초기화 되지 않는다.

최초로 getInstance()가 호출되면 그제서야 LoggerHolder가 초기화 되고 로드된다.

이 때 Logger instance가 static 키워드를 가지므로 한번만 호출되고 final이므로 딱 한번 값이 할당된다. 

즉, JVM 에서 class를 로드하는 시점을 이용한 방법이고, thread-safe하다. 

#

 

————————

 

그외 패턴들.. 

 

빌더

 

// 점층적 생성자 패턴 코드의 예 : 회원가입 관련 코드

public class Member {

 

    private final String name;      // 필수 인자

    private final String location;  // 선택적 인자

    private final String hobby;     // 선택적 인자

 

    // 필수 생성자

    public Member(String name) {

        this(name, "출신지역 비공개", "취미 비공개");

    }

 

    // 1 개의 선택적 인자를 받는 생성자

    public Member(String name, String location) {

        this(name, location, "취미 비공개");

    }

 

    // 모든 선택적 인자를 다 받는 생성자

    public Member(String name, String location, String hobby)   {

        this.name = name;

        this.location = location;

        this.hobby = hobby;

    }

}

 

// 호출 코드만으로는 각 인자의 의미를 알기 어렵다.

NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 3, 35, 27);

NutritionFacts pepsiCola = new NutritionFacts(220, 10, 110, 4, 30);

NutritionFacts mountainDew = new NutritionFacts(230, 10);

 

// Effective Java의 Builder Pattern

public class NutritionFacts {

    private final int servingSize;

    private final int servings;

    private final int calories;

    private final int fat;

    private final int sodium;

    private final int carbohydrate;

 

    public static class Builder {

        // Required parameters(필수 인자)

        private final int servingSize;

        private final int servings;

 

        // Optional parameters - initialized to default values(선택적 인자는 기본값으로 초기화)

        private int calories      = 0;

        private int fat           = 0;

        private int carbohydrate  = 0;

        private int sodium        = 0;

 

        public Builder(int servingSize, int servings) {

            this.servingSize = servingSize;

            this.servings    = servings;

        }

 

        public Builder calories(int val) {

            calories = val;

            return this;    // 이렇게 하면 . 으로 체인을 이어갈 수 있다.

        }

        public Builder fat(int val) {

            fat = val;

            return this;

        }

        public Builder carbohydrate(int val) {

            carbohydrate = val;

            return this;

        }

        public Builder sodium(int val) {

            sodium = val;

            return this;

        }

        public NutritionFacts build() {

            return new NutritionFacts(this);

        }

    }

 

    private NutritionFacts(Builder builder) {

        servingSize  = builder.servingSize;

        servings     = builder.servings;

        calories     = builder.calories;

        fat          = builder.fat;

        sodium       = builder.sodium;

        carbohydrate = builder.carbohydrate;

    }

}

 

 

// 각 줄마다 builder를 타이핑하지 않아도 되어 편리하다.

NutritionFacts cocaCola = new NutritionFacts

    .Builder(240, 8)    // 필수값 입력

    .calories(100)

    .sodium(35)

    .carbohydrate(27)

    .build();           // build() 가 객체를 생성해 돌려준다.

 

#

 

 

팩토리메서드 

 

public class TypeFactory {

    public Type createType(String type){

        Type returnType = null;

        switch (type){

            case "A":

                returnType = new TypeA();

                break;

 

            case "B":

                returnType = new TypeB();

                break;

 

            case "C":

                returnType = new TypeC();

                break;

        }

 

        return returnType;

    }

}

...

 

TypeFactory factory = new TypeFactory();

Type returnType = factory.createType(type);

 

객체 생성부분을 팩토리 클래스에 위임. 객체간 결합도가 낮아지고 유지보수에 좋음

#

 

 

추상팩토리

 

img1

img2

클라이언트는 내부 구현은 신경쓰지 않고 추상 클래스를 활용하면 된다. 

실제 객체들은 각 추상 클래스를 구현한 콘크리트 구현체에서 구현하고 리턴한다. 

 

 

#

 

 

 

반복자 (Iterator)

 

출처: 디자인패턴 반복자 패턴 (Iterator Pattern) :: 기술과 인문, 그리고 인

자바의 컬렉션은 다양한 자료구조를 통해 데이터를 관리한다. 

각자 다른 방식으로 접근한다면 매우 불편할 것.

반복자 패턴은 모든 집합체에 대해 동일한 인터페이스로 모든 원소를 순회활 수 있도록 한다. 

Iterator 인터페이스를 구현한 클래스를 만들어 순회활 수 있는 기능을 정의해야 한다. 

public class Sheep implements Animal {

    @Override

    public void eat() {

        System.out.println("길쭉한 풀 뜯어먹음");   

    }

 

    @Override

    public void sleep() {

        System.out.println("앉아서 잠"); 

    }

 

    @Override

    public void sound() {

        System.out.println("메에에");

    }

}

 

public class Shepherd {

    public static final int MAX_SHEEP = 100;

    private int sheepNum = 0;

    public static final  Sheep[] SHEEPS = new Sheep[MAX_SHEEP];

 

    public Shepherd() {

        super();

        int i;

        for (i = 0; i < 30; ++i) {

            SHEEPS[i] = new Sheep();

        }

        sheepNum = i;

    }

    

    public void addGoat() {

        SHEEPS[++sheepNum] = new Sheep(); 

    }

    

    public void removeGoat() {

        SHEEPS[sheepNum--] = null; 

    }

 

    public Iterator<Sheep> createIterator() {

        return new SheepIterator(SHEEPS);

    }

 

    public int getSheepNum() {

        return sheepNum;

    }

}

 

public class SheepIterator implements Iterator<Sheep> {

    private Sheep[] sheeps;

    int position = 0;

 

    public SheepIterator(Sheep[] sheeps) {

        super();

        this.sheeps = sheeps;

    }

 

    @Override

    public boolean hasNext() {

        if (position >= sheeps.length || sheeps[position] == null) {

            return false;

        } else {

            return true;

        }

    }

 

    @Override

    public Sheep next() {

        Sheep tempSheep = sheeps[position];

        position++;

        return tempSheep;

    }

}

...

 

public static void main(String[] args) {

        Goatherd goatherd = new Goatherd();

        Shepherd shepherd = new Shepherd();

        

        Iterator<Integer> goatIter = goatherd.createIterator();

        Iterator<Sheep> sheepIter = shepherd.createIterator();

        

        while(goatIter.hasNext()) {  

            Goat goat = goatherd.GOATS.get(goatIter.next());

            goat.sound();

        }

        

        while(sheepIter.hasNext()) {

            Sheep sheep = sheepIter.next();

            sheep.sound();

        }

    }

 

자바를 뜯어보면 컬렉션 인터페이스를 구현하는 모든 객체는 Iterator를 구현하고 있다.

public interface Collection<E> extends Iterable<E>

...

ListIterator<E> listIterator();

ListIterator<E> listIterator(index);

...

 

 

// ArrayList

 

public Iterator<E> iterator() { return listIterator(); }

// 함수를 정의하고 있음

public ListIterator<E> listIterator(final int index) {

...

}

 

 

 

#

 

 

 

옵저버

 

 

 

 

 

 

 

데코레이터

'프로그래밍 > JAVA' 카테고리의 다른 글

자바 Thread 순서 보장하기  (0) 2020.05.26
Java trouble shooting  (0) 2020.04.03
Thread Dump & Heap Dump  (0) 2019.08.31
자바8 Optional  (0) 2019.04.23
WebFlux & Non-blocking  (0) 2019.04.22