절차 지향 프로그래밍을 만들고 객체 지향으로 바꿔보자!
<MusicPalyerMain1.java>
package oop1;
public class MusicPalyerMain1 {
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("음악 플레이어를 종료합니다");
}
}
<결과>
MusicPlayerData 라는 클래스로 사용되는 데이터들을 여기에 묶어서 멤버변수로 사용하자
<MusicPlayerData.java>
package oop1;
public class MusicPlayerData {
int volume=0;
boolean isOn = false;
}
<MusicPalyerMain2.java>
package oop1;
public class MusicPalyerMain2 {
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("음악 플레이어를 종료합니다");
}
}
다양한 변수들이 추가되더라도 쉽게 관리 할 수 있다.
중복되는 부분들을 정리하자 -> 재사용하기 쉬움
<MusicPlayerMain3.java>
package oop1;
public class MusicPalyerMain3 {
public static void main(String[] args) {
MusicPlayerData data = new MusicPlayerData();
//음악 플레이어 켜기
on(data);
//볼륨 증가
volumeUp(data);
//볼륨 증가
volumeUp(data);
//볼륨 감소
volumeDown(data);
//음악 플레이어 상태
showStatus(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 showStatus(MusicPlayerData data){
System.out.println("음악 플레이어 상태 확인");
if (data.isOn) {
System.out.println("음악 플레이어 ON, 볼륨:" + data.volume);
} else {
System.out.println("음악 플레이어 OFF");
}
}
}
각각의 기능을 메서드로 만들어 모듈화 시켰다.
=> 중복 제거, 변경영향범위(기능 수정 시 해당 메서드만 수정), 메서드이름을 통해 코드를 쉽게 이해할 수 있다.
절차지향프로그램의 한계
데이터(MusicPlayerData)와 기능(MusicPlayerMain3)이 분리되어 있어서 유지보수 관점에서 2곳을 관리하여야한다.
=> 객체지향 프로그램으로 데이터와 기능을 온전히 하나로 묶어서 사용
<ValueData.java>
package oop1;
public class ValueData {
int value;
}
<ValueDataMain.java>
package oop1;
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);
}
}
<결과>
클래스 내부에 속성(데이터)과 기능(메서드)을 함께 포함할 수 있다.
<ValueData.java>
package oop1;
public class ValueData {
int value;
void add(){
value++;
System.out.println("숫자증가 value= " + value);
}
}
<ValueObjectMain.java>
package oop1;
public class ValueObjectMain {
public static void main(String[] args) {
ValueData valueData = new ValueData();
valueData.add();
valueData.add();
valueData.add();
System.out.println("최종 숫자 = " + valueData.value);
}
}
- 클래스는 속성(데이터, 멤버 변수)과 기능(메서드)을 정의할 수 있다.
- 객체는 자신의 메서드를 통해 자신의 멤버 변수에 접근할 수 있다
- 객체의 메서드 내부에서 접근하는 멤버 변수는 객체 자신의 멤버 변수이다.
음악 플레이어를 만들어서 제공하는 개발자와 플레이어를 사용하는 개발자가 분리되어있다고 생각하자!
- 속성 : volume, isOn
- 기능 : on(), off(), volumeUp(), volumeDown(), showStatus()
<MusicPlayer.java>
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 showStatus(){
System.out.println("음악 플레이어 상태 확인");
if (isOn) {
System.out.println("음악 플레이어 ON, 볼륨:" + volume);
} else {
System.out.println("음악 플레이어 OFF");
}
}
}
=> 음악 플레이어를 사용하는데 필요한 모든 속성과 기능이 하나의 클래스에 포함되어 있다
<MusicPlayerMain4.java>
package oop1;
public class MusicPlayerMain4 {
public static void main(String[] args) {
MusicPlayer player = new MusicPlayer();
//음악 플레이어 켜기
player.on();
//볼륨 증가
player.volumeUp();
//볼륨 증가
player.volumeUp();
//볼륨 감소
player.volumeDown();
//음악 플레이어 상태
player.showStatus();
//음악 플레이어 끄기
player.off();
}
}
MusicPlayer 를 사용하는 입장에서는 이제 MusicPlayer 내부에 어떤 속성(데이터)이 있는지 전혀 몰라도 된다. MusicPlayer 를 사용하는 입장에서는 단순하게 MusicPlayer 가 제공하는 기능 중에 필요한 기능을 호출해서 사용하기만 하면 된다.
MusicPlayer 를 보면 음악 플레이어를 구성하기 위한 속성과 기능이 마치 하나의 캡슐에 쌓여있는 것 같다. 이렇게 속성과 기능을 하나로 묶어서 필요한 기능을 메서드를 통해 외부에 제공하는 것을 캡슐화라 한다.
<RectangleProceduralMain.java>
package oop1.ex;
public class RectangleProceduralMain {
public static void main(String[] args) {
int width=5;
int 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){
int area = width*height;
return area;
}
static int calculatePerimeter(int width,int height){
int perimeter = (width+height)*2;
return perimeter;
}
static boolean isSquare(int width,int height){
return width == height;
}
}
<RectangleProceduralMain.java>
package oop1.ex;
public class RectangleProceduralMain {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.width=5;
rectangle.height=8;
int area = rectangle.calculateArea();
System.out.println("넓이: " + area);
int perimeter = rectangle.calculatePerimeter();
System.out.println("둘레 길이: " + perimeter);
boolean square = rectangle.isSquare();
System.out.println("정사각형 여부: " + square);
}
}
<Rectangle.java>
package oop1.ex;
public class RectangleProceduralMain {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.width=5;
rectangle.height=8;
int area = rectangle.calculateArea();
System.out.println("넓이: " + area);
int perimeter = rectangle.calculatePerimeter();
System.out.println("둘레 길이: " + perimeter);
boolean square = rectangle.isSquare();
System.out.println("정사각형 여부: " + square);
}
}
<Account.java>
package oop1.ex;
public class Account {
int balance;
void deposit(int amount){
balance+=amount;
}
void withdraw(int amount){
if(balance>=amount){
balance-=amount;
}else{
System.out.println("잔액이 부족합니다.");
}
}
}
<AccountMain.java>
package oop1.ex;
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);
}
}
객체를 생성하는 시점에 어떤 작업을 하고 싶다면 생성자(Constructor)를 이용하면 된다.
<MemberInit.java>
package construct;
public class MemberInit {
String name;
int age;
int grade;
}
<MemberInitMain1.java>
package construct;
public class MethodInitMain1 {
public static void main(String[] args) {
MemberInit member1= new MemberInit();
member1.name = "user1";
member1.age = 15;
member1.grade=90;
MemberInit member2 = new MemberInit();
member2.name = "user2";
member2.age = 16;
member2.grade=80;
MemberInit[] members = {member1,member2};
for(MemberInit s: members){
System.out.println("이름: "+s.name+" 나이:"+s.age+" 성적:"+s.grade);
}
}
}
=> 회원의 초기값을 설정하는 부분이 반복 된다. 메서드(initMember)를 사용하여 반복을 제거하자.
<MemberInitMain2.java>
package construct;
public class MethodInitMain2 {
public static void main(String[] args) {
MemberInit member1= new MemberInit();
initMember(member1,"user1",15,90);
MemberInit member2 = new MemberInit();
initMember(member2,"user2",16,80);
MemberInit[] members = {member1,member2};
for(MemberInit s: members){
System.out.println("이름:"+ s.name+" 나이:"+s.age+ " 성적: "+ s.grade);
}
}
static void initMember(MemberInit member, String name, int age, int grade){
member.name = name;
member.age = age;
member.grade = grade;
}
}
<MemberInit.java> 에 initMember() 추가
package construct;
public class MemberInit {
String name;
int age;
int grade;
//추가
void initMember(String name, int age, int grade){
this.name = name;
this.age = age;
this.grade = grade;
}
}
<MemberInitMain3.java>
package construct;
public class MethodInitMain3 {
public static void main(String[] args) {
MemberInit member1= new MemberInit();
member1.initMember("user1",15,90);
MemberInit member2 = new MemberInit();
member2.initMember("user2",16,80);
MemberInit[] members = {member1,member2};
for(MemberInit s: members){
System.out.println("이름:"+ s.name+" 나이:"+s.age+ " 성적: "+ s.grade);
}
}
}
this
this 는 생략할 수 있다. 이 경우 변수를 찾을 때 가까운 지역변수(매개변수도 지역변수다)를 먼저 찾고 없으면 그 다음으로 멤버 변수를 찾는다. 멤버 변수도 없으면 오류가 발생한다.
<MemberThis.java>
package construct;
public class MemberThis {
String nameField;
void initMember(String nameParameter){
nameField = nameParameter;
}
}
항상 this를 사용하는 스타일
<MemberThis.java>
package construct;
public class MemberThis {
String nameField;
void initMember(String nameParameter){
this.nameField = nameParameter;
}
}
=> 헷갈릴 땐 그냥 추가 해도된다. 이렇게하면 이 코드가 멤버 변수를 사용한다는 것을 눈으로 쉽게 확인할 수 있다. BUT 요즘은 잘 안쓴다. 권장하지 않는다.
프로그래밍을 하다보면 객체 생성후 바로 초기값을 할당해야하는 경우가 생기는데 그럴 때마다 매번 메서드를 만들어야 한다.
그래서 대부분의 객체지향언어는 객체를 생성하자마자 즉시 필요한 기능을 좀 더 편리하게 수행할 수 있도록 생성자라는 기능을 제공한다.
<MemberConstruct.java>
package construct;
public class MemberConstruct {
String name;
int age;
int grade;
//생성자
MemberConstruct(String name, int age, int grade){
System.out.println("생성자 호출 name= "+name+", age="+age+", grade="+grade);
this.name=name;
this.age=age;
this.grade = grade;
}
}
- 생정자의 이름은 클래스 이름과 동일해야한다. 따라서 첫글자도 대문자로 시작한다.
- 생정자는 반환타입이 없다.
- 나머지는 메서드와 동일
<ConstructMain1.java>
package construct;
public class ConstructMain1 {
public static void main(String[] args) {
MemberConstruct member1 = new MemberConstruct("user1", 15,90);
MemberConstruct member2 = new MemberConstruct("user2", 16 ,80);
MemberConstruct[] members = {member1,member2};
for (MemberConstruct s : members) {
System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" +
s.grade);
}
}
}
<MemberInit.java>, <MethodInitMain3.java> 을 보면 알 수 있다.
<MemberDefault.java>
package construct;
public class MemberDefault {
String name;
//java에서 기본생성자 만들어줌
// MemberDefault(){
//
// }
//직접 기본생성자 정의
MemberDefault() {
System.out.println("생성자 호출");
}
}
<MemberDefaultMain.java>
package construct;
public class MemberDefaultMain {
public static void main(String[] args) {
MemberDefault memberDefault = new MemberDefault();
}
}
<MemberConstruct.java>
package construct;
public class MemberConstruct {
String name;
int age;
int grade;
//오버로딩 추가
MemberConstruct(String name, int age){
this.name =name;
this.age = age;
this.grade = 50;
}
//변경
// MemberConstruct(String name, int age){
// this(name, age,50);
// }
//생성자
MemberConstruct(String name, int age, int grade){
System.out.println("생성자 호출 name= "+name+", age="+age+", grade="+grade);
this.name=name;
this.age=age;
this.grade = grade;
}
}
기존의 MemberConstruct에 생성자를 하나 추가해서 생성자가 2개가 됨
MemberConstruct(String name, int age)
MemberConstruct(String name, int age, int grade)
새로 추가한 생성자는 grade를 받지 않고 50점으로 초기화.
<ConstructMain2.java>
package construct;
public class ConstructMain2 {
public static void main(String[] args) {
MemberConstruct member1 = new MemberConstruct("user1", 15,90);
MemberConstruct member2 = new MemberConstruct("user2", 16);
MemberConstruct[] members = {member1,member2};
for (MemberConstruct s : members) {
System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" +
s.grade);
}
}
}
<결과>
=> 오버로딩으로 성적 입력이 필요한 경우에는 grade가 있는 생성자 호출, 없는 경우에는 grade가 없는 생성자 호출!
MemberConstruct.java 의 중복된 코드
this.name ... =>
<MemberConstruct.java>
package construct;
public class MemberConstruct {
String name;
int age;
int grade;
//오버로딩 추가
// MemberConstruct(String name, int age){
// this.name =name;
// this.age = age;
// this.grade = 50;
// }
//변경
MemberConstruct(String name, int age){
this(name, age,50);
}
//생성자
MemberConstruct(String name, int age, int grade){
System.out.println("생성자 호출 name= "+name+", age="+age+", grade="+grade);
this.name=name;
this.age=age;
this.grade = grade;
}
}
첫번째 생성자 내부에서 두번째 생성자를 호출한다.
MemberConstruct(String name, int age) -> MemberConstruct(String name, int age,int grade)
=> this()를 사용하면 생성자 내부에서 다른 생성자를 호출 할 수 있다 -> 중복 제거
public MemberConstruct(String name, int age) {
System.out.println("go");
this(name, age, 50);
}
<Book.java>
package construct.ex;
public class Book {
String title;
String author;
int page;
Book(String title, String author){
this(title, author,0);
}
Book(String title, String author, int page){
this.title = title;
this.author=author;
this.page = page;
}
}
<BookMain.java>
package construct.ex;
public class BookMain {
public static void main(String[] args) {
Book book0 = new Book("","");
Book book1 = new Book("Hello Java", "Seo");
Book book2 = new Book("JPA 프로그래밍", "kim",700);
Book[] books = {book0,book1,book2};
for (Book b:books){
System.out.println("제목: "+b.title+" 저자:"+b.author+ " 페이지:"+b.page);
}
}
}