객체지향 프로그램 설계하기

Java의 상속

  • 수직적 구조로 설계하여 자식이 부모의 것을 사용할 수 있는 것.
  • Java의 상속은 한 클래스에 하나만 가능.
  • 객체를 수평적인 구조로 설계할 경우 1)중복코드 발생 2)새로운 요구사항 반영이 어려움=유지보수가 어려움 3)확장성이 떨어짐 의 단점이 생긴다.

예를 들어 사원 클래스를 설계한다고 할 경우 평사원, 임원, 비서, 임시직 모두 유사한 속성을 가지게 되므로 중복적인 요소가 생길 수 밖에 없다.

=> 수직적인 구조로 변경 시 부모 클래스로 사원 클래스를 만들어 공통속성을 만들어 준 후 평사원, 임원, 비서, 임시직클래스를 자식클래스로 만들어 상속받으면 수평적 구조의단점을 해결할 수 있다.

super class(상위 클래스, 부모 클래스) : 일반화, 추상화, 개념화, 포괄적

extends(상속, 확장)

sub class(하위 클래스, 자식 클래스) : 구체화, 세분화

protected

상속 관계에서 하위 클래스가 상위 클래스로의 접근을 허용하는 접근권한.
또한 같은 패키지 내에서만 접근을 허용한다.

상위 클래스의 멤버변수를 private로 선언하면 하위 클래스의 접근까지 막아버려 상속의 의미가 없어진다.
반대로 public으로 선언하면 모든 접근을 허용하므로 상속 시 하위 클래스의 접근만을 허용하는 protected를 사용한다.

super()

상위 클래스의 생성자를 호출하는 메서드.
직접 작성하지 않아도 기본메서드처럼 기본적으로 들어있다.

상속에서는 상위 클래스가 먼저 메모리에 생성된 후 하위 클래스를 생성해야 한다.

예제
일반사원 한 명의 데이터를 저장하고 출력하는 예제

세 클래스 모두 같은 패키지 내에 두고 실습.
- Employee -

// 전 사원 공통사항
public class Employee {
	protected String name;
    protected int age;
    protected String phone;
    protected String empDate;
    ...
    public Employee(){
    	super();
    }
    // toString() 생성
}

- RempVO -

// 일반사원
public class RempVO extends Employee{
	public RempVO(){
    	super();
    }
}

- EmployeeTest -

public class EmployeeTest {
    public static void main(String[] args) {
        RempVO vo = new RempVO();

        vo.name = "김사원";
        vo.age = 25;
        vo.phone = "010-1111-1111";
        vo.empDate = "2020-10-01";
        vo.dept = "홍보부";
       System.out.println(vo.toString());
        // 결과: Employee{name='김사원', age=25, phone='010-1111-1111', empDate='2020-10-01', dept='홍보부', marriage=false}
    }
}

-> RempVO클래스의 super()는 Employee클래스의 생성자를 의미한다.
고로 RempVO클래스를 생성하면 메모리에 Employee클래스가 먼저 생성된 후 RempVO클래스가 생성된다.
(상위 클래스 생성 → 하위 클래스 생성 의 순서가 중요)

메모리상으로는 상위 클래스와 하위 클래스가 각각의 영역을 가지지만 실제로는 하위 클래스가 상위 클래스의 영역까지 포함한 접근범위를 가지게 된다.

상속관계에서의 객체생성

상속을 사용하면 하위 클래스가 상위 클래스에 접근할 수 있는 장점이 있으나 정보은닉 차원에서는 문제가 될 수 있다.

=> 상위 클래스에 멤버변수는 private로 선언하며, 초기화는 자신의 클래스에서만 가능하도록 한다.
즉, 상위 클래스의 생성자 메서드에서 초기화를 하고 하위 클래스에서는 상위 클래스의 생성자를 호출하여 초기화를 하도록 한다.

예제
- Employee -

// 전 사원 공통사항
public class Employee {
    private String name;
    private int age;
    private String phone;

    public Employee() {
    }
    public Employee(String name, int age, String phone) {
        this.name = name;
        this.age = age;
        this.phone = phone;
    }
    // toString() 자동생성
}

- RempVO -

public class RemVO extends Employee{
    public RemVO(){}       // 상위 클래스의 생성자 호출

    public RemVO(String name, int age, String phone){
        // 하위 클래스에서 상위 클래스의 메모리에 초기화를 진행
        super(name, age, phone);    // 상위 클래스의 생성자 호출
    }
}

- EmployeeTest -

public class EmployeeTest {
    public static void main(String[] args) {
        RemVO vo = new RemVO("홍길동", 30, "010-1111-2222");
        System.out.println(vo.toString());
        // 결과: Employee{name='홍길동', age=30, phone='010-1111-2222'}
    }
}

-학습정리 & quiz-
1. 상속
2. extends
3. super()
4. protected
5. 중복코드 최소화, 사용자의 요구사항 반영에 유리(유지보수에 유리), 확장성이 좋다


동작 측면에서의 상속

method의 동작을 생각해보자.
보통은 보안을 위해 .java파일 그러니까 소스코드 원본을 제공하지 않는다. 실행코드인 .class파일만 받아 접근하게 되는데 이때 데이터를 주고받는 클래스 사이에 interface를 두어 interface를 상위 클래스로, 접근을 실행할 클래스를 하위 클래스로 두어 상속구조를 이루면 객체지향 측면에서 유리하다.
=> 보안상 클래스 내부에 직접 접근하는것이 불가능하게 만들면 하위 클래스에 무슨 코드가 작성되어있는지 알 수 없다.
이때 상위 클래스를 하위 클래스처럼 업캐스팅(Upcasting)해준다.

업캐스팅에 대한 설명은 다형성 파트에서 계속.

상속 체이닝

  • 최상단의 부모 클래스부터 객체가 생성되어 자식까지 연결되는구조.

오버라이드

  • 상위 클래스의 메서드를 하위 클래스의 메서드에서 재정의하는 것.

상위 클래스가 가진 데이터가 하위 클래스에 완전히 정합하지 않을때 하위 클래스에 맞춰 재정의하여 사용한다.
=> 상위 클래스의 객체를 생성 후 하위 클래스에서 재정의(오버라이드)한 메서드를 사용할 경우 동적바인딩을 통해 실행시점에서 사용될 메서드를 찾아 재정의된 메서드가 실행된다.

패캠Java/Spring 3주차 - Overloading 오버로딩

예제
- Animal -

public class Animal {
    public void eat(){
        System.out.println("동물이 먹다");
    }
}

- Dog -

public class Dog extends Animal{
	// Override 재정의
    public void eat(){
        System.out.println("개처럼 먹다");
    }
}

- Cat -

public class Cat extends Animal{
	// Override 재정의
    public void eat(){
        System.out.println("고양이처럼 먹다");
    }
    public void night(){
        System.out.println("고양이는 밤에 눈이 빛난다");
    }
}

- OverrideTest -

public class OverrideTest {
    public static void main(String[] args) {
    	// Upcasting : Dog.java(x), Animal <---> Dog.class(o)
        Animal dog = new Dog();
        dog.eat();  // Animal --(동적바인딩)--> Dog
		// Upcasting : Cat.java(x), Animal <---> Cat.class(o)
        Animal cat = new Cat();
        cat.eat();
    }
}


객체의 생성(Upcasting)과 재정의(Override)를 주목하여 볼 것.

super()

상위 클래스의 생성자를 호출한다.
메서드에서 가잣 첫 문장에서 사용.
super()는 상위 클래스의 기본생성자를 호출함.


-학습정리 & quiz-
1. 생성자?? → super()
2. 업캐스팅 Upcasting
3. 상속체이닝
4. 동적바인딩
5. 재정의 Override


다형성 Polymorphism

Java의 객체지향 프로그래밍에서 가장 중요한 개념.

상속 관계를 전제로 상위 클래스가 동일한 메시지로 하위 클래스를 서로 다르게 동작 시키는 객체지향 이론.

다형성의 전제조건
1. 상속관계
2. Override(재정의)
3. Upcasting(업캐스팅)
4. 동적바인딩

객체 형 변환 (Object casting)

형 변환의 일종.
상속 관계에서 부모와 자식 간의 형 변환이 가능함.

부모는 여러 명의 자식을 가리킬 수 있다.
부모를 알면 자식을 관리하기 용이함.

* 업캐스팅과 다운캐스팅의 예제는 아래의 오버라이드 예제를 바탕으로 함

업캐스팅 Upcasting

  • 상속관계에서 하위 클래스의 타입을 상위 클래스의 타입으로 바꾸는 행위
  • 자동 형 변환

Animal클래스를 Dog클래스가 상속방을 때
Dog x = new Dog() => x 하위 클래스에서 객체를 생성하는것은 권장하지 않음.
Animal x = new Dog() => o

다운캐스팅 Downcasting

  • 상속관계에서 상위 클래스의 타입을 하위 클래스의 타입으로 바꾸는 행위
  • 강제 형 변환

예제
- ObjectCasting -

public class ObjectCasting {
    public static void main(String[] args) {
        // Animal --> Dog, Cat
        Animal ani = new Dog();
        ani.eat();

        ani = new Cat();	// 업캐스팅 Upcasting
        ani.eat();
//        ani.night();    // 접근불가
    }
}


eat()메서드 : 부모가 자식들에게 동일한 메시지를 보냈으나 두 객체의 반응이 다름. → 다형성 개념으로 이어짐

ani.night()는 부모에게 없는 Cat클래스만의 고유 메서드이므로 다운캐스팅이 필요함.
상속관계에서는 부모 클래스는 자식 클래스의 메모리 영역에 접근권한이 없으므로 직접 접근 할 수 없다.

Cat c = (Cat) ani;
c.night();
// 혹은
((Cat)ani).night();

두 행에 걸친 방법과 한 행에 걸친 방법 모두 알아둘 것.

- ObjectCasting -

public class ObjectCasting {
    public static void main(String[] args) {
        // Animal --> Dog, Cat
        Animal ani = new Dog();
        ani.eat();

        ani = new Cat();
        ani.eat();
//        ani.night();    // 접근불가

        // Downcasting 다운캐스팅
        ((Cat)ani).night();
    }
}


다형성 개념을 제대로 알아야 Java의 api를 활용할 수 있으므로 제대로 익혀두어야 함.

다형성 활용(인수, 배열)

다형성 인수

다형성을 인수에 활용하면 코드의 중복을 줄일 수 있다.
예제처럼 인수로 넘어갈 클래스의 자료형(각자 Dog, Cat으로 다름)이 다를 때 공통의 부모 클래스인 Animal클래스로 인수를 받으면 다형성으로 인해 메서드가 정상동작하게 된다.

용어알기 : 인수와 매개변수의 차이 참고링크
매개변수는 메서드 선언부에 정의되어 있는 것, 인수는 매서드를 호출할 때 넘겨주는 값.
아래 예제에서 display(dog);dog가 인수, public static void display(Animal ani){...}ani가 매개변수가 된다.

예제

public class PolyMethodTest {
    public static void main(String[] args) {
        Dog dog = new Dog();
        display(dog);

        Cat cat = new Cat();
        display(cat);

    public static void display(Animal ani){
        ani.eat();
//        ani.night();	// 에러발생
    }
}

아래는 다형성 인수를 적용하지 않을 경우

private static void display(Dog d){ d.eat(); }
private static void display(Cat c){ c.eat(); }

각 클래스마다 display메서드를 만들 경우 위와 같이 만들어야 하나, 두 클래스의 부모 클래스가 같으므로 Animal클래스를 이용하여 인수를 보낸다.

문제점
eat()메서드는 Dog, Cat 클래스 모두에게 있는 메서드이지만 night()메서드는 Cat클래스만이 가지는 고유한 메서드이다.
이럴때 업캐스팅(Upcasting)을 이용하면 Cat클래스만 가지는 고유한 메서드를 자료형에 관계없이 모든 클래스가 사용할 수 있게 되어 문제가 발생한다.
=> if문을 이용해 instanceof메서드를 이용해 인수로 보내준 자료형의 타입을 확인하여 Cat클래스인 경우에만 다운캐스팅 후 night()메서드를 실행하도록 해준다.

public static void display(Animal ani){
    ani.eat();
    // Cat타입으로 받아온 경우에만 다운캐스팅 후 night메서드 실행
    if(ani instanceof Cat){
        ((Cat) ani).night();
    }
}

다형성 배열

배열은 동일한 자료형만 저장할 수 있지만 부모 타입의 배열은 자식 타입을 저장할 수 있다.

public class PolyArrayTest {
    public static void main(String[] args) {
        Dog d = new Dog();
        Cat c = new Cat();
        // Dog, Cat 타입을 저장할 배열을 생성 => 다형성 배열
        Animal[] ani1 = new Animal[2];
        ani1[0] = d;
        ani1[1] = c;
        Animal[] ani2 = {new Dog(), new Cat()};

        display(ani1);
    }
    public static void display(Animal[] ani) {
        for (int i = 0; i < ani.length; i++) {
            ani[i].eat();
        // Cat타입으로 받아온 경우에만 다운캐스팅 후 night메서드 실행
            if (ani[i] instanceof Cat) {
                ((Cat) ani[i]).night();
            }
        }
    }
}

night()메서드의 실행은 다형성 인수의 예제처럼 instanceof로 타입확인 후 다운캐스팅하여 사용.


-학습정리 & quiz-
1. 다운캐스팅 Downcasting
2. 다형성 Polymorphism
3. 상속관계, Override 재정의, Upcasting 업캐스팅, 동적 바인딩
4. instanceof
5. 다형성 배열, 상위타입 배열


추상 클래스와 인터페이스

다형성을 보장하기 위해 등장한 개념들.

다형성을 보장한다 = 하나의 객체나 메서드가 다양한 형태를가질 수 있다.

- Animal -

public class Animal {
    public void eat(){
        System.out.println("?");
    }
}

- Dog -

public class Dog extends Animal {
}

- Cat -

public class Cat extends Animal {
    public void night(){
        System.out.println("고양이는 밤에 눈에서 빛이 난다");
    }
}

- IsNotOverride -

public class IsNotOverride {
    public static void main(String[] args) {
        // 재정의(Override)를 하지 않았으므로 부모의 명령에 오동작함
        Animal ani = new Dog();
        ani.eat();
        ani = new Cat();
        ani.eat();
    }
}


다형성이 보장되지 않은 예시.
부모의 명령에 따라 클래스마다 다른 동작을 해야 하지만 똑같은 동작을 하고 있다.

→ 강제로 재정의를 하도록 만들어야 함.
여기서 추상 클래스와 인터페이스의 개념이 나오게 된다.

추상 클래스

  • 추상 메서드를 가진 클래스.
  • 불완전한 상태이므로 사용하려면 하위 클래스에서 반드시 재정의가 필요하다.
  • 키워드 : abstract
  • 부모가 추상 클래스일 경우 다형성이 보장됨.
  • 서로 비슷한 클래스의 공통부분을 묶을 때 사용함.
  • 추상 클래스는 단독으로 객체생성이 불가능.
    Animal클래스가 추상 클래스일 경우 Animal ani = new Animal();처럼 객체생성 불가.
    업캐스팅이 필요함.
  • 추상 클래스는 추상 메서드, 생성자, 필드(멤버변수), 구현 메서드(일반 메서드)를 가질 수 있다.

추상 메서드

구현부가 없는 불완전한 메서드.
구현부는 하위 클래스에서 만들어줘야 한다.

public abstract class Animal {
    public abstract void eat();	// 구현부가 없음 => 추상 메서드
}

=> 추상 클래스를 상속받은 클래스는 마찬가지로 불완전한 클래스가 된다.
재정의(Override)를 통해 구현이 필요. (미구현 시 에러발생)

인터페이스 Interface

추상 클래스는 추상 메서드뿐만 아니라 일반적인 구현 클래스도 가질 수 있기때문에 하위 클래스에서 오동작을 할 위험이 있다.

  • 서로 다른 동작을 가지는 클래스
  • Java는 다중 상속을 지원하지 않는데, 인터페이스를 통해 다중 상속의 장점을 구현할 수 있다.
  • 추상 클래스와는 달리 추상 메서드만 가질 수 있다. = 구현 메서드(일반 메서드)는 가질 수 없다.
  • 키워드 : interface, implements
    => 인터페이스는 추상 메서드만 가질 수 있으므로 메서드에 abstract키워드는 생략한다.
  • 상속은 단일 상속만 가능한것과 달리 인터페이스의 구현은 다중구현이 가능함.
    => 마치 다중상속을 한 것 같은 효과를 얻을 수 있음
    또한 인터페이스가 인터페이스를 상속하는 것 역시 가능.

예제1
- Remocon -

public interface Remocon {
    // chUp(), chDown(), volUp(), volDown()
    public void chUp();
    public void chDown();
    public void volUp();
    public void volDown();
    // interface는 추상 메서드만 가질 수 있다
//    public void internet(){
//        System.out.println("인터넷이 구동됨");
//    }
}

- Radio -

public class Radio implements Remocon{
    @Override
    public void chUp() {
        System.out.println("Radio의 채널이 올라감");
    }
    @Override
    public void chDown() {
        System.out.println("Radio의 채널이 내려감");
    }
    @Override
    public void volUp() {
        System.out.println("Radio의 음량이 올라감");
    }
    @Override
    public void volDown() {
        System.out.println("Radio의 음량이 내려감");
    }
}

- Tv -

public class Tv implements Remocon{
    @Override
    public void chUp() {
        System.out.println("Tv의 채널이 올라감");
    }
    @Override
    public void chDown() {
        System.out.println("Tv의 채널이 내려감");
    }
    @Override
    public void volUp() {
        System.out.println("Tv의 음량이 올라감");
    }
    @Override
    public void volDown() {
        System.out.println("Tv의 음량이 내려감");
    }
}

- InterfaceTest -

public class InterfaceTest {
    public static void main(String[] args) {
        Remocon remo = new Radio();
        remo.chUp();
        remo.chDown();
        remo.volUp();
        remo.volDown();
//        remo.internet();
        remo = new Tv();
        remo.chUp();
        remo.chDown();
        remo.volUp();
        remo.volDown();
    }
}


인터페이스(Interface)를 이용하면 다형성을 보장된다.
인터페이스는 부모의 역할은 가능.

예제2
다중구현 예시
- Dog -

public class Dog extemds Animal implements Pet, Robots{
	Animal r = new Dog();
    Pet r = new Dog();
    Robots r = new Dog();
}

예제3
인터페이스가 인터페이스를 상속
- A -

public interface A{
	...
}

- B -

public class B extemds A{
	...
}

- C -

public class C implements B{
	...
}

인터페이스와 final static 상수

  • 인터페이스에서 변수선언 시 final static이 기본으로 적용된다 = final static상수로 선언됨 = final static은 생략가능
  • 상수의 이름은 관례상 대문자로 선언한다.
    final : 더 이상 해당 변수의 값을 수정할 수 없음 = 상수

=> final static인 이유: 인터페이스는 객체를 생성할 수 없다. 고로 객체를 생성하지 않고도 접근할 수 있는 static키워드를 사용, 값을 수정을 막기 위해 final키워드 사용.

- Remocon -

public interface Remocon {
    public final static int MAXCH = 99;
    public final static int MINCH = 1;
    public void chUp();
    public void chDown();
    public void volUp();
    public void volDown();
}

- Tv -

public class Tv implements Remocon {
    private int currch = 10;

    @Override
    public void chUp() {
        currch++;
        if (currch > MAXCH) {
            currch = 1;
        }
        System.out.println("Tv의 채널이 올라감");
    }

    @Override
    public void chDown() {
        currch--;
        if (currch < MINCH) {
            currch = 99;
        }
        System.out.println("Tv의 채널이 내려감");
    }
    // 이하 생략
}

final과 static의 순서는 무관. 생략해도 무관.


-학습정리 & quiz-
1. 추상 클래스 abstract class
2. 인터페이스 interface
3. final static상수, 추상 메서드 abstract method
4. 추상 클래스 abstract class, 인터페이스 interface
5. public class Dog extends Animal implements Pet{}


Object 클래스

Object클래스는 모든 클래스의 최상위 클래스로, 모든클래스가 상속받고 있지만 보통은 생략된다.
패키지 위치는 java.lang.Object

예제1
- A -

public class A {
    public void printGO(){
        System.out.println("나는 A");
    }
}

- B -

public class B extends Object{
    public void printGO(){
        System.out.println("나는 B");
    }
}

- ObjectTest -

public class ObjectPolyTest {
    public static void main(String[] args) {
        A a = new A();
        display(a);
        B b = new B();
        display(b);
    }
    public static void display(Object obj){
        if (obj instanceof A){
            ((A)obj).printGO();
        }
        if (obj instanceof B){
            ((B)obj).printGO();
        }
    }
}

Object타입으로 Upcasting이 되면 사용하기 전에 반드시 자신의 타입으로 Downcasting을 해주어야 한다.

예제2
- ObjectPolyArray -

public class ObjectPolyArray {
    public static void main(String[] args) {
        Object[] obj = new Object[2];
        obj[0] = new A();   // Upcasting
        obj[1] = new B();   // Upcasting
        display(obj);
    }
    public static void display(Object[] obj){
        for (int i=0; i< obj.length; i++){
            if (obj[i] instanceof A){
                ((A)obj[i]).printGO();
            }
            if (obj[i] instanceof B){
                ((B)obj[i]).printGO();
            }
        }
    }
}

방식은 앞서 했던 예제들과 동일함.

Object 클래스의 toString()

toString() : 해당 객체의 번지를 문자열로 출력하는 메서드
Object클래스에는 toString()메서드가 미리 구현되어 있음.
=> 재정의(Override)를 해주지 않고 Object클래스의 toString()메서드 사용 시 메모리 내의 객체의 번지를 출력하게 됨.
보통은 객체가 가진 데이터를 출력하기 위해 toString()을 사용하므로 재정의를 해주어야 함.



4주차 수강후기

제네릭, List, 예외처리, 스레드, 람다 등 생각보다 과정에 없는 개념이 많아 이 부분은 따로 공부가 필요할듯.
빠진 부분의 개념이 부족했는데 아쉽다.

profile
천 리 길도 가나다라부터

0개의 댓글

Powered by GraphCDN, the GraphQL CDN