OOP

8 분 소요




OOP

어플리케이션을 구성하는 요소들을 객체로 보고,
객체들을 유기적으로 연결하여 상호작용하는 방식으로 프로그래밍 하는걸 말한다.
데이터와 이를 처리하는 루틴을 하나의 독립된 객체 로 보고 기능적으로 관련된
데이터와 메서드를 묶는 것을 중심으로 한다.
재사용성, 유지보수 에 용이하고 대형 프로젝트에 적합하다.
하지만 속도가 상대적으로 느리고, 설계 시 많은 리소스가 들어간다.


  • 객체
    • 속성과 동작을 가진다.
    • 속성은 객체의 상태를 나타내고, 동작은 객체의 행동을 나타낸다.
    • 자동차 객체를 예로 들면
      • 속성 : 색상, 모델, 브랜드 등
      • 동작 : 가속, 감속, 정지 등


  • 추상화
    • 목적과 관련이 없는 부분을 제거하여 필요한 부분만을 표현하기 위한 개념


  • 캡슐화
    • 정보은닉 - 접근제어자
    • 코드의 의존성을 줄이고, 결합도를 낮추는 것
    • 필요한 부분만 외부에 노출시키고 나머지는 숨긴다.
    • 외부에서 불필요한 의존을 할 일이 없어지고 의존성이 줄어드는 만큼 유지보수도 편해진다.


  • 상속
    • 코드의 재사용성에 이점이 있지만 코드의 재사용성만을 위해 사용은 지양한다.
    • 흔히 재사용성 뿐 아니라 계층구조 같은 구조를 만들 때 사용하자


  • 다형성
    • 객체지향의 꽃이라고도 불린다.
    • 형태가 같지만 다른 기능을 하는 것을 말한다.
    • 하나의 타입에 여러 객체를 대입할 수 있는 성질이다.
      • 업캐스팅
        • 서로 다른 클래스의 인스턴스를 동일한 타입에 할당할 수 있게 한다.
      • 동적바인딩
        • 메시지를 수신했을 때 실행도리 메서드가 런타임에 결정된다.
      • 오버로딩
      • 오버라이딩





OOP 왜쓰는데?

  • 재사용성

    클래스와 상속을 사용해 이미 작성된 코드를 쉽게 재사용 할 수 있고,
    이로인해 코드의 일관성을 유지할 수 있다.


  • 코드의 구조화와 유지보수

    객체를 사용해 기능적으로 관련된 데이터와 메서드를 묶어서 관리할 수 있다.
    이로인해 코드의 일관성을 유지할 수 있고,
    새로운 기능이 추가될 때 기존 코드를 변경하지 않아도 되므로 유지보수성을 높일 수 있다.


  • 캡슐화

    객체간의 상호작용을 캡슐화 하여 데이터의 안정성을 유지할 수 있다.
    객체는 자신만의 속성과 동작을 가지고 있으며, 다른 객체와 상호작용 할때 인터페이스를 통해
    메시지를 주고받으므로 데이터가 보호되고, 객체간의 의존성을 줄일 수 있다.


  • 상속을 통한 코드 재사용

    상속이란 이미 작성된 코드를 확장하거나 변경하여 새로운 클래스를 생성하는 방식이다.
    기존 클래스를 기반으로 함으로 재사용성, 유지보수성을 높일 수 있다.


  • 다형성

    다형성이란 객체가 다양한 형태로 동작할 수 있는 방식이다.
    객체가 동일한 메시지를 받을때, 각각 다르게 동작할 수 있고 이로인해 코드를 유연하게 작성할 수 있다.


  • 현실세계와의 유사성

    현실세계를 반영하므로 코드를 보다 직관적으로 작성할 수 있다.
    예를들어, 자동차 객체를 만들때 자동차의 특징과 동작을 모델링하여 코드를 작성할 수 있다.


  • 협업

    객체는 개별적인 역할과 책임을 가지므로 여러 개발자가 동시에 작업할 수 있다.





OOP 단점

  • 복잡성

    객체지향적인 설계는 추상화를 통해 복잡성을 감추지만, 구현을 위해 많은 코드가 필요하며
    이로인해 코드의 복잡성이 증가할 수 있다.
    또한 어플리케이션이 커질수록 객체 간의 상호작용과 의존성이 많아지고 그럴수록 복잡성은 증가한다.


  • 성능

    객체를 생성하고 관리하기 위한 추가적인 오버헤드가 있으므로 성능 저하가 발생 할 수 있다.
    또한 캡슐화 때문에 데이터에 접근하기 위한 추가비용이 들어가고 이는 데이터에 직접 접근하는
    절차지향 프로그래밍 보다 느릴 수 있다.


  • 설계

    명확한 구조와 규칙을 필요로 하고 객체간의 관계를 적절히 설계해야 하는 등 설계에 많은 리소스가 들어간다.


  • 상속 남용

    상속을 남용하면 중복코드가 생기고 클래스간의 관계가 복잡해질 수 있고
    하위 클래스가 상위클래스에 의존하게 되어 하위클래스 수정이 상위클래스까지 영향을 미칠 수 있다.





OOP의 특징


OOP와 캡슐화


객체를 추상화 하고, 객체의 내부 상태와 동작을 외부로부터 보호하는것을 의미한다.
객체의 내부 구현을 외부에 숨기고, 객체와 객체간의 상호작용을 인터페이스를 통해서 해서
객체의 상태를 외부에서 변경할 수 없도록 하고, 변경에 대한 책임을 객체 스스로가 갖도록 한다.
즉, 객체의 구현 세부사항을 숨기고 객체간의 결합도를 낮추는 효과를 가져온다.
객체의 구현 세부 사항을 숨기는 것을 객체지향설계 에선 정보은닉 이라고 부른다.
상속과 다형성의 개념과도 관련이 있는데,
부모클래스의 캡슐화된 멤버 변수와 메서드를 재사용할 수 있도록 하고
캡슐화된 인터페이스를 이용해 서로 다른 객체를 동일한 방식으로 다룰 수 있도록 한다.
캡슐화는 OOP 에서 객체의 추상화, 모듈화, 재사용성 등의 장점을 실현하는데 중요한 역할을 한다.





OOP와 상속


상속이란 부모클래스가 자식클래스에게 자신의 속성과 행위들을 물려주는 것을 의미한다.
이를 통해 자식클래스는 부모클래스의 기능을 그대로 물려받고, 새로운 속성과 행위를 추가할 수 있다.
부모 클래스에서 구현한 코드를 자식클래스에서 사용할 수 있으므로 코드의 재사용성이 높아지며
부모 클래스의 코드를 수정하면 자식클래스에서도 이를 반영하므로 유지보수가 용이해 진다.
자식클래스는 오버라이딩 를 통해 부모클래스의 메서드를 재정의 하여 사용할 수 있고 확장성이 좋아진다.

하지만 상속을 남용할 경우, 클래스 간의 복잡한 계층구조가 형성되고 복잡해 지며 유지보수가 어려워 진다.
또한, 결합도가 높아지기 때문에 부모클래스의 변경이 자식클래스에게도 영향을 미치게 된다.


상속을 사용할 때

  • 부모클래스의 기능을 그대로 사용하면서 새로운 기능을 추가하려는 경우
  • 부모와 자식클래스 간의 상속관계를 통해 다형성을 구현하려는 경우
  • 기존 클래스를 수정하지 않고 확장하려는 경우
  • 공통적인 기능이 있는 클래스들을 만들 때


상속을 사용하지 않을 때

  • 계층구조가 복잡할 때
  • 결합도가 높아질 때
  • 상속구조가 고정되어 있을때
    • 이 경우 인터페이스를 이용해 클래스간의 관계를 정의하는 편이 더 나을 수 있다.


합성(Composition)?

합성은 객체를 조합해 새로운 객체를 만드는 방법이다. 합성을 이용하면 코드의 재사용성을 높이면서
클래스간의 결합도를 낮출 수 있다. 다른 클래스의 인스턴스를 생성하여 이를 이용하는 방법인데
상위클래스와 하위클래스 간의 강한 결합을 피할 수 있다.
상속은 부모와 자식간의 강한 계층구조를 가지지만 합성은 클래스 간의 관계를 느슨하게 유지한다.
클래스간의 관계를 조정하여 필요한 기능만을 조합하여 사용하기 때문에 클래스간의 결합도가 낮아진다.
예를들어 자동차 클래스에서 엔진, 타이어, 핸들 클래스를 각각 상속받는게 아니고, 자동차 클래스에서
엔진, 타이어, 핸들 클래스의 인스턴스를 생성하여 이를 이용하는 방법이다.
이를통해 자동차, 엔진, 타이어, 핸들 클래스 간의 결합도를 낮출 수 있다.

상속보단 합성?

  • 객체간의 결합도가 낮은 경우 : 객체가 변경되도 다른객체에 영향을 주지 않는다.
  • 변경이 상위 객체에 영향을 주지 않는 경우 : 객체의 변경에 유연성을 가진다.
class Engine {
    void start() {
        System.out.println("Engine started");
    }
}

class Car {
    private Engine engine;

    Car (Engine engine) {
        this.engine = engine;
    }
}

public class Main {
    public static void main(String[] args) {
        Engine engine = new Engine();
        Car car = new Car(engine);
        car.start();
    }
}



서브클래싱


상속을 통해 부모 클래스의 특성을 상속받아 새로운 클래스를 만드는 것을 말한다.
자식클래스는 부모클래스로부터 상속받은 속성과 메서드를 사용할 수 있고,
상속받은 기능을 추가하거나 변경할 수 있다.
즉, 부모클래스의 기능을 재사용하고, 그 기능을 수정하거나 확장할 수 있다.

  • 장점
    • 부모클래스에 구현된 코드로 재사용성을 높임
    • 서브클래싱으로 같은 인터페이스를 갖는 클래스를 여러개 만들어 다형성을 지원
  • 단점
    • 부모 자식 클래스 간의 강한 결합으로 인한 유지보수의 어려움
    • 부모클래스의 모든 기능을 상속받기때문에 불필요한 기능 또는 데이터까지 상속받아 객체가 거대해 짐
    • 서브클래스가 많아 질 수록 클래스간의 계층이 복잡해져서 의존성이 높아지고 가독성과 수정이 어려워짐



서브타이핑


상속을 통해 부모클래스와 자식클래스 간의 포함 관계를 정의하는 것을 말한다.
서브타이핑을 이용하면 자식클래스는 부모클래스의 모든 속성과 메서드를 상속받아 사용할 수 있다.
또한 자식클래스는 상속받은 특성을 그대로 사용할 수도, 재정의하거나 새로운 메서드를 추가할 수 있다.
서브타이핑을 통해 부모와 자식클래스 간의 포함 관계를 정의하고, 부모클래스 타입의 변수에 자식클래스
객체를 할당하여 다형성을 구현하는 다형성의 핵심 개념중 하나이다.
하지만 부모 자식간의 밀접한 결합이 발생하고, 서브타입 다형성을 사용하면 객체가 어떤 메서드를 호출할지
런타임에 타입체크를 수행해야 하므로 불필요한 오버헤드가 발생할 수 있다.



둘의 차이 및 정리


둘다 객체지향 프로그래밍에서 다형성을 구현하기 위해 사용되는 개념이다.

서브클래싱 은 부모 클래스에서 상속받은 메서드와 속성을 그대로 사용하면서, 자식클래스에서는
새로운 기능을 추가하거나 변경하는걸 의미한다. 이는 코드의 재사용성과 확장성을 높이는 역할을 한다.

서브타이핑 은 부모클래스의 하위타입 인 자식클래스를 생성하고, 이를통해 객체의 다형성을 구현하는 것이다.
즉, 부모클래스의 인스턴스를 자식클래스의 인스턴스로 대체 할 수 있도록 구현하는 것이다.

둘의 가장 큰 차이점은 상속에 대한 의미이다.

서브클래싱 은 부모 클래스로부터 상속받은 기능을 그대로 사용하면서 기능을 변경하거나 확장하는 것이고,
클래스의 계층구조를 만들어 코드의 재사용성과 확장성을 높이는데 주로 사용된다.

서브타이핑 은 부모 자식 클래스 간의 포함관계를 만들어 객체의 다형성을 구현하는것이 목표이다.
인터페이스와 추상클래스를 사용하여 다형성을 구현하는데 주로 사용된다.

/*
서브클래싱
Dog 클래스는 Animal 클래스를 상속받아 eat() 메서드를 사용하면서,
새로운 기능인 bark() 메서드를 추가해서 사용한다.
*/
class Animal {
    void eat() {
        System.out.println("The animal eats.");
    }
}

class Dog extends Animal {
    void bark() {
        System.out.println("The dog barks.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();
        dog.bark();
    }
}

interface Animal {
    void eat();
}

/*
서브타이핑
Animal 인터페이스를 구현한 Dog 클래스에서 eat() 메서드를 구현하고, bark() 메서드를 추가한다.
그리고 Animal 인터페이스를 구현한 Dog 클래스를 생성해 feedAnimal() 메서드에 전달한다.
feedAnimal() 메서드는 Animal 인터페이스 타입의 매개변수를 받기 때문에,
Dog 클래스의 객체를 Animal 인터페이스로 대체할 수 있게 된다.
*/
class Dog implements Animal {
    void eat() {
        System.out.println("The dog eats.");
    }
    
    void bark() {
        System.out.println("The dog barks.");
    }
}

public class Main {
    static void feedAnimal(Animal animal) {
        animal.eat();
    }
    
    public static void main(String[] args) {
        Animal dog = new Dog();
        feedAnimal(dog);
    }
}





OOP와 추상화


추상화 는 객체를 설계하는 과정에서 필요한 정보만 선택하고 나머지는 숨겨서 단순하게 만드는걸 말한다.
캡슐화 의 정의와 비슷하지만 캡슐화 는 객체의 상태와 행동을 외부로부터 보호하고 객체 간 결합도를 낮추는데
목적이 있다면, 추상화 는 객체를 단순화 하여 코드의 가독성과 유지보수성을 높이는데에 목적이 있다.
캡슐화 에선 접근제어자를 사용하지만, 추상화 에선 인터페이스와 추상클래스를 사용한다.

  • interface
    • 추상메서드와 상수만을 가질 수 있다.
    • 인터페이스를 구현하는 클래스에선 모든 메서드를 구현해야 한다.
    • 다중 상속 지원
    • 인터페이스 타입으로 변수 선언 후, 변수에 다양한 구현 객체를 대입할 수 있다.

    • 장점
      • 다중상속을 지원해 다양한 클래스 간의 유연한 상호작용 가능
      • 구현객체간의 관계를 약화시켜 코드의 결합도를 낮출 수 있음
    • 단점
      • 인터페이스를 구현하는 클래스에서 모든 메서드를 구현해야 하므로, 코드의 양이 증가
// 여러개의 클래스에서 같은 인터페이스를 구현하면, 해당 인터페이스를 모든 코드에서 동일하 게 다룰 수 있다. 즉, 다형성
public interface Vehicle {
    void start();
    void stop();
}

public class Car implements Vehicle {
    public void start() {
        System.out.println("Car start");
    }
    
    public void stop() {
        System.out.println("Car stopp");
    }
}

public class Motorcycle implements Vehicle {
    public void start() {
        System.out.println("Motorcycle start");
    }
    
    public void stop() {
        System.out.println("Motorcycle stopp");
    }
}

// 인터페이스 타입으로 변수를 선언하고, 이 변수에 다양한 구현객체를 대입할 수 있다. 이를 통해 코드의 유연성을 높일 수 있다.
Vehicle car = new Car();
car.start();
car.stop();

Vehicle motorcycle = new Motorcycle();
motorcycle.start();
motorcycle.stop();
  • abstract class
    • 추상메서드와 일반 메서드도 가질 수 있다.
    • 추상클래스를 구현하는 클래스에선 추상 메서드를 구현해야 한다.
    • 단일 상속만 지원
    • 인스턴스 생성 불가
    • 추상클래스 타입으로 변수 선언 후, 변수에 다양한 구현 객체를 대입할 수 있다.

    • 장점
      • 공통된 코드를 추상클래스에 구현하여 재사용성을 높일 수 있음
      • 일반메서드를 포함할 수 있으므로, 공통된 기능을 쉽게 구현할 수 있음
    • 단점
      • 단일상속만을 지원해서, 기능확장에 한계가 있음
public abstract class Vehicle {
    public abstract void start();
    public abstract void stop();
}

public class Car extends Vehicle {
    public void start() {
        // 자동차 악셀
    }
    
    public void stop() {
        // 자동차 브레이크
    }
}

// 추상클래스 타입으로 변수를 선언하고, 이 변수에 다양한 구현객체를 대입할 수 있다.이를 통해 코드의 유연성을 높일 수 있다.
Vehicle car = new Car();
Vehicle motorcycle = new Motorcycle();





OOP와 다형성


다형성 이란 객체가 동일한 메시지를 받았을 경우, 각각의 객체가 다르게 반응하도록 하는걸 말한다.
즉, 객체들이 동일한 메서드를 호출할 때, 이 메서드가 각 클래스에 따라 다르게 동작하는 것이다.
이러면 하나의 메서드가 다양한 형태로 동작하므로 재사용성과 유지보수성이 증가한다.
여러개의 클래스가 하나의 인터페이스를 구현할 수 있고, 인터페이스에 선언된 메서드는 각 클래스에서
오버라이딩 되어 동작하므로, 이 인터페이스의 메서드를 호출하는 코드는 각 클래스에 따라 동적으로 변하게 된다.

하지만, 다형성 은 객체의 실제 타입을 런타임에 결정하기 때문에, 컴파일러가 정적으로 최적화 할 수 없어
성능저하를 가져올 수 있고 런타임 시에 오류가 발생할 수 있다.
또한 남발되면 객체 생성이 많아지고 코드의 가독성이 떨어지고 디버깅이 힘들어 지게 된다.





OOP와 Getter / Setter

Getter / Setter캡슐화 의 원칙을 따르는 메서드 이다.
클래스의 인스턴스 변수에 직접 접근하는 걸 막고 클래스 내부에서 제어를 통해 인스턴스 변수에 접근하도록 한다.
하지만 과도하게 사용되면 변수에 직접 접근하지 않아도 간접적으로 접근하므로 캡슐화의 장점을 없애게 된다.

인스턴스 변수에 직접 접근하는것과 Getter / Setter 를 통해 접근하는것과 뭐가 다르냐고 생각할 수 있지만
인스턴스 변수가 public 으로 열려있으면 어디에서든지 접근할 수 있어서 캡슐화에 원칙에 완전히 위배되지만

Getter / Setter 를 통해 간접적으로 접근을 할 경우 적절한 제어를 통해 캡슐화를 유지할 수 있다.
여기서 적절한 제어란, Setter 에서 유효성 검사를 통해, 잘못된 값이 들어오는 걸 방지하는걸 말한다.






댓글남기기