[TIM] 객체지향에 관하여

byeol·2023년 6월 7일
0

데브코스

목록 보기
1/2

안녕하세요.
몇 가지 규칙을 만들어 글을 정리합니다.
단순히 제가 배운 내용을 정리하는 용도의 글입니다.

데브코스 백엔드 과정에서 배운 객체지향 첫 번째 수업을 듣고 정리한 내용이며
기초 강의가 아니기에 자바의 정석을 복습하며 내용을 정리하였습니다.

궁금하고 이상한
새롭게 알게된
그래서 더 찾아본

1. 객체지향 프로그래밍

  • 왜 등장했는가?
    프로그램이 거대화되면서 빠르게, 내가 아닌 다른 사람이 유지보수해야 하는 상황, 지속적인 관리를 하기 어려워서 객체 지향 프로그래밍이 등장하게 되었습니다.
    즉, 작게 만들어서 합치는 방법입니다.
  • 프로그램의 동작을 객체들에게 나눠서 수행시키는 것입니다.
  • 객체와 class와 instance의 차이는?
    • 객체 : 개념적인 용어
    • 클래스, 인스턴스 : Java에서 사용되는 객체의 기술적인 용어
  • 객체는 작은 기능을 수행하며 객체와 객체는 서로 협력한다.
  • 즉 개발자는 프로그램에서 객체를 잘 만들기 위해서는 기능들을 쪼개 객체들에게 위임하고 객체들 간의 협력을 만들어줘야 한다,
  • 객체는 Type(형)으로 구분됩니다.
     String str = "Hello World";
    여기서 객체는 String이며 그것이 Type입니다.
  • 객체를 만든다 = Type을 만든다 = class 만든다

2. 객체지향의 특성

1) 캡슐화

  • 캡슐처럼 완성도가 있다
    • 기능을 수행하는 단위으로서의 완전함 = 객체
    • 외부로의 의존성이 낮다
    • 정보 은닉
      • 객체의 정보가 밖으로 전달되거나 밖에서 객채의 내부 정보를 접근할 수 없다
      • 정보 은닉을 가능하게 접근지시자
        • private : 같은 클래스에서만 접근 가능
        • default(friendly) : 같은 패키지
        • protected : 같은 패키지 + 상속받은 클래스
        • public : 어디서든

    객체는 스스로 동작할 수 있는 환경을 가지고 있어야 하며 외부에 의존적이이지 않고 외부 침략을 제한할 수 있다.

2) 상속

Java 상속의 개념

  • 자손 클래스는 조상 클래스의 모든 멤버를 상속받는다.

  • 생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상송된다.

  • 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.

  • 접근제어자가 private 또는 default인 멤버들은 상속되지 않는다가 아니라 상속을 받지만 자손클래스로의 접근이 제한된다.

  • Java는 단일 상속만을 지원한다.

  • 따라서 다른 클래스로부터 상속받지 않는 모든 클래스들은 자동적으로 Object 클래스를 상속받게 된다. 그렇지만 다른 클래스로부터 상속받는다고 하더라고 계층관계도에 따라 위로 올라가면 마지막에 Object 클래스가 있을 것이다!

  • super
    조상 클래스로부터 상속받은 멤버도 자손 클래스 자신의 멤버이므로 super 대신 this를 사용할 수 있다. 그래도 조상 클래스의 멤버와 자손클래스의 멤버가 중복되어 정의되어 서로 구별해야하는 경우에만 super를 사용하는 것이 좋다.

    class Parent {
      int x =10;
    }
    class Child extends Parent {
      void method() {
        System.out.println("x="+x);
        System.out.println("this.x="+this.x);//10
        System.out.println("super.x="+super.x);//10
    
      }
    }

    메서드 역시 super를 써서 호출할 수 있다.
    특히 자손 클래스에서 오버라이딩한 경우에 super를 사용

    class Point{
      int x;
      int y;
      String getLocation(){
        return "x:"+x+",y:"+y;
      }
    }
    
    class Point3D{
       int x;
       String getLocation(){
        return super.getLocation()+",z:"+z; 
       }
    }
  • super() : 조상 클래스의 생성자

    • super()는 조상 클래스의 생성자를 호출하는데 사용된다.
    • 자손 클래스의 인스턴스를 생성하면 이 인스턴스는 부모 클래스의 멤버까지 사용할 수 있는 상황이 된다. 즉 부모 클래스의 멤버 변수들은 모두 초기화가 된 상태여야 한다는 것이다. 따라서 자손 클래스의 생성자 첫 줄에는 반드시 조상 클래스의 생성자가 호출되어야 한다.
    • 결국 마지막에는 Object의 생성자 Object()까지 거슬러 올라가게 된다.
    • 그리고 만약 자손 클래스의 생성자 첫 줄에 부모 클래스의 생성자를 호출하지 않는다면 컴파일러가 생성자의 첫 줄에 super()를 자동적으로 추가한다.

🤔 그렇다면 포함과 상속의 차이는?

~은 ~이다 -> 상속관계
~은 ~를 가지고 있다 -> 포함관계

상속을 어떻게 객체 지향적으로 만들 수 있는가?

🤔 자손들의 공통적인 기능을 가지도록 부모 클래스를 만든다? 오해
기능적인 관점이 아니라 추상과 구체의 관점에서 상속의 관계가 맺어진다

3) 추상화

추상클래스 = 미완성 설계도
인터페이스 = 기본 설계도 (더 추상적)

추상클래와 인터페이스의 개념

추상 클래스

  • 미완성된 메서드를 포함하고 있다.
  • 인스턴스를 생성할 수 없고 자손 클래스의 상속을 통해서만 완성될 수 있다.
  • 추상 클래스의 키워드 'abstract'
  • 이 선언부를 보고 추상 메서드가 있으니 상속을 통해서 구현해주어야 한다는 것을 쉽게 알 수 있다.
  • 추상 메서드를 가지고 있다는 것 외에는 일반 클래스와 다르지 않는다.
  • 생성자도 있고 멤버 변수와 메서드도 가질 수 있다
  • 단 추상메서드가 없어도 abstract를 붙여서 추상클래스로 지정할 수 있다. 추상메서드가 없는 완성된 클래스라 할지라도 추상클래스로 지정되면 클래스의 인스턴스를 생성할 수 없다.
  • 만약 추상 클래스를 상속 받은 자손 클래스가 추상 클래스의 모든 추상메서드를 구현하지 않았다면 이 자손클래스 또한 추상 클래스가 된다.
  • 생각해보면 몸통이 빈 일반 메서드로 선언해도 되지 않을까? 라는 의문이 들 수 있지만 이는 추상메서드와 달리 강제성이 없다.

인터페이스

  • 인터페이스는 일종의 추상클래스이다.
  • 추상클래스보다 추상의 정도가 더 높다
  • 추상클래스는 일반메서드, 멤버변수, 추상메서드, 생성자를 가질 수 있지만
  • 인터페이스는 추상메서드와 상수만을 가질 수 있다.
  • 인터페이스는 접근제어자로 public 또는 default를 사용할 수 있다.
    interface 인터페이스이름 {
      public static final 타입 상수이름 =;
      public abstract 메서드이름 (매개변수목록) ;
    }
  • 모든 멤버변수는 public static final이며 이를 생략할 수 있다.
  • 모든 메서드는 public abstract이며 이를 생략할 수 있다.
    단. static 메서드와 디폴드 메서드는 예외이다.(JDK 1.8)
  • 인터페이스는 인터페이스로부터만 상속받을 수 있다.
  • 만약 인터페이스의 메서드 중 일부만 구현한다면 abstract를 붙여서 추상 클래스로 선언해야 한다.
  • 인터페이스를 이용한 다중상속에 관하여 오해하는 부분은 인터페이스가 다중상속을 위한 것이라고 이해하는 것이다, 그러나 인터페이스의 다중상속을 구현하는 경우는 거의 없다.
  • 만약에 두 개의 클래스로부터 상속받아야 하는 경우라면 두 조상 클래스 중에서 비중이 높은 쪽은 선택하고 나머지 하나는 인터페이스로 만드는 것이다.

4) 다형성

다형성의 개념

여러 가지 형태를 가질 수 있는 능력을 의미하며 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.
🕵️조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다.

  • 모든 참조 변수는 null 또는 4 byte의 주소값이 저장되며, 참조변수의 타입은 참조할 수 있는 객체의 종류와 사용할 수 있는 멤버의 수를 결정한다.

  • 조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.
    반대로 자손타입의 잠조변수로 조상 타입의 인스턴스를 참조할 수는 없다.

  • 서로 상속관계에 있는 클래스 사이에서만 참조변수의 형변환이 가능하다.

  • 따라서 모든 참조변수는 Object 클래스 타입으로 형변환이 가능하다.

  • 자손타입에서 조상 타입으로 가는 up-casting은 형변환 생략 가능

  • 그 반대는 불가능

    Car car = null;
    FireEngine fe = new FireEngine();
    FireEngine fe2 = null;
    car = fe;
    fe2 = (FireEngine) car;
    
    Tv t = new CaptionTv(); // 이도 (TV)생략된 형태
  • 메서드의 경우 조상 클래스의 메서드를 자손의 클래스에서 오버라이딩한 경우에도 참조 변수의 타입에 관계없이 항상 실제 인스턴스의 메서드가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라진다.

    예시 1)

     class BindingTest{
       public static void main(String[] args){
       
          Parent p = new Child();
          Child c = new Child();
          
          System.out.println("p.x= "+p.x);//100
          p.method();//Child method
           
          System.out.println("c.x= "+c.x);//200
          c.method();//Parent method
       }
     } 
     class Parent{
       int x = 100;
       void method(){
        System.out.println("Parent Method");
       }
     }
     
     class Child extends Parent {
       int x =200;
       void method(){
        System.out.println("Child Method");
       }
     }

    예시 2)

    class BindingTest{
      public static void main(String[] args){
      
         Parent p = new Child();
         Child c = new Child();
         
         System.out.println("p.x= "+p.x);//100
         p.method();//Parent method
          
         System.out.println("c.x= "+c.x);//100
         c.method();//Parent method
      }
    } 
    class Parent{
      int x = 100;
      void method(){
       System.out.println("Parent Method");
      }
    }
    
    class Child extends Parent {}
  • 매개 변수의 다형성의 예시

    class Product{
     int price;
     int bonusPoint;
     
     Product(int price){
       this.price=price;
       bonusPoint = (int) (price/10.0);
     }
    }
    class Tv extends Product {
       Tv(){
         super(100);
       }
       public String toString() {return "TV";}
    }
     class  Computer extends Product {
       Tv(){
         super(200);
       }
       public String toString() {return "TV";}
    }

🤔 인스턴스 타입과 일치하는 참조변수를 사용하면 인스턴스의 멤버들을 모두 사용할 수 있을 텐데 왜 조상타입의 참조변수를 사용해서 인스턴스의 일부 멤버만을 이용하도록 할까?
팀원들에게 물어보았습니다.
저의 관점은

List<Integer> list = new ArrayList<>();
List<Integer> list = new LinkedList<>();

ArrayList에서 LinkedList로 바꾸는 상황이라면 위와 같이 코드를 수정하는 과정은 똑같이 일어난다고 생각했기 때문입니다.
하지만 팀원들의 답변을 통해서
더 명확하게 개념을 잡을 수 있었습니다.
결국 모든 코드들은 List의 메서드의 공통적인 부분에 따라 돌아갑니다.
아마도 저는 저 list들을 많은 곳들에 사용하게 될 것입니다.
만약 제가

ArrayList<Integer> list = new ArrayList<>();

로 선언하고 LinkedList로 바꾸는 상황이라면 저 한줄의 코드 이외에도
list를 사용한 모든 코드들을 변경해야 합니다.
하지만 공통적인 부분을 하나로 뽑음으로서 코드의 객체지향적인 설계가 가능하게 되어 한줄의 코드 변경만으로 코드를 이전과 같은 설계를 가지도록 유지할 수 있습니다.

3. 객체지향설계

UML

객체지향 프로그래밍은 객체에 기능을 나눠서 위임하고 이 객체들간의 관계가 존재한다.

개발자는 객체들간의 관계를 설명할 도구가 필요하고
그것이 UML이다.

아래는 계산기 미션을 하며 그려본 UML이다.
관계를 선으로 나타내며 접근제어자는 +(public),-(private),없음(default)로 나타낸다.

디자인 패턴

SOLID 잘 지키기 -> 그러나 이를 잘 지키기는 힘드니 개발자들이 이를 잘 지키는 여러가지 방법을 고안했고 그 방법이 디자인 패턴이다

23개의 디자인 패턴이 존재한다.
출처 : https://refactoring.guru/design-patterns/catalog

내용이 길어질거 같아 따로 포스팅하였습니다.

오늘의 회고

사실은 아직도 객체 지향에 관하여 제대로 알고 있다는 생각이 들지 않습니다.
오늘 배운 내용들은 알고 있지만 알고 있다고 말하기 어렵고
항상 새로운 것들이 생겨납니다.

그래도 제가 항상 노력해야하는 부분은
오늘 배운 내용을 잘 소화하도록 노력하는 것

profile
꾸준하게 Ready, Set, Go!

0개의 댓글