디자인 패턴을 쓰는 이유?
여러 프로그래머들의 경험과 지혜가 모여 문제를 해결하는 데 도움이 되도록 발전된 디자인이기 때문.
그리고 어떤 문제에 대해 해결책을 논할 때 디자인 패턴이 간결한 용어모음을 제공한다는 점 때문.
하지만 디자인 패턴을 잘못 쓰면 애플리케이션이 쓸 데 없이 복잡해질 수 있고, 비효율적으로 구현되버려 버그가 생기거나 성능이 나빠질 수도 있다.
————————
싱글톤
인스턴스가 최대 한개를 넘지 않도록 한다
한번만 생성되었으므로 메모리 방지를 막을 수 있고, 다른 객체들이 자원 공유를 하기 좋다.
캐시, 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하다.
#
————————
그외 패턴들..
빌더
- 출처: link
// 점층적 생성자 패턴 코드의 예 : 회원가입 관련 코드
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() 가 객체를 생성해 돌려준다.
#
팩토리메서드
- 출처: link
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);
객체 생성부분을 팩토리 클래스에 위임. 객체간 결합도가 낮아지고 유지보수에 좋음
#
추상팩토리
클라이언트는 내부 구현은 신경쓰지 않고 추상 클래스를 활용하면 된다.
실제 객체들은 각 추상 클래스를 구현한 콘크리트 구현체에서 구현하고 리턴한다.
#
반복자 (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 |