[클래스]
문제1) 두 명의 학생 정보를 출력하는 프로그램 작성
각 학생은 이름, 나이, 성적을 가지고 있다.
public class classStart1 {
public static void main(String[] args) {
String[] studentNames={"학생1","학생2", "학생3", "학생4"};
int[] studentAges={15,16, 17, 18};
int[] studentGrade={90,80, 50, 70};
for(int i=0; i<studentNames.length; i++){
System.out.println("이름: " + studentNames[i] + " 나이: " + studentAges[i] + " 성적: " + studentGrade[i]);
}
}
}
클래스 사용하는 이유: 지금처럼 이름, 나이, 성적을 나누어서 관리하는 것은 관리하기 좋은 방식이 아니다. 사람이 관리하기 좋은 방식은 하나의 개념을 하나로 묶는 것이다. 그리고 각각의 학생 별로 본인의 이름, 나이, 성적을 관리하는 것이다.
class키워드를 사용하여 학생 클래스를 정의한다. 학생 클래스는 내부에 이름, 나이, 성적 변수를 가진다.
이렇게 클래스에정의한 변수들을 멤버 변수 또는 필드라고 한다.
멤버변수: 특정 클래스에 소속된 멤버라서 이렇게 부름
필드: 데이터 항목을 가르키는 전통적인 용어,
자바에서 멤버 변수와 필드는 같은 뜻, 클래스에 소속된 변수를 뜻한다.
* 클래스는 관례상 대문자로 시작하고 낙타 표기법을 사용한다.
클래스 도입해서 개선한 방식 ▼
예) Student, User, MemberService
student 클래스
public class Students {
String name; // 프로그램안에 학생이라는 개념을 만든다
int age;
int grade;
}
// students클래스는 name, age, grade 멤버 변수를 가지고 있다. 이 변수를 사용하는데 필요한 메모리 공간도 함께 확보한다.
student 클래스 사용
public class ClassStar3 {
public static void main(String[] args) {
Students student1; // 첫번째 학생을 담을 수 있는 변수 선언
student1= new Students(); // 학생을 실제 메모리에 만든다.(students1에 참조값을 넣는다.) -> x001
student1.name="학생1"; // . 을 통해서 클래스 접근 가능
student1.age=15;
student1.grade=90;
Students student2= new Students(); // x002
student2.name="학생2";
student2.age=15;
student2.grade=80;
System.out.println("이름" + student1.name + "나이: " + student1.age + "성적: "+student1.grade);
System.out.println("이름" + student2.name + "나이: " + student2.age + "성적: "+student2.grade);
}
}
[클래스와 사용자 정의 타입]
타입은 데이터의 종류나 형태를 나타낸다.
int라고 하면 정수 타입, string이라고 하면 문자 타입
클래스를 사용하면 int, string과 같은 타입을 직접 만들 수 있다.
사용자가 직접 정의하는 사용자 정의 타입을 만들려면 설계도가 필요로 하다. 이 설계도가 바로 클래스이다.
설계도인 클래스를 사용해서 실제 메모리에 만들어진 실체를 객체 또는 인스턴스라고 한다.
클래스를 통해서 사용자가 원하는 종류의 데이터 타입을 마음껏 정의할 수 있다.
클래스 -> 붕어빵 틀 / 객체(인스턴스) -> 붕어빵
객체는 .(dot)을 통해서 메모리 상에 생성된 객체를 찾아가야 사용할 수 있다.
배열은 [ ]를 통해서 메모리 상에 생성된 배열을 찾아가야 사용할 수 있다.
기본형을 제외한 나머지는 모두 참조형이다
- 기본형은 소문자로 시작한다 ex) int, long, double, boolean 모두 소문자로 시작한다.
클래스는 대문자로 시작한다 ex) Student, Strint(클래스임)
* String은 사실 클래스이다. 따라서 참조형이다. 그런데, 기본형처럼 문자 값을 바로 대입할 수 있다. 문자는 매우 자주 다루기 때문에 자바에서 특별하게 편의 기능을 제공한다.
객체를 사용하려면 먼저 설계도인 클래스를 기반으로 객체(인스턴스)를 생성해야 한다.
. -> 참조값을 읽어서 메모리에존재하는 객체에 접근한다.
ex) student.name =학생 -> x001접근해서 name에다가 "학생" 데이터를 넣어라.
정리
클래스 -> 객체를 생성하기 위한 "틀" 또는 "설계도"
인스턴스(객체) -> 붕어빵
객체 vs 인스턴스
객체는 서로 독립적인 상태를 가진다. (student1, student2)
인스턴스는 객체가 어떤 클래스에 속해있는지 강조할 때-> 객체보다 관계에 맞춘 용어
[문제풀이]
1) 영화 리뷰 관리하기1
클래스
public class MovieReview {
String title, review;
}
메인함수
public class ClassPractice1 {
public static void main(String[] args) {
MovieReview movie=new MovieReview();
movie.title="인셉션";
movie.review="인생은 무한 루프";
MovieReview movie2=new MovieReview();
movie2.title="어바웃 타임";
movie2.review="인생 시간 영화";
System.out.println("영화 제목 : " + movie.title + "영화 리뷰 : " + movie.review);
System.out.println("영화 제목 : " + movie2.title + "영화 리뷰 : " + movie2.review);
}
}
배열을 사용하여
public class ClassPractice1 {
public static void main(String[] args) {
MovieReview[] movies=new MovieReview[2];
MovieReview movie = new MovieReview();
movie.title="인셉션";
movie.review="인생은 무한 루프";
movies[0] = movie;
MovieReview movie2=new MovieReview();
movie2.title="어바웃 타임";
movie2.review="인생 시간 영화";
movies[1] = movie2;
for(int i=0; i<movies.length; i++){
System.out.println("영화제목: " + movies[i].title + " 리뷰: " + movies[i].review);
}
}
}
2) 상품 주문 시스템 개발
문제 설명
요구사항)
1. 상품 주문 정보를 담을 수 있는 productOrder클스를 만들어보자.
(productName, Price, quantity)
2. productOrderMain클래스 안에 main()메서드를 포함하여, 여러 상품의 주문 정보를 배열로 관리하고, 그 정보들을 출력하고, 최종 결제 금액을 계산하여 출력
3. 출력 예시와 같도록 출력하면 된다.
public class ProductOrder {
String productName;
int price, quantity;
}
public class ProductOrderMain {
public static void main(String[] args) {
ProductOrder[] orders=new ProductOrder[3];
ProductOrder order1=new ProductOrder();
order1.productName="두부";
order1.price=2000;
order1.quantity=2;
orders[0] = order1;
ProductOrder order2=new ProductOrder();
order2.productName="김치";
order2.price=5000;
order2.quantity=1;
orders[1] = order2;
ProductOrder order3=new ProductOrder();
order3.productName="콜라";
order3.price=1500;
order3.quantity=2;
orders[2] = order3;
int totalAmount=0;
for (ProductOrder order : orders) {
System.out.println("상품명: " + order.productName + " 가격:" + order.price + " 수량: " + order.quantity);
totalAmount +=order.price * order.quantity;
}
System.out.println(totalAmount);
}
}
[기본형 vs 참조형]
참조형은 객체 또는 배열에 사용된다.(주소가 들어가 있다.)
실체가 있는 곳으로 이동해야 한다.
Student s1= new Student(); // 실제로는 s1=x001이 대입됨.
Student s2=s1;
-> s1의 값이 변하면 s2도 값이 변한다.
s1와 s2의 주소값이 같아져서 s2변수 값이 변경되어도 s1변수 값도 변경됨.
[참조형과 메서드 호출]
자바에서 메서드의 매개변수는 항상 값에 의해 전달된다. 그러나 이 값이 실제 값인지? 참조 값인지? 따라서 동작이 달라진다.
기본형: 메서드로 기본형 데이터를 전달하면, 해당 값이 복사되어 전달된다. 이 경우, 메서드 내부에서 파라미터 값을 변경해도 호출자의 변수 값에는 영향이 없다.
참조형: 메서드로 참조형 데이터를 전달하면, 참조값이 복사되어 전달된다. 이 경우, 메서드 내부에서 파라미터로 전달된 객체의 멤버 변수를 변경하면 , 호출자의 객체도 변경된다.
public class Student {
String name;
int age, grade;
}
1단계
package Class1;
public class Method1 {
public static void main(String[] args) {
Student student1 = new Student();
student1.name = "학생1";
student1.age = 15;
student1.grade = 98;
Student student2 = new Student();
student2.name = "학생2";
student2.age = 16;
student2.grade = 88;
printStudent(student1);
printStudent(student2);
}
static void printStudent(Student student){
System.out.println("이름: " + student.name + " 나이: " + student.age + " 성적: "+ student.grade);
}
}
2단계
public class Method1 {
public static void main(String[] args) {
Student student1 = new Student();
initStudent(student1, "학생1", 15, 90);
Student student2 = new Student();
initStudent(student2, "학생2", 16, 88);
printStudent(student1);
printStudent(student2);
}
static void initStudent(Student student, String name, int age, int grade){
student.name=name;
student.age = age;
student.grade=grade;
}
static void printStudent(Student student){
System.out.println("이름: " + student.name + " 나이: " + student.age + " 성적: "+ student.grade);
}
}
3단계(생성하면서 초기화)
매서드 하나로 객체 생성과 초기값 설정이 가능하다.
public class Method1 {
public static void main(String[] args) {
Student student1= createStudent("학생1", 15, 90);
Student student2= createStudent("학생2", 16, 88);
printStudent(student1);
printStudent(student2);
}
static Student createStudent(String name, int age, int grade){
Student student = new Student();
student.name=name;
student.age = age;
student.grade=grade;
return student;
}
static void printStudent(Student student){
System.out.println("이름: " + student.name + " 나이: " + student.age + " 성적: "+ student.grade);
}
}
이렇게 줄일 수 있다.
int는 0으로 초기화 / boolean은 false / String 은 null로 초기화 된다.
[NULL]
택배를 보낼 때 제품은 준비가 되었지만, 보낼 주소지가 아직 결정되지 않아서, 주소지가 결정될 때까지는 주소지를 비워두어야 할 수 있다.
참조형 변수에는 항상 객체가 있는 위치를 가리키는 참조값이 들어간다. 그런데, 아직 가리키는 대상이 없거나, 가리키는 대상을 나중에 입력하고 싶다면?
참조형 변수에서 아직 가리키는 대상이 없다면 NULL이라는 특별한 값을 넣어둘 수 있다. NULL은 값이 존재하지 않는/ 없다는 뜻이다.
public class NullMain1 {
public static void main(String[] args) {
Data data=null; // null
System.out.println("1. data = "+ data);
data=new Data(); //참조값
System.out.println("2. data = "+ data);
data=null; // null(데이터에 null값을 참조해서 더이상 참조값을 사용할 수 없다.)
System.out.println("3. data = " + data);
}
}
GC(가비지 컬렉션) : 아무도 참조하지 않는 인스턴스의 최후
data에 null을 할당했다. 따라서 앞서 생성한 x001 data인스턴스를 더이상 아무도 참조하지 않는다. 이렇게 아무도 참조하지 않게 되면 x001이라는 참조값을 다시 구할 방법이 없다. 따라서 해당 인스턴스에 다시 접근할 방법이 없다.
이렇게 아무도 참조하지 안는 인스턴스는 사용되지 않고 메모리 용량만 차지할 뿐이다.
C와 같은 과거 프로그래밍 언어는 개발자가 직접 명령어를 사용해서 인스턴스를 메모리에서 제거해야 했다. 만약 실무로 인스턴스 삭제를 누락하면 메모리에 사용하지 않는 객체가 가득해져서 메모리 부족 오류가 발생한다.
자바는 이러한 과정을 자동으로 처리해준다. 아무도 참조하지 않는 인스턴스가 있으면 JVM의 GC(가비지 컬렉션)가 더이상 사용하지 않는 인스턴스라 판단하고 해당 인스턴스를 자동으로 메모리에서 제거해준다.
객체는 해당 객체를 참조하는 것이 있으면, JVM이 종료할 때까지 계속 생존한다. 근데, 중간에 해당 객체를 참조하는 곳이 모두 사라지면 그때는 JVM은 필요없는 객체로 판단하고 GC(가비지 컬렉션)를 사용해서 제거한다.
[NullPointerException]
택배를 보낼 때 주소지 없이 택배를 발송하면 어떤 문제가 발생할까? 만약 참조값 없이 객체를 찾아가면 어떤 문제가 발생할까?
이 경우 NullPointerException이라는 예외가 발생하는 데, 개발자를 가장 많이 괴롭히는 예외이다.
NullPointerException은 이름 그대로 null을 가리키다. 인데, 이떄 발생하는 예외다. NULL은 없다는 뜻으로 결국 주소가 없는 곳을 찾아갈 떄 발생하는 예외이다.
객체를 참조할 때는 .(dot)을 사용한다. 이렇게 하면 참조값을 사용해서 해당 객체를 찾아갈 수 있다. 그런데, 참조값이 NULL이라면 값이 없다는 뜻으로, 찾아갈 수 있는 객체(인스턴스)가 없다. NullPointerException 은 이처럼 null에 .을 찍었을떄 발생한다.
public class NullMain1 {
public static void main(String[] args) {
Data data=null; // null
data.value=10;
System.out.println("data = " + data.value);
}
}
이처럼 data객체에 null을 넣어서 참조값을 박살냇는데, x001.value = 10을 하면 x001은 이미 사라지고 없잖아? 이처럼 오류 발생
public class BigData {
Data data;
int count;
}
public class NullMain3 {
public static void main(String[] args) {
BigData bigData=new BigData();
System.out.println("bigData.count = " +bigData.count);
System.out.println("bigData.data = " +bigData.data);
System.out.println("bigData.data.value = " + bigData.data.value);
}
}
이러면 마지막 문장 오류 발생 왜냐하면 bigData까지는 주소였으나, data는 null이 들어가 있음.
그래서 주소를 찾지 못해서 오류 발생
그럼 해결하는 방법은?
public class NullMain3 {
public static void main(String[] args) {
BigData bigData=new BigData();
bigData.data=new Data(); // 참조값을 주면 됨
System.out.println("bigData.count = " +bigData.count);
System.out.println("bigData.data = " +bigData.data);
System.out.println("bigData.data.value = " + bigData.data.value);
}
}
[문제]
앞에서 한 물품 주문하기 리팩토링하기
package Class1;
public class ProductOrder {
String productName;
int price, quantity;
}
public class ProductOrderMain {
public static void main(String[] args) {
ProductOrder[] orders = new ProductOrder[3];
ProductOrder order1= new ProductOrder();
order1.productName="두부";
order1.price=2000;
order1.quantity=2;
orders[0] = order1;
ProductOrder order2= new ProductOrder();
order2.productName="김치";
order2.price=5000;
order2.quantity=1;
orders[2] = order2;
ProductOrder order3= new ProductOrder();
order3.productName="콜라";
order3.price=1500;
order3.quantity=2;
orders[0] = order3;
int totalAmount=0;
for(ProductOrder order : orders){
System.out.println("상품명: " + order.productName + ", 가격: " + order.price + ", 수량: " + order.quantity);
totalAmount+=order.price * order.quantity;
}
System.out.println("총 결재 금액: " + totalAmount);
}
}
여기서 리팩토링 시작
리팩토링 1단계
public class ProductOrderMain {
public static void main(String[] args) {
ProductOrder[] orders = new ProductOrder[3];
orders[0]=createOrder("두부", 2000, 2);
orders[1]=createOrder("김치", 5000, 1);
orders[2]=createOrder("콜라", 1500, 1);
int totalAmount=0;
for(ProductOrder order : orders){
System.out.println("상품명: " + order.productName + ", 가격: " + order.price + ", 수량: " + order.quantity);
totalAmount+=order.price * order.quantity;
}
System.out.println("총 결재 금액: " + totalAmount);
}
static ProductOrder createOrder(String productName, int price, int quantity){
ProductOrder order=new ProductOrder();
order.productName=productName;
order.price=price;
order.quantity=quantity;
return order;
}
}
리팩토링 마지막
public class ProductOrderMain {
public static void main(String[] args) {
ProductOrder[] orders = new ProductOrder[3];
orders[0]=createOrder("두부", 2000, 2);
orders[1]=createOrder("김치", 5000, 1);
orders[2]=createOrder("콜라", 1500, 1);
printOrders(orders);
int totalAmount=getTotalAmount(orders);
System.out.println("총 결재 금액: " + totalAmount);
}
static ProductOrder createOrder(String productName, int price, int quantity){
ProductOrder order=new ProductOrder();
order.productName=productName;
order.price=price;
order.quantity=quantity;
return order;
}
static void printOrders(ProductOrder[] orders){
for(ProductOrder order : orders){
System.out.println("상품명: " + order.productName + ", 가격: " + order.price + ", 수량: " + order.quantity);
}
}
static int getTotalAmount(ProductOrder[] orders){
int totalAmount=0;
for (ProductOrder order : orders) {
totalAmount += order.price * order.quantity;
}
return totalAmount;
}
}
사용자 입력을 받아서 해보기
public class ProductOrderMain {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
System.out.print("입력할 주문의 개수를 입력하세요: ");
int n = scanner.nextInt();
scanner.nextLine(); // 다음 라인으로 넘겨야 함.
ProductOrder[] orders = new ProductOrder[n];
for(int i=0; i< orders.length; i++){
System.out.println((i+1)+"번 째 주문 정보를 입력하세요.");
System.out.print("상품명: ");
String ProductName=scanner.nextLine();
System.out.print("가격: ");
int price=scanner.nextInt();
System.out.print("수량: ");
int quantity=scanner.nextInt();
scanner.nextLine(); // 입력 버퍼를 비우기 위한 코드
orders[i] = createOrder(ProductName, price, quantity);
}
printOrders(orders);
int totalAmount=getTotalAmount(orders);
System.out.println("총 결재 금액: " + totalAmount);
}
static ProductOrder createOrder(String productName, int price, int quantity){
ProductOrder order=new ProductOrder();
order.productName=productName;
order.price=price;
order.quantity=quantity;
return order;
}
static void printOrders(ProductOrder[] orders){
for(ProductOrder order : orders){
System.out.println("상품명: " + order.productName + ", 가격: " + order.price + ", 수량: " + order.quantity);
}
}
static int getTotalAmount(ProductOrder[] orders){
int totalAmount=0;
for (ProductOrder order : orders) {
totalAmount += order.price * order.quantity;
}
return totalAmount;
}
}
[객체 지향 프로그래밍]
절차지향 프로그래밍 vs 객체 지향 프로그래밍
절차 지향 프로그래밍이란?
실행순서를 중요시하는 방식. 프로그램 흐름을 순차적으로 따르며 처리하는 방식
즉, "어떻게"를 중심으로 프로그래밍한다.
객체 지향 프로그래밍이란?
객체를 중요하게 생각하는 방식
실제 세계의 사물이나 사건을 객체로 보고, 이러한 객체들 간의 상호작용을 중심으로 프로그래밍하는 방식.
즉, "무엇을" 중심으로 프로그래밍을 한다.
* 둘의 중요한 차이
절차 지향은 데이터와 해당 데이터에 대한 처리 방식이 분리되어 있다. 반면에, 객체 지향에서는 데이터와 그 데이터에 대한 행동이 하나의 "객체" 안에 함꼐 포함되어 있다.
절차 지향 프로그래밍에서 점진적으로 객체지향으로 넘어갈 것이다.
[문제1]-> 절차 지향 프로그래밍
1. 음악 플레이어를 켜고 끌 수 있어야 한다.
2. 음악 플레이어의 볼륨을 증가, 감소할 수 있어야 한다.
3. 음악 플레이어의 상태를 확인할 수 있어야 한다.
public class MusicPlayerMain1 {
public static void main(String[] args) {
int volume = 0;
boolean isOn =false;
// 음악 플레이어 켜기
isOn=true;
System.out.println("음악 플레이어를 시작합니다.");
// 볼륨 증가
volume++;
System.out.println("음악 플레이어 볼륨: " + volume);
// 볼륨 증가
volume++;
System.out.println("음악 플레이어 볼륨: " + volume);
// 볼륨 감소
volume--;
System.out.println("음악 플레이어 볼륨: " + volume);
// 음악 플레이어 상태
System.out.println("음악 플레이어 상태 확인");
if(isOn){
System.out.println("음악 플레이어 ON, 볼륨: " + volume);
}else{
System.out.println("음악 플레이어 OFF");
}
// 음악 플레이어 끄기
isOn=false;
System.out.println("음악 플레이어를 종료합니다.");
}
}
[문제2]-> 0.1객체지향 프로그래밍
데이터 묶음
앞서 작성한 코드에 클래스를 도입하자. MusicPlayerData라는 클래스를 만들고, 음악 플레이어에 사용되는 데이터들을 여기에 묶어서 멤버 변수로 사용하자.
public class MusicPlayerData {
int volume=0;
boolean isOn=false;
}
public class MusicPlayerMain2 {
public static void main(String[] args) {
MusicPlayerData data=new MusicPlayerData();
// 음악 플레이어 켜기
data.isOn=true;
System.out.println("음악 플레이어를 시작합니다.");
// 볼륨 증가
data.volume++;
System.out.println("음악 플레이어 볼륨: " + data.volume);
// 볼륨 증가
data.volume++;
System.out.println("음악 플레이어 볼륨: " + data.volume);
// 볼륨 감소
data.volume--;
System.out.println("음악 플레이어 볼륨: " + data.volume);
// 음악 플레이어 상태
System.out.println("음악 플레이어 상태 확인");
if(data.isOn){
System.out.println("음악 플레이어 ON, 볼륨: " + data.volume);
}else{
System.out.println("음악 플레이어 OFF");
}
// 음악 플레이어 끄기
data.isOn=false;
System.out.println("음악 플레이어를 종료합니다.");
}
}
음악 플레이어 관련된 변수들은 한 군데 뭉쳐 있어서 관리가 쉬워진다.
[문제3]-> 0.2객체지향 프로그래밍
메서드 추출
위에 보면 중복되는 코드들이 많다. 그리고 각각의 기능들은 이후에 재사용될 가능성이 높다.
- 음악 플레이어 켜기, 끄기
- 볼륨 증가, 감소
- 음악 플레이어 상태 출력
-> 이것들은 메서드를 사용하면 된다.
public class MusicPlayerMain2 {
public static void main(String[] args) {
MusicPlayerData data=new MusicPlayerData();
// 음악 플레이어 켜기
on(data);
// 볼륨 증가
volumeUp(data);
// 볼륨 증가
volumeUp(data);
// 볼륨 감소
volumeDown(data);
// 음악 플레이어 상태
checkStatus(data);
// 음악 플레이어 끄기
off(data);
}
static void on(MusicPlayerData data){ // 음악 플레이어 켜기
data.isOn=true;
System.out.println("음악 플레이어를 시작합니다.");
}
static void off(MusicPlayerData data){ // 음악 플레이어 끄기
data.isOn=false;
System.out.println("음악 플레이어를 종료합니다.");
}
static void volumeUp(MusicPlayerData data){
data.volume++;
System.out.println("음악 플레이어 볼륨: " + data.volume);
}
static void volumeDown(MusicPlayerData data){
data.volume--;
System.out.println("음악 플레이어 볼륨: " + data.volume);
}
static void checkStatus(MusicPlayerData data){
System.out.println("음악 플레이어 상태 확인");
if(data.isOn){
System.out.println("음악 플레이어 ON, 볼륨: " + data.volume);
}else{
System.out.println("음악 플레이어 OFF");
}
}
}
장점
1) 중복 제거: 로직 중복이 제거되었다. 같은 로직이 필요하면 해당 메서드를 여러 번 호출하면 된다.
2) 변경 영향 범위 : 기능을 수정할때 메서드 내부만 변경하면 된다.
3) 매서드 이름 추가: 메서드 이름을 통해 코드를 더 쉽게 이해
모듈화란? 레고 블럭, 필요한 블럭을 가져다가 꼽아서 사용할 수 있다. 음악 플레이어에 기능이 필요하면 해당 기능을 메서드 호출 만으로 손 쉽게 사용할 수 있다. 이제 음악 플레이어와 관련된 메서드를 조립해서 프로그램을 작성할 수 있다.
절차지향 프로그래밍의 한계
지금까지 클래스를 사용해서 관련된 데이터를 하나로 묶고, 또 메서드를 사용해서 각각의 기능을 모듈화했다. 덕분에 상당히 깔끔하고 읽기 좋고, 유지보수 하기 좋은 코드를 작성할 수 있었다. 하지만 여기서 더 개선할 수 는 없을까?
우리가 작성한 코드의 한계는 데이터와 기능이 분리되어 있다는 점이다. 음악 플레이어의 데이터는 MusicPlayerData에 있는데, 그 데이터를 사용하는 기능은 MusicPlayerMain3에 있는 각각의 메서드에 분리되어 있다. 그래서 음악 플레이어와 관련된 데이터는 MusicPlayerData를 사용해야 하고, 음악 플레이어와 관련된 기능은 MusicPlayerMain3의 각 메서드를 사용해야 한다.
데이터와 그 데이터를 사용하는 기능은 매우 밀접하게 연관되어 있다. 각각의 메서드를 보면 대부분은 MusicPlayerData의 데이터를 사용한다. 따라서, 이후에 관련 데이터가 변경되면 MusicPlayerMain3 부분의 메서드들도 함께 변경해야 한다. 그리고 이렇게 데이터와 기능이 분리되어 있으면 유지보수 관점에서도 관리 포인트가 두 곳으로 늘어난다.
과거는 이러한 방식이 최선이었지만, 현재 객체지향 프로그래밍이 나오면서 데이터와 기능을 온전히 하나로 묶어서 사용할 수 있게 되었다.
[문제4]
클래스는 데이터인 멤버 변수 뿐만 아니라, 기능 역할을 하는 메서드도 포함할 수 있다.
먼저 멤버 변수만 존재하는 클래스로 간단한 코드를 작성하자.
public class ValueData {
int value;
}
public class ValueDataMain {
public static void main(String[] args) {
ValueData valueData=new ValueData();
add(valueData);
add(valueData);
add(valueData);
System.out.println("최종 숫자 = "+ valueData.value);
}
static void add(ValueData valueData){
valueData.value++;
System.out.println("숫자 증가 value= " + valueData.value);
}
}
자바와 같은 객체 지향 언어는 클래스 내부에 속성(데이터)과 기능(메서드)을 함께 포함할 수 있다. 클래스 내부에 멤버 변수 뿐만 아니라 메서드도 함께 포함할 수 있다는 뜻이다.
public class ValueData {
int value;
void add(){
value++;
System.out.println("숫자 증가 value=" + value);
// 데이터가 있는 부분과 함수가 함께 있음.
}
}
메서드는 객체를 생성해야 호출할 수 있다. 그런데, static이 붙으면 객체를 생성하지 않고도, 메서드를 호출할 수 있다.
public class ValueObjectMain {
public static void main(String[] args) {
ValueData valueData=new ValueData();
valueData.add(); // valueData의 value도 사용할 수 있지만, 메서드도 사용할 수 있게 된다.
valueData.add();
valueData.add();
System.out.println("최종 숫자 = "+ valueData.value);
}
}
add메서드를 호출하면 메서드 내부에서 value++을 호출하게 된다. 이때 value에 접근해야 하는데, 기본으로 본인 인스턴스에 있는 멤버 변수에 접근한다. 본인 인스턴스가 x002참조값을 사용하므로 자기 자신인 x002.value에 접근하게 된다.
[음악 플레이어 수정 버전]
지금까지 개발한 음악 플레이어는 데이터와 기능이 분리되어 있었다. 이제는 데이터와 기능을 하나로 묶어서 음악 플레이어라는 개념을 온전히 하나의 클래스에 담아보자. 프로그램을 작성하는 절차도 중요하지만, 지금은 음악 플레이어라는 개념을 객체로 온전히 만드는 것이 더 중요하다.
-> 음악 플레이어 클래스를 만드는 것에 더 집중.
음악 플레이어가 어떤 속성을 가지고 어떤 기능을 제공하는지 이 부분에 초점을 맞추어야 한다.
음악 플레이어
속성: volume, isOn
기능: on(), off(), volumeUp(), volumeDown(), showStatus()
package Oop1;
public class MusicPlayer {
int volume=0;
boolean isOn=false;
// 자기자신의 데이터를 사용하기 때문에 참조 필요하지 않음
void on(){ // 음악 플레이어 켜기
isOn=true;
System.out.println("음악 플레이어를 시작합니다.");
}
void off(){ // 음악 플레이어 끄기
isOn=false;
System.out.println("음악 플레이어를 종료합니다.");
}
void volumeUp(){
volume++;
System.out.println("음악 플레이어 볼륨: " + volume);
}
void volumeDown(){
volume--;
System.out.println("음악 플레이어 볼륨: " + volume);
}
void checkStatus(){
System.out.println("음악 플레이어 상태 확인");
if(isOn){
System.out.println("음악 플레이어 ON, 볼륨: " + volume);
}else{
System.out.println("음악 플레이어 OFF");
}
}
}
public class MusicPlayerMain4 {
public static void main(String[] args) {
MusicPlayer player=new MusicPlayer();
// 음악 켜기
player.on();
player.volumeUp();
player.volumeUp();
player.volumeDown();
player.checkStatus();
player.off();
}
}
함수 안에 뭐가 들었는지 모르지만, 그냥 클래스에서 함수를 불러와서 사용할 수 있다.
캡슐화
MusicPlayer를 보면 음악 플레이어를 구성하기 위한 속성과 기능이 마치 하나의 캡슐처럼 쌓여 있는 것 같다. 이렇게 속성과 기능을 하나로 묶어서 필요한 기능을 메서드를 통해 외부에 제공하는 것을 캡슐화라 한다.
객체지향 프로그래밍 덕분에 음악 플레이어 객체를 사용하는 입장에서 진짜 음악 플레이어를 만들고 사용하는 것처럼 친숙하게 느껴진다. 그래서 더 코드가 읽기 쉬운 것은 물론이고, 속성과 기능이 한 곳에 있어서 변경도 더 쉽다. ex) MusicPlayer내부 코드가 변하는 경우에 다른 코드는 변하지 않아도 된다. MusicPlayer의 volume이라는 필드 이름이 다른 이름으로 변한다고 할 때, MusicPlayer내부만 변경하면 된다. 또 음악 플레이어가 내부에서 출력하는 메시지를 변경할 때도 MusicPlayer 내부만 변경하면 된다. 이 경우, MusicPlayer를 사용하는 개발자는 코드를 전혀 변경하지 않아도 된다. 물론 외부에서 호출하는 MusicPlayer의 메서드 이름을 변경한다면, MusicPlayer를 사용하는 곳의 코드도 변경해야 한다.
객체지향으로 오면서 순서 보다는 어떻게 속성과 기능을 잘 만들어야 하는지가 중요해짐
[문제]
절차 지향 직사각형 프로그램을 객체지향으로 변경하기
다음은 직사각형의 넓이, 둘레길이, 정사각형 여부를 구하는 프로그램이다.
1. 절차지향 프로그래밍 방식으로 되어 있는 코드를 객체 지향 프로그래밍 방식으로 변경하라.
2. Rectangle 클래스를 만들어라.
3. RectangleOopMain에 해당 클래스를 사용하는 Main()코드를 만들어라.
<절차지향형 코드>
public class RectableProceduralMain {
public static void main(String[] args) {
int width=5, height=8;
int area=calculateArea(width, height);
System.out.println("넓이: " + area);
int perimeter=calculatePerimeter(width, height);
System.out.println("둘레 길이: " + perimeter);
boolean square=isSquare(width, height);
System.out.println("정사각형 여부: " + square);
}
static int calculateArea(int width, int height){
return width*height;
}
static int calculatePerimeter(int width, int height){
return 2*(width+height);
}
static boolean isSquare(int width, int height){
return width==height;
}
}
<처음 내가 바꾼 코드>
public class Rectangle {
int width, height;
int calculateArea(int width, int height){
return width*height;
}
int calculatePerimeter(int width, int height){
return 2*(width+height);
}
boolean isSquare(int width, int height){
return width==height;
}
}
public class RectableProceduralMain {
public static void main(String[] args) {
Rectangle rect=new Rectangle();
rect.width=5;
rect.height=8;
int area=rect.calculateArea(rect.width, rect.height);
System.out.println("넓이: " + area);
int perimeter=rect.calculatePerimeter(rect.width, rect.height);
System.out.println("둘레 길이: " + perimeter);
boolean square=rect.isSquare(rect.width, rect.height);
System.out.println("정사각형 여부: " + square);
}
}
[문제]
객체 지향 계좌
은행 계좌를 객체로 설계해야 한다.
Account 클래스를 만들어라
- int balance 잔액
- deposit(int amount) : 입금 메서드 -> 입금 시 잔액이 증가한다.
- withdraw(int amount): 출금 메서드 => 출금이 잔액이 감소한다. 만약 잔액이 부족하면 잔액 부족을 출력해야 한다.
- AccountMain클래스를 만들고 main()메서드를 통해 프로그램을 시작해라
ex) 계좌에 10000원을 입금하라 -> 9000원을 출금하라 -> 2000원 출금을 시도하라-> 잔액 부족 출력을 확인 -> 잔고를 출력하라(잔고: 1000)
public class Account {
int balance;
void deposit(int amount) {
balance += amount;
}
void withdraw(int amount){
if(balance<=amount){
System.out.println("잔액부족");
}else{
balance-=amount;
}
}
}
public class AccountMain {
public static void main(String[] args) {
Account account=new Account();
account.deposit(10000);
account.withdraw(9000);
account.withdraw(2000);
System.out.println("잔고: "+ account.balance);
}
}