느리더라도 꾸준히

[OOP]객체 지향 프로그래밍(OOP) - (1) 상속 본문

Java

[OOP]객체 지향 프로그래밍(OOP) - (1) 상속

테디규 2022. 11. 16. 16:18

이 글을 읽으시기 전에

전체척인 설계를 담은 아래 글을 읽어주세요.

객체 지향 프로그래밍(OOP) - (0)설계

목표

객체 지향적이지 않은 코드를 객체 지향으로 바꾸면서 객체지향의 4가지 특징을 이해한다.

최종적으로 객체 지향적인 코드를 만들어보고, 이를 미리 구현 해 놓은 Springframework를 이해해보자..

(1) 상속

(2) 추상화 및 다형성를 통한 DI(Dependency Injection)

(3) 캡슐화

(4) SRP(Single-Relationship Principle)

정리 - (1) 상속

상속을 이용하면 기존의 코드를 재사용하여 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있다. 그러므로 코드 중복을 줄일 수 있다.(재사용성)

또한 상속관계에 있는 클래스들은 메서드 오버라이딩 또는 상위 클래스 타입으로 참조변환을 이용하여 다형적 표현이 가능하다.(유연한 확장)

본문

상위 클래스 Customer

public class Customer implements Comparable<Customer>{
    private int id;
    private String name;
    private String customerType;

    public Customer(int id, String name, String customerType) {
        this.id = id;
        this.name = name;
        this.customerType = customerType;
    }

    public String getCustomerType() {
        return customerType;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public int compareTo(Customer c) {
        return name.compareTo(c.getName());
    }
}

고객 클래스가 있다고 하자. 이 고객은 id, name을 필드로 가지고 있고, 생성자를 통해 값을 주입받는다. 또한 각 필드들에 대한 getter/setter가 존재한다.

Comparable 과 compareTo() 는 나중에 이름순으로 Customer를 정렬을 위해서 사용한 인터페이스 이다.

하위 클래스 Employee, Student

public class Employee extends Customer{

    private String martName;

    public Employee(int id, String name, String customerType, String martName) {
        super(id, name, customerType);
        this.martName = martName;
    }

    public String getMartName() {
        return martName;
    }
}
public class Student extends Customer{
    private String schoolName;

    public Student(int id, String name, String schoolName) {
        super(id, name);
        this.schoolName = schoolName;
    }

    public String getSchoolName() {
        return schoolName;
    }
}

고객에도 여러 종류의 고객들이 있다. 우리의 경우, 최대한 간단히 만들기 위해 직원, 학생 그리고 이외는 모두 일반직업이라고 하겠다.

우리는 상속을 이용하여 이러한 객체들의 관계를 Java 소스코드에 구현 할때, Customer 클래스를 확장시킨 Student 와 Employee 클래스를 생성할 수 있다.

  • extends keyword를 사용한다.
  • super() 를 통해 상위클래스의 생성자를 호출한다.
  • 예시에는 없지만 같은 메서드 시그니처를 가진 관계일 때는 메서드 오버라이딩이 동작한다.

동작시키기

public class Main {
    public static void main(String[] args) {
          Customer customer = new Student(2, "최학생", "한국고등학교");
        Customer customer2 = new Employee(3, "이직원", "코리아마트");
        System.out.println(customer.getName());
        System.out.println(customer2.getName());
    }
}
/* 출력 결과
2
*/

상위 클래스의 메서드인 getName()을 사용하는 모습이 보인다.

하위 클래스(Student, Employee) 객체를 상위 클래스(Customer) 타입의 참조변수에 할당 할 수 있다.

  • 이를 통해 상위 클래스 타입의 참조변수는 다형적인 표현이 가능합니다.

상속의 특징

  • 기존의 코드를 재사용하여 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있다. 그러므로 코드 중복을 줄일 수 있다.(재사용성)
  • 상속관계에 있는 클래스들은 메서드 오버라이딩 또는 상위 클래스 타입으로 참조변환을 이용하여 다형적 표현이 가능하다.(유연한 확장)
  • Java는 단일 상속(single inheritance)만을 허용한다

상속이 없었다면?

만약 상속이 없었다고 가정하고, 기존 코드를 재사용하는 부분이 얼마나 강점인지 느껴보자.

public class Employee{
        private int id;
        private String name;
    private String martName;

    public Employee(int id, String name, String martName) {
        super(id, name);
        this.martName = martName;
    }

    public String getMartName() {
        return martName;
    }

        id와 name의 getter/setter ...
}

ㅠㅠ 우리는 Customer에서 작성한 코드를 중복해서 작성해야한다.

상속은 포함관계나 인터페이스와 무엇이 다를까?

포함관계

포함(composite)은 상속처럼 클래스를 재사용할 수 있는 방법으로, 클래스의 멤버로 다른 클래스 타입의 참조변수를 선언하는 것을 의미한다.

package extends_class;

public class Employee {
    int id;
    String name;
    Address address;

    public Employee(int id, String name, Address address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }

    void showInfo() {
        System.out.println(id + " " + name);
        System.out.println(address.city+ " " + address.country);
    }

    public static void main(String[] args) {
        Address address1 = new Address("서울", "한국");
        Address address2 = new Address("도쿄", "일본");

        Employee e = new Employee(1, "김코딩", address1);
        Employee e2 = new Employee(2, "박해커", address2);

        e.showInfo();
        e2.showInfo();
    }
}

class Address {
    String city, country;

    public Address(String city, String country) {
        this.city = city;
        this.country = country;
    }
}

1 김코딩
서울 한국
2 박해커
도쿄 일본

일반적으로 상속은 ~은 ~이다.(IS A) 의 관계이고, 포함관계는 ~은 ~을 가지고있다.(HAS A의 관계이다.)

우리 예시의 경우 고객은 학생일수 있다. 고객은 직원일수 있다. 가 더 적절하므로 상속을 사용해야한다.

인터페이스

상속보다 더욱 추상적인 개념으로 인터페이스가 있다.

인터페이스로도 상속이 한 것 처럼 구현할 수 있지않을까?

public interface InterfaceEx {
    public static final int rock =  1; // 인터페이스 인스턴스 변수 정의
    final int scissors = 2; // public static 생략
    static int paper = 3; // public & final 생략

    public abstract String getPlayingNum();
        void call() //public abstract 생략 
}

인터페이스의 필드는 항상 public static final 하게 생성된다. 즉 인스턴스 변수를 사용할 수가 없다.

또한 메서드는 추상메서드를 선언해놓고 인터페이스를 구현시 재정의해서 사용한다.

기존의 필드와 메서드들이 존재하고 확장한다는 개념이랑은 많이 다른 것을 알 수 있다. 코드 재사용성 측면에서 우리의 예시(Customer - Employee, Student)는 인터페이스 보다는 상속이 더욱 어울린다.


CustomerRepository

public class CustomerRepository {
    // 아래와 같이 총 세명의 고객이 존재한다고 하자.
    private Customer[] customers = new Customer[]{
            new Customer(1, "김무개"),
            new Student(2, "최학생", "한국고등학교"),
            new Employee(3, "이직원", "코리아마트")
    };

    public Customer[] findAll(){
        return customers;
    }
}

위에서 작성한 클래스들을 통해 생성되는 인스턴스들을 관리해주는 저장소의 역할을 지닌 CustomerRepository를 생성한다.

추후 설명하겠지만, 객체지향의 단일 책임 법칙에 따라 그저 실행 파일인 Main 클래스에 너무 많은 책임을 전가하지 않기위해 따로 저장소를 생성해주었다.

현재까지 작업 경로

Comments