객체지향 다시보기
1. 객체지향 프로그래밍의 시작 캡슐화
객체 지향이란? 실세계의 일들을 객체로 사용해서 모델링하는 것
무엇을 클래스로 작성할지 모르겟다.
프로그래이란 절차가 전부이다.
프로그램 -> 방법1(구조적) -> 방법2 (절차적)
구조적으로 잘라서 만들다보니 어떤 문제가 잇엇으며 새로운 방식이 필요햇을 것이다.
절차적인 프로그래밍 + 구조적인 프로그래밍 + 문제 + 객체지향 프로그래밍
1.1 캡슐화
https://youtu.be/yGBmRdgc1Ic
프로그램이 너무 길어서 잘라서 만드는데 이것이 구조적인 프로그래밍이다.
잘라서 만든느 것은 잘게 쪼개진 구성품으로 만들어진다.
그런데 점점길어지면 이 함수가 너 무 많아짐 이런것들을 어떻게 정리하나?
이것들을 어떤기준으로 어떻게 수납할 것인가?
1.기능에 따라 분리 / 2.데이터에 따라 분리
데이터에 따라 분리묶어줘야한다.
왜? 함수는 데이터를 이용해야한다.
구조화된 데이터를 사용하는 함수 모듈의 독립성을 침해하는 문제를 해결할 수있기 때문에 데이터별로 나눠야한다.
함수는 외부의 수정에 절대 영향을 받아서는 안된다.
함수는 돌아갈때 독립적으로 돌아가야한다.
구조화된 데이터를 사용하면 구조화된데이터의 속성이 변화하면 문제가 발생한다.
해결방법 두가지가 있다.
1.절대변수명을 바꾸지않는다.
2.바꾸면 모든 코드를 바꿔서 바꾼다.
둘다 좋지않음. 고치면 같이 바뀌도록 모아놓는다.
범위가 한정되서 수정, 유지보수가 쉬워진다.
데이터와 함수를 모아둬서 '캡슐'이라고 한다.
따라서 캡슐화하면 데이터 구조에 따른 코드의 수정범위를 캡슐 범위로 한정할 수 있게 된다.
이런 것들을 모으다 보니 모든 프로그램이 캡슐이 되었다.
2. 함수들을 캡슐화하기
캡슐화 : 데이터 구조와 함수를 하나의 영역에 함께 모아 놓는 작업
데이터에 따라 오류가 나는 함수들을 옮겼다.
프로그램
//초기화 함수
ExamList.init(list);
case 1:
ExamList.inputList(list);
break;
case 2:
ExamList.printList(list);
break;데이터 캡슐
public class ExamList {
Exam[] exams;
int current;
static void printList(ExamList list) {
printList(list, list.current);
}
static void printList(ExamList list, int size) {
...
}
static void inputList(ExamList list) {
...
}
public static void init(ExamList list) {
list.exams = new Exam[3];
list.current = 0;
}
}3. 인스턴스(Instance) 메소드
객체와 함수의 관계 문제를 해결하는 방법으로 나왓던걸 알려준다.
절차를 작성하다보니 문제가 발생함. 함수를 사용하는것에서 불편한게 생김.
ExamList list = new ExamList();
ExamList.inputList(list); 구조화된걸 실체화해서 사용해야한다.
문제는 이 객체가 잘안보인다. 행위에 대한 주체가 잘보이지 않는다.
list를 이용한 처리 실제 주체가 안보인다.
->list.inputList(); 주체를 먼저 말한다.
list(ExamList)를 이용한 입력 list를 이용한 출력 등등
list.하는게 훨씬편하다. 함수가 사용할 수있도록 넘겨받게 파라미터를 하는게아니라
.을 통해서 부르면 기본적으로 전달받게 하자.
이것을 인스턴스함수라고 한다.
캡슐한테 list객체를 이용해서 하려면 알아서 객체가 담아주게 해야한다.
메인함수에서는 함수의 속성이나 알고리즘을 사용할 수없다. 알고리즘을 구현해줄래라고 해야한다.
속성을이용한다거나하면 명령을 하는 것이다.
list야 입력해, list야 출력해라고 해버리게 해줘야한다. 책임을 전가해야한다.
구조환된게 캡슐화되면 역할도 부여해서 주체가되서 사용하게 해야한다.
단순하게 데이터를 넣는게아니라 책임을 주는 것 도구가아니라 주체가 되도록하기
구조화된게 캡슐화된것이라면 역할로서 주체를 한다.
객체가 눈에 띄고 객체가 하도록하기
4. 인스턴스(Instance) 메소드 구현하기
매개변수와 static을 없애야한다.
함수와 메소드의 차이 가 무엇인가?
함수는 처리하는 것이고 메소드는 명령이다. 서비스를 위한 함수 = 메소드이다.
다른 언어에서는 함수 / 메소드라고 한다. 자바에서는 static(정적)메소드 / 인스턴스메소드라고 한다.
정적메소드는 원래 형태의 함수(메소드)라고 보면된다. static는 식별의 의미라고 생각하면된다.
static일경우에는 모든데이터가 파라미터로 전달받아서 파라미터가 잇어야햇는데
인스턴스는 파라미터로 전달받는게 아닌데 넘겨받아야한다.
인스턴스일때는 똑같은 로직이지만 넘겨받은거는 어떻게 사용하나?
this로 하면된다.
void printList(int size) {
System.out.println("┌──────────────────────────┐");
System.out.println("│ 성적출력 │");
System.out.println("└──────────────────────────┘");
System.out.println();
Exam[] exams = this.exams;
for (int i = 0; i < size; i++) {
Exam exam = exams[i];
int kor = exam.kor;
int eng = exam.eng;
int math = exam.math;
int total = kor + eng + math;
float avg = total / 3.0f;
System.out.printf("국어: %3d\n", kor);
System.out.printf("영어: %3d\n", eng);
System.out.printf("수학: %3d\n", math);
System.out.printf("총점 : %3d\n", total);
System.out.printf("평균 : %6.2f\n", avg);
System.out.println("────────────────────────");
}
}이렇게 만들면 훨씬더 어떤 객체를 사용하고 있는지 구분이가능하다.
옛날버전
inputList(list1);
현재
list.inputList();
list1.inputList();
list2.inputList();
책임을 가지게 만드는것임. 의인화하는 것이 객체지향이다.
그런데 this는 캡슐안이라면 무조건 받게 되서 지워도된다.
그러면 지우느게 맞나 쓰는게 맞나? 지워도되면 지우는게 좋다.
식별해야되서 못지울땐 안지우면된다.
5. 캡슐의 은닉성과 접근 지정자
캡슐에서 오류가나면 그 캡슐에서 오류가 난다.
함수가 다른 캡슐의 데이터를 사용하려고 넘겨받으면?
캡슐을 깨뜨리는 것이다. 사용하려면 함수룰 호출하는 방향으로 가야한다.
자바는 보호막을 씌워서 쓰고싶어도 못쓰게 만들 수있다.
캡슐을 깨지 못하게 하는 접근제어지시자가 그 역할을 한다.
키워드를 통해 공개할지 말지를 정할 수 있다.
private와 public이 있다.
바깥에서 쓰려고하면 쓸수잇다.
private int current;하면 사용이 불가해진다.
공개하려는 서비스함수는 사용해야하니 public으로 해준다.
6. 생성자(Constructor)
생성자는 객체 초기화하는 것이다.
new ExamList();하면 객체가 생성하면 실체화하는 작업이 이루어지고 데이터구조가 메모리에 올리게 된다.
이걸 식별하기 위해 list라는 이름을 부여한다.
처음 값을 초기화하기 위해서 init함수를 만들었다.
이런식으로 업무를 하는데 누군가가 또 호출하면 current가 0이된다.
초기화라기보단 리셋인 함수가 되버린다.
우리가 원하는 것은
1.객체가 생성되자마자 무조건 제일 먼저 실행되어야한다.
2.생성될때 단 한번만 실행되어야만 한다.
이걸 만족하는 함수가 생성자라고 한다. 생성자는 언제든지 호출할 수없게 이름을 가지고 있지는 않다.
특별한 함수이다. 생성자라고 하자.
어떻게 호출하나? 지금까지 객체를 생성할때마다 생성자를 사용하고 있엇다.
new ExamList + (); new ExamList라는 객체를 실체화해달라 ()는 만들어진 객체를 초기화하는 함수를 호출해달라.
생성자는 반환을 안하니 반환타입이 없다. 목적은 객체를 넘겨받기만 한다.
정의할때 함수명은 초기화할 객체를 한정하기 위한 한정사로 객체와 같은 이름을 가져야한다.
public ExamList() {
exams = new Exam[3];
current = 0;
}7. 생성자 오버로드(Constructor Overload)
생성자도 함수의 특징을 가지고 있어서 오버로드를 할 수있다.
생성자를 오버로드하면 원하는 생성자를 사용할 수 있다.
만약 기본생성자를 제거한다면 기본생성자를 호출할 수 없다.
의도적이라면 맞는데 실수라면 코드 사용불가능하다. 상속에서도 난제들을 만나게 된다.
일부러 그랫다면 조치를 취하면된다.
오버로드햇다면 중복을 제거해야한다.
함수이름이 아닌데 생성자가 생성자를 어떻게 호출하나? 객체를 만들어서 호출해야한다.
this로 호출하는데 값을 안넣으면 자기가 자기를 부르는것이 된다. 값을 넣으면 파라미터가 있는 생성자를 부르게 되는 것이다.
지금까지 생성자를 만든적이 없는데 어떻게 써왓나?
기본생성자는 컴파일러가 자동으로 만들어준다.
참조변수는 null 값변수는 0으로 세팅을 한다. 원하지 않으면 반드시 추가해주자.
기본생성자를 제거하고 오버로드만 햇을경우? 기본생성자를 안만들어주니 주의하자.
생성자가 하나도 없을때만 관여해서 만들어준다.
8. Getters와 Setters 그리고 이것을 써야하는 이유
또다른 데이터 구조가 있는데 Exam이 있다.
현재 데이터 구조만 가지고 잇어서 함수화되어잇지않다.
속성을 변경해보면 ExamList에서 에러가 난다.
printList ExamList구조를 사용하고 있어서 여기에 잇어야하는데 Exam의 데이터르 사용한다.
그런데 다른 캡슐의 데이터를 쓰고자 한다. 보조데이터의 속성을 사용하고 싶다.
부탁을 해야한다. 직접쓰는건 무리이니 간접적으로 사용하게 해달라.
데이터를 이용할 수잇게 함수를 만들어달라고 해야한다.
int kor = exam.kor; 이 아니라
int kor = exam.getKor(); 국어성적을 달라고 할 수있다.
int eng = exam.getEng();
int math = exam.getMath();
간접적으로 사용할 수 있게 만들어주자.
public int getKor() {
return kor;
}
public int getEng() {
return eng;
}
public int getMath() {
return math;
}inputList는
exam이가지고 있는 kor에 값을 설정해야한다.
exam.kor = kor; 직접 사용하는게 아니라 간접적으로 설정하게 해야한다.
exam.kor = kor; 이 아니라
exam.setKor(kor); kor성적 설정해줘라고 해야한다.
public void setKor(int kor) {
this.kor = kor;
}
public void setEng(int eng) {
this.eng = eng;
}
public void setMath(int math) {
this.math = math;
}매개변수로 들어온 값을 각 필드에 저장한다.
그래서 여기서 변수명이 바뀌어도 매개변수로 받아온 것을 전달해주면된다.
getter setter를 왜 만들어야하나??
코드량이 더 많고 복잡해보이는데 왜 이렇게 사용해야하나?
메인함수(캡슐밖)에서 데이터 구조를 사용하려고한다. private면 넘볼수없어서 에러가 난다.
그럼 값을 어떻게 설정하나? setter로 설정하면된다.
이게 비효율적이다라고 느껴지면? 왜 사용하나
속성명이 변경되는 것때문 --> 이런일은 별로없다
데이터 구조가 변경되는 것때문임.-->
캡슐화는 데이터 구조를 모아두는 것인데 데이터구조가 변경되면 문제가 발생할 수 있다.
캡슐화는 데이터 구조를 사용하는데 데이터구조에 따라 종속되는 것을 막으려고 하는 것이다.
구조가 변경된다는 것은 무엇일까?
책을 정리하는데 박스를 만든다. 시험이 구체적인 설명이 필요해서 제목 등이 추가기되어다.
그런데 속성이 많아져서 방정리를 다시해야되서 과목이라는 울타리로 뺏을때 구조화된 데이터가 중첩된다.
구조가 깊어지고 원래 사용하던게 없어져서 변화를 줘야한다.
9.- Exam 클래스의 캡슐화 완성
가져다쓰려면 getter setter를 무조건 작성해야한다.
출력에 total과 avg계산 누구의 속성사용하나? Exam의 속성을 사용한다.
책임을 다시 생각해야한다. 이것을 너가해 라고 한다.
데이터 구조만을 이용하는것은 Exam이하는게 바람직하다.
public int total() {
return kor + eng + math;
}
public float avg() {
return total() / 3.0f;
}값을 설정할때 setter가 아니라 생성자로도 받을 수있다.
public Exam(int kor, int eng, int math) {
this.kor = kor;
this.eng = eng;
this.math = math;
}kor가 지역변수 kor가 높기때문에 식별하기 위해 this를 꼭 해주자.
10. UI 코드는 분리하는 것이 기본
잘 갖추어진 캡슐을 깨는작업을 할것이다.
같은 캡슐이라도 분리되어야 하는 것이 잇다.
ui는 코드를 뺄 필요가 있다.
input()은 사용자로부터 입력을 받은 것을 / 성적 목록에 추가하는 함수
print()는 목록에서 성적을 꺼내서 / 사용자에게 출력하는 함수
List는 입력이나 추가하는 임무를 가져야하는데 입출력이 같이 들어있다.br>
두가지 업무로 나눌 수있다. 이걸 두는게 좋은지 조치를 취하는게좋은지?
입/출력은 사용자와 상호작용한다. 콘솔/윈도우/웹/모바일 등을 사용한다.
어떤 플랫폼을 쓰냐에 따라 ui가 달라질수잇고 다른 함수들은 그대로 사용할 수있다.
그래서 다른 곳에서 재사용할 수 없으니 입출력부분은 콘솔로부터 자유로워야한다.br>
input() + add() / get() + print()으로 나누어야 한다.
사용자 입출력을 위해서 ExamConsole를 만드려고한다.
list:ExamList를 가질것이다.
11. ExamConsole 클래스 구현하기
Console을 구상하기 위해서 ExamList를 사용하고 있다.
Exam은 데이터의 기본단위임.
ExamConsole 캡슐에서 ExamList캡슐을 멤버로 사용하고 있다.
각각 캡슐을 가지고 있는 것을 has a 상속이다.
12. Has A 상속
https://youtu.be/6wKyPg9rxtw 영상
캡슐로 구성되어잇는것들을 어떻게 엮을 것인지가 프로그래밍이다.
Program->ExamConsle->ExamList->Exam이런식으로 이용하고 잇다.
그런데 ExamList이 Exam을 가지고 잇지만 사용하고 있나라고 생각하면 아니다.
일반적으로 List는 가지고만잇고 사용하지는 않는다.br>
각각 우측것을 포함하고 있지만 사용하긴 하지만 그릇으로서만을 기능을 가지고 있고
실질적으로는 ExamConsle이 Exam을 사용하고 있다.br>
실질적인 관계와 구성하는 관계가 맞지 않는다.
일반적으로는 사용관계와 구성관계를 일치하게 만든다.
그릇은 살짝 지워버리고 사용관계와 구성관계를 잇는다.
그래서 사용관계는 구성관계가 같다는 관계를 Has A 관계라고 한다.br>
이것을 Has A상속이라고 한다.
상속은 물려받는 건데 가지고 있음으로서 기능을 사용하는 것을의미한다.
이 상속을 부품으로 물려받앗다고 한다.
캡슐이 다른캡슐을 가지고 있는 상태를 의미한다.br>
객체가 만들어질때 객체가 그 안에서 만들어지는 경우 Composion has a상속햇다.
Aggregation has a는 필요할때마다 가져다 사용하는 것이다.br>
현재 관계는 후자이다.br>
메인함수는 필요하는 것으로 무조건 ExamConsle을 사용하는데 멤버로 구성하는 것은 아니다.
이 관계를 의존관계 dependency관계라고 한다.br>
ExamConsole사용할때 ExamList이 부품으로 필요하다.
ExamConsole을 만들때 부품도 만들어지는 상태이니 Composion has a관계라고 한다.
public ExamConsole() {
list = new ExamList();
}
ExamList를 보면
public ExamList(int size) {
exams = new Exam[size];
current = 0;
}만들어질때 Exam객체를 사용하는게 아니라 생성자에서 이것을 참조하기 위한 배열을 만드는 것이다.
만들어진 관계가 아니기때문에 필요할때마다 가져다 쓰는
Aggregation has a관계이다.br>
이관계들의 용어를 이해하는게 중요하다.
13. 코드 재사용이란?
새로운 프로그램을 만들고 싶다. 다른 학원에서 이 것을 사용한다고 생각해보자
그런데 ui나 코드들이 비슷하다.
맨땅에 처음부터 만들면? 어렵다. 그래서 재사용하면 편하다.
그런데 재사용은 소스코드를 재사용하는건지? 컴파일러로 한것을 재사용하는건지?
바로 배포된 binary를 재사용하는 것이다. 소스코드는 그냥 사용이다.br>
베포하는 과정은 다음의 과정을 거친다.
1.컴파일 -> Exam.class
2.압축 -> Exam.zip
3.jar -> Exam.jar
14. IS A 상속이란?
객체지향에서 반드시 알아야한다.
객체지향에는 3가지 덕목이 있다. 캡슐화 상속 다형성이 있다.
여기서 상속은 has a일수도 잇지만 보통은 is a이다.br>
프로그램을 만들때 원하는 부품들이 다있다면? has a 상속으로 쉽게만들 수있다.
만약 하나의 부품이 없다면? 부담이생긴다.
그런데 딱 내가 원하는 모든 것이 아닌데 어느정도 구성되어있다면?
가져다가 프레임을 사용할 수도 있다.
이렇게 틀로 가져다 쓰는 상속이 is a상속이다.br>
is a상속을 할수있다면 원하는 것에 근접할 수록 소요되는 돈과 시간을 절약할 수있다.
이 틀이 잇으면 틀을가지고 생겅하는것 이 틀이 framework이고 틀이잇다면 생산성이 매우 오른다.
요즘엔 framework를 거의 많이 사용한다.
장점은 생산성이 좋고 단점은 기성품이라 흔하다. 유닉할필요가 없다면 프레임워크를 가져다쓰는게 낫다.
그런데실제로는 성장속도가 빨라서 혼자 다만드는건 거의 불가능하다.
90%쓰고 한 10% 5%만 추가해서 유닉하게 만든다.
15. Exam을 IS A 상속하기
Nelec학교에서 NewlecExam을 하는데 거의 흡사하다면 그냥 가져다 사용하면된다.
만약 jar로 받아온다면 기존 소스코드를 마음대로 사용할 수 없기때문에
틀을 상속해서 그위에 구현하는 것으로 사용해야한다.br>
public class NewlecExam extends Exam { }
Exam이가지고잇는 것들을 NewlecExam이 가지고 있는 것처럼 사용한다.
식별하기 위해서 이름을 붙이는데
부모클래스/상위클래스/기반클래스 <---> 자식클래스/하위클래스/파생클래스 라고 한다.
가장많이 사용하는 것은 이 3가지 이다.br>
모자란 부분을 확장했는데 재사용하려는데 문제점으로 total을 사용하지 못하는 경우가 발생한다.
이를 해결하기 위해 다양한 방법이 필요하다. 이 방법은 다음장에서 보자.
public class newlecExam extends Exam {
private int com;
public int getCom() {
return com;
}
public void setCom(int com) {
this.com = com;
}
}16. Override(우선순위가 높은) 메소드
이전 문제를 어떻게 해결하나? total을 구현하고 있는것은 Exam이고 Exam에는 com이 없기때문이다.
상속받은거로 new를 하면? 객체를 두개 만들어야한다.
Exam을 만들고 com이 있는 NewlexExam을 확장해서 만든다.
부모를 먼저 만들고 자식객체를 확장해서 붙이게 된다.
전달될때는 내부에서 this를 사용하고 부모를 사용하려면 super로 지정할 수 있다.br>
NewlexExam에 total이잇다면 이것을 우선순위로 사용하고 없다면 차선책으로 부모의 것을 사용한다.
이전의 문제를 해결하려면 NewlexExam에 total을 정의하면된다.
이것을 override 메소드라고 한다.
재정의, 가리는 함수라고 해서 override method라고 한다.
@Override
public int total() {
return super.total() + com ;
}부모의 total을 불러와서 사용해야한다. 그냥 냅두면 this.total()을 불러오게 된다.
@Override
public float avg() {
return total() / 4.0f;
}같은 방식으로 total()을 불러와서 실행하면된다.
17. 자식 클래스의 객체 초기화
자식을 만들때는 부모를 먼저 만들고 자식을 만들 수 있다.
확장된 영역을 자식 클래스라고 볼수잇다.
public newlecExam(int kor, int eng, int math, int com) {
super(kor, eng, math);
this.com = com;
}그래서 생성자도 오버로드 할 수 있다.
부모의 생성자를 호출하고 com과목만 추가할 수 있다.
오버로드하려는게 부모거라면 부모를 가져와서 사용하고 오버로드하자.
18. 참조형식과 호출되는 메소드의 관계
is a 상속을해서 newlecExam을 햇다.
Exam exam = new NewlecExam();은 부모 자식관계라 가능하다.br>
참조변수 다참조하는게아니라 부모의 일부만 참조하는 것이 가능하다.
참조하려는 객체가 존재하면 괜찮다.
부모에서 확장해서 자식객체를 만드는 형식이기 때문이다.br>
그런데 여기서 문제가 발생한다.
오버라이드가 되엇을때 nelecc~으로 호출하면 뭐가 호출되나? 당연히 newlec이 호출된다.
Exam exam을 참조형식으로 햇을때 total은 누구것이 호출되나? newlec이 호출된다.br>
자바는 참조형식타입의 함수보다 객체 형식의 함수호출을 우선으로 한다.
참조형식이 exam이라해도 객체가 newlec이면 newlec것이 사용된다.br>
하지만 그래도 참조형식이 가지고있는 메소드가 있어야 호출이 가능하다.
19. 메소드의 관계 연습하기
퀴즈 풀이로 확인 학습하기
https://youtu.be/vlkOwD828yg
부모-부모것을 호출하더라도 오버라이드된게 호출된다.
오버라이드되면 중간에 가로챈다.
'기초단계 > JAVA' 카테고리의 다른 글
| 2023.02.27 -2 Java복습 (0) | 2023.02.27 |
|---|---|
| 2023.02.27 Java복습 (0) | 2023.02.27 |
| 2023.02.24 Java복습 (0) | 2023.02.24 |
| 2023.02.23 Java복습 (0) | 2023.02.23 |
| 2023.02.21-2 Java복습 (0) | 2023.02.21 |