Java_Class

KKH_94·2023년 6월 5일
0

JAVA

목록 보기
6/36

https://github.com/JoseRFJuniorBigData/book-2/blob/master/java/Effective.Java.3rd.Edition.2018.1.pdf

객체 지향 프로그래밍

객체 지향 프로그래밍의 역사는 1960년대 후반으로 거슬러 올라갑니다.

처음에는 소프트웨어 공학에서 어떻게 복잡성을 관리할지에 대한 문제를 해결하기 위한 한 방법으로 고안되었습니다. 그 중요성이 인정되면서 1970년대에는 이 개념을 체계적으로 정리하고 적용하기 시작했습니다.

그 후 1980년대와 1990년대에는 객체 지향 프로그래밍 언어가 널리 사용되기 시작했습니다. 대표적인 객체 지향 프로그래밍 언어로는 Java, C++, Python 등이 있습니다.

객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 소프트웨어 개발 방법론 중 하나로, 객체(object)라는 개념을 중심으로 프로그래밍하는 방식을 의미합니다.

OOP의 주요 개념들로는 클래스(Class), 객체(Object), 상속(Inheritance), 캡슐화(Encapsulation), 다형성(Polymorphism) 등이 있습니다.

  • 클래스(Class) : 객체를 만들어 내기 위한 설계도나 틀로써, 특정 객체의 상태를 나타내는 필드와 메소드로 이루어져 있습니다.

  • 객체(Object) : 클래스에 따라서 만들어진 실체입니다. 실제 프로그래밍 작업에서 사용되는 데이터 단위입니다.

  • 상속(Inheritance) : 이미 정의된 클래스의 필드와 메소드를 다른 클래스가 물려받아 사용하는 것을 말합니다.

  • 캡슐화(Encapsulation) : 데이터의 보호를 위해 필드와 메소드를 하나로 묶는 것입니다.

  • 다형성(Polymorphism) : 하나의 메소드나 클래스가 있을 때 이것들이 다양한 방법으로 동작하는 것을 말합니다.

"클래스 객체의 인스턴스"라는 표현은 객체 지향 프로그래밍에서 중요한 개념입니다.

여기서 "클래스", "객체", "인스턴스"라는 세 단어를 이해하는 것이 중요합니다.

  1. 클래스(Class) : 클래스는 객체를 생성하기 위한 템플릿이나 블루프린트입니다. 클래스는 객체의 상태를 나타내는 필드(변수)와 객체의 행동을 나타내는 메소드(함수)를 정의합니다.

  2. 객체(Object) : 객체는 클래스에 정의된 대로 생성된 실체입니다. 예를 들어, 'Dog' 클래스를 정의하고 'Dog' 클래스의 특성을 갖는 'myDog'를 생성한다면, 'myDog'는 객체입니다.

  3. 인스턴스(Instance) : 인스턴스는 클래스의 객체를 의미합니다. 즉, 특정 클래스를 기반으로 메모리에 할당되어 실제로 사용되는 객체를 의미합니다. 클래스에 정의된 구조에 따라 메모리에 할당되는 실체를 인스턴스라고 합니다. 예를 들어, 'myDog'는 'Dog' 클래스의 인스턴스입니다.

따라서, "클래스 객체의 인스턴스"라는 표현은 사실상 "클래스의 인스턴스" 또는 단순히 "객체"와 같은 의미입니다. 특정 클래스를 기반으로 메모리에 생성된 실체를 가리킵니다.

이 인스턴스는 해당 클래스의 모든 필드와 메소드를 가지며, 이를 통해 프로그램 내에서 다양한 작업을 수행할 수 있습니다.


  • 클래스

객체 지향 프로그래밍에서 클래스(Class)는 객체를 생성하기 위한 틀 혹은 설계도입니다. 클래스는 필드(멤버 변수)와 메소드(멤버 함수)로 구성됩니다.

필드(Field)는 객체의 상태를 나타내는 멤버 변수로서 클래스 내에서 정의됩니다. 메소드(Method)는 특정 작업을 수행하기 위한 코드 블럭(멤버 함수)입니다.

객체는 클래스를 기반으로 생성되며, 클래스의 인스턴스(실체)라고 볼 수 있습니다. 한 클래스로부터 여러 객체를 생성할 수 있으며, 각 객체는 독립적인 상태와 행동을 가집니다.

  • 클래스 정의와 선언

클래스의 정의와 선언은 객체 지향 프로그래밍에서 클래스를 사용하기 위한 단계입니다.

클래스 선언은 클래스의 이름과 멤버 변수, 멤버 함수의 시그니처를 명시하는 작업입니다. 이를 통해 컴파일러는 해당 클래스를 사용하는 코드에서 클래스의 인스턴스를 생성하고, 멤버 변수와 멤버 함수에 접근할 수 있도록 합니다. 클래스 선언은 클래스의 헤더 파일(.h 또는 .hpp)에 작성될 수 있습니다.

클래스 정의는 클래스의 멤버 변수와 멤버 함수의 구현 내용을 작성하는 것을 의미합니다. 클래스 정의는 클래스의 선언과 구현을 결합한 것으로, 클래스의 멤버 변수의 선언과 초기화, 멤버 함수의 동작을 구체적으로 정의하는 코드를 작성합니다. 클래스 정의는 일반적으로 클래스의 소스 파일(.cpp)에 작성됩니다.

예를 들어, 다음은 C++에서 클래스의 선언과 정의를 보여주는 예입니다:

// 클래스 선언
class Rectangle {
private:
    int width;
    int height;

public:
    void setDimensions(int w, int h);
    int getArea();
};

// 클래스 정의
void Rectangle::setDimensions(int w, int h) {
    width = w;
    height = h;
}

int Rectangle::getArea() {
    return width * height;
}

위의 코드에서 class Rectangle {...};Rectangle이라는 이름의 클래스를 선언합니다. private: 섹션에서는 멤버 변수 widthheight를 선언합니다. public: 섹션에서는 멤버 함수 setDimensionsgetArea를 선언합니다.

그리고 void Rectangle::setDimensions(int w, int h) {...}int Rectangle::getArea() {...}Rectangle 클래스의 멤버 함수들의 정의를 제공합니다.

클래스의 선언은 클래스의 인터페이스를 제공하고, 정의는 클래스의 구현을 제공합니다. 선언과 정의는 서로 다른 파일에 작성될 수도 있고, 같은 파일에 작성될 수도 있습니다.

다음은 자바에서 클래스와 객체를 사용하는 예시입니다:

(자바에서 클래스의 정의와 선언은 일반적으로 하나의 파일에 함께 작성됩니다)


자바에서 public과 private은 접근 제어자(access modifier)로 사용되며, 클래스의 멤버(필드, 메서드, 생성자)의 접근 범위를 지정합니다.

public: public으로 선언된 멤버는 어떤 클래스에서든 접근할 수 있습니다. 다른 클래스에서 인스턴스를 생성하거나 해당 멤버를 호출할 수 있습니다.
예시:

public class MyClass {
    public int publicField;

    public void publicMethod() {
        // 메서드 내용
    }
}
  • private : private으로 선언된 멤버는 동일한 클래스 내에서만 접근할 수 있습니다. 다른 클래스에서는 직접 접근할 수 없고, 해당 멤버에 접근하기 위해 public으로 선언된 메서드를 통해 간접적으로 접근해야 합니다.
    예시:
public class MyClass {
    private int privateField;

    private void privateMethod() {
        // 메서드 내용
    }

    public void accessPrivateField() {
        // privateField에 접근하여 값을 읽거나 수정
    }

    public void accessPrivateMethod() {
        // privateMethod 호출
    }
}

public과 private은 캡슐화(Encapsulation) 개념의 일부로 사용됩니다.

private으로 선언된 멤버는 외부로부터의 직접적인 접근을 제한하고, public으로 선언된 메서드를 통해 캡슐화된 멤버에 접근할 수 있도록 합니다.

이는 객체 지향 프로그래밍의 중요한 개념 중 하나로, 클래스 내부 구현의 상세한 내용을 숨기고 외부에 필요한 기능을 노출시킴으로써 코드의 유연성과 안정성을 향상시킵니다.

자바에서 public과 private은 접근 제어자(access modifier)로 사용되며, 클래스의 멤버(필드, 메서드, 생성자)의 접근 범위를 지정합니다.

public: public으로 선언된 멤버는 어떤 클래스에서든 접근할 수 있습니다. 다른 클래스에서 인스턴스를 생성하거나 해당 멤버를 호출할 수 있습니다.
예시:

public class MyClass {
    public int publicField;

    public void publicMethod() {
        // 메서드 내용
    }
}

private: private으로 선언된 멤버는 동일한 클래스 내에서만 접근할 수 있습니다.

다른 클래스에서는 직접 접근할 수 없고, 해당 멤버에 접근하기 위해 public으로 선언된 메서드를 통해 간접적으로 접근해야 합니다.

예시:

public class MyClass {
    private int privateField;

    private void privateMethod() {
        // 메서드 내용
    }

    public void accessPrivateField() {
        // privateField에 접근하여 값을 읽거나 수정
    }

    public void accessPrivateMethod() {
        // privateMethod 호출
    }
}

public과 private은 캡슐화(Encapsulation) 개념의 일부로 사용됩니다.

private으로 선언된 멤버는 외부로부터의 직접적인 접근을 제한하고 class 내부에서만 access 가능, public으로 선언된 메서드를 통해 캡슐화된 멤버에 접근할 수 있도록 합니다.

이는 객체 지향 프로그래밍의 중요한 개념 중 하나로, 클래스 내부 구현의 상세한 내용을 숨기고 외부에 필요한 기능을 노출시킴으로써 코드의 유연성과 안정성을 향상시킵니다.


  • this 포인터 : this는 자바에서 사용되는 특별한 참조 변수입니다. 이 변수는 현재 객체를 가리키는 포인터 역할을 합니다.

객체가 생성되면, 해당 객체의 메서드 내에서 this를 사용하여 현재 객체를 참조할 수 있습니다.

  • static 매서드와 구별하려고 자바에서 채용.

  • static 매서드와 객체 매서드

  • vtable : 시작 주소

  • Getter(접근자 메서드):
    필드의 값을 가져오는 메서드입니다.
    보통 필드 이름 앞에 get 접두어를 붙여서 메서드 이름을 작성합니다.
    일반적으로 해당 필드의 값을 반환합니다.
    매개변수가 없으며, 반환 값이 필드의 데이터 유형과 일치합니다.

  • Setter(설정자 메서드):

필드에 값을 설정하는 메서드입니다.
보통 필드 이름 앞에 set 접두어를 붙여서 메서드 이름을 작성합니다.
일반적으로 해당 필드에 값을 설정하거나 유효성 검사 등의 로직을 수행합니다.
매개변수로 필드의 데이터 유형과 일치하는 값을 받습니다. 반환 값이 없습니다.


도메인 역순으로 폴더 구조를 만드는 것은 일반적인 웹 개발 관행 중 하나입니다.

이 방식은 코드의 가독성, 모듈화, 유지 보수 등 여러 가지 이점을 제공합니다. 주요한 이유는 다음과 같습니다:

유사한 기능의 모듈을 그룹화: 도메인 역순으로 폴더 구조를 만들면 서로 관련된 기능을 한 폴더에 그룹화할 수 있습니다.

예를 들어, 사용자 관리와 관련된 기능은 com.example.usermanagement 폴더에 위치하고, 상품 관리와 관련된 기능은 com.example.productmanagement 폴더에 위치합니다.

이러한 구조는 코드베이스를 보다 체계적으로 구성할 수 있고, 유사한 모듈을 쉽게 찾아 수정, 유지 보수할 수 있습니다.

  • 네임스페이스 분리

도메인 역순으로 폴더 구조를 만들면 패키지의 이름이 도메인 이름을 기반으로 구성됩니다.

이는 서로 다른 도메인을 다루는 애플리케이션에서 네임스페이스 충돌을 방지하는 데 도움이 됩니다.

각 도메인의 패키지는 고유한 식별자 역할을 하며, 코드를 구분하고 이해하기 쉽게 합니다.

  • 코드 가독성 향상

도메인 역순 폴더 구조는 코드의 가독성을 향상시킵니다. 도메인 이름은 애플리케이션의 핵심 개념을 나타내는 경우가 많으므로, 폴더 구조에서 도메인 이름이 먼저 나타나면 코드를 읽는 사람이 해당 모듈의 역할을 빠르게 이해할 수 있습니다.

  • 모듈화와 관심사 분리

도메인 역순 폴더 구조는 모듈화와 관심사 분리를 장려합니다. 관련된 기능을 한 폴더에 그룹화하면 모듈 간의 의존성을 최소화하고 독립성을 강화할 수 있습니다.

이는 소프트웨어의 유지 보수 및 확장성을 개선하는 데 도움이 됩니다.

물론, 도메인 역순 폴더 구조가 항상 필수적인 것은 아니지만, 일반적으로 개발자들 사이에서 권장되는 관행입니다.

이러한 구조는 프로젝트의 크기와 복잡성이 증가할수록 특히 유용하며, 협업과 유지 보수를 용이하게 합니다.


public class Person {
    // 필드  
    //private 액세스 접근 제한자 public 키워드로 정의된 키워드들을 이용해서 접근할수있다.
    private String name;
    private int age;
    private String gender;
    private String address;
    private String phoneNumber;

    // 생성자
    public Person(String name, int age, String gender, String address, String phoneNumber) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.address = address;
        this.phoneNumber = phoneNumber;
    }

    // getter 메소드
    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public String getGender() {
        return this.gender;
    }

    public String getAddress() {
        return this.address;
    }

    public String getPhoneNumber() {
        return this.phoneNumber;
    }

    // setter 메소드
    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    // 메소드
    public void introduce() {
        System.out.println("안녕하세요, 제 이름은 " + name + "이고, 제 나이는 " + age + "살 입니다.");
    }

    public void celebrateBirthday() {
        this.age++;
        System.out.println(name + "의 생일을 축하합니다! 이제 나이는 " + age + "살입니다.");
    }
}

public class MainClass {
    public static void main(String[] args) {
        // Person 객체 생성
        Person person = new Person("John Doe", 30, "Male", "123 Main St", "123-456-7890");
        
        // getter를 사용하여 정보 출력
        System.out.println(person.getName());
        System.out.println(person.getAge());
        
        // setter를 사용하여 정보 수정
        person.setAddress("456 High St");
        System.out.println(person.getAddress());

        // 메소드 호출
        person.introduce();
        person.celebrateBirthday();
    }
}

위의 Person 클래스는 name, age, gender, address, phoneNumber 필드들을 정의하였습니다. 이 필드들은 모두 private로 선언되어 클래스 내부에서만 직접 접근할 수 있습니다.

이렇게 함으로써 객체의 상태를 보호하고, 클래스의 사용자로부터 내부 구현을 숨기는 캡슐화를 이룰 수 있습니다.

또한, 각 필드에 대해 get 메소드(즉, getter)와 set 메소드(즉, setter)를 제공합니다. 이 메소드들은 필드의 값을 간접적으로 읽거나 변경하는 방법을 제공합니다.

MainClass에서는 Person 객체를 생성하고, getter와 setter를 사용하여 필드의 값을 읽고 변경하며, 메소드를 호출하여 특정 행동을 수행시킵니다.

이렇게 하나의 클래스를 기반으로 여러 개의 객체를 만들고 각 객체의 상태와 행동을 독립적으로 관리할 수 있는 것이 객체 지향 프로그래밍의 핵심입니다.


  • 상속

객체 지향 프로그래밍에서 상속(Inheritance)은 한 클래스의 속성과 메소드를 다른 클래스가 물려받아 사용하는 것을 말합니다. 이를 통해 코드의 재사용성을 높이고, 중복을 줄일 수 있습니다.

자바에서는 extends 키워드를 사용하여 상속을 구현할 수 있습니다. 예를 들어, Person 클래스를 상속받아 Employee 클래스를 만들어 보겠습니다.

public class Employee extends Person {
    // 추가 필드
    private String employeeId;
    private String department;

    // 생성자
    public Employee(String name, int age, String gender, String address, String phoneNumber, String employeeId, String department) {
        super(name, age, gender, address, phoneNumber);
        this.employeeId = employeeId;
        this.department = department;
    }

    // 추가 getter와 setter
    public String getEmployeeId() {
        return this.employeeId;
    }

    public String getDepartment() {
        return this.department;
    }

    public void setEmployeeId(String employeeId) {
        this.employeeId = employeeId;
    }

    public void setDepartment(String department) {
        this.department = department;
    }

    // 메소드
    public void work() {
        System.out.println(name + " is working in " + department + ".");
    }
}

public class MainClass {
    public static void main(String[] args) {
        // Employee 객체 생성
        Employee employee = new Employee("John Doe", 30, "Male", "123 Main St", "123-456-7890", "001", "Engineering");
        
        // 메소드 호출
        employee.introduce();
        employee.work();
    }
}

위의 Employee 클래스는 Person 클래스를 상속받아 employeeIddepartment 필드를 추가하고, work 메소드를 새로 정의하였습니다. super 키워드를 사용하여 부모 클래스의 생성자를 호출하고 있습니다.

이제 Employee 클래스는 Person 클래스의 모든 필드와 메소드를 상속받아 사용할 수 있습니다. 이를 통해 중복된 코드를 작성하지 않고도 Person 클래스의 기능을 Employee 클래스에서 이용할 수 있게 되었습니다.

MainClass에서 Employee 객체를 생성하고, introducework 메소드를 호출하면, introduce 메소드는 Person 클래스에서 상속받은 것이고, work 메소드는 Employee 클래스에서 새로 정의한 것임을 확인할 수 있습니다.

그리고, Employee 클래스를 상속한 Professor 클래스를 정의해 보겠습니다.

교수님의 경우, 강의하는 과목(subjects)을 가르칠 수 있을 것입니다.

이를 표현하기 위해 subjects라는 필드를 리스트 형태로 추가하고, 과목을 추가하는 addSubject 메소드를 정의하겠습니다.

import java.util.ArrayList;
import java.util.List;

public class Professor extends Employee {
    // 추가 필드
    private String majorField;
    private List<String> subjects;

    // 생성자
    public Professor(String name, int age, String gender, String address, String phoneNumber, String employeeId, String department, String majorField) {
        super(name, age, gender, address, phoneNumber, employeeId, department);
        this.majorField = majorField;
        this.subjects = new ArrayList<>();
    }

    // 추가 getter와 setter
    public String getMajorField() {
        return this.majorField;
    }

    public List<String> getSubjects() {
        return this.subjects;
    }

    public void setMajorField(String majorField) {
        this.majorField = majorField;
    }

    // 과목 추가 메소드
    public void addSubject(String subject) {
        this.subjects.add(subject);
    }

    // 메소드
    public void teach() {
        System.out.println(name + " is teaching " + majorField + ".");
        System.out.println("Subjects: " + subjects);
    }
}

public class MainClass {
    public static void main(String[] args) {
        // Professor 객체 생성
        Professor professor = new Professor("Dr. Smith", 50, "Male", "789 University St", "123-456-7892", "003", "Engineering", "Computer Science");
        
        // 과목 추가
        professor.addSubject("Data Structures");
        professor.addSubject("Algorithms");

        // 메소드 호출
        professor.introduce();
        professor.teach();
    }
}

위의 Professor 클래스에서 subjects 필드는 ArrayList 타입으로 선언되어 강의하는 과목을 저장하며, addSubject 메소드를 통해 과목을 추가할 수 있습니다. teach 메소드를 호출하면 교수가 가르치는 전공필드와 과목 리스트가 출력됩니다.

MainClass에서 Professor 객체를 생성하고, addSubject 메소드를 통해 과목을 추가한 후, introduce와 teach 메소드를 호출하면 각각 Person 및 Employee 클래스에서 상속받은 메소드와 Professor 클래스에서 새로 정의한 메소드의 동작을 확인할 수 있습니다.

또한, Employee 클래스를 상속하는 대학 행정 직원 클래스를 정의하겠습니다.

행정 직원의 경우, 관리하는 프로젝트(projects)나 특정 업무(tasks)를 가질 수 있습니다.

이를 표현하기 위해 projectstasks라는 필드를 리스트 형태로 추가하고, 프로젝트와 업무를 추가하는 addProjectaddTask 메소드를 정의하겠습니다.

import java.util.ArrayList;
import java.util.List;

public class AdministrativeStaff extends Employee {
    // 추가 필드
    private List<String> projects;
    private List<String> tasks;

    // 생성자
    public AdministrativeStaff(String name, int age, String gender, String address, String phoneNumber, String employeeId, String department) {
        super(name, age, gender, address, phoneNumber, employeeId, department);
        this.projects = new ArrayList<>();
        this.tasks = new ArrayList<>();
    }

    // 추가 getter와 setter
    public List<String> getProjects() {
        return this.projects;
    }

    public List<String> getTasks() {
        return this.tasks;
    }

    // 프로젝트 및 업무 추가 메소드
    public void addProject(String project) {
        this.projects.add(project);
    }

    public void addTask(String task) {
        this.tasks.add(task);
    }

    // 메소드
    public void manageAdministration() {
        System.out.println(name + " is managing administrative tasks in " + getDepartment() + ".");
        System.out.println("Projects: " + projects);
        System.out.println("Tasks: " + tasks);
    }
}

public class MainClass {
    public static void main(String[] args) {
        // AdministrativeStaff 객체 생성
        AdministrativeStaff staff = new AdministrativeStaff("Ms. Johnson", 40, "Female", "789 University St", "123-456-7893", "004", "Administration");
        
        // 프로젝트 및 업무 추가
        staff.addProject("Budget Planning");
        staff.addTask("Preparing financial report");

        // 메소드 호출
        staff.introduce();
        staff.manageAdministration();
    }
}

위의 AdministrativeStaff 클래스에서 projectstasks 필드는 각각 ArrayList<String> 타입으로 선언되어 관리하는 프로젝트와 업무를 저장하며, addProjectaddTask 메소드를 통해 프로젝트와 업무를 추가할 수 있습니다.

manageAdministration 메소드를 호출하면 직원이 관리하는 프로젝트와 업무 리스트가 출력됩니다.

MainClass에서 AdministrativeStaff 객체를 생성하고, addProjectaddTask 메소드를 통해 프로젝트와 업무를 추가한 후, introducemanageAdministration 메소드를 호출하면 각각 PersonEmployee 클래스에서 상속받은 메소드와 AdministrativeStaff 클래스에서 새로 정의한 메소드의 동작을 확인할 수 있습니다.

Person 클래스를 상속하는 Student 클래스를 정의해보도록 하겠습니다.

public class Student extends Person {
    // 추가 필드
    private String studentId;
    private String major;

    // 생성자
    public Student(String name, int age, String gender, String address, String phoneNumber, String studentId, String major) {
        super(name, age, gender, address, phoneNumber);
        this.studentId = studentId;
        this.major = major;
    }

    // 추가 getter와 setter
    public String getStudentId() {
        return this.studentId;
    }

    public String getMajor() {
        return this.major;
    }

    public void setStudentId(String studentId) {
        this.studentId = studentId;
    }

    public void setMajor(String major) {
        this.major = major;
    }

    // 메소드
    public void study() {
        System.out.println(name + " is studying " + major + ".");
    }
}

public class MainClass {
    public static void main(String[] args) {
        // Student 객체 생성
        Student student = new Student("Jane Doe", 20, "Female", "456 High St", "123-456-7891", "002", "Computer Science");
        
        // 메소드 호출
        student.introduce();
        student.study();
    }
}

위의 Student 클래스는 Person 클래스를 상속받아 studentId와 major 필드를 추가하고, study 메소드를 새로 정의하였습니다. super 키워드를 사용하여 부모 클래스의 생성자를 호출하고 있습니다.

MainClass에서 Student 객체를 생성하고, introduce와 study 메소드를 호출하면, introduce 메소드는 Person 클래스에서 상속받은 것이고, study 메소드는 Student 클래스에서 새로 정의한 것임을 확인할 수 있습니다.


  • 캡슐화

객체 지향 프로그래밍에서 캡슐화는 클래스의 데이터(상태)와 그 데이터를 조작하는 메소드(행동)를 하나로 묶는 것을 의미합니다.

이렇게 하면 클래스의 내부 구조를 외부에서 직접 접근하거나 변경하는 것을 제한하고, 메소드를 통해서만 데이터를 변경하거나 액세스할 수 있게 합니다.

즉, 데이터를 '캡슐' 안에 보호하고, 외부에서는 해당 '캡슐'에 어떻게 접근하고 조작할 수 있는지에 대한 인터페이스만 제공하는 것입니다.

자바에서는 이를 구현하기 위해 접근 제한자(access modifiers)를 사용합니다. public, private, protected, 그리고 default(선언되지 않은 경우)가 있습니다.

private 키워드를 사용하면 해당 필드나 메소드는 같은 클래스 내에서만 접근할 수 있습니다. 이를 통해 클래스의 내부 데이터를 외부에서 직접 변경하거나 액세스하는 것을 방지할 수 있습니다.

(자식 클래스도 접근할 수 없습니다)
Person 클래스를 다시 살펴보면 캡슐화의 예를 볼 수 있습니다.

public class Person {
    // 필드 선언
    private String name;
    private int age;
    private String gender;
    private String address;
    private String phoneNumber;

    // 생성자 선언
    public Person(String name, int age, String gender, String address, String phoneNumber) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.address = address;
        this.phoneNumber = phoneNumber;
    }

    // Getter와 Setter 메소드 선언
    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public String getGender() {
        return this.gender;
    }

    public String getAddress() {
        return this.address;
    }

    public String getPhoneNumber() {
        return this.phoneNumber;
    }

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

    public void setAge(int age) {
        this.age = age;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    // 행동을 정의하는 메소드 선언
    public void introduce() {
        System.out.println("Hello, my name is " + name + ". I am a " + age + " year old " + gender + ".");
    }
}

위 코드에서 필드(name, age, gender, address, phoneNumber)는 private으로 선언되어 있어 클래스 외부에서 직접 접근할 수 없습니다.

이 필드들의 값을 얻거나 설정하기 위해서는 각 필드에 대한 getter와 setter 메소드를 통해야 합니다.

이 메소드들은 public으로 선언되어 있어 클래스 외부에서 접근 가능합니다. 이렇게 함으로써 Person 클래스의 필드는 캡슐화되었으며, 이들을 안전하게 보호하고 관리할 수 있습니다.

즉, 객체의 상태를 직접 변경하지 않고 메소드를 통해서만 상태를 변경하도록 함으로써 객체의 상태를 안전하게 보호하고 유지하는 것이 캡슐화의 핵심 원리입니다. 이를 통해 데이터의 무결성을 보장하고, 잘못된 사용으로 인한 버그를 방지할 수 있습니다.


  • protected

기존에 정의했던 Person, Employee, Professor, AdministrativeStaff 클래스에서 protected 키워드를 이용해 새로운 클래스를 정의하겠습니다.
먼저, Person 클래스의 필드 중 name을 protected로 바꿔봅시다.

public class Person {
    protected String name;
    private int age;
    private String gender;
    private String address;
    private String phoneNumber;

    public Person(String name, int age, String gender, String address, String phoneNumber) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.address = address;
        this.phoneNumber = phoneNumber;
    }

    // getter와 setter는 위에 정의한 것과 같습니다...
}

이제 Person 클래스를 상속하는 Employee 클래스를 살펴보겠습니다.

public class Employee extends Person {
    private String employeeId;
    private String department;
    
    public Employee(String name, int age, String gender, String address, String phoneNumber, String employeeId, String department) {
        super(name, age, gender, address, phoneNumber);
        this.employeeId = employeeId;
        this.department = department;
    }

    // getter와 setter는 위에 정의한 것과 같습니다...
}

Employee 클래스에서는 Person 클래스의 name 필드에 직접적으로 접근할 수 있습니다. 왜냐하면 name 필드가 protected로 선언되어 있기 때문에 같은 패키지에 있는 하위 클래스에서는 접근이 가능합니다.

이제 새로운 패키지에서 Person 클래스를 상속받아 Stranger 클래스를 만들어봅시다.

package other;

import people.Person;

public class Stranger extends Person {
    private String country;

    public Stranger(String name, int age, String gender, String address, String phoneNumber, String country) {
        super(name, age, gender, address, phoneNumber);
        this.country = country;
    }

    public void introduce() {
        System.out.println("Hello, my name is " + name + " from " + country);  // 에러 발생
    }
}

이 코드에서 Stranger 클래스는 Person 클래스를 상속받았지만, 다른 패키지에 위치하므로 name 필드에 직접 접근할 수 없습니다.

따라서 introduce 메소드에서 name 필드에 직접 접근하려고 하면 컴파일 에러가 발생합니다.

이를 해결하려면 Person 클래스에서 name에 대한 public getter 메소드를 제공해야 합니다.


  • default

자바에서 default 접근 제한자(또는 package-private)는 클래스, 메소드, 변수에 대해 명시적으로 접근 제한자를 지정하지 않았을 경우 적용되는 접근 수준입니다. default 접근 제한자는 해당 클래스나 메소드, 변수가 정의된 패키지 내에서만 접근이 가능하도록 합니다.

즉, 같은 패키지 내에 있는 다른 클래스에서는 접근할 수 있지만, 다른 패키지의 클래스에서는 접근할 수 없습니다.

Person 클래스의 name 필드를 default 접근 제한자로 바꿔보겠습니다.

public class Person {
    String name; // 이제 'name' 필드는 default 접근 제한자를 가집니다.
    private int age;
    private String gender;
    private String address;
    private String phoneNumber;

    // ... 나머지 코드 ...
}

이제 같은 패키지의 다른 클래스에서 name 필드에 접근해 보겠습니다.

public class SamePackageClass {
public void test() {
Person person = new Person("John", 30, "Male", "123 St", "123-456-7890");
System.out.println(person.name); // 가능, 같은 패키지 내에 있으므로 'name' 필드에 접근할 수 있습니다.
}
}

그러나 다른 패키지의 클래스에서 name 필드에 접근하려고 하면 컴파일 에러가 발생합니다.

package other;

import people.Person;

public class OtherPackageClass {
    public void test() {
        Person person = new Person("John", 30, "Male", "123 St", "123-456-7890");
        System.out.println(person.name); // 불가능, 다른 패키지에 있으므로 'name' 필드에 접근할 수 없습니다.
    }
}

이처럼 default 접근 제한자를 이용하면 같은 패키지 내에서만 접근을 허용하므로, 패키지 구조를 통해 적절한 캡슐화 수준을 제공할 수 있습니다.


  • 다형성(Polymorphism)

다형성(Polymorphism)은 객체 지향 프로그래밍의 중요한 개념 중 하나로, "하나의 타입에 여러 객체를 대입할 수 있는 능력"을 의미합니다. 이를 통해 코드의 재사용성과 유연성을 향상시킬 수 있습니다.

다형성을 구현하는 기본적인 방법은 상속과 인터페이스를 이용하는 것입니다. 상속을 통해 하위 클래스는 상위 클래스의 타입을 가질 수 있고, 인터페이스를 구현한 클래스는 해당 인터페이스 타입을 가질 수 있습니다. 이를 통해 하나의 변수나 메소드 매개변수가 다양한 객체를 참조할 수 있게 됩니다.

자바에서 다형성을 구현하는 방법 중 하나는 "메소드 오버라이딩"입니다. 메소드 오버라이딩은 상위 클래스의 메소드를 하위 클래스에서 재정의하는 것을 말합니다. 이를 통해 하위 클래스의 객체를 상위 클래스 타입의 참조로 다룰 수 있게 됩니다.

Person, Employee, Professor, AdministrativeStaff 클래스를 이용해 이 개념을 설명해 보겠습니다. 먼저, Person 클래스에 work 메소드를 추가하겠습니다.

public class Person {
    // ... 기존 코드 ...

    public void work() {
        System.out.println(name + " is working.");
    }
}

그리고 Employee, Professor, AdministrativeStaff 클래스에서 work 메소드를 오버라이드하겠습니다.

public class Employee extends Person {
    // ... 기존 코드 ...

    @Override
    public void work() {
        System.out.println(name + " is working as an employee.");
    }
}

public class Professor extends Employee {
    // ... 기존 코드 ...

    @Override
    public void work() {
        System.out.println(name + " is teaching in the university.");
    }
}

public class AdministrativeStaff extends Employee {
    // ... 기존 코드 ...

    @Override
    public void work() {
        System.out.println(name + " is managing administrative tasks.");
    }
}

이제 Person 타입의 변수를 이용해 다양한 타입의 객체를 참조할 수 있게 됩니다.

Person person1 = new Person("John", 30, "Male", "123 St", "123-456-7890");
Person person2 = new Employee("Anna", 25, "Female", "456 Ave", "456-789-0123", "E01", "Sales");
Person person3 = new Professor("David", 40, "Male", "789 Blvd", "789-012-3456", "E02", "Computer Science", "Artificial Intelligence");
Person person4 = new AdministrativeStaff("Emma", 35, "Female", "012 Ln", "012-345-6789", "E03", "Human Resources", "Recruiting");

person1.work();  // 출력: John is working.
person2.work();  // 출력: Anna is working as an employee
person3.work();  // 출력: David is teaching in the university.
person4.work();  // 출력: Emma is managing administrative tasks.

여기서 Person 타입의 person1, person2, person3, person4 변수는 각각 Person, Employee, Professor, AdministrativeStaff 타입의 객체를 참조하고 있습니다. work 메소드를 호출할 때, 실제로 참조하고 있는 객체의 타입에 따라 서로 다른 메소드가 실행됩니다. 이처럼 다형성을 이용하면 하나의 참조 변수로 다양한 타입의 객체를 다룰 수 있게 됩니다.


  • 템플릿

"템플릿"이라는 용어는 일반적으로 제네릭 프로그래밍과 관련이 있습니다. 이는 객체 지향 프로그래밍과는 조금 다른 개념입니다.제네릭 프로그래밍은 데이터 타입을 일반화하거나 추상화해서 코드의 재사용성을 높이는 방법입니다. "템플릿"이라는 개념은 C++에서 주로 사용되며, Java에서는 "제네릭"이라는 용어를 사용합니다.

다형성과 제네릭 모두 코드의 재사용성을 높이는 방법이지만, 접근 방식에는 차이가 있습니다. 다형성은 서브클래스가 슈퍼클래스의 형태를 가질 수 있게 함으로써 재사용성을 높이는 반면, 제네릭은 타입 매개변수를 이용해 다양한 타입에 대해 동일한 코드를 작성하게 해 줍니다.

따라서 "템플릿" 혹은 "제네릭"은 다형성의 한 종류라기보다는, 객체 지향 프로그래밍의 다른 측면을 보완하는 별개의 기법입니다. 둘은 서로 보완적인 관계에 있어서 다형성을 이용하면서 제네릭 프로그래밍 기법을 함께 사용하는 것이 가능합니다.

객체 지향 프로그래밍의 의의

  • 재사용성(Reusability) : 한 번 정의된 클래스는 다시 사용될 수 있어 코드의 재사용성이 높아집니다. 이는 개발 시간을 단축하고, 코드의 일관성을 유지하는 데에도 도움이 됩니다.
  • **확장성(Scalability) : 기능 추가나 수정이 필요할 때 해당 클래스나 메소드만 수정하면 되므로 소프트웨어의 확장성이 높아집니다.
  • **유지 보수성(Maintainability) : 객체 지향 프로그래밍은 코드의 가독성을 높이고, 디버깅을 용이하게 만듭니다. 이는 유지 보수를 쉽게 해주는 장점을 가집니다.
  • 추상화(Abstraction) : 실세계의 복잡성을 클래스와 객체라는 추상화를 통해 단순화 시킬 수 있습니다. 이를 통해 문제를 좀 더 이해하기 쉽고 관리하기 쉽게 만들어 줍니다.
  • 재사용성(Reusability)

객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 재사용성(Reusability)이라는 중요한 개념을 지원합니다.

재사용성이란 이미 작성된 코드나 클래스를 다른 곳에서도 재사용하여 개발 시간을 줄이고, 코드의 일관성을 높이며, 코드 관리를 용이하게 하는 것을 의미합니다. OOP에서는 주로 상속(Inheritance)과 다형성(Polymorphism)을 통해 이 재사용성을 실현합니다.

우리가 이미 정의한 Person, Employee, Professor, AdministrativeStaff 클래스를 통해 이 개념을 설명해 보겠습니다.

  1. 상속 (Inheritance) : Person 클래스를 상속받는 Employee, Professor, AdministrativeStaff 클래스를 살펴봅시다. Person 클래스에 정의된 필드와 메소드를 Employee, Professor, AdministrativeStaff 클래스가 재사용하고 있습니다. 이것이 상속을 통한 재사용성입니다.
public class Person {
    protected String name;
    // ... other fields and methods ...
}

public class Employee extends Person {
    private String employeeId;
    // Employee 클래스는 'name' 필드와 그 외 Person 클래스의 메소드를 재사용합니다.
    // ... other fields and methods ...
}
  1. 다형성 (Polymorphism): 부모 클래스 타입의 참조변수를 통해 자식 클래스의 인스턴스를 다룰 수 있습니다. 이를 통해 여러 자식 클래스에 공통적인 메소드를 한 번에 호출할 수 있습니다.
public class Main {
    public static void main(String[] args) {
        Person person1 = new Person("John");
        Person person2 = new Employee("Mike");
        Person person3 = new Professor("David");
        Person person4 = new AdministrativeStaff("Linda");

        person1.introduce(); // 출력: Hello, I'm a person and my name is John
        person2.introduce(); // 출력: Hello, I'm an employee and my name is Mike
        person3.introduce(); // 출력: Hello, I'm a professor and my name is David
        person4.introduce(); // 출력: Hello, I'm an administrative staff and my name is Linda
    }
}

위의 코드에서 Person 클래스 타입의 참조변수를 사용하여 Employee, Professor, AdministrativeStaff 클래스의 introduce 메소드를 각각 호출하고 있습니다. 이는 코드의 재사용성을 높여주는 다형성의 좋은 예입니다.

이와 같이 상속과 다형성을 통해 이미 작성된 코드를 재사용하고, 확장성과 유지 보수성을 높일 수 있습니다. 이러한 특성은 OOP의 주요 장점 중 하나입니다.


확장성(Scalability)

객체 지향 프로그래밍(Object-Oriented Programming, OOP)에서 확장성(Extensibility)은 매우 중요한 개념입니다. 확장성이란 기존의 코드를 수정하지 않고 기능을 추가하거나 변경할 수 있는 능력을 의미합니다. OOP에서는 주로 클래스의 상속(Inheritance)을 통해 이 확장성을 실현합니다.

이미 정의한 Person, Employee, Professor, AdministrativeStaff 클래스를 통해 이 개념을 설명해 보겠습니다.

  1. 상속 (Inheritance): 상속은 기존 클래스의 필드와 메소드를 물려받아 새로운 클래스를 생성하는 방법입니다. 이를 통해 새로운 클래스는 기존 클래스의 기능을 그대로 이어받고, 필요한 기능을 추가하거나 변경할 수 있습니다.
public class Person {
    protected String name;
    // ... other fields and methods ...
}

public class Employee extends Person {
    private String employeeId;
    // 'Employee' 클래스는 'Person' 클래스의 'name' 필드와 그 외 메소드를 이어받습니다.
    // 또한 새로운 필드인 'employeeId'를 추가하여 확장하였습니다.
    // ... other fields and methods ...
}

이처럼 Employee 클래스는 Person 클래스의 필드와 메소드를 그대로 이어받고, 새로운 필드를 추가하여 클래스를 확장하였습니다. 이는 기존 Person 클래스의 코드를 수정하지 않고도 새로운 기능을 추가할 수 있음을 보여주며, 이것이 바로 확장성의 예시입니다.

  1. 메소드 오버라이딩 (Method Overriding): 자식 클래스에서 부모 클래스의 메소드를 재정의하는 것을 메소드 오버라이딩이라 합니다. 이를 통해 상속받은 기능을 그대로 사용하지 않고 자식 클래스에서 변경하여 사용할 수 있습니다.
public class Person {
    protected String name;
    public void introduce() {
        System.out.println("Hello, I'm a person and my name is " + name);
    }
    // ... other fields and methods ...
}

public class Employee extends Person {
    private String employeeId;
    @Override
    public void introduce() {
        System.out.println("Hello, I'm an employee and my name is " + name);
    }
    // ... other fields and methods ...
}

이처럼 Employee 클래스는 Person 클래스의 introduce 메소드를 오버라이딩하여 자신만의 introduce 메소드를 정의하였습니다. 이렇게 메소드를 오버라이딩함으로써, 부모 클래스의 기능을 유지하면서 자신만의 동작을 추가하거나 변경할 수 있습니다.
이와 같이 상속과 메소드 오버라이딩을 통해 객체 지향 프로그래밍에서는 기존 코드의 변경 없이 새로운 기능을 추가하거나 기존 기능을 변경하는 확장성을 얻을 수 있습니다. 이러한 특성은 OOP의 주요 장점 중 하나입니다.


  • 유지 보수성(Maintainability)

객체 지향 프로그래밍(Object-Oriented Programming, OOP)에서 유지 보수성(Maintainability)은 매우 중요한 특징입니다. 유지 보수성은 시간이 지나도 코드를 이해하고 수정하거나 향상시키기 쉽게 하는 능력을 의미합니다. OOP에서는 클래스와 객체, 그리고 이들을 이용한 캡슐화, 상속, 다형성 등의 특징을 통해 이 유지 보수성을 실현합니다.
이미 정의한 Person, Employee, Professor, AdministrativeStaff 클래스를 통해 이 개념을 설명해 보겠습니다.

  1. 캡슐화 (Encapsulation): 캡슐화는 관련 데이터와 기능을 하나의 객체 안에 담는 기법입니다. 이를 통해 외부에서 객체의 내부 데이터에 직접 접근하는 것을 제한하고, 객체가 제공하는 메소드를 통해서만 접근하도록 하여 데이터의 안정성을 보장합니다. 이러한 캡슐화는 코드의 유지 보수성을 높이는데, 객체의 내부 구현을 변경해도 외부에서는 해당 객체가 제공하는 메소드의 인터페이스만 알면 되기 때문입니다.
public class Person {
    private String name;
    // 외부에서 'name' 필드에 직접 접근하는 것을 제한하고,
    // 'getName' 과 'setName' 메소드를 통해서만 접근하도록 하여 캡슐화를 실현합니다.
    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }
    // ... other fields and methods ...
}
  1. 상속 (Inheritance): 상속은 코드의 재사용성을 높이고, 중복 코드를 줄이는데 도움을 줍니다. 이를 통해 유지 보수성을 높일 수 있습니다. 예를 들어, 공통적인 기능을 가진 부모 클래스를 정의하고, 이를 상속받아 자식 클래스에서는 특수한 기능만을 추가하거나 변경하도록 하면, 나중에 공통 기능에 문제가 발생하였을 때 부모 클래스만을 수정하면 모든 자식 클래스에 적용되므로 유지 보수성이 높아집니다.
public class Person {
    protected String name;
    // ... other fields and methods ...
}

public class Employee extends Person {
    private String employeeId;
    // 'Employee' 클래스는 'Person' 클래스의 'name' 필드와 그 외 메소드를 재사용합니다.
    // 이로 인해 코드의 중복을 줄이고 유지 보수성을 높입니다.
    // ... other fields and methods ...
}
  1. 다형성 (Polymorphism): 다형성은 코드의 유연성을 높이고, 새로운 기능을 추가하거나 기존 기능을 변경할 때 유용합니다. 예를 들어, 부모 클래스 타입의 참조 변수를 통해 자식 클래스의 인스턴스를 다룰 수 있으며, 이를 통해 다양한 타입의 객체를 하나의 배열이나 리스트에 담아 일관되게 처리할 수 있습니다. 이는 코드의 가독성과 유지 보수성을 높입니다.
public class Main {
    public static void main(String[] args) {
        Person person1 = new Person("John");
        Person person2 = new Employee("Mike");
        // 여러 타입의 객체를 하나의 'Person' 타입 배열에 담아 일관되게 처리할 수 있습니다.
        Person[] people = {person1, person2};
        for (Person person : people) {
            person.introduce();
        }
    }
}

이러한 OOP의 특징들은 코드의 유지 보수성을 크게 향상시키며, 이는 OOP의 주요 장점 중 하나입니다.


  • 추상화(Abstraction)

객체 지향 프로그래밍에서 추상화(Abstraction)는 복잡한 시스템을 단순화시키는 프로세스를 의미합니다. 이는 필요한 정보만을 강조하고, 불필요한 세부 사항은 숨기는 방법입니다.

자바에서 추상화는 주로 추상 클래스(abstract class)나 인터페이스(interface)를 이용해 구현합니다. 그러나, 이미 정의한 Person, Employee, Professor, AdministrativeStaff 클래스를 이용해 설명하면:

추상화의 한 예로, Person이라는 클래스를 정의하고 이를 상속받는 여러 자식 클래스를 만드는 것을 들 수 있습니다. Person 클래스는 이름, 나이, 성별 등과 같은 일반적인 특징을 가집니다. 이는 모든 사람이 공통적으로 가지는 속성입니다.

public abstract class Person {
    protected String name;
    protected int age;
    protected String gender;

    // 공통 메소드
    public void introduce() {
        System.out.println("Hello, my name is " + name);
    }
    
    // 추상 메소드
    public abstract void work();
}

이제 Person을 상속하는 Employee, Professor, AdministrativeStaff와 같은 클래스를 만들 수 있습니다. 이 클래스들은 각각 Person의 특성을 상속받고, 그들만의 특성을 추가로 가질 수 있습니다. 또한 추상 메소드인 work()를 각 클래스에 맞게 구현할 수 있습니다.

public class Employee extends Person {
    private String employeeId;

    @Override
    public void work() {
        System.out.println(name + " is working as an employee.");
    }
    // ... other methods ...
}

public class Professor extends Person {
    private String academicField;

    @Override
    public void work() {
        System.out.println(name + " is working as a professor in the field of " + academicField);
    }
    // ... other methods ...
}

public class AdministrativeStaff extends Person {
    private String department;

    @Override
    public void work() {
        System.out.println(name + " is working in the " + department + " department.");
    }
    // ... other methods ...
}

이렇게 Person 클래스를 상속하여 여러가지 특별한 유형의 사람 클래스를 만들 수 있습니다. 이는 공통된 특성을 강조하고, 클래스마다 다르게 동작해야 하는 세부 사항은 각 클래스에서 구현하여 추상화를 실현하는 좋은 예입니다.

profile
_serendipity

0개의 댓글