객체지향 프로그래밍3 - 상속, 캡슐화

김소희·2023년 2월 27일
1

<상속>

상속을 이용하면 클래스의 맴버를(메소드, 필드, 이너클래스) 다른 클래스에서도 쓸 수 있게 된다. 객체지향 프로그래밍이 지향하는 '변화와 확장에 유연한 프로그램'을 만들기 위해서 도입된 문법으로 자바에서는 단일 상속만 허용한다.
상속을 이용했을 때의 장점은 코드의 재사용성을 높여 코드의 중복을 없애므로 코드의 양을 줄일 수 있다. 유지보수의 편의성을 높이고, 다형적 표현이 가능하게 해준다. (다형 = 다양한 형태)

상속관계를 살펴보면 상위 클래스와 하위 클래스로 나눌 수 있는데, 하위 클래스는 상위 클래스가 가진 모든 맴버를 상속 받게 된다. 따라서 하위 클래스의 멤버 개수는 언제나 상위 클래스의 맴버수와 비교했을 때 같거나 많다. 이는 하위 클래스는 상위 클래스로부터 확장되었다고 표현할 수 있다.
상위 클래스는 주로 공통적인 속성과 기능을 담고, 하위 클래스는 개별적이고 독자적인 속성과 기능을 추가하여 확장하는 모양새가 많을 것 같다.

class Student extends Person { //Student클래스는 Person로부터 확장(상속)되었다.

포함(composite)

포함은 상속과 비슷하게 클래스를 재사용 할 수 있는 방법이다.
클래스의 멤버로 다른 클래스 타입의 참조변수를 선언하여 사용한다.
다시말해 클래스의 속성 값에 다른 클래스의 인스턴스를 포함하여 사용하는 것이다.

상속와 포함중에 어떤 것을 써야할지 고민될 때는
아래와 같은 문장에 대입해보면 알 수 있다.

--는 --이다. (상속)
--는 --을 가지고 있다 (포함)

Student는 Person이다. (상속)
Student는 StudentId를 가지고 있다.(포함)

public class Main {
    public static void main(String[] args) {

        College suwon = new College("수원과학대","수원");
        Student sohee = new Student("소희","세무회계",suwon,2);
        sohee.speech();

        College yungjin = new College("영진전문대","대구");
        Student chanuk = new Student("찬욱","컴퓨터공학",yungjin,3);
        chanuk.speech();

    }
}

 class Student {
     String name;
     String major;
     College col;
     int grade;

     public Student(String name, String major, College col, int grade) {
         this.name = name;
         this.major = major;
         this.col = col;
         this.grade = grade;
     }

     void speech(){
         System.out.printf("저는 %s학교에 다니는 %s 입니다. 전공은 %s이고, 현재 %d학년 입니다.\n"
                 ,this.col.name, this.name, this.major, this.grade);
     }
 }

 class College {
    String name;
    String country;

    public  College(String name, String country) {
        this.name = name;
        this.country = country;
    }
}

메서드 오버라이딩(Overriding)

지난 시간에 배운 오버로딩은 과적하다는 의미가 있었는데, 오버라이딩은 덮어쓰다는 의미가 있다.
오버라이딩은 상위 클래스로부터 상속받은 메서드의 동작을 하위 클래스에 맞게 변경하고자 할 때 메소드를 재정의하는 것을 의미한다. 오버라이딩을 통해 자유도를 높일 수 있을 것 같다.
오버라이딩을 쓰기 위한 조건이 3가지 있는데,
1.메소드의 선언부가 상위클래스와 완전히 일치해야한다.
(메소드의 이름, 매개 변수, 반환타입이 동일해야한다.)
2.접근제어자 범위가 상위 클래스 메소드보다 같거나 넓어야 한다.
3.예외는 상위클래스 보다 많이 선언할 수 없다.

public class Main {
    public static void main(String[] args) {
        Bike bike = new Bike();
        Car car = new Car();
        
        bike.run();
        car.run();
    }
}

class Vehicle {
    void run() {
        System.out.println("Vehicle is running");
    }
}

class Bike extends Vehicle {
    void run() {
        System.out.println("Bike is running");
    }
}

class Car extends Vehicle {
    void run() {
        System.out.println("Car is running");
    }
}
// 결과값
Bike is running
Car is running

super 와 super()

지난시간에 배운 this는 자신의 객체, this() 메서드는 자신의 생성자 호출을 의미했었는데
오늘 배운 Super는 상위 클래스의 객체, super()는 상위 클래스의 생성자를 호출하는 것을 의미한다.

public class main {
    public static void main(String[] args) {
        SubClass sub = new SubClass();
        sub.callNum();
    }
}

class SuperClass {
    int count = 20; // super.count
}

class SubClass extends SuperClass {
    int count = 15; // this.count

    void callNum() {
        System.out.println("count = " + count);
        System.out.println("this.count = " + this.count);
        System.out.println("super.count = " + super.count);
    }
}

// 결과값
15
15
20
public class main {
    public static void main(String[] args) {
        Student s = new Student();
    }
}

class Person {
    Person() {
        System.out.println("상위 클래스 생성자");
    }
}

class Student extends Person { 
    Student() {    
        super(); 
        System.out.println("하위 클래스 생성자");
    }
}

// 결과값
상위 클래스 생성자
하위 클래스 생성자

super()도 this()와 마찬가지로 생성자 안에서만 사용 가능하고, 반드시 첫 줄에 와야한다. 이때 상위클래스에 기본 생성자가 없으면 에러가 발생하게 된다.
즉 상속을 받은 경우에 상위클래스에는 생성자가 있어야하고, 하위클래스 생성자의 첫줄에는 반드시 this()나 super()가 있어야 한다. 그래야 제대로 상속을 받은 셈이기 때문이다.

상속의 상속을 타고 올라가다보면 최상위에는 Object클래스가 있다. 그래서 Object 클래스에 있는 equals()나 toString()은 따로 정의하지 않아도 바로 쓸 수 있다.
자바 컴파일러는 컴파일 과정에서 다른 클래스로부터 아무런 상속을 받지 않는 클래스에는 자동적으로 extends Object를 추가하여 Object 클래스를 상속받도록 한다.

<캡슐화>

캡슐화란 특정 객체 안에 관련된 속성과 기능을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것이다.
캡슐화는 데이터를 보호하고, 내부적으로만 사용하는 데이터들을 외부로 노출시키지않게 한다.
따라서 정보를 은닉할 수 있고, 외부로부터 속성과 기능이 함부로 변경되지 못하게 막고, 혹여 데이터가 변경되더라도 다른 객체에 영향을 주지 않으니 독립성이 확보된다.
따라서 유지보수나 확장시 오류의 범위가 최소화되어 효과적으로 유지보수하기에 용이하다.

패키지

패키지(package)란 특정한 목적을 공유하는 클래스와 인터페이스의 묶음을 의미한다.
클래스를 정의할때 관련있는 속성과 기능을 묶어 데이터를 효율적으로 관리했듯이
패키지는 클래스들을 그룹 단위로 묶어 효과적으로 관리하기 위해 사용된다.
또 패키지를 사용하면 클래스들의 충돌을 방지하는데 도움이 된다.

패키지는 1개의 디렉토리이며 계층구조를 가지고 있다.
다른 패키지에 담긴 클래스를 사용할 땐 import문을 쓰면 된다.

접근제어자

제어자(Modifier)는 클래스, 필드, 메서드, 생성자에 부가적인 의미를 부여하는 키워드이다.

  • 접근제어자
public, protected, default, private
접근제어자접근제한범위
private동일 클래스에서만 접근가능
default동일 패키지내에서만 접근가능
protected동일 패키지 + 다른 패키지의 하위 클래스에서만 접근가능
public접근제한 없음

접근제어자를 사용하면 클래스 외부로의 불필요한 데이터 노출을 방지할 수 있다.(data hiding)
외부로부터 데이터가 임의로 변경되지 않도록 막을 수 있다.
접근제어자를 붙이지 않으면 기본 설정인 default가 된다.

  • 기타제어자
static, final, abstract, native...
기타제어자접근제한범위
StaticStatic변수와 Static메소드를 만들 수 있다. (클래스맴버)
finalfinal변수와 final메소드를 만들 수 있다. 처음 정의된 상태가 변하지 않는다. (고정적)
abstract추상클래스와 추상메소드를 만들 수 있다.
native자바가 아닌 언어(보통 C나 C++)로 구현한 후 자바에서 사용하려고 할 때 이용하는 키워드이다.

기타제어자는 여러개 사용할 수 있다. 더 많은 종류들이 있으니 기회가 되면 추가해야겠다.

Setter()와 getter()

캡슐화의 목적을 달성하면서도 데이터의 변경이 필요한 경우는 Setter()와 getter()를 쓰면된다.
setter()는 외부에서 메서드에 접근하여 조건에 맞을 경우 데이터 값을 변경할 수 있다.
getter()는 이렇게 설정한 변수 값을 읽어오는 데 사용하는 메서드이다.

class Student {
    private String name; //접근제어자가 설정된 변수
    
    public String getName() { // 변경된 멤버변수의 값 
        return name;
    }

    public void setName(String name) { // 멤버변수의 값 변경
        this.name = name;
    }
}    
profile
백엔드 개발자 소희의 모험

0개의 댓글