* 어노테이션(Annotation) 이란 ?

- 클래스나 메소드 등의 선언 시에 @ 를 사용하는 것을 말한다.

참고로 클래스, 메소드, 변수 등 모든 요소에 선언할 수 있다.

메타 데이터(Metadata) 라고도 불리며, JDK 5 부터 등장했다고 한다.



* 언제 사용할까?

- 컴파일러에게 정보를 알려주거나

- 컴파일 할 때와 설치 시의 작업을 지정하거나

- 실행할 때 별도의 처리가 필요할 때



* 일반적으로 사용가능한 Annotation 3가지 (JDK 6 기준)


(1) @Override

- 해당 메소드가 부모 클래스에 있는 메소드를 오버라이드 했다는 것을 명시적으로 선언한다.

자식 클래스에 메소드가 여러 개 있을 때 어떤 메소드가 오버라이드 된 것인지

쉽게 알 수 있고, 깜빡하고 빼먹은 매개변수가 있는지 컴파일러에게 알려달라고 생각하면 된다.

이클립스 같이 편리한 개발툴을 사용하다 보면 자주 볼수 있는 키워드다.


(2) @Deprecated

- 더 이상 사용되지 않은 클래스나 메소드 앞에 추가해준다.

'그냥 지워버리면 되는 거 아닌가?' 라고 생각할 수 있지만 여러 사람이 개발하는 프로젝트에서

갑자기 있던 걸 훅 지워버리면... 욕 엄청 먹을 수 있다ㅋ 이런 알림을 통해 서서히 지우자.

나의 경우 안드로이드 개발할 때 API 문서에서 이 키워드를 몇 번 본 적이 있다.


(3) @SuppressWarnings

- 프로그램에는 문제가 없는데 간혹 컴파일러가 경고를 뿜을 때가 있다.

내 성격상 경고 뿜어대는 게 은근 신경쓰이고 거슬릴 때가 있다. 

그럴 때는 이 Annotation을 추가해서 컴파일러에게 

'나도 알고 있고 일부러 그런 거다 그러니 좀 닥치고 있어'

라고 말해주는 거라고 생각하면 된다. 하지만 거슬린다고 너무 남용하진 말자.


- 참고로 다른 Annotation 과 다르게 속성값을 지정해 줄 수도 있다.

ex) @SuppressWarnings("deprecation"), @SuppressWarnings("serial")

개발하다보면 특히 저 serial 어쩌구 저쩌구를 자주 보는데 그럴 때는

http://blog.naver.com/fochaerim/70105895049 여기 링크를 통해 해결하자.



* Meta Annotation

- Annotation 을 선언할 때 사용한다. 

메타 어노테이션은 다음과 같이 4가지가 존재하고 반드시 모두 사용할 필요는 없다.


(1) @Target 

- 어떤 것에 어노테이션을 적용할 지 선언할 때 사용한다. 적용 가능 대상은 아래와 같다.


요소 타입 

대상 

CONSTRUCTOR 

생성자 선언시 

FIELD 

enum 상수를 포함한 필드값 선언시 

LOCAL_VARIABLE 

지역 변수 선언시 

METHOD 

메소드 선언시 

PACKAGE 

패키지 선언시 

PARAMETER 

매개 변수 선언시 

TYPE 

클래스, 인터페이스, enum 등 선언시 


(2) @Retention

- 얼마나 오래 어노테이션 정보가 유지되는 지를 선언한다.


대상

SOURCE

어노테이션 정보가 컴파일시 사라짐 

CLASS 

 클래스 파일에 있는 어노테이션 정보가 컴파일러에 의해 참조 가능함.

 하지만 가상 머신에서는 사라짐.

RUNTIME 

실행시 어노테이션 정보가 가상 머신에 의해서 참조 가능 


(3) @Documented

- 해당 어노테이션에 대한 정보가 JavaDocs(API) 문서에 포함된다는 것을 선언한다.


(4) @Inherited

- 모든 자식 클래스가 부모 클래스의 어노테이션을 사용할 수 있다는 것을 선언한다.


+ 추가로 @interface 도 존재한다.

이 어노테이션은 어노테이션을 선언할 때 사용한다.



솔직히 메타 어노테이션은 당최 무슨 말인지 잘 이해가 안 갔다.

이럴 때는 코드를 보는 게 더 빠르니 예제 코드 ㄱㄱ~

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAnnotation {
    public int number();
    public String text() default "This is first annotation"
}

@interface로 선언했으니 이제 @UserAnnotation 으로 사용 가능한

어노테이션이 하나 만들어진 셈이다.


이제 만든 것을 사용해보고, 어노테이션에 선언한 값들을 확인해보자.

@UserAnnotation(number=0)
public class UserAnnotationSample 
{
    @UserAnnotation(number=1)
    public static void main(String[] args) {
        UserAnnotatonSample sample = new UserAnnotation();
        sample.checkAnnotations(UserAnnotationSample.class);
    }

    @UserAnnotation(number=2)
    public void annotationSample1() {
    }
    @UserAnnotation(number=3, text="second")
    public void annotationSample2() {
    }
    @UserAnnotation(number=4, text="third")
    public void annotationSample3() {
    }

    public void checkAnnotations(Class useClass){
        Method[] methods = useClass.getDeclaredMethods();
        for ( Method tempMethod:methods ){
            UserAnnotation annotation = tempMethod.getAnnotation(UserAnnotation.class);
            if ( annotation != null ){
                int number = annotation.number();
                String text = annotation.text();
                System.out.println(tempMethod.getName() + "() : number=" + number + " text=" + text);
            } else {
                System.out.println(tempMethod.getName() + "() : annotation is null");
            }
        }
    }
}

바로 위의 checkAnnotations() 에서

Class, Method 라는 것은 Java 의 Reflection 이라는 API 에서 제공하는 클래스들이라고 한다.


그리고 결과 값은 아래와 같이 얻을 수 있다.


checkAnnotations() : annotation is null

annotationSample1() : number=2 text=This is first annotation

annotationSample2() : number=3 text=second

annotationSample3() : number=4 text=third

main() : number=1 text=This is first annotation



* Annotation 은 상속이 안 된다.

- enum 클래스가 상속을 지원하지 않듯이 Annotation 도 마찬가지로 상속이 안 되므로 확장이 불가능하다. 



어노테이션을 직접 만들 일은 거의 없지만...

그래도 상위의 Annotation 3개는 꼭 알아두자 - @Override, @Deprecated, @SuppressWarnings

나같이 초보 개발자도 자주 마주치는 키워드라서 그리 생소하지 않은 요소이다.



by kelicia 2014. 5. 24. 18:05


Java 에서는 클래스 안에 클래스가 들어갈 수 있다.

이를 Nested Class 라고 부르고 다음과 같이 분류할 수 있다.



* NestClass 를 만드는 이유 :

1) 코드를 간단하게 표현하기 위함

1-1) 소스의 가독성과 유지보수성을 높이고 싶을 때

2) Static Nested Class : 

- 한 곳에서만 사용되는 클래스를 논리적으로 묶어서 처리할 필요가 있을 때

3) Inner Class : 

- 캡슐화가 필요할 때 (ex. class A에 private 변수가 있다고 하자. 

이 변수에 접근하고 싶은 class B를 선언하고 B를 외부에 노출시키고 싶지 않을 경우가 이에 속한다) 



그런데 오해가 있을 수 있는데 아래의 코드를 보자. (PublicClass.java)

public class PublicClass {
}
class JustNotPublicClass {
}

1개의 자바 파일 안에 2개의 클래스가 선언되어도 문제될 건 없다.

하지만 JustNotPublicClass 는 단순히 Public Class 가 아닐 뿐이지, 이를 내부 클래스라고 할 수 없다.



* Static Nested Class :

Static 이기 때문에 외부 클래스 변수라고 다 참조하지 않는다. static 변수만 참조 가능.

길게 설명할 것 없이 어떻게 선언되는지 살펴보자.

public class OuterOfStatic { 
    static class StaticNested { 
        private int value = 0; 
        public int getValue() { 
            return value; 
        } 
    }
}

위의 클래스를 컴파일 하면 2개의 클래스가 만들어진다.

(OuterOfStatic.class, OuterOfStatic$StaticNested.class)


이렇게 선언해놓고 StaticNested 클래스 객체를 만들어 보겠다.

public static main(String[] args) { 
    OuterOfStatic.StaticNested staticNested = new OuterOfStatic.StaticNested(); 
    System.out.println(staticNested.getValue()); 
}

위와 같이 Static스럽게(?) 객체를 생성하면 된다.


왜 이렇게까지 하면서 만들어야 될까?

답변을 하자면, 클래스를 묶어서 가독성을 높이기 위해서이다.



* Inner Class :

여기서는 객체 생성 방법이 조금 다르다.

public class OuterOfInner { 
    class Inner { 
        private int value = 0; 
        public int getValue() { 
            return value; 
        } 
    }
}
public static main(String[] args) {
    OuterOfInner outer = new OuterOfInner();
    OuterOfInner.Inner inner = outer.new Inner();
    System.out.println(inner.getValue()); 
}


캡슐화를 위해 Inner Class 를 사용한다고 했었다.

A 클래스에서 어떤 공통적인 작업을 수행하는 B 클래스가 필요한데,

다른 클래스에서는 B 클래스가 전혀 필요가 없을 때는 이렇게 내부 클래스를 만들어 사용한다.


내부 클래스는 GUI 관련 프로그램을 개발할 때 가장 많이 사용된다고 한다.

(ex. EventListener)



다음은 익명 내부 클래스(Anonymous Inner Class)의 예제 코드이다.

public class NestedSample {
    public void setButtonListener() {
        Button btn = new Button();
        btn.setListener(new EventListener() {
            public void onClick() {
                System.out.println("Button was Clicked");
            }
        });
    } 
}

예제 코드라 코드가 조금 요상하지만_-_ 

어쨌든 주목할 부분은 Line 4 부터다. Java 로 GUI 코딩해보신 분들은 익숙할 법한 코드이다.


위와 같이 클래스의 이름도 없고, 객체의 이름도 없으면 외부에서 참조할 수가 없다. 


만약 재사용하고 싶다면

EventListener listener = new EventListener(){ // 생략 };

위와 같이 선언하면 된다. 또는 일반 내부 클래스처럼 만들어서 재사용해도 된다.



* 익명 클래스의 장점 :

클래스를 만들고 그 클래스를 호출하면 그 정보는 메모리에 올라간다.

즉, 클래스를 많이 만들면 만들수록 메모리는 많이 필요해지고 애플리케이션을 시작할 때 

더 많은 시간이 소요된다. 따라서 자바에서는 이렇게 익명으로 객체 생성이 가능하게끔 되어있다.


익명 클래스나 내부 클래스는 모두 다른 클래스에서 재사용할 일이 없을 때 만들어야 한다.

가독성을 높일 수 있다는 장점이 있지만 남용할 경우 가독성을 떨어뜨리기 때문에 주의하자.



* Nested Class 의 특징 :

- Static Nested 클래스는 외부 클래스의 static 변수만 참조할 수 있다.

- 그 외의 내부 클래스(Inner, Anonymous)는 외부 클래스의 어떤 변수라도 참조할 수 있다. (private도 가능!)

- 반대로 외부 클래스도 내부 클래스(Static, Inner, Anonymous)의 인스턴스 변수에 모두 접근할 수 있다.


위의 특징들은 꼭 기억하자.


by kelicia 2014. 5. 22. 18:03


이번 장에서는 한 차원 높은 단계의 캡슐화를 다루는데,

바로 메소드 호출을 캡슐화하는 것이다. 


이를 활용해서 스케줄링, 로그 기록 시스템을 디자인할 수도 있고

취소(undo) 기능을 구현하기 위해 재사용할 수도 있다.


* Command Pattern :

요구 사항을 객체로 캡슐화 할 수 있으며, 매개변수를 써서 여러가지 다른 요구 사항을 집어넣을 수도 있다.

또한 요청 내역을 큐에 저장하거나 로그로 기록할 수도 있고 작업 취소 기능도 지원이 가능하다.

요청을 하는 객체그 요청을 수행하는 객체를 분리하고 싶다면 이 패턴을 사용하자!



Command 패턴을 이해하기 위해 간단하게 예를 들어보겠다.

다음의 그림은 식당에서 클라이언트의 주문이 실제 수행되는 과정이다.

(출처 : http://www.prayer-laputa.com/blog/archives/370)


웨이트리스는 클라이언트로부터 주문(Order)를 받아 - takeOrder()

주방장에게 그 주문을 전달해준다. - orderUp()

그러면 주방장은 주문서에 따라서 음식을 준비할 것이다. - make...()


여기서 주문서가 주문한 메뉴를 캡슐화 한다는 것을 숙지하자.


그리고 위 그림을 읽는 방법을 설명하자면

1) Client 는 Order 의 createOrder() 를 호출.

2) Order 는 웨이트리스의 takeOrder() 를 호출.

3) 웨이트리스는 Order 의 orderUp() 을 호출.

4) Order 는 주방장의 make...() 을 호출.

- 3) 이 이해가 잘 안 갈 수도 있지만 너무 신경안써도 된다. 흐름만 이해하자.



위 그림을 어느 정도 이해한 다음에

Command 패턴 다이어그램으로 넘어가보자.


위에서 보았던 다이어그램과 매우 유사하다는 것을 느낄 수 있다.


이해를 위해 설명을 하자면,

① Client 는 Command 객체를 생성한다.

② Invoker 에 ① 에서 생성한 Command 객체를 저장하기 위해 Client 는 setCommand() 를 호출한다.

③ 차후 Client 는 Invoker 에게 그 Command 객체를 실행시켜달라고 요청한다.




- Command 패턴의 흐름을 이해했다면 예제 코드로 넘어간다.

책에서는 가정 내에서 사용하는 전자제품들을 컨트롤할 수 있는 리모컨 API 를 디자인하는 예를 들었다.

아래 Class Diagram 안 보이면 Click.

(참고로 나는 리모컨에 Undo 기능이 있는 건 처음본다)


Client :

Concrete Command 를 생성하고 Receiver를 설정한다.


- Invoker :

Command 들을 관리하고 있고 Command 의 execute() 를 호출해서 Command 객체에게

특정 작업을 수행해 달라고 요청을 한다.


- Command :

모든 Command 객체에서 구현되어야 하는 인터페이스.


- Concrete Command :

execute() 에서 Receiver 에 있는 Action 들을 호출해 요청받은 작업을 수행하도록 한다.

undo() 의 예를 들면, LightOnCommand 의 경우 undo() 의 내용은 light.off() 정도 될 것이다.


- Receiver :

요구 사항을 처리하기 위해 어떤 일을 수행해야 하는지 알고 있다.


- Macro Command :

여러 Command 들을 한번에 실행시킬 수 있는 새로운 종류의 Command. 

아래의 예제 코드를 보자.

public class MacroCommand implements Command {
    Command[] commands;
    public MacroCommand(Command[] commands){
        this.commands = commands;
    }
    public void execute() {
        for ( int i=0; i < commands.length; i++ ){
            commands[i].execute();
        }
    }
}

- Null 객체 :

Client 에서 따로 null 처리를 하지 않아도 되도록 만들어 놓은 객체.

execute(), undo() 모두 아무 일도 하지 않는다. 여러 디자인 패턴에서 유용하게 쓰인다고 한다.

public void onButtonWasPushed(int slot) {
    if ( onCommands[slot] != null ) {
         onCommands[slot].execute(); 
    }
}

Null 객체를 활용하면 클라이언트는 위의 코드처럼 작성할 필요가 없게된다.



Invoker 인 RemoteControl 클래스의 코드를 자세히 알고 싶다면 

이 포스팅 글 상단에 걸어놓은 링크를 따라가면 된다. 중국어의 압박이 있긴하지만 코드는 읽을 수 있으니까.



* Command 패턴 활용 :

(1) 요청을 Command 객체에 캡슐화해서 작업 큐(Queue) 에 저장하기

큐의 한쪽은 커맨드를 추가, 다른 한쪽은 커맨드를 처리하기 위한 스레드(Thread)들이 대기하고 있다고 하자.

각 스레드에서는 우선 커맨드의 execute() 메소드를 호출하고, 이 작업을 완료하면 커맨드 객체를

보내버리고 새로운 커맨드 객체를 가져오는 작업을 하게 된다.


위와 같은 방법으로 스케줄러, 스레드 풀, 웹 서버 등에 활용할 수 있다.


(2) 요청을 로그(Log)에 기록하기

어떤 애플리케이션에서는 모든 행동을 기록해놨다가 그 애플리케이션이 다운되었을 경우,

나중에 그 행동들을 다시 호출해서 복구를 할 수 있도록 해야 한다.

이를 위해 Command 인터페이스에 execute(), undo() 이외에 store(), load() 를 추가하자.


어떤 명령을 실행하면서 디스크에 실행 히스토리를 기록해 애플리케이션이 다운되면

커맨드 객체를 다시 로딩하고 execute() 메소드들을 자동으로 순서대로 실행하면 된다.


매번 저장하기 힘든 경우에는 마지막 체크 포인트 이후로 한 모든 작업을 저장한 다음에

다운되었을 때 기존 체크 포인트에 최근 수행된 작업을 다시 적용하는 방법을 사용하면 된다.


위와 같은 테크닉을 확장해서 DB의 트랜잭션을 활용해 commit, rollback 연산을 구현할 수 있을 것이다.



* 객체지향의 원칙 :

1) 바뀌는 부분은 캡슐화 한다.

2) 상속보다는 구성을 활용한다.

3) 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.

4) 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.

5) 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다(OCP).

6) 추상화된 것에 의존해라. 구상 클래스에 의존해서는 안 된다(의존성 뒤집기 법칙).



* 이 장에서의 정리 :

- Command 패턴을 이용하면 요청을 하는 객체와 그 요청을 수행하는 객체를 분리할 수 있다.

- 또한 작업 취소(Undo) 기능을 지원할 수 있고, 로그나 트랜잭션 시스템 구현에도 활용된다.

- Command 객체는 Action 을 수행하는 Receiver 를 캡슐화 한다.

- Command 객체의 execute() 는 Receiver 의 Action 을 호출한다.

- Macro Command 는 여러 개의 Command 를 한꺼번에 호출할 수 있게 해주는 간단한 방법이다.

Macro Command 에서도 어렵지 않게 Undo() 기능을 지원할 수 있다.


by kelicia 2014. 5. 21. 20:46

* JAVA 의 접근 제어자 (Access Modifier)

- public : 

모든 클래스들이 접근 가능.

- protected : 

같은 패키지 내에 있거나 또는 상속받은 경우에만 접근 가능.

- package-private : 

아무런 접근 제어자를 명시해주지 않는 경우에 해당. 

같은 패키지 내에 있을 때만 접근 가능. (참고로 나는 default 제어자로 알고 있었다)

- private :

해당 클래스 내에서만 접근 가능.



* 클래스 접근 제어자 선언할 때 유의할 점

- 자바에서는 1개의 .java 파일 안에 여러 개의 클래스를 선언할 수 있다.

PublicClass.java

package test;
class PublicClass {
    public static void main(String[] args){
    }
}
class PublicSecondClass {
}

위의 코드 처럼 작성이 가능하고 컴파일도 정상적으로 된다.


package test;
public class PublicClass {
    public static void main(String[] args){
    }
}
class PublicSecondClass {
}

위 코드도 마찬가지이다.


하지만 아래의 경우 에러가 발생한다.

package test;
class PublicClass {
    public static void main(String[] args){
    }
}
public class PublicSecondClass {    // error
}


결국 3가지 코드를 통해 말하고자 하는 바는,

public 으로 선언된 클래스가 파일 내에 있다면

그 소스코드 파일의 이름은 public 인 클래스의 이름과 동일해야만 한다는 점이다.




* 오버라이딩 (Overriding) : 상속과 관련.

- 부모 클래스의 메소드를 자식인 자신의 클래스 내에서 재정의한다.

- 접근 제어자, 리턴 타입, 메소드 이름, 매개 변수 타입 및 갯수가 모두 동일.

- 단! 접근 제어자는 부모 클래스의 메소드와 반드시 동일할 필요는 없으나 접근 권한이 확장되는 경우만 가능하다.

(접근 권한 : public > protected > package-private > private)


예를 들어 부모 클래스의 메소드 접근 제어자가 public 일 때,

자식 클래스의 메소드 접근 제어자가 private 인 경우는 접근 권한이 축소된 경우라서 컴파일 에러.



* 오버로딩 (Overloading) : 한 클래스 내에서.

- 메소드 이름은 동일하되 매개 변수만 다르게 하는 것.

- 반환 타입은 상관 없다. 중요한 건 같은 메소드 이름, 매개 변수가 들어가는 '()' 안의 내용이다.


public int add(int x, int y) { }
public double add(double x, double y) { }
public void print(int intData, long longData, String strData) { }
public void print(int intData, String strData) { }
public void print(String strData, int intData) { }


위 코드 라인 4, 5와 같이 타입의 순서가 달라도 다른 메소드 처럼 인식된다는 점.


by kelicia 2014. 5. 21. 03:33

- 이번 장의 특성상 따로 Diagram 을 생성하지 않았다. 그리고 책 내용에 좀 더 충실하게 포스팅해봤다. -


* Singleton Pattern

해당 클래스의 인스턴스가 오직 하나 만들어지고,

어디서든지 그 인스턴스에 전역 접근할 수 있도록 하기 위한 패턴이다.


- 어떤 상황에서 사용될까?

스레드풀이라든가 캐시, 대화상자, 사용자 설정 또는 레지스트리 설정을 처리하는 객체,

로그 기록용 객체, 프린터나 크래픽 카드 같은 디바이스를 위한 디바이스 드라이버 같은 걸 예로 들 수 있다.

이런 상황에서 2개 이상의 인스턴스를 만들게 된다면, 프로그램이 이상하게 돌아간다든가

자원을 불필요하게 잡아먹는다든가 일관성 없는 결과를 초래할 수 있다.


- 전역변수(Global Variable)도 방법이 될 수 있지 않나?

전역변수에 객체를 대입하면 애플리케이션이 시작될 때 객체가 생성될 것이다.

그런데 여기서 그 객체가 자원을 많이 차지한다고 가정했을 때, 그 객체가 애플리케이션 종료 시까지

한 번도 사용되지 않는다면 괜히 자원만 잡아먹는 쓸데없는 녀석이 될 수 있다.

Singleton 패턴의 경우에는 필요할 때만 객체를 생성할 수가 있다.



< 고전적인 구현법 >

public class Singleton {
    private static Singleton uniqueInstance;
    private Singleton() { }
    public static Single getInstance() {
        if ( uniqueInstance == null ) {
            // 인스턴스가 필요한 상황이 닥치기 전에는 아예 인스턴스를 생성하지 않는다.
            // 이 기법을 '게으른 인스턴스 생성(Lazy instantiation)' 이라 한다.
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

클래스의 객체가 자원을 많이 잡아먹는 경우에는 위와 같이 '게으른 인스턴스 생성' 기법이 꽤 유용하다.

위 코드를 읽을 때는 접근한정자와 static 키워드를 주의깊게 읽자.



하지만 주의해야 할 점이 있다. 

* 고전적인 방법이 다중 스레드 환경에서도 아무 문제 없을까?


1번 스레드 

2번 스레드 

uniqueInstance 값 

 public static Single getInstance() {

 

null

  public static Single getInstance() {

 

  if ( uniqueInstance == null ) {

 

 

   if ( uniqueInstance == null ) {

 

   uniqueInstance = new Singleton();

 

object 1 

   return uniqueInstance;

 

 

    uniqueInstance = new Singleton(); 

object 2

 

   return uniqueInstance;

 


위의 경우, 객체가 2 개가 만들어져 Singleton 의 의미가 없어지는 문제가 발생한다.

어떻게 해야할까?

public static synchronized Singleton getInstance() {
    if ( uniqueInstance == null ) {
        uniqueInstance = new Singleton();
    }
    return uniqueInstance;
}

synchronized 키워드를 추가하면, 한 스레드가 사용을 끝내기 전까지 다른 스레드는 기다려야 한다. 

다시 말해서 2개 이상의 스레드가 동시에 실행시키는 일은 발생하지 않는다.

그러나 ! !

책에 의하면... 메소드를 동기화하면 성능이 100배 정도 저하된다는 것은 기억하라고 한다.



* Singleton 패턴에서의 3 가지 해법 :

(1) getInstance() 의 속도 이슈가 그렇게 중요하지 않다면 그냥 동기화를 해도 된다.

- 애플리케이션에 큰 부담을 주지 않는다면 바로 위의 코드처럼 놔둬도 된다.

(2) 인스턴스를 필요할 때 생성하지 말고 처음부터 만들어 버린다.

- 애플리케이션에서 반드시 Singleton의 인스턴스를 생성하고 항상 사용한다면 or

 인스턴스를 실행중에 수시로 만들고 관리하기 성가시다면 정적으로 초기화 해버린다.

 이 방법은 JVM 에서 유일한 인스턴스를 생성하기 전까진 그 어떤 스레드도 uniqueInstance 에 접근 불가.

public class Singleton {
    private static Singleton uniqueInstance = new Singleton();
    private Singleton() { }
    public static Singleton getInstance() { return uniqueInstance; }
}

(3) DCL(Double-Checking Locking) 을 써서 getInstance() 에서 동기화되는 부분을 줄인다.

- 인스턴스가 생성되어 있는지 확인한 다음, null 인 경우만 동기화 할 수 있기때문에

 처음에만 동기화를 하고 이후에는 동기화하지 않아도 된다. 

 이 방법으로 오버헤드를 극적으로 줄여 속도를 향상시킬 수 있다. 

- volatile 키워드를 사용하면 다중 스레딩을 쓰더라도 uniqueInstance = new Singleton(); 의 과정이

 올바르게 진행될 수 있다.

public class Singleton {
    private volatile static Singleton uniqueInstance;
    private Singleton() { }
    public static Singleton getInstance() { 
        if ( uniqueInstance == null ) {
            synchronized ( Singleton.class ) {
                if ( uniqueInstance == null ) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance; 
    }
}

단, (3) 방법은 자바 1.4 이전 버전에는 사용할 수 없다고는 하나 

현재 14.05.20 기준으로는 버전 1.8 까지 나왔으니 크게 염려할 필요는 없지않나 싶다.


3가지 방법 모두가 다중 스레딩 환경에서 발생하는 문제를 해결할 수 있다.

하지만 속도 이슈와 자원 문제, 구현 시나리오를 염려해두면서 적절한 해법을 선택하도록 하자.



* 객체지향의 원칙 :

1) 바뀌는 부분은 캡슐화 한다.

2) 상속보다는 구성을 활용한다.

3) 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.

4) 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.

5) 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다(OCP).

6) 추상화된 것에 의존해라. 구상 클래스에 의존해서는 안 된다(의존성 뒤집기 법칙).



이 장에서의 정리 :

- 어떤 클래스에 Singleton 패턴을 적용하면 애플리케이션에 그 클래스의 인스턴스가

 최대 1개까지만 있도록 할 수 있다. 또한 그 유일한 인스턴스에 전역 접근할 수 있다.

- 사실 모든 애플리케이션은 다중 스레딩을 쓸 수 있다고 생각해야하기 때문에 고전적인 구현은 되도록 피하자.

- 클래스 로더가 여러 개 있으면 싱글턴이 제대로 작동하지 않고 여러 개의 인스턴스가 생길 수 있다.

 (클래스 로더마다 서로 다른 네임스페이스를 정의하기 때문이다. 

 클래스 로더가 2개 이상일 때, 각 클래스 로더마다 한번씩 같은 클래스를 여러 번 로딩할 수도 있다. 

 그러니 클래스 로더를 직접 지정해서 이 문제를 피할 수 있다고 한다.)


※ 사실 클래스 로더가 뭔지 잘 모르겠다.


by kelicia 2014. 5. 20. 00:33

- 예제 코드 Diagram -



* Factory Method Pattern :

객체를 생성하기 위한 인터페이스를 만든다. 

어떤 클래스의 인스턴스를 만드는 것을 서브클래스에서 결정하도록 한다. 

이 패턴을 이용하면 인스턴스를 만드는 일을 서브클래스에게 맡길 수 있다.



아래 그림을 참고해서 위의 그림을 설명하자면,

(출처 : http://www.cnblogs.com/zhuqiang/archive/2012/05/05/2484930.html )

Pizza 라는 추상 클래스(or 인터페이스)는 Product, 이를 상속받은 피자들이 Concrete Product.

PizzaStore 라는 추상 클래스(or 인터페이스)Creator, 이를 상속받은 체인점들이 ConcreteCreator 에 해당된다.

그리고 createPizza() 라는 메소드는 factoryMethod() 에 해당된다.


PizzaStore 는 생산자로서 Pizza 를 생산할 뿐이지 실질적으로 어떤 피자가 만들어질 지는 전혀 모른다.

(여기서 PizzaStore 는 클라이언트 코드다)

실질적으로 어떤 피자가 만들어질 지는 Pizza 의 서브클래스에 따라 결정이 된다.



* 의존성 뒤집기 원칙 (Dependency Inversion Principle) :

- 추상화된 것에 의존하도록 만들어라. 구상 클래스에 의존하지 않도록 만들어야한다.

- 이를 지키는 데 도움이 될만한 3가지 가이드 라인

. 어떤 변수에도 구상 클래스에 대한 레퍼런스를 저장하지 않는다.

. 구상 클래스에서 유도된 클래스를 만들지 않는다.

. Base 클래스에 이미 구현되어 있는, 모든 서브클래스에서 공유하는 메소드를 오버라이드하지 않는다.

 


* Abstract Factory Pattern :

서로 연관된, 또는 의존적인 객체들로 이루어진 제품군(Product Family)을 생성하기 위한 인터페이스를 제공한다.

구상 클래스는 서브클래스에 의해 만들어진다.

예제의 NYPizzaStore 클래스의 createPizza() 코드 中

Pizza pizza = null;

PizzaIngredientFactory inredientFactory = new NYPizzaIngredientFactory();

pizza = new CheesePizza(ingredientFactory);


PizzaIngredientFactory 가 위 그림의 Abstract Factory, NYPizzaIngredientFactory 가 Concrete Factory,

NYPizzaStore 가 Client 에 해당한다.


그 외에 Abstract Product A 는 피자 도우 정도 되고

그 하위의 Product A1, A2 는 각각 얇은 도우, 두꺼운 도우 정도라고 생각하면 된다.

마찬가지로 Abstract Product B 도 치즈 정도에 해당, Product B1, B2 는 모짜렐라 치즈 등에 해당된다.


Concrete Factory 1 에서는 Product A1, B1 을 제품군으로 이루고 있고,

Concrete Factory 2 에서는 Product A2, B2 를 제품군으로 이루고 있다.


그리고 위 그림의 Factory 가 생산자(Creator)라고 생각하면 

Abstract Factory 패턴에서는 Factory Method 패턴이 쓰이는 걸 볼 수 있다.



* Factory Method Abstract Factory

- 공통점

서브클래스를 통해서 객체를 만듦으로서 클라이언트(PizzaStore) 에서는

자신이 사용할 추상 형식(Pizza)만 알면 되고, Concrete 형식은 서브클래스에서 처리해준다.

즉, 클라이언트와 Concrete 형식을 분리시켜주는 역할을 한다.

객체 생성을 캡슐화해서 애플리케이션이 결합을 느슨하게 만들고 

특정 구현에 대한 의존성을 낮힐 수 있다.

- Factory Method

상속을 통해 Product 의 객체를 만든다. ex ) PizzaStore 추상 클래스의 createPizza() 를 오버라이드

클라이언트 코드와 인스턴스를 만드는 구상 클래스를 분리시켜야 할 때 활용할 수 있다.

- Abstract Factory

객체 구성을 통해 Product 의 객체를 만든다. ex ) pizza = new CheesePizza(ingredientFactory)

팩토리를 이용하고 싶으면 일단 인스턴스를 생성하고 추상 형식을 써서 만든 코드(Pizza)에게 이를 전달.

Concrete Factory 구현할 때, 종종 Factory Method 패턴을 사용하는 경우가 있다.

일련의 연관된 제품을 하나로 묶을 수 있다는 장점이 있다.

단, 제품군에 제품을 추가시킬 경우 Abstract Factory 의 createProductC() 를 추가해야 하는데 요약하자면!

인터페이스를 바꿔야해서 모든 서브클래스에도 영향을 미치게 된다는 단점이 있다.



* 객체지향의 원칙 :

1) 바뀌는 부분은 캡슐화 한다.

2) 상속보다는 구성을 활용한다.

3) 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.

4) 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.

5) 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다(OCP).

6) 추상화된 것에 의존해라. 구상 클래스에 의존해서는 안 된다(의존성 뒤집기 법칙).



이 장에서의 정리 :

- Factory 를 쓰면 객체 생성을 캡슐화 할 수 있다.

- Factory Method 패턴에서는 어떤 클래스에서 인스턴스를 만드는 일을 서브클래스에게 넘긴다.

- Abstract Factory 패턴에서는 구상 클래스에 직접 의존하지 않고도 

서로 관련된 객체들로 이루어진 제품군을 만들기 위한 용도로 쓰인다.

- Factory 는 구상 클래스가 아닌 추상 클래스 / 인터페이스에 맞춰서 코딩할 수 있게 해주는 강력한 기법이다!


by kelicia 2014. 5. 19. 02:13

- 예제 코드 Diagram -



* Decorator Patterns

(상속 또는 인터페이스를 이용해 형식(Type)을 맞춤으로서객체에 추가적인 요건을 동적으로 첨가할 수 있다.

Decorator 는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다.

주의할 건 Decorator 는 상속을 통해서 행동을 물려받는 것이 목적이 아니다!


위 Diagram 를 통해 설명하자면,

- Beverage 가 추상 구성요소 (Component)

- DarkRoast, HouseBlend, Decaf, Espresso 가 구상 구성요소 (Concrete Component)

- CondimentDecorator 가 추상 데코레이터 (Decorator)

- Mocha, SteamMilk, Whip, Soy 가 구상 데코레이터 (Concrete Decorator)


※ Beverage 클래스가 추상 클래스인데, 이는 기존의 코드가 추상 클래스로 되어있었고

기존 코드는 가능한 고치지 않는 것이 좋기 때문에 그대로 쓴 것이다. 인터페이스로 구현하는 것도 가능하다.



* 예제 코드

Beverage beverage = new DarkRoast();
beverage = new Mocha(beverage);
beverage = new Whip(beverage);
System.out.println(beverage.cost());

Beverage 의 구상요소들 cost() 의 내용은

return (음료 가격);


Condiment Decorator 의 구상요소들 cost() 의 내용은

return (데코 가격) + (생성자에서 전달받은 음료).cost();




* java.io 클래스와 Decorator 패턴


(출처:http://i.stack.imgur.com/O7pqc.png)



* OCP (Open-Closed Principle) :

- 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다 !



* Decorator 패턴의 장점 & 단점

- OCP 에 충실하면서 유연한 디자인을 만들어 낼 수 있다.

- Decorator 를 바로 끼워넣어도 클라이언트 쪽에서는 Decorator 를 사용한다는 것을 전혀 알 수 없다.

- 자잘한 클래스들이 엄청나게 추가되는 경우가 종종 발생해 이해하기 힘든 디자인이 만들어 질 수 있다.

(ex. java.io 클래스)

- Decorator 를 도입하면 구성 요소를 초기화하는 데 필요한 코드가 훨씬 복잡해진다.

구성 요소 인스턴스만 만드는 것 뿐만 아니라 꽤 많은 Decorator 로 감싸는 경우가 자주 발생한다.

(but 이 문제는 차후 다루는 장 ~팩토리, 빌더~ 를 통해 해결할 수 있다고 한다)


* 객체지향의 원칙 :

1) 바뀌는 부분은 캡슐화 한다.

2) 상속보다는 구성을 활용한다.

3) 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.

4) 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.

5) 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다(OCP).



이 장에서의 정리 :

- 상속 또는 인터페이스 구현을 통해서 Decorator 가 감쌀 클래스와 같은 형식을 가지게 된다.

- Decorator 에서는 자기가 감싸고 있는 구성요소의 메소드를 호출한 결과에

 새로운 기능을 더함으로써 행동을 확장한다.

- 구성요소를 감싸는 Decorator 의 갯수에는 제한이 없으나

 자잘한 객체들이 너무 많이 추가될 수 있고, 이로 인해 코드가 필요 이상으로 복잡해질 수도 있다. 

 주의해서 남용하는 일이 없도록 하자!


by kelicia 2014. 5. 18. 02:44

- 예제 코드 Diagram -


쉽게 이해하자면,

출판사(Subject:주제) + 구독자(Observer) = Observer Pattern.


* Observer Pattern

- 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고,

자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다.



* 느슨한 결합 (Loose Coupling) 

- 두 객체가 상호작용을 하긴 하지만 서로에 대해 잘 모른다!

- Observer Pattern 에서는 이러한 디자인을 제공한다. Why ?

(1) Subject 가 Observer 에 대해서 아는 것은 Observer 가 특정 인터페이스를 구현한다는 것 뿐이다.

(2) Observer 는 언제든지 추가, 삭제할 수 있다.

(3) 새로운 타입의 Observer 를 추가하려고 해도 Subject 를 변경할 필요가 전혀 없다.

(4) Subject 와 Observer 는 서로 독립적으로 사용이 가능하다.

(4) Subject 또는 Observer 가 바뀌더라도 서로한테 영향을 미치지 않는다.



* Push 방식 vs. Pull 방식

Push 방식 : Subject 는 데이터가 변경될 때 마다 Observer 들에게 알려준다.

- 장점

Observer 는 Subject 에게 따로 데이터를 요청할 필요가 없다.

- 단점

Observer 가 불필요한 데이터까지 받아야 되는 경우가 생긴다.


Pull 방식 : Observer 가 갱신된 데이터가 필요할 때 Getter 메소드를 이용해 Subject 로부터 데이터를 가져온다.

- 장점

Observer 는 필요한 데이터만 가져올 수 있다.

Subject 가 확장되더라도 Subject 는 notify 메소드를 수정할 필요없이 Getter 메소드만 추가하면 된다.

- 단점

Observer 는 필요한 데이터가 많을 수록 Getter 메소드를 여러 번 호출해야만 한다.

(Pull 방식이 더 옳은 것으로 간주된다고 한다)



* java.util.Observable, java.util.Observer

(이미지 출처 : http://dellabate.wordpress.com/2012/03/03/gof-patterns-observer/)


- 처음의 Diagram 에서는 인터페이스를 이용해 Observer Pattern 을 구현했지만 

바로 위의 이미지와 같이 Java 에서 API 로 지원하기도 한다.


- Observable 의 주의점*

1) Interface 가 아닌 Class 라는 점!

- 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다는 객체지향의 원칙 3)에 위배된다.

- 클래스이기 때문에 서브클래스를 만들어야 한다는 것이 문제.

  이미 다른 수퍼클래스를 확장하고 있는 클래스에 Observable 의 기능을 추가할 수 없기 때문이다.

  그래서 재사용성에 제약이 생기게 된다.

- Observable 인터페이스가 없어 Java 의 Observer API 하고 잘 맞는 클래스를 직접 구현하는 것이 불가능.

2) Observable 클래스의 핵심 메소드를 외부에서 호출할 수 없다!

- protected 로 선언된 setChanged(), clearChanged() 는 Observable 의 서브클래스에서만 호출 가능하다.

- 상속보다는 구성을 활용한다는 객체지향의 원칙 2)에 위배된다.



* 객체지향의 원칙 :

1) 바뀌는 부분은 캡슐화 한다.

2) 상속보다는 구성을 활용한다. - Subject 와 Observer 사이의 관계는 상속이 아니라 구성에 의해 이루어진다!

3) 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.

4) 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.



이 장에서의 정리 :

- Subject (또는 Observable 객체) 는 동일한 인터페이스를 써서 Observer 에게 연락을 한다.

- Observer Pattern 은 Swing 및 여러 GUI 프레임워크, JavaBeans 나 RMI 같이 광범위하게 쓰인다.

ex ) Swing API 에서 JButton 의 ActionListener 등록


by kelicia 2014. 5. 16. 20:11

- 예제 코드 UML Diagram -



* Strategy Pattern :

- 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.

이를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.




객체지향의 기초 :

1) 추상화    2) 캡슐화    3) 다형성    4) 상속



객체지향의 원칙 :

1) 바뀌는 부분은 캡슐화 한다. (무엇이 바뀌고, 안 바뀌는지 잘 구분해야한다)

2) 상속보다는 구성을 활용한다.

3) 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.



이 장에서의 정리 :

- 훌륭한 객체지향의 디자인이라면... 재사용성, 확장성, 관리의 용이성을 갖춰야 한다.



# Reference

- '캡슐화'에 대한 강좌 (네이버 검색 키워드 : 객체지향 캡슐화, 카페 이름 : GIS 프로그래밍 연구소)

http://cafe.naver.com/gisdev/543


by kelicia 2014. 5. 16. 19:10


Stack, Queue 에 이어서 Linked List.

사실 이걸 먼저 포스팅 했어야 되는데 ㅡ_ㅡ; 어쩌다보니 순서가 바꼈다.



<< Linked List >>


(1) 포인터를 이용하여 데이터를 저장한다.


(2) Array 와는 다르게 물리적 구조가 순차적이지 않다.

Array 의 경우, 메모리에 순차적으로 저장되지만 

Linked List 는 포인터를 이용하기 때문에 데이터가 메모리에 순차적으로 저장되어 있지 않다.


(3) Linked List 의 기본 단위는 Node 구조체로 이루어져 있다.

Node 구조체에 데이터와 Link 를 저장해놓는다.


(4) 각 Node 의 생성은 동적 할당으로 이루어지고, 

첫번째 Node 를 가르키는 노드인 Header Node 가 존재한다.

(5) Linked List 에는 3가지 종류가 있다.

- Single Linked List / Circular Linked List / Double Linked List

- 각 리스트 별로 삽입(Insert), 삭제(Remove) 연산이 있고, 

각 연산 별로 Header / Middle / Tail 에서 일어나는 경우를 살펴볼 것이다.

(이미지 출처 : http://blog.naver.com/zkd1750/90183988309)


연산 처리 과정을 그림으로 이해하기 쉽게 정리하면 좋겠지만

이미 다 배운 내용을 정리하는 차원이라 굳이 그리지는 않겠다. 귀찮다.



1. Single Linked List


(1) Insert

- Header

[Head] -> [A] -> [B] -> [C:Tail]  이런 상태에서

[Head] -> [?] -> [A] -> [B] -> [C:Tail] 로 [?] 라는 노드를 삽입할 것이다.


방법은 간단하다. 다음과 같은 순서로 노드의 삽입이 이루어진다.

① [?].next = [Head].next

② [Head].next = [?]


- Middle

[Head] -> [A] -> [B] -> [C:Tail]  이런 상태에서

[Head] -> [A] -> [B] -> [?] -> [C:Tail] 로 [?] 라는 노드를 삽입할 것이다.


① [?].next = [B].next

② [B].next = [?]


- Tail

[Head] -> [A] -> [B] -> [C:Tail]  이런 상태에서

[Head] -> [A] -> [B] -> [C] -> [?:Tail] 로 [?] 라는 노드를 삽입할 것이다.


① [C:Tail].next = [?]

② [?].next = NULL

(1번과 2번의 순서가 바뀌어도 상관은 없다.)

③ *tail = [?]


(2) Remove

- Header

[Head] -> [A] -> [B] -> [C:Tail]  이런 상태에서 [A] 를 삭제하면,

[Head] -> [B] -> [C:Tail]  이렇게 된다.


① [Head].next = [A].next    또는    [Head].next = [Head].next.next

② [A].next = NULL    // ① 연산으로 [A]를 가르키는 노드가 없으니 ① 연산 전에 [A]를 따로 저장

③ free([A])


- Middle

[Head] -> [A] -> [B] -> [C:Tail]  이런 상태에서 [B] 를 삭제하면,

[Head] -> [A] -> [C:Tail]  이렇게 된다.


① [A].next = [B].next    또는    [A].next = [A].next.next

② [B].next = NULL    // ① 연산으로 [B]를 가르키는 노드가 없으니 ① 연산 전에 [B]를 따로 저장

③ free([B])


- Tail

[Head] -> [A] -> [B] -> [C:Tail]  이런 상태에서 [C:Tail] 를 삭제하면,

[Head] -> [A] -> [B:Tail]  이렇게 된다.


① free([B].next)    // next 가 tail 인 녀석을 찾아서 메모리 해제

② [B].next = NULL

③ *tail = [B]




2. Circular Linked List


(1) Insert

- Header

[Head] -> [A] -> [B] -> [C:Tail] -> [A]  이런 상태에서

[Head] -> [?] -> [A] -> [B] -> [C:Tail] -> [?] 로 [?] 라는 노드를 삽입할 것이다.


① [?].next = [Head].next

② [Head].next = [?]

③ [C:Tail].next = [?]


- Middle

S.L.L 와 동일하다.


- Tail

[Head] -> [A] -> [B] -> [C:Tail] -> [A]  이런 상태에서

[Head] -> [A] -> [B] -> [C] -> [?:Tail] -> [A] 로 [?] 라는 노드를 삽입할 것이다.


① [?].next = [C:Tail].next    또는    [?].next = [Head].next

② [C:Tail].next = [?]

③ *tail = [?]


(2) Remove

- Header

[Head] -> [A] -> [B] -> [C:Tail] -> [A]  이런 상태에서 [A] 를 삭제하면,

[Head] -> [B] -> [C:Tail] -> [B]  이렇게 된다.


① [Head].next = [A].next    또는    [Head].next = [Head].next.next

② [C:Tail] = [Head].next

③ [A].next = NULL    // ① 연산으로 [A]를 가르키는 노드가 없으니 ① 연산 전에 [A]를 따로 저장

④ free([A])


- Middle

S.L.L 와 동일하다.


- Tail

[Head] -> [A] -> [B] -> [C:Tail] -> [A]  이런 상태에서 [C:Tail] 를 삭제하면,

[Head] -> [A] -> [B:Tail] -> [A]  이렇게 된다.


① [B].next.next = NULL    // next 가 Tail 인 녀석을 찾아보니 [B]. 즉, [B].next = Tail

연산 후 모습 : [Head] -> [A] -> [B] -> [C:Tail]

② free([B].next)

연산 후 모습 : [Head] -> [A] -> [B] -> [ ]

③ [B].next = [Head].next 

연산 후 모습 : [Head] -> [A] -> [B] -> [A]

④ *tail = [B]

[Head] -> [A] -> [B:Tail] -> [A]




3. Double Linked List

- previous, next 가 존재해서 조금 귀찮다.

- 어떤 책은 prev / next 로 하는 책도 있고, left /right 로 하는 책도 있는데 전자를 선택하겠다.

- 앞에 보았던 노드의 구조가 [Data, Next] 라면 지금은 [Prev, Data, Next] 정도 되겠다.


(1) Insert

- Header

[Head] ↔ [A]  [B]  [C:Tail]  이런 상태에서

[Head]  [?]  [A]  [B]  [C:Tail]  [?] 라는 노드를 삽입할 것이다.


① [?].prev = [Head] 

② [?].next = [Head].next

③ [Head].next = [?]

④ [?].next.prev = [?]


- Middle (Header 와 똑같다고 보면 된다)

[Head] ↔ [A]  [B]  [C:Tail]  이런 상태에서

[Head]  [A]  [?]  [B]  [C:Tail]  [?] 라는 노드를 삽입할 것이다.


① [?].prev = [A] 

② [?].next = [A].next

③ [A].next = [?]

④ [?].next.prev = [?]


- Tail

[Head] ↔ [A]  [B]  [C:Tail]  이런 상태에서

[Head]  [A]  [B]  [C]  [?:Tail]  [?] 라는 노드를 삽입할 것이다.


① [?].prev = [C:Tail] 

② [?].next = NULL

③ [C:Tail].next = [?]

④ *tail = [?]


(2) Remove

- Header

[Head]  [A]  [B]  [C:Tail이런 상태에서 [A] 를 삭제하면,

[Head]  [B]  [C:Tail] 이렇게 된다.


① [Head].next = [A].next    또는    [Head].next = [Head].next.next

② [Head].next.prev = [Head]

③ [A].prev = NULL    //  연산으로 [A]를 가르키는 노드가 없으니 ① 연산 전에 [A]를 따로 저장

④ [A].next = NULL

⑤ free([A])


- Middle (Header 와 똑같다고 보면 된다)

[Head]  [A]  [B]  [C:Tail이런 상태에서 [B] 를 삭제하면,

[Head]  [A [C:Tail] 이렇게 된다.


① [A].next = [B].next    또는    [A].next = [A].next.next

② [A].next.prev = [A]

③ [B].prev = NULL    // ①,  연산으로 [B]를 가르키는 노드가 없으니 ① 연산 전에 [B]를 따로 저장

④ [B].next = NULL

⑤ free([B])


Tail

[Head]  [A]  [B]  [C:Tail이런 상태에서 [C] 를 삭제하면,

[Head]  [A]  [B:Tail] 이렇게 된다.


① [B].next.prev = NULL    // next 가 Tail 인 녀석을 찾아보니 [B]. 즉, [B].next = Tail

② free([B].next)

③ [B].next = NULL

④ *tail = [B]



# Linked List 사용 사례 : 

- Stack, Queue, Tree 구현할 때 사용될 수 있다.

- 많이 쓰이기는 하는데 막상 사용 예제를 검색해보면 딱히 나오지는 않는 것 같다...



아, 드럽게 많네.

그림 별로 안 올려도 포스팅 하는데 겁나 오래걸린다ㅠㅠ


( + 이 포스팅에서 free 나 *tail 은 C언어로 쓰인 자료 참고하느라 추가했다.

원래 Java 로 코드를 한번 짜봤었는데 C 로는 안 해봐서 조금 지저분해도 써봤다. )


'Programming' 카테고리의 다른 글

[C]printf()에서 "%ul"와 "%lu"의 차이?  (0) 2015.07.05
언리얼엔진4 : DataTable 오브젝트 생성하기  (0) 2014.09.22
Delegate (대리자)  (1) 2013.12.20
[Python, C#]Lambda Form  (0) 2013.12.19
PHP vs. Ruby vs. Python  (0) 2013.12.19
by kelicia 2014. 4. 25. 02:36
| 1 2 3 4 |