이제 과일 바구니에 진짜 객체지향을 담아보자

MongCheol·2023년 3월 17일
0

부트캠프

목록 보기
14/15


이전 글에서 객체지향을 적용하지 않은 과일들과 바구니들을 구현해 보았다.
https://velog.io/@mongcheol/과일-바구니에-안객체지향-담기

객체지향을 적용하지 않아도 정상적으로 동작을 잘 하는 코드를 만들수 있었는데
새로운 과일을 추가하는 일이 생기거나, 과일들에 대해 새로운 함수를 추가해야 할 때
해야하는 일이 너무 많아서 라꾸라꾸를 주문해야 한다는 문제가 발생했었다.(집에 갈수 없어😭)

우리가 무사히 집에가서 따뜻한 침대에서 잠을 자기 위한 방법이 객체지향의 적용이다.
객체 지향은 크게 추상화, 상속, 캡슐화, 다형성 4가지로 이루어져 있다.

객체지향

각 개념을 짧게 설명해 보자면

추상화

  • 클래스들의 중복 되는 공통적인 요소들을 뽑아서 상위 클래스를 만드는 것

상속

  • 추상화 과정에서 만든 상위 클래스를 상속받아서 코드를 물려 받는 것

캡슐화

  • 객체의 내부 구조 및 데이터를 외부에서 직접 볼 수 없게 은닉하여 보호하는 것

다형성

  • 상위 클래스 타입의 참조변수를 통해 하위 클래스들의 객체를 참조할 수 있도록 하는 것

역시나 개념 설명은 말도 어렵고 이해도 쉽지 않다.
이전글에서 안객체지향으로 구현한 코드에 하나씩 객체지향을 입혀보며 개념들을 익혀보자!

안객체지향 코드

사과

public class Apple {
	private String name;
    private String color;
    private int price;
    
    public Apple(String name, String color, int price) {
    	this.name = name;
 		this.color = color;
 		this.price = price;
        if(price < 0) {
        	this.price = 0;
        }
    }
    
    public void printAppleName() {
    	System.out.printf("내 이름은 %s\n야!", name);
    }
    
    public String getName() {
    	return name;
    }
    
    public String getColor() {
    	return color;
    }
    
    public int getPrice() {
    	return price;
    }
    
    public void setName(String name) {
    	this.name = name;
    }
    
    public void setColor(String color) {
        this.color = color;
    }
    
    public void setPrice(int price) {
    	if(price < 0) {
        	System.out.println("가격은 음수가 될 수 없습니다.");
            return;
        }
        
        this.price = price;
    }
}

망고

public class Mango {
    private String name;
    private String color;
    private int price;

    public Mango(String name, String color, int price) {
        this.name = name;
        this.color = color;
        this.price = price;
        if(price < 0) {
            this.price = 0;
        }
    }

    public void printMangoName() {
        System.out.printf("내 이름은 %s\n야!", name);
    }

    public String getName() {
        return name;
    }

    public String getColor() {
        return color;
    }

    public int getPrice() {
        return price;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void setPrice(int price) {
        if(price < 0) {
            System.out.println("가격은 음수가 될 수 없습니다.");
            return;
        }

        this.price = price;
    }
}

포도

public class Grape {
    private String name;
    private String color;
    private int price;

    public Grape(String name, String color, int price) {
        this.name = name;
        this.color = color;
        this.price = price;
        if(price < 0) {
            this.price = 0;
        }
    }

    public void printGrapeName() {
        System.out.printf("내 이름은 %s야!\n", name);
    }

    public String getName() {
        return name;
    }

    public String getColor() {
        return color;
    }

    public int getPrice() {
        return price;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void setPrice(int price) {
        if(price < 0) {
            System.out.println("가격은 음수가 될 수 없습니다.");
            return;
        }

        this.price = price;
    }
}

바구니

public class Basket {
    Apple apple;
    Mango mango;
    Grape grape;

    public Basket(Apple apple, Mango mango, Grape grape) {
        this.apple = apple;
        this.mango = mango;
        this.grape = grape;
    }

    public void printBasket() {
        apple.printAppleName();
        mango.printMangoName();
        grape.printGrapeName();
    }

    public void printTotalPrice() {
        int totalPrice = 0;
        totalPrice += apple.getPrice() + mango.getPrice() + grape.getPrice();
        System.out.printf("총 가격 : %d원", totalPrice);
    }
}

메인 클래스

public class Main {
    public static void main(String[] args) {
        Apple apple = new Apple("사과", "빨강", 1000);
        Mango mango = new Mango("망고", "노랑", 2000);
        Grape grape = new Grape("포도", "보라", 1500);
        Basket basket = new Basket(apple, mango, grape);

        basket.printBasket();
        basket.printTotalPrice();
    }
}

실행 결과

내 이름은 사과야!
내 이름은 망고야!
내 이름은 포도야!
총 가격 : 4500원

왜 velog에는 notion에 있는 접기 기능이 없는거야 궁시렁궁시렁
하는거라곤 바구니에 과일들을 담아서 이름을 출력하고 가격의 총합 밖에 출력하지 않는 주제에 코드 양이 꽤 길다. 긴 스크롤을 내리며 코드들을 보고있으면 한가지를 느낄 수 있다.
중복 된 코드들을 어떻게 못없애나?
그래서 등장하는 것이 바로 추상화상속이다.

추상화

사과, 망고, 포도는 모두 이름, 색깔, 가격 변수를 가지고
이름 출력 함수, get 함수, set 함수들을 가지고있다.
중복되는 요소들을 모아서 사과, 망고, 포도를 모두 아우를 수 있는 과일 클래스를 만들어보자.

public class Fruit {
	private String name;
    private String color;
    private int price;
    
    public Fruit(String name, String color, int price) {
    	this.name = name;
        this.color = color;
        this.price = price;
        if(price < 0) {
            this.price = 0;
        }
    }
    
    public void printFruitName() {
        System.out.printf("내 이름은 %s야!\n", name);
    }

    public String getName() {
        return name;
    }

    public String getColor() {
        return color;
    }

    public int getPrice() {
        return price;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void setPrice(int price) {
        if(price < 0) {
            System.out.println("가격은 음수가 될 수 없습니다.");
            return;
        }

        this.price = price;
    }
}

이렇게 중복된 요소들을 뽑아서 상위클래스를 만들어 주기만 해선 아무 소용이 없다.
열심히 만든 이녀석을 사용하려면 이녀석을 상속받을 하위 클래스를 만들어 줘야 한다.

상속

사과, 망고, 포도의 중복 요소들을 뽑아서 만든 상위 클래스인 과일 클래스를 상속받아서 사과, 망고, 포도를 구현해보자.

public class Apple extends Fruit {
	public Apple(String name, String color, int price) {
    	super(name, color, price);
    }
}
public class Mango extends Fruit {
	public Mango(String name, String color, int price) {
    	super(name, color, price);
    }
}
public class Grape extends Fruit {
	public Grape(String name, String color, int price) {
    	super(name, color, price);
    }
}

각 클래스의 생성자 안에 있는 super는 상위 클래스의 생성자를 가르킨다.

???
고작 이정도만 적어주면 된다고?!🤔
라고 할 수도 있지만
안객체지향 코드에 Fruit를 추가하고 Apple, Mango, Grape 클래스를 위와같이 수정하고,
Basket 클래스의 printBasket() 함수를 아래와 같이 수정한 뒤 Main 클래스를 실행해보면

public class Basket {
    Apple apple;
    Mango mango;
    Grape grape;

    public Basket(Apple apple, Mango mango, Grape grape) {
        this.apple = apple;
        this.mango = mango;
        this.grape = grape;
    }

    public void printBasket() {
        apple.printFruitName();	// printAppleName() 에서 printFruitName() 로 변경
        mango.printFruitName();	// printMangoName() 에서 printFruitName() 로 변경
        grape.printFruitName();	// printGrapeName() 에서 printFruitName() 로 변경
    }

    public void printTotalPrice() {
        int totalPrice = 0;
        totalPrice += apple.getPrice() + mango.getPrice() + grape.getPrice();
        System.out.printf("총 가격 : %d원", totalPrice);
    }
}

실행 결과

내 이름은 사과야!
내 이름은 망고야!
내 이름은 포도야!
총 가격 : 4500원

놀랍게도 이전과 같은 결과를 보여준다.
추상화상속을 이용하면 코드의 재사용이 가능해져서 효과적인 작업이 가능하다.

+
글을 적다보니 이럴거면 그냥 Fruit 클래스만 이용해서 사과, 망고, 포도를 만들면 되지않나?
라는 생각이 들었는데
사과에 사과한다() 라는 함수를 추가하려고 한다면 Fruit 클래스만을 사용해서는 구현이 어렵고 코드가 지저분해진다. 그리고 객체지향을 학습하기 위한 클래스들 이므로 대충 그러려니 하고 넘어가자!


이제 흐름상 캡슐화에 대한 이야기가 나와야 하는데 사실 과일 바구니 프로젝트는 복잡한 로직도 없고 숨겨야 할 만한 정보도 없기 때문에 다른 예시를 들어서 설명해보겠습니드앙.

캡슐화

profile
자그마한 개미

0개의 댓글