언어 자료구조 알고리즘/디딤돌 Java 언어 Part1

[Java] 5. OOP 상속과 다형성, 5.1 상속

언제나휴일 2016. 4. 4. 09:46
반응형

출간일 2016년 11월 28일

판매가 2000원

형태 ebook


이 책의 모든 내용은 http://ehpub.co.kr에 공개하고 있습니다.

학습에 도움이 되시면 ebook을 구입하여 소장하시면 감사하겠습니다.

언제나 휴일 출판사의 수익금의 대부분은 아프리카에 기부하고 있습니다.



 5. OOP 상속과 다형성

 

 이번에는 OOP의 특징 중에 상속과 다형성에 관하여 살펴봅시다.

 

 상속과 다형성은 캡슐화와 더불어 OOP 언어의 주요 특징입니다. 상속은 유사한 클래스의 공통적인 부분을 기반 형식으로 정의하고 이를 파생하여 세부적인 사항을 추가 및 변경하여 파생 형식을 정의하는 방법입니다. 이러한 특징은 기반 형식을 확장하여 다양한 파생 클래스를 정의할 수 있게 해 줍니다.

 

 그리고 다형성은 파생 형식 개체를 기반 형식의 변수로 참조할 수 있고 멤버 메서드를 호출하면 참조하는 형식의 메서드가 아닌 실제 개체의 메서드를 호출할 수 있게 하여 상속의 장점을 극대화시킵니다.

 

5.1 상속

 

 Java 언어에서는 여러 클래스의 공통점을 기반 클래스에 정의하고 파생 클래스를 정의할 때 기반 형식에서 확장하여 추가 및 변경할 수 있습니다. UML에서는 기반 클래스와 파생 클래스 사이에 삼각형과 실선으로 상속을 일반화 관계로 표현하며 피아니스트는 음악가이다.처럼 두 형식 사이에 이다.(is a)로 나타낼 수 있을 때 사용합니다.

 

 

[그림 5.1] UML에서 일반화 관계

 

 

 

 

 Java 언어에서는 파생 클래스를 정의할 때 어떤 형식을 기반으로 할 것인지 extends 키워드를 이용하여 표현합니다.

 

class 파생 클래스명 extends 기반 클래스명{

}

 

 예를 들어 음악가 형식에 연주하다. 메서드를 정의하고 음악가 형식을 기반으로 파생 클래스 피아니스트를 정의하면 피아니스트 형식은 기반 형식에 정의한 멤버를 상속받습니다. 따라서 피아니스트 개체의 연주하다. 메서드를 호출할 수 있다는 것입니다.

 

public class Musician {

    public void play(){

        System.out.println("연주하다.");

    }

}

public class Pianist extends Musician{

    public void tuning(){

        System.out.println("조율하다.");

    }

}

public class Program {

    public static void main(String[] args){

        Pianist pianist = new Pianist();

        pianist.play();

        pianist.tuning();

    }

}

연주하다.

조율하다.

[소스 5.1] Musician을 기반으로 파생 클래스 Pianist를 정의하여 상속을 활용한 예

 

 

 

5.1.1 super 키워드를 이용한 기반 형식부분 생성

 

 이처럼 기반 형식에서 확장하여 파생 클래스를 정의하면 파생 클래스 형식 개체를 생성하면 기반 형식부분을 생성한 후에 파생 형식부분을 생성하여 하나의 개체가 만들어집니다.

 

 이러한 이유로 기반 형식에 매개 변수가 있는 생성자만 존재할 때 파생 클래스의 생성자에서는 기반 형식부분을 생성할 때 어떠한 인자를 전달하여 생성해야 하는지 반드시 명시해야 합니다. 이 때 super 키워드를 이용합니다. 이러한 이유로 Java 언어에서는 기반 클래스를 슈퍼 클래스라고도 부릅니다. 그리고 파생 클래스를 서브 클래스라고도 부릅니다.

public class Musician {

    String name;

    public Musician(String name){

        this.name = name;

    }

}

public class Pianist extends Musician {

    public Pianist(String name){

        super(name);

    }

}

 

 

public class Musician {

    String name;

    public Musician(String name){

        this.name = name;

    }

    public String getName(){

        return  name;

    }

    public void play(){

        System.out.println("음악가 "+name+" 연주하다.");

    }

}

public class Pianist extends Musician {

    public Pianist(String name){

        super(name);

    }

    public void tuning(){

        System.out.println("피아니스트 "+getName()+" 조율하다.");

    }

}

public class Program {

    public static void main(String[] args){

        Pianist pianist = new Pianist("홍길동");

        pianist.play();

        pianist.tuning();

    }

}

음악가 홍길동 연주하다.

피아니스트 홍길동 조율하다.

[소스 5.2] 파생 클래스 생성자에서 super 키워드로 기반 형식부분 생성하는 예

 

 

5.1.2 메서드 재정의(override)

 

 상속은 비슷한 형식의 공통적인 부분을 기반 형식으로 정의하고 이를 파생하여 다른 부분을 추가하거나 변경하는 문법입니다.

 

 만약 기반 형식을 정의할 때 제공할 메서드는 공통적으로 정의할 수 있지만 메서드 내부에 정의할 구체적 알고리즘이 다를 때는 어떻게 해야 할까요? 이를 위해 OOP 언어에서는 재정의에 관한 문법을 제공하고 있습니다.

 

 재정의란 기반 형식에 정의한 메서드를 파생 형식에서 구체적 행위를 다르게 정의하는 문법으로 파생 클래스에서 재정의할 메서드 앞에 @Override 키워드를 명시하여 정의합니다. 이렇게 파생 클래스에서 기반 형식의 메서드를 재정의하면 기반 형식에 정의한 메서드 이름은 무효화합니다.

 

public class Musician {

    public void play(){

        System.out.println("음악가 연주하다.");

    }

}

public class Pianist extends Musician{

    @Override public void play(){

        System.out.println("딩동댕");

    }

}

public class Program {

    public static void main(String[] args){

        Pianist pianist = new Pianist();

        pianist.play();

    }

}

딩동댕

[소스 5.3] 파생 클래스에서 기반 형식의 play 메서드를 재정의하는 예

 

 만약 파생 형식에서 무효화한 기반 형식의 메서드를 호출하고자 한다면 super 키워드를 이용하여 사용합니다.

 

public class Musician {

    public void play(){

        System.out.println("음악가 연주하다.");

    }

}

public class Pianist extends Musician {

    @Override public void play(){

        super.play();

        System.out.println("딩동댕");

    }

}

public class Program {

    public static void main(String[] args){

        Pianist pianist = new Pianist();

        pianist.play();

    }

}

음악가 연주하다.

딩동댕

[소스 5.4] 파생 클래스에서 무효화한 기반 클래스의 메서드 사용하는 예

  

 

 

5.1.3 필드 재정의(override)

 

 만약 기반 클래스에 캡슐화한 멤버 필드와 같은 이름으로 파생 클래스에서 멤버 필드를 캡슐화하면 어떻게 될까요?

 

 이 때도 기반 클래스에 캡슐화한 멤버 필드는 무효화하여 파생 형식에서는 직접적으로 사용할 수는 없습니다. 하지만 무효화한 메서드를 호출하여 사용하는 것처럼 super 키워드를 이용하면 무효화 멤버 필드를 사용할 수 있습니다.

 

 실제 프로그래밍에서 메서드 재정의는 많은 곳에서 사용하지만 멤버 필드를 재정의는 개발자에게 혼돈만 가중시켜서 거의 사용하지 않습니다. 이에 관한 사항은 여러분께서 다양한 프로그래밍 과정을 통해 느끼고 판단할 수 있을 것입니다.

 

 예를 들어 음악가의 멤버 필드에 음악가 이름을 name 멤버 필드로 캡슐화하고 음악가를 파생하는 피아니스트에서 피아니스트 이름을 name 멤버 필드를 재정의한다고 가정합시다. 이 때 피아니스트 개체에는 기반 형식부분인 음악가 이름도 있고 피아니스트 이름도 존재하는 꼴이 됩니다. 이는 개발 과정에서 혼란만 가중할 뿐입니다.

 

public class Musician {

    protected String name;

    public Musician(String name){

        this.name = name;

    }

    public String getName(){

        return name;

    }

    public void play(){

        System.out.println(name+" 연주하다.");

    }

}

 

 

 

public class Pianist extends Musician{

    String name;

    public Pianist(String name,String mname){

        super(mname);

        this.name = name;

    }

    @Override public void play(){

        super.play();

        System.out.println(super.name+", 피아니스트 "+name+" 딩동댕");

    }

}

public class Program {

    public static void main(String[] args){

        Pianist pianist = new Pianist();

        pianist.play();

    }

}

홍피아리스트 연주하다.

홍피아리스트, 피아니스트 홍길동 딩동댕

[소스 5.5]기반 형식의 멤버 필드를 파생 클래스에서 재정의한 예

 

 

 

5.1.4 접근 지정자 protected

 

 캡슐화를 설명할 때 멤버 필드의 가시성을 지정하는 접근 지정자 중에서 protected 에 관한 설명은 생략하고 상속을 다룰 때 다시 설명하기로 하였습니다.

 

 public으로 접근 지정하면 다른 형식에서도 접근할 수 있고 private으로 지정하면 해당 형식에서만 접근할 수 있다는 것은 캡슐화 과정에서 살펴보았습니다.

 

 protected로 지정한 멤버는 해당 형식과 이를 기반으로 파생한 형식에서는 접근할 수 있고 그 외의 형식에서는 접근할 수 없게 합니다.

 

 예를 들어 음악가가 연주를 하면 연주 스킬이 향상하게 구현하고자 합니다. 그리고 음악가를 파생한 피아니스트에서 레슨을 받으면 마찬가지로 연주 스킬을 향상하게 구현한다고 가정합시다.

 

 이 때 음악가 클래스 연주 스킬의 접근 지정을 private으로 정하면 파생한 피아니스트 클래스에서도 접근할 수 없어서 레슨을 받으면 연주 스킬을 향상시키는 부분을 구현할 수 없습니다.

 

 그리고 음악가 클래스 연주 스킬의 접근 지정을 public으로 정하면 다른 곳에서도 접근할 수 있어서 정보 은닉할 수 없어 신뢰성이 떨어질 수 있습니다.

 

 이럴 때 protected로 접근 지정하면 만족하는 결과를 얻을 수 있습니다. 물론 연주 스킬의 변화 범위가 존재할 수 있기에 연주 스킬에 관한 멤버 필드를 private로 정하고 연주 스킬에 관한 설정자 메서드를 protected로 정하면 보다 나은 결과를 얻을 수 있습니다.

 

public class Musician {

    int skill;

    static final int MAX_SKILL = 100;

    static final int MIN_SKILL = 0;

    public Musician(){

        setSkill(MIN_SKILL);

    }

    protected void setSkill(int value) {

        if(value>MAX_SKILL){

            value = MAX_SKILL;

        }

        if(value<MIN_SKILL){

            value = MIN_SKILL;

        }

        skill = value;

    }

    public void play(int tcnt){

        System.out.println(tcnt + "시간 연주하다.");

        setSkill(skill+tcnt);

    }

    public int getSkill(){

        return skill;

    }

}

 

public class Pianist extends Musician{

    public void lesson(int tcnt){

        System.out.println(tcnt + "시간 레슨받다.");

        setSkill(getSkill()+tcnt);

    }

}

public class Program {

    public static void main(String[] args){

        Pianist pianist = new Pianist();

        System.out.println("연주 스킬:"+ pianist.getSkill());

        pianist.play(3);

        System.out.println("연주 스킬:"+ pianist.getSkill());

        pianist.lesson(4);

        System.out.println("연주 스킬:"+ pianist.getSkill());

    }

}

연주 스킬:0

3시간 연주하다.

연주 스킬:3

4시간 레슨받다.

연주 스킬:7

[소스 5.6] 기반 형식의 protected로 접근 지정한 멤버를 파생 형식에서 접근하는 예

 

반응형