2014년 3월 1일 토요일

[Design Patter] Observer Pattern, 신문사와 구독자의 관계

** 옵저버패턴

옵저버 패턴은 크게 Subject와 Observer로 구성된다.
이둘의 관계는 신문사와 구독자의 관계로 비유할 수 있다.

1) Subject : 변경된 정보를 Observer에 제공하는 입장이다. 신문사에 해당한다. 정보 변경을 Observer에 갱신해 주는 역할을 한다. 
Subject interface를 구현하고, 
- registerObserver()
- removeObserver()
- notifyObservers()
를 포함한다.

2) Observer : 변경된 정보를 Subject에서 받아본다. 구독자에 해당한다. 정보 변경을 Subject에서 보내주면 그에 따라 정보를 갱신한다.
Observer interface를 구현하고,
- update()
를 포함한다.

[Android] implementation of view by programmatically (setting size, alignment, etc.)

안드로이드 화면을 hard하게 코딩해보았다.
그런데 문제가 화면 만드는 것 까지는 지난번에 해봐서 괜찮았는데,
만들어진 화면의 크기 조정이나, 화면들간의 위치 조정이 문제였다.

하지만

구글링 + stackoverflow + 책 = 해결

까먹으면 억울하니 기록으로 남겨보자.


1) view 생성

일단 지난번에 해보긴 했지만 또 언제 까먹을지 모르므로 적어놓으련다.

View viewName = new View(this); //해당 View 객체 생성
a) TextView의 경우
viewName.setText(string);

b) ImageView의 경우
viewName.setImageResource(R.drawable.id);


1-1) view 크기 조절
view 크기 설정은 view의 속성을 직접 제어하는 것이 아니라, LayoutParams 객체를 새로 생성해서 view를 부모 view에 add할때 함께 add한다.

ex) 부모 view = RelativeLayout, 자식 view = TextView
RelativeLayout relativeLayout = new RelativeLayout(this);
TextView textView = new TextView(this);
textView.setText("테스트중");
textView.setTextSize(20);
RelativeLayout.LayoutParams textParams = new RelativeLayout.LayoutParams(
              RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT)
// --> new ~~~.LayoutParams(가로크기, 세로크기);
relativeLayout.addView(textView, textParams);


1-2) view 속성 설정
view의 속성 같은 경우에도 LayoutParams 객체를 이용한다.
RelativeLayout의 경우 addRule을 통해서 부모, 형제들간의 상대 위치를 설정한다.

ex1) RelativeLayout에서 부모와의 상대 위치 결정(ex에 이어서)
textParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
// 부모 view 맨 위에

ex2) RelativeLayout에서 형제와의 상대 위치 결정
ImageView imageVIew = new ImageView(this);
imageView.setImageResource(R.drawable.testImage);
textParams.addRule(RelativeLayout.BELOW, imageView.getId());
// imageView의 아래에 textView가 위치하도록 params를 설정하였다.
// -->~~.addRule(상대위치속성, 기준형제뷰 ID)

ex3) Margin설정
textParams.setMargins(20, 0, 0, 10);
// ~~.setMargins(left, top, right, bottom);

c.f) LayoutGravity 설정
textParams.gravity = Gravity.RIGHT;
// 오른쪽으로 정렬된다.
textParams.gravity = Gravity.RIGHT | Gravity.BOTTOM;
// 오른쪽 아래로 정렬. ' | ' 를 이용하면 여러 방향을 동시에 정할 수 있다.

c.f2) Padding 설정
textView.padding = (30, 30, 30, 30);
// padding은 view의 필드중 하나로 직접 조정한다.

결론 : params는 크기 + 다른 element와의 관계를 설정할때 사용한다.


2) view group생성
별거 없다.
위에서도 언급했듯이.
RelativeLayout relativeLayout = new RelativeLayout(this);
와 같이 정의하고 최상위 뷰일 경우 xml에서 조정하고, 아닐 경우 상위 뷰의 params 지정해서 상위 뷰에 add해주면된다.
나머지는 view group도 view의 일종이기 때문에 똑같음.

3) view를 view group에서 지우기
relativeLayout.removeView(textView);
//viewGroup.removeView(View);
와 같은식으로 삭제한다.
한줄이면 된다.

2014년 2월 14일 금요일

[Android] Major to Minor

우연히 Youtube에서 1박2일 동영상을 보게됐다. 그런데 일반인 참가자 중에
정말 매력적으로 you are my sunshine을 부르는 사람이 있었다.
서울서 제주도 놀러왔다가 두달이나 눌러앉은 여자였는데 성시경 열혈팬이었다.
엄마도 두달째 연락이 잘 안된다는데 성시경한테 날 가지란다 ㅋㅋㅋㅋ

강렬한 인상의 the very 처자



무튼 짧지만 강렬했는데, 불렀던 you are my sunshine은 minor였다. 원래 major인 곡을 minor로 부르니 색다른 매력이 있어서 '그래 그러면 다른 곡도 minor로 바꿔보자'는 단순한 생각에 인터넷에서 major to minor를 검색했다.

안나왔다.

그래서 만들었다. MajorToMinor.
매우 단순하다. Major/Minor 버튼이 있고, Major는 기본 건반, Minor는 일반 건반을 누르면 그에 해당하는 Minor음계가 나온다.

원래는 이랬었는데.. 실상은 C major to C minor다.
조변경이야 그냥 음계 몇칸씩 동시에 내리면 되지만, minor변경은 내려야 하는 음이 온음인지 반음인지 각기 조마다 다르기 때문에 일괄적으로 변경하는게 불가능하다.
다음에 보완해서 각 장조/단조 마다 음이 나게 해볼까 생각도 드는데 다만들고 보니 영
생각보다 쓸모가 없어서 고민중이다.

여튼 나의 앱 처녀작이라는데 의의를 두자.
















만드는 중에 새로 배운 것
- Relative Layout : 왜 쓰나 싶었는데 상대 위치로 view들을 조정할 수 있어서 하나만 제대로 위치를 잡아놓으면 나머지는 굳이 절대 위치를 지정하지 않아도 된다. 그래서 좋다.
- SoundPool Class : SoundPool 클래스는 소리를 출력하는 클래스다.

생성자->load -> play 순으로 쓰인다. 자세한 설명은 귀찮으니
Android Sound 출력 참조

[Android] 안드로이드 RelativeLayout

[출처 jwandroid.tistory.com/158]


1. 형제뷰 기준으로 정렬

해당 element값="@id/기준뷰"



2. 부모뷰 기준으로 정렬

해당 element값="true" or "false"




2014년 2월 13일 목요일

[Effective JAVA] 13. 불변클래스를 써라.

1. 객체를 변경하는 메소드를 제공하지 않는다.
setter금지

2. 재정의 할 수 있는 메소드를 제공하지 않는다.
- 클래스를 final로 재정의해서 상속 자체를 원천 봉쇄한다.
- 모든 메소드를 final로 정의한다.
- 생성자를 private이나 package-private으로 정의하고 public static 팩토리 메소드를 제공한다. (생성자가 private인 경우 상속이 불가능하다. 하지만 package-private으로 작성하면 같은 package내에서는 상속이 가능하다. 원하는 인스턴스를 골라서 리턴할 수도 있다.)

3. 모든 필드를 final로 만든다.

4. 모든 필드를 private으로 만든다. 
Q: 기본 타입이나 불변 객체를 참조하는 public final 필드를 불변 클래스에 둘 수는 있지만, 향후 배포판에서 클래스의 내부 표현을 바꿀 수 없기 때문에 바람직하지 않다.?

5. 가변 객체를 참조하는 필드는 배타적으로 접근해야 한다.(ex 방어복사)
클라이언트가 이 객체에 대한 참조를 얻을 수 없게 해야 한다. 해당 필드를 final로 선언했다 할지라도 해당 필드가 어떤 객체를 참조하는가 하는 사실만 불변(final)이지 참조하는 객체의 내부는 변경할 수 있기 때문이다.

* 방어복사 : 객체를 복사할 때 객체의 참조를 리턴하는 것이 아니라 객체의 복사본을 리턴하는 등의 방법을 통해 객체를 복사하는 방법.

2014년 2월 12일 수요일

reservation application Code refactoring 후기

1. 새로 배웠거나 향상되었다고 느끼는 점

- 코드 중복을 줄이는 것이 좀더 능숙해졌다.

- 변수명을 rename하는 요령이 생겼다.

- 디버깅 능력이 이전과 비교해 많이 상승하였다.(슬프지만 이부분이 가장 큰 듯)

- git 사용법을 익혔다.


2. 코드 중복을 줄이고, 변수명을 rename하였는 데도 가독성이 떨어지는 코드.

사수님 질문
- 이 코드 뭉치(?)는 어떤 역할을 하나요?
   -> 코드 부분이 갖는 기능적 역할을 명확히 구분짓고 정의하면, 개념적으로 코드들을 분리해서 다른 method로 빼낼 수 있다.

- parameter로 실질적인 input이 아닌 값을 받지 않으려면 어떻게 해야하나요?
   -> 실질적인 input이 아님에도 parameter로 받아야하는 값을 해당 메소드에서 사용할 수 있도록 한다. ex) 기존 method외부에 변수를 선언하여 사용가능하게끔 바꿈, 기존 method에 선언하였던 변수를 새로운 method에 선언함.



** 중요 : 코드 가독성을 높이는 데는 개념적으로 코드를 분리하는 것이 중요하다.

2014년 2월 10일 월요일

[Java] parameter에 "변수명 = 값"을 넣으면?

원래 parameter에 "변수명 = 값"이라고 넣은 건
parameter에 값을 전달하고 해당 변수명에 값을 할당하려는 시도였다.

시도했던 코드 : 
while(reader.ready() && !data[0].equals(line = username + "\t" + password
                    + "\t" + phoneNumber + "\t" + String.valueOf(reservedOrNot))){
                readLine = reader.readLine();
                data = readLine.split(";");
            }

굵은 글씨 부분이다.

그런데 java 문법에 어긋난 행위다. 에러남

???????????


package classchum;

public class Test1212 {
public static void main(String argv[]){
int a, b;
a = test(b=2, 1);
System.out.println(a);
System.out.println(b);
}

static public int test(int a, int b){
return b;
}
}

결과:
1
2

2014년 1월 15일 수요일

간단하게 Activity stack 처리하는 방법 (Android)

Activity를 그냥 intent생성해서 만들면 계속해서 새로 Activity를 start하고 스택으로 쌓이는 문제가 발생한다.
예를 들어서 A, B, C Activity를 A->B->C->A->C->B로 이동하고 뒤로가기를 누르면 다시 화면이 B->C->A->C->B->A 순으로 왔던 순서 반대로 나타난다. 그래서 뜨면 안될 화면도 뜨게 된다. (*회원가입 화면이 그대로 다시뜬다던가 삭제한 화면이 뜬다던가 등) 혹은 그냥 다시 뒤로가면 되는 화면인데 새로운 화면을 만들어서 그 위에 쌓아나간다.

여기서 생기는 두가지 문제,
1. 접근해선 안되는 이전화면으로 돌아가버린다.
2. 굳이 이전 화면의 Activity를 새로 생성해서 위에 쌓는다.

를 해결해보자.


1. 접근해선 안되는 이전화면으로 돌아가버리는 문제

->해당 Activity 의 JAVA 소스 파일에서 intent를 생성한 바로 다음에
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
를 추가해준다.

여기서 FLAG_ACTIIVTY_CLEAR_TASK는 기존에 쌓여있던 task(stack이 모여 형성하는 작업의 단위(?))를 모두 삭제하는 조건(?)을 받는 flag 상수다. Intent 클래스에 statitc final로 선언되어있다.

즉 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 는 기존에 쌓여있던 스택을 모두 없앤다.

이때 FLAG_ACTIVITY_NEW_TASK로 task를 새로 생성한다. 이때 root activity는 intent가 새로 생성한 activity가 된다.


2. 이전 화면의 Activity로 돌아가야하는 문제

-> 돌아가기 이전 Activity의 JAVA 소스 파일에서 intent를 생성한뒤에
intent.addFlags(intent.FLAG_ACTIVITY_CLEAR_TOP);
을 추가해준다.

FLAG_ACTIVITY_CLEAR_TOP 은 이전 Activity를 새로운 Activity로 지정하고 현재 Activity는 Clear하는 flag상수다. 역시 Intent 클래스에 static final로 선언되어 있다.

따라서, 현재 Activity를 없애고 이전 화면을 새로운 화면으로 지정하기 때문에 자연스럽게 이전화면으로 돌아간다.

** 2.번 문제는 Manifest파일에 android:launchmode="singleTop"을 추가해 주면 비슷하게 문제를 해결할 수 있다.
자세한건
http://developer.android.com/guide/components/tasks-and-back-stack.html
(tasks and back stack에 대한 android document)
참조.


2014년 1월 13일 월요일

Singleton = 특정클래스의 인스턴스를 하나만 생성하는 방법

특정 클래스의 인스턴스를 하나만 생성해야 하는 경우가 있다.

왜 그런상황이 생기는지 회원가입, 로그인을 예로 들어보자.
회원DB 클래스의 인스턴스를 회원가입과 로그인에서 
각각 A인스턴스, B인스턴스로 따로 생성하였다. 이때 회원 이모씨가 서비스에 가입하였다. 이모씨의 회원가입은 A인스턴스가 실행하였다. 그런데 이모씨는 제대로 로그인이 되는지 확인하기 위해 잠깐 로그아웃을 하고 다시 로그인을 시도하였다. 그런데 이때 사용된 B인스턴스에는 이모씨의 정보가 추가되어 있지 않다. 이모씨는 로그인에 실패하여 어플을 지워버렸다.
이 상황에서 B인스턴스 입장에서는 A인스턴스를 통해서 회원가입이 일어났는지 확인할 길이 없다. 한 클래스에서 여러 인스턴스를 생성하면서 발생하는 문제다.

이 경우 반드시 하나의 인스턴스만을 생성하여 해당 인스턴스를 하상 호출하여 사용하도록 하면 문제가 해결된다. 

이때 사용되는 하나의 인스턴스만을 사용하도록 하는 '디자인 패턴'을 Singleton이라고 한다.


Singleton은 
1. 클래스 내에 인스턴스를 생성하고

2. 생성자를 private으로 선언하여 
외부에서 해당 인스턴스를 생성하지 못하도록 한다.

3. 외부에서 해당 인스턴스를 사용하려고 할 경우 getInstance()를 이용하여 
클래스 내에 이미 선언된 인스턴스만을 사용하도록 한다.

고로 해당 클래스 내에서 static으로 생성된 인스턴스만 사용되는 것이다.
프로그램내의 모든 곳에서 해당 클래스의 특성 인스턴스만 사용한다.


간단한 코드 예시

public class MemberManager {
1.   private static MemberManager instance = null;
    

2.   private MemberManager() {
        this.members = this.getMembersFromFile();
    }
3.   public static MemberManager getInstance() {
// 아직 인스턴스가 생성되지 않았을 경우 최초 생성한다.
        if(MemberManager.instance == null)
            MemberManager.instance = new MemberManager();
        return MemberManager.instance;
    }
}



+추가
private static MemberManager instance로 선언하는 이유가 뭐냐.
그냥 private으로 선언하면 어떻게 됨?

-> 3.에서 변수 instance를 사용하는데 (그냥 private이면)static이 아니므로
1) 객체를 생성하지 않고 사용하면 컴파일이 되지않는다.
2) 객체를 생성하면 해당 static 메소드를 호출할때마다 객체를 생성하기 때문에
하나의 특정 객체만을 생성하고 사용하는 싱글톤이 더이상 아니게 된다.

++추가 static 메소드, 변수는 왜 쓰는 건가요?
-> 클래스 자체에 관련해서 설명이 필요한 경우 사용한다.
ex) 사용자가 로그인을 할때마다 사용자 객체를 생성하는 클래스가 있다.
현재 사용중인 사용자 목록을 가져오는 메소드가 필요한 경우
static으로 선언하여 사용한다. 어떤 특정 객체에 연관되지 않고 사용할 메소드이기 때문.
물론 일반 메소드로 선언하여 사용할 수도 있으나, 특정 객체를 지정하여 메소드를 호출하여야 하고, 부자연스럽다.
결론 : static 메소드, 변수가 필요한 이유는 어느 객체에도 속하지 않는 행위, 데이터의 처리가 필요한 경우?

2014년 1월 7일 화요일

C언어 : structure를 활용한 파일처리(I/O)

클린코드 읽고 배운대로 함해봄.

목표 :
1) score.txt로부터 성적을 입력받아서, 학생 각각의 평균을 산출하고, 평균을 바탕으로 등수를 매긴다.
2) 이름, 성적, 평균, 등수를 콘솔에 출력한다.
3) 이름, 성적, 등수를 학생이름으로 된 txt파일에 출력한다.

--코드--
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct report{
char* name;
int korean;
int math;
int english;
int number_of_students;
float average;
}REPORT;

int countStudents(FILE* fp_input);
void putInformation(FILE* fp_input, REPORT** reports_pointer, int number_of_students);
void calculateAndPutAverageOfEachStudentInReport(REPORT* reports);
void orderByScore(REPORT* reports);
void printChart(REPORT* reports);
void makeReportFiles(REPORT* reports);

int main()
{
FILE* fp_input;
REPORT* reports;
int number_of_students;
float* average;

fp_input = fopen("score.txt", "r");
if(fp_input == NULL){
fprintf(stderr, "File error!\n");
exit(1);
}

number_of_students = countStudents(fp_input);
rewind(fp_input);
putInformation(fp_input, &reports, number_of_students);
calculateAndPutAverageOfEachStudentInReport(reports);
orderByScore(reports);
printChart(reports);
makeReportFiles(reports);

return 0;
}

int countStudents(FILE* fp_input)
{
int number_of_students=0;
char string_for_count[20];
while(!feof(fp_input)){
fscanf(fp_input, "%s", string_for_count);
number_of_students++;
}
number_of_students /= 4;

return number_of_students;
}

void putInformation(FILE* fp_input, REPORT** reports_pointer, int number_of_students)
{
int i=0;

*reports_pointer = (REPORT*)malloc(sizeof(REPORT)*number_of_students);

while(!feof(fp_input)){
(*reports_pointer)[i].name = (char*)malloc(sizeof(char)*20);
fscanf(fp_input, "%s", (*reports_pointer)[i].name);
fscanf(fp_input, "%d", &(*reports_pointer)[i].korean);
fscanf(fp_input, "%d", &(*reports_pointer)[i].math);
fscanf(fp_input, "%d", &(*reports_pointer)[i].english);
(*reports_pointer)[i].number_of_students = number_of_students;
i++;
}
}

void calculateAndPutAverageOfEachStudentInReport(REPORT* reports)
{
int i=0;
for(i; i<reports[0].number_of_students; i++){
reports[i].average = (float)(reports[i].korean + reports[i].math + reports[i].english) / 3;
}
}

void orderByScore(REPORT* reports)
{
int i, j, temp_korean, temp_math, temp_english;
float temp_average;
char* temp_name;
for(i=0; i<reports[0].number_of_students-1; i++)
for(j=i+1; j<reports[0].number_of_students; j++){
if(reports[i].average < reports[j].average){
temp_average = reports[i].average;
reports[i].average = reports[j].average;
reports[j].average = temp_average;

temp_name = reports[i].name;
reports[i].name = reports[j].name;
reports[j].name = temp_name;

temp_korean = reports[i].korean;
reports[i].korean = reports[j].korean;
reports[j].korean = temp_korean;

temp_math = reports[i].math;
reports[i].math = reports[j].math;
reports[j].math = temp_math;

temp_english = reports[i].english;
reports[i].english = reports[j].english;
reports[j].english = temp_english;
}
}
}

void printChart(REPORT* reports)
{
int i;
printf("      이름        국어  수학  영어   평균     등수\n");
printf("-----------------------------------------------------\n");
for(i=0; i<reports[0].number_of_students; i++)
printf("%15s %5d %5d %5d %8.2f %6d\n", reports[i].name, reports[i].korean, reports[i].math, reports[i].english, reports[i].average, i+1);
}

void makeReportFiles(REPORT* reports)
{
int i;
FILE* fp_output;
for(i=0; i<reports[0].number_of_students; i++){
fp_output = fopen(strcat(reports[i].name, ".txt"), "w");
fprintf(fp_output, "이 름 : %s\n", reports[i].name);
fprintf(fp_output, "국어  : %d\n", reports[i].korean);
fprintf(fp_output, "수학  : %d\n", reports[i].math);
fprintf(fp_output, "영어  : %d\n", reports[i].english);
fprintf(fp_output, "등수  : %d\n", i+1);
}
}

아쉬운점:
* calculateAndPutAverageOfEachStudentInReport는 평균을 계산하는 작업과
평균 값을 각report structure에 넣는 작업 두가지를 한 함수에서 동시에 해결하고 있다.
** 각각의 함수는 출력 인수를 사용하고 있다.

여튼 그래도 ㅈㄴ 뿌듯 ㅎ