객체 지향 언어

이동규·2023년 4월 9일

정리

캡슐화

  • 클래스 속 필드 영역에서 변수들을 private으로 설정하여 외부에서 직접 접근을 할 수 없도록 제어하고, getter & setter 메서드를 사용하여 해당 변수들에 간접적으로 접근하도록 하는 것
  • 클래스 내부의 변수나 메서드가 외부에 노출되는 것을 막을 수 있다.
  • 클래스의 내부 구현이 변경되더라도, 외부에서 사용하는 코드에 영향을 미치지 않도록 하는 것이 가능하게 해준다.
  • getter & setter를 만들어 주는 단축키가 존재한다.
    • Alt + Insert

상속

  • 기존의 클래스를 재사용하여 새로운 클래스를 만드는 것
    • 부모 클래스 → super Class
    • 자식 클래스 → sub Class
    • 자식 클래스는 부모 클래스의 모든 멤버를 상속 받음
  • extends키워드를 사용하여 상속을 구현
  • 장점
    • 코드 재사용으로 코드 중복을 줄일 수 있다.
    • @Override 같은 메스드를 통해 유연성을 확보한다. 즉, 부모 클래스에 있는 메서드를 자식 클래스에서 재정의하여 사용 가능하다.
    • 부모 클래스에 있는 메서드를 자식 클래스들이 자신만의 방식으로 메서드를 실행할 수 있는 다형성을 확보
  • 적절한 상속 계층을 설계하고, 오버라이딩을 올바르게 사용하며, 의미 없는 상속 관계를 만들지 않도록 주의!
  • super
    • 부모 클래스의 생성자, 멤버 변수, 메서드를 호출하기 위해 사용
    • 상위 클래스의 생성자 호출: 서브 클래스에서 생성자를 호출할 때, super()를 사용하여 상위 클래스의 생성자를 먼저 호출할 수 있다.
    • 상위 클래스의 메서드 호출: 서브 클래스에서 오버라이딩한 메서드가 아닌, 상위 클래스의 메서드를 호출해야 하는 경우 super키워드를 사용할 수 있다.
    • 상위 클래스의 멤버 변수 사용: 서브 클래스에서 상위 클래스의 멤버 변수에 직접 접근할 수 없는 경우 super키워드를 사용하여 간접적으로 접근할 수 있다. → 서브 클래스에서 상위 클래스의 멤버 변수를 private으로 선언한 경우, 서브 클래스에서 해당 변수에 직접 접근할 수 없다.

추상화

  • 필요한 부분만을 추출하여 객체를 모델링하고, 이들 사이의 관계를 정의
  • 추상 클래스 생성 → abstract class 클래스 이름 {} 사용
    • 하위 클래스는 extends를 통해 추상 클래스를 받음
    • 추상 클래스는 직접 객체를 생성할 수 없으므로 하위 클래스에서 상속받아서 객체를 생성
    • 상속보다 더 유연한 설계를 할 수 있다.
  • Interface
    • 추상 메서드를 포함하고 있으며, 구현부가 없는 메서드
    • 다중 상속이 가능, 클래스에서 상속받는 것이 아니라 implements키워드를 사용하여 구현
      • interface 상속
        • 인터페이스 상속은 extends 키워드를 사용하여 이루어지며, 하나 이상의 인터페이스를 상속할 수 있다.

        • 예제 코드 → 코드를 보면 이해하기 쉽다.

          public interface Shape {
              void draw();
          }
          
          public interface Colored {
              String getColor();
          }
          
          // 다중 상속
          public interface ShapeColored extends Shape, Colored {
              void fill();
          }
          
          public class Circle implements ShapeColored {
              private String color;
          
              public Circle(String color) {
                  this.color = color;
              }
          
              @Override
              public void draw() {
                  System.out.println("Circle is drawn");
              }
          
              @Override
              public String getColor() {
                  return color;
              }
          
              @Override
              public void fill() {
                  System.out.println("Circle is filled with color " + color);
              }
          }
          
          public static void main(String[] args) {
              Circle circle = new Circle("red");
              circle.draw(); // "Circle is drawn" 출력
              System.out.println(circle.getColor()); // "red" 출력
              circle.fill(); // "Circle is filled with color red" 출력
          }
    • 인터페이스는 객체를 생성할 수 없으므로 인터페이스를 구현한 클래스의 객체를 생성
    • 다형성과 유연한 코드 구조를 구현할 수 있고, 객체 간의 결합도를 낮추어 유지보수를 향상
    • 인터페이스는 구현 클래스에서 인터페이스에 정의된 모든 메서드를 반드시 구현해야 함
  • 예제 코드
//추상 클래스 & 인터페이스 생성
public abstract class Animal {
   public abstract void makeSound();
}

public interface Vehicle {
   public void start();
   public void stop();
}

//추상 클래스를 상속 받은 하위 클래스 & 인터페이스가 정의된 클래스
public class Dog extends Animal {
   public void makeSound() {
      System.out.println("멍멍");
   }
}

public class Car implements Vehicle {
   public void start() {
      System.out.println("차가 출발합니다.");
   }

   public void stop() {
      System.out.println("차가 멈춥니다.");
   }
}

//추상 클래스 & 인터페이스 객체 생성
Animal animal = new Dog();
Vehicle vehicle = new Car();
  • default 메서드, static 메서드
    • default 메서드와 static 메서드는 자바 8부터 추가된 인터페이스의 기능

    • default 메서드

      • default 메서드는 인터페이스 내에서 기본적인 구현을 가지는 메서드를 정의할 수 있게 해줌
      • default 메서드를 사용하면, 인터페이스를 구현한 클래스에서 구현하지 않아도 되는 메서드를 정의할 수 있다.
      • 인터페이스를 구현한 클래스에서 default 메서드를 호출하면, 인터페이스에서 구현한 내용이 실행 (아래 예제 참고)
    • static 메서드

      • 인터페이스 내에서 정적 메서드를 정의할 수 있게 해줌
      • 인터페이스를 구현한 클래스에서 이 메서드를 호출할 수 있다.
      • 인터페이스를 구현한 클래스에서 해당 static 메서드를 사용하려면, 인터페이스명.메서드명() 형태로 호출하면 된다. (아래 예제 참고)
    • 예제 코드

      public interface MyInterface {
          // 추상 메서드
          void doSomething();
          
          // default 메서드
          default void doSomethingElse() {
              System.out.println("Doing something else...");
          }
          
          // static 메서드
          static void doAnotherThing() {
              System.out.println("Doing another thing...");
          }
      }
      
      public class MyClass implements MyInterface {
          public void doSomething() {
              System.out.println("Doing something...");
          }
      }
      
      public static void main(String[] args) {
          MyClass obj = new MyClass();
          
          obj.doSomething(); // "Doing something..." 출력
          obj.doSomethingElse(); // "Doing something else..." 출력
          MyInterface.doAnotherThing(); // "Doing another thing..." 출력
      }
    • interface 다향성 → 형변환

      • 인터페이스는 객체를 다형성으로 다루기 위한 매우 유용한 도구

      • 자동 형변환은 상속 관계에 있는 클래스들 간의 형변환에서 사용되고, 인터페이스 강제 형변환은 인터페이스를 구현하는 클래스들 간의 형변환에서 사용

      • 강제 형변환

        • 다형성 구현, 코드 유연성 향상, 객체의 추상화를 위해 사용
      • 자동 형변환

        • 일반적으로 인터페이스를 구현한 클래스의 객체를 사용해야 하는 경우에 사용
      • 예제 코드

        public interface Shape {
            void draw();
        }
        
        public class Circle implements Shape {
            @Override
            public void draw() {
                System.out.println("Circle is drawn");
            }
        }
        
        public class Rectangle implements Shape {
            @Override
            public void draw() {
                System.out.println("Rectangle is drawn");
            }
        }
        
        //자동 형변환
        Shape shape1 = new Circle();
        Shape shape2 = new Rectangle();
        
        shape1.draw(); // "Circle is drawn" 출력
        shape2.draw(); // "Rectangle is drawn" 출력
        
        //강제 형변환
        Shape shape3 = new Rectangle();
        Rectangle rectangle = (Rectangle) shape3;
        rectangle.draw(); // "Rectangle is drawn" 출력
      • 인터페이스를 구현한 클래스를 인스턴스화하여 사용하는 것도 가능하지만, 해당 클래스의 특정한 구현에 의존하게 되어 유연성이 떨어질 수 있다. 또한, 인터페이스를 인스턴스화하고 구현 클래스를 대입하여 사용하면, 구현 클래스가 변경되더라도 인터페이스를 사용하는 코드는 수정하지 않아도 된다. 즉, 유지보수성과 확장성을 높여 준다.

다형성

  • 하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미 → 같은 클래스로부터 생성된 객체라 하더라도, 그 객체가 다른 클래스 타입으로 취급될 수 있다는 것
  • 참조 변수의 타입 변환
    • 강제 형변환
      • 자식 클래스의 객체를 부모 클래스의 참조 변수로 참조할 때, 강제로 형변환을 해주는 것
      • 자식 클래스에서 추가된 멤버 변수나 메서드는 부모 클래스에는 없으므로 부모 클래스의 참조 변수로는 접근할 수 없다. → 자식 클래스에서 추가된 멤버 변수나 메서드는 부모 클래스에 존재하지 않기 때문 하지만, 다형성의 개념에 의해 자식 클래스의 객체를 부모 클래스의 참조 변수로 참조할 수 있다. 즉, 부모 클래스의 참조 변수로 자식 클래스의 객체를 참조하면, 부모 클래스에서 정의된 멤버 변수와 메서드는 그대로 사용할 수 있고, 자식 클래스에서 추가된 멤버 변수나 메서드는 사용할 수 없게 된다. 이때 강제 형변환을 하게 되면?
        • 예제 코드

          class Animal {
              String name;
              
              public Animal(String name) {
                  this.name = name;
              }
              
              public void makeSound() {
                  System.out.println("Animal sound");
              }
          }
          
          class Cat extends Animal {
              String color;
              
              public Cat(String name, String color) {
                  super(name);
                  this.color = color;
              }
              
              public void scratch() {
                  System.out.println("Cat is scratching.");
              }
          }
          
          public class Main {
              public static void main(String[] args) {
                  Animal animal1 = new Animal("Bob");
                  Cat cat1 = new Cat("Whiskers", "Gray");
                  Animal animal2 = new Cat("Tom", "Black");
                  
                  animal1.makeSound(); // Animal sound
                  cat1.makeSound(); // Animal sound
                  animal2.makeSound(); // Animal sound
                  
                  // animal2.scratch(); // Error: Animal 클래스에는 scratch() 메서드가 없습니다.
                  
                  // Cat 타입으로 강제 형변환
                  ((Cat)animal2).scratch(); // Cat is scratching.
                  
                  // instanceof 연산자로 타입 체크
                  if(animal2 instanceof Cat) {
                      ((Cat)animal2).scratch(); // Cat is scratching.
                  }
              }
          }

          → 다형성 기능으로 인해 해당 클래스 객체의 원래 클래스명을 체크하는것이 필요한데 이때 사용할 수 있는 명령어가 instance of

    • 자동 형변환
      • 부모 클래스의 객체를 자식 클래스의 참조 변수로 참조할 때, 자동으로 형변환을 해주는 것 → 명시적으로 형변환을 할 필요가 없다.
  • 자동 형변환과 강제 형변환을 적절히 활용하면 다형성을 구현할 수 있다.

클래스 설계

  • 클래스는 객체를 정의하는데 사용되는 추상화 도구 즉, 설계도
  • 고려할 사항
    • 클래스가 가지는 속성(인스턴스 변수)과 행동(메서드)을 정의 → 속성은 필드 생성 부분에 행동은 메서드 부분에
    • 클래스의 책임(responsibility)과 역할(role)을 명확하게 정의
      • 책임은 클래스가 제공하는 기능 또는 서비스를 의미 → 객체의 행위와 관련이 있으며, 클래스가 외부에 제공하는 인터페이스를 통해 나타남
      • 역할은 객체가 수행하는 일반적인 행위를 의미 → 클래스가 수행하는 역할은 다른 클래스와의 관계와 상호작용에서 중요
      • 추가로 인터페이스 정의도 포함됨 → 클래스가 외부에 제공하는 인터페이스를 정의 → 인터페이스는 클래스의 책임과 역할을 기반으로 구성
    • 클래스 간의 관계를 고려하여 상속, 인터페이스, 연관 등의 개념을 적절히 활용
      • 객체 지향 프로그래밍에서 유지보수성이 높은 코드를 작성할 수 있음
    • 클래스의 수정이 용이하도록 유지보수성을 고려하여 설계
      • 클래스를 수정하거나 확장할 때 다른 부분에 영향을 덜 주도록 하는 것 → 기능별로 클래스를 분리하는 것 또한 유지 보수성을 고려한 것 → ex) 계산기 클래스를 생성하고 사칙 연산에 대한 각각의 클래스도 생성한다.

객체 생성과 참조형 변수

  • 객체는 클래스를 기반으로 생성
    • 클래스는 객체를 생성하기 위한 일종의 틀
  • 객체를 사용하기 위해서는 변수를 선언하고 객체를 생성해야 한다. → 이때 변수는 해당 객체의 주소를 저장하는 참조형 변수여야 한다.
  • 예제 코드
// Car Class 생
public class Car {
    private String model;
    private String color;
    private int year;

    public Car(String model, String color, int year) {
        this.model = model;
        this.color = color;
        this.year = year;
    }

    public String getModel() {
        return model;
    }

    public String getColor() {
        return color;
    }

    public int getYear() {
        return year;
    }
}

public class Main {
    public static void main(String[] args) {
				//new 키워드를 사용하여 Car 클래스의 객체를 생성
        Car myCar = new Car("Tesla", "Red", 2022);
        System.out.println("Model: " + myCar.getModel());
        System.out.println("Color: " + myCar.getColor());
        System.out.println("Year: " + myCar.getYear());
    }
}

기본형 매개변수 & 참조형 매개변수

  • 기본형 매개변수는 메서드 호출 시 값이 복사되어 전달 → 따라서 메서드 내에서 매개변수 값을 변경하더라도 호출한 곳의 값은 변경되지 않는다.
  • 참조형 매개변수는 객체의 주소값이 전달 → 따라서 메서드 내에서 매개변수의 값을 변경하면 호출한 곳에서도 변경된 값을 확인 가능

인스턴스 멤버 & 클래스 멤버

  • 멤버 : 필드 + 메서드
  • 인스턴스 멤버는 클래스의 인스턴스(객체)를 생성할 때마다 생성
    • 인스턴스 변수는 각 객체마다 독립적으로 존재
  • 클래스 멤버는 클래스가 로딩될 때 생성
    • 클래스 변수는 모든 객체가 공유
    • 해당 클래스의 인스턴스를 생성하지 않아도 호출할 수 있다.
    • 필드와 메서드를 클래스 멤버로 만들기 위해서는 static 키워드를 사용
  • 인스턴스 메서드는 객체(인스턴스)를 특정지어 변경 또는 조회할 때 사용되고, 클래스 메서드는 클래스의 속성을 변경 또는 조회할 때 사용
  • 예제 코드
public class Rectangle {
    // 인스턴스 변수
    private int width;
    private int height;

    // 인스턴스 메서드
    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getArea() {
        return width * height;
    }

    // 클래스 메서드
    public static int getArea(int width, int height) {
        return width * height;
    }
}

public class Main {
    public static void main(String[] args) {
        // 인스턴스 메서드 사용
        Rectangle rect = new Rectangle();
        rect.setWidth(5);
        rect.setHeight(10);
        int area1 = rect.getArea(); // 50

        // 클래스 메서드 사용
        int area2 = Rectangle.getArea(5, 10); // 50
    }
}
  • 구분 해서 사용해야 할 상황
    • 멤버 변수의 공유 여부에 따라
      • 멤버 변수를 공유해야 하는 경우에는 클래스 멤버를 사용하고, 공유하지 않아야 하는 경우에는 인스턴스 멤버를 사용해야 한다.
        • 인스턴스 멤버 변수는 객체마다 고유한 값을 가지고 있으며, 객체마다 서로 다른 값을 가질 수 있다.
        • 클래스 멤버 변수는 모든 객체가 공유하는 값을 가지고 있으며, 모든 객체가 동일한 값을 가진다.
    • 멤버 메서드에서 참조해야 하는 멤버 변수에 따라
      • 멤버 메서드에서 인스턴스 멤버 변수를 참조해야 하는 경우에는 인스턴스 멤버 메서드를 사용하고, 클래스 멤버 변수만 참조해야 하는 경우에는 클래스 멤버 메서드를 사용해야 한다.
        • 인스턴스 멤버 메서드는 인스턴스 멤버 변수를 참조하고 조작할 수 있다.

        • 클래스 멤버 메서드는 인스턴스 멤버 변수를 직접 참조할 수 없으며, 클래스 멤버 변수만 참조할 수 있다.

        • 클래스 멤버 메서드는 인스턴스 멤버 변수를 직접 참조할 수 없는 예시

          public class MyClass {
            private int value;
          
            public void setValue(int newValue) {
              value = newValue;
            }
          
            public int getValue() {
              return value;
            }
          
            public static void add(int x, int y) {
              // value 변수를 참조할 수 없으므로 오류가 발생합니다.
              int sum = x + y + value; // compile error
              System.out.println("Sum: " + sum);
            }
          }

final

  • final키워드를 사용하면 해당 대상이 더 이상 변경될 수 없다는 것을 나타냄
  • final 변수 사용 예제
public class FinalExample {
    final int number = 10;

    public static void main(String[] args) {
        FinalExample example = new FinalExample();
        System.out.println(example.number); // 출력 결과: 10
        // example.number = 20; // 에러 발생, 변경할 수 없는 변수입니다.
    }
}
  • final키워드는 메서드나 클래스에도 사용될 수 있다.
    • final메서드를 정의하면 해당 메서드가 더 이상 Override될 수 없다.
    • final클래스를 정의하면 해당 클래스가 상속될 수 없다.
  • final 클래스, 메서드 사용 예제
public class FinalMethodExample {
    public final void printMessage() {
        System.out.println("Hello, World!");
    }
}

public class FinalClassExample final {
    // ...
}

printMessage메서드는 더 이상 오버라이드될 수 없고, FinalClassExample클래스는 상속 불가

생성자

  • 객체를 생성할 때 호출되는 특별한 메서드로, 객체가 생성될 때 필요한 초기화 작업을 수행하는 역할
  • 만약 클래스에 생성자를 하나도 선언하지 않았다면 컴파일러는 기본 생성자를 바이트 코드 파일에 자동으로 추가시켜 준다. → 즉, 생성자는 객체 생성 시점에 자동으로 호출되며, 객체가 생성될 때 한 번만 호출
  • this
    • this는 자기 자신 객체를 가리키는 참조 변수
    • 인스턴스 변수와 지역 변수의 이름이 같을 때, 인스턴스 변수를 가리키기 위해 this 키워드를 사용
    • this()는 같은 클래스 내의 다른 생성자를 호출하기 위해 사용 → 다른 생성자에서 공통적으로 수행되는 작업이 있다면, 해당 작업을 수행하는 생성자를 만들고 다른 생성자에서 this()를 사용하여 호출할 수 있다.
      • 예제 코드

        public class Person {
            private String name;
            private int age;
        
            public Person() {
                this("Unknown", 0);
            }
        
            public Person(String name) {
                this(name, 0);
            }
        
            public Person(int age) {
                this("Unknown", age);
            }
        
            **public Person(String name, int age) {
                this.name = name;
                this.age = age;
            }**
        
            public void printInfo() {
                System.out.println("Name: " + this.name);
                System.out.println("Age: " + this.age);
            }
        }
  • 생성자 예시 코드
public class Person {
    private String name;
    private int age;

    // 기본 생성자
    public Person() {
        this.name = "Unknown";
        this.age = 0;
    }

    // 매개변수가 있는 생성자
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter/Setter 생략
}
  • 생성자는 클래스 이름과 동일한 메서드명을 가지며, 리턴 타입을 지정하지 않는다. → 생성자는 객체를 반환하지 않는다.

접근 제어자

  • 접근 제어자는 클래스, 변수, 메서드 등의 멤버에 대한 접근을 제한하는 역할을 한다.
  • 4가지 접근 제어자가 제공
    • private : 해당 클래스 내에서만 접근이 가능
    • default(package-private) : 동일한 패키지 내에서만 접근이 가능
    • protected : 동일한 패키지 내에서는 접근 가능하며, 다른 패키지에서는 해당 클래스를 상속받은 자식 클래스에서만 접근 가능
    • public : 모든 클래스에서 접근이 가능
  • 객체 지향 프로그래밍의 캡슐화를 실현할 수 있다.
  • 생성자에서도 접근 제어자를 통해 생성자를 제한하거나 공개할 수 있다.
  • 외부에서 객체의 private 한 필드를 읽을 필요가 있을 때 Getter/Setter를 사용
  • 예제 코드
public class MyClass {
    private int privateVar;
    int defaultVar;
    protected int protectedVar;
    public int publicVar;

    private void privateMethod() {
        // privateVar 접근 가능
    }

    void defaultMethod() {
        // privateVar, defaultVar 접근 가능
    }

    protected void protectedMethod() {
        // privateVar, defaultVar, protectedVar 접근 가능
    }

    public void publicMethod() {
        // privateVar, defaultVar, protectedVar, publicVar 접근 가능
    }
}

자바 코드를 유연하게 짜는 방법

  • 인터페이스와 추상 클래스 활용하기
  • 느슨한 결합 사용하기
    • 객체 간의 의존성을 최소화하는 것

    • DI(Dependency Injection) 사용하기
      - 객체 간의 의존성을 관리하기 위한 디자인 패턴 중 하나
      - DI를 사용하면 객체 간의 의존성을 외부에서 주입하도록 하여 느슨한 결합을 유지할 수 있다.

      → 객체 간 의존성이 높은 코드

      public class Order {
          private Item item;
          private Payment payment;
      
          public Order() {
              item = new Item();
              payment = new Payment();
          }
      
          public void processOrder() {
              item.prepareItem();
              payment.makePayment();
          }
      }
      
      public class Item {
          public void prepareItem() {
              System.out.println("Preparing item for order");
          }
      }
      
      public class Payment {
          public void makePayment() {
              System.out.println("Making payment for order");
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Order order = new Order();
              order.processOrder();
          }
      }

      → 객체 간 의존성을 낮추도록 수정한 코드

      //인터페이스 사용
      public interface Item {
          void prepareItem();
      }
      
      public interface Payment {
          void makePayment();
      }
      
      public class Order {
          private Item item;
          private Payment payment;
      
          public Order(Item item, Payment payment) {
              this.item = item;
              this.payment = payment;
          }
      
          public void processOrder() {
              item.prepareItem();
              payment.makePayment();
          }
      }
      
      public class ItemImpl implements Item {
          @Override
          public void prepareItem() {
              System.out.println("Preparing item for order");
          }
      }
      
      public class PaymentImpl implements Payment {
          @Override
          public void makePayment() {
              System.out.println("Making payment for order");
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Item item = new ItemImpl(); // 자동 형변환
              Payment payment = new PaymentImpl(); // 자동 형변환
              Order order = new Order(item, payment);
              order.processOrder();
          }
      }
    • SOLID 윈칙 적용하기

      • SOLID는 객체 지향 프로그래밍에서 유지보수 가능하고 확장 가능한 소프트웨어를 만들기 위한 원칙

        1. SRP (Single Responsibility Principle) : 단일 책임 원칙
          • 하나의 클래스는 단 하나의 책임을 가져야 한다.
        2. OCP (Open-Closed Principle) : 개방-폐쇄 원칙
          • 확장에는 열려 있고, 변경에는 닫혀 있어야 한다.
        3. LSP (Liskov Substitution Principle) : 리스코프 치환 원칙
          • 자식 클래스는 부모 클래스의 역할을 대체할 수 있어야 한다.
        4. ISP (Interface Segregation Principle) : 인터페이스 분리 원칙
          • 인터페이스는 클라이언트가 필요로 하는 메서드만 포함해야 한다.
        5. DIP (Dependency Inversion Principle) : 의존 역전 원칙
          • 추상화된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다.
      • 확장성이 낮은 예시 코드

        public class Rectangle {
            private int width;
            private int height;
        
            public Rectangle(int width, int height) {
                this.width = width;
                this.height = height;
            }
        
            public int getWidth() {
                return width;
            }
        
            public void setWidth(int width) {
                this.width = width;
            }
        
            public int getHeight() {
                return height;
            }
        
            public void setHeight(int height) {
                this.height = height;
            }
        
            public int calculateArea() {
                return width * height;
            }
        }

        →만약 원의 넓이를 계산하는 메소드를 추가하려면 Rectangle클래스를 수정해야 하기 때문에, 코드의 확장성이 매우 떨어진다.

      • 확장성이 좋은 예시 코드

        //인터페이스 사용
        public interface Shape {
            double calculateArea();
        }
        
        public class Rectangle implements Shape {
            private double width;
            private double height;
        
            public Rectangle(double width, double height) {
                this.width = width;
                this.height = height;
            }
        
            @Override
            public double calculateArea() {
                return width * height;
            }
        }
        
        public class Circle implements Shape {
            private double radius;
        
            public Circle(double radius) {
                this.radius = radius;
            }
        
            @Override
            public double calculateArea() {
                return Math.PI * radius * radius;
            }
        }
        
        public class Main {
            public static void main(String[] args) {
                Shape rectangle = new Rectangle(5, 10);// 자동 형변환 사용
                Shape circle = new Circle(5); 
                System.out.println("Rectangle area: " + rectangle.calculateArea());
                System.out.println("Circle area: " + circle.calculateArea());
            }
        }

        calculateArea()메소드를 인터페이스에서 선언하고, 구현 클래스에서 각각의 방식으로 구현함으로써 코드의 확장성이 향상된다. 또한, 재사용성이 좋은 코드이기도 하다.

      • 종합 적으로 객체 지향 프로그래밍의 특징을 활용한 예시 코드

        interface Shape {
            double area();
        }
        
        class Circle implements Shape {
            private double radius;
        
            public Circle(double radius) {
                this.radius = radius;
            }
        		
        		@Override
            public double area() {
                return Math.PI * radius * radius;
            }
        }
        
        class Rectangle implements Shape {
            private double width;
            private double height;
        
            public Rectangle(double width, double height) {
                this.width = width;
                this.height = height;
            }
        		
        		@Override
            public double area() {
                return width * height;
            }
        }
        
        public class Main {
            public static void main(String[] args) {
                Shape shape1 = new Circle(5);
                Shape shape2 = new Rectangle(3, 4);
        
                System.out.println("Circle area: " + shape1.area());
                System.out.println("Rectangle area: " + shape2.area());
            }
        }
      • 객체 지향 특징을 사용하지 않은 예시 코드

        public class Main {
            public static void main(String[] args) {
                String shape = "circle";
                double radius = 5;
                double width = 3;
                double height = 4;
                double area = 0;
        
                if (shape.equals("circle")) {
                    area = Math.PI * radius * radius;
                } else if (shape.equals("rectangle")) {
                    area = width * height;
                }
        
                System.out.println(shape + " area: " + area);
            }
        }

        → 확실히 유지 보수성, 확장성, 재사용성 면에서 객체 지향 프로그래밍이 좋다.

profile
진짜 개발자가 되고 싶다

0개의 댓글