본문 바로가기
안드로이드/Kotlin

[Kotlin] 객체 지향 프로그래밍(Object-Oriented Programming)

by jinwo_o 2024. 12. 8.

프로그래밍 방식

1. 명령형 프로그래밍(Imperative Programming, IP)

  • 프로그래밍의 상태와 상태를 변경하는 구문의 관점에서 연산을 설명하는 방식
  • 무엇(What)을 ‘어떻게(How)’ 할 것인지에 집중한다.
  • 1-1. 절차지향 프로그래밍(Procedural Programming, PP) : 문제를 순차적으로 처리하여 프로그램을 만드는 방식 (C, C++)
    • 절차지향 프로그래밍은 함수를 구조화할 뿐 데이터 자체를 구조화하지 못하기 때문에, 소프트웨어의 규모가 커지거나 변화가 생기면 각각의 함수에 의존하는 부분을 매번 고쳐야 하는 등 작업이 복잡해져 스파게티 코드를 유지보수하는 데 어려움을 겪는다.
  • 1-2. 객체지향 프로그래밍(Object-Oriented Programming, OOP) : 객체들 간의 상호작용을 통해 프로그램을 만드는 방식 (C++, Java, C#)

 

2. 선언형 프로그래밍Declarative Programming, DP)

  • 프로그램이 무엇과 같은 지를 설명하는 방식
  • 무엇(What)을’ 할 것인지에 집중한다.
  • 2-1. 함수형 프로그래밍(Functional Programming, FP) : 수학적 함수의 계산을 통해 자료를 처리하고 함수를 조합하여 프로그램을 만드는 방식 (Kotlin)
 

[Kotlin] 함수형 프로그래밍(Functional Programming), 람다(lambda)

프로그래밍 방식1. 명령형 프로그래밍(Imperative Programming, IP)프로그래밍의 상태와 상태를 변경하는 구문의 관점에서 연산을 설명하는 방식무엇(What)을 ‘어떻게(How)’ 할 것인지에 집중한다.1-1.

dev-baik.tistory.com


객체 지향 프로그래밍

  • 객체들이 서로 유기적으로 협력하고 결합하여 문제를 해결하는 컴퓨터 프로그래밍의 패러다임
  • 프로그램을 객체들의 집합으로 구성하는 방법
  • 데이터와 해당 데이터를 조작하는 함수를 객체 단위로 묶는다.
  • 각각의 객체는 데이터와 함께 관련된 동작을 가지고 있고, 서로 상호작용하며 프로그램을 구성한다.
  • 이때 객체의 속성(데이터)과 동작(메서드)을 정의하는 단위를 클래스(Class)라고 부른다.
  • 장점각 객체가 독립적인 역할을 가지기 때문에 코드의 변경을 최소화하고 유지보수를 하는 데 유리하다. 더 나아가, 코드의 재사용을 통해 반복적인 코드를 최소화하고, 코드를 최대한 간결하게 표현할 수 있다.

 

객체(Object)

  • 사전적 정의 : 인식 가능한 물체/물건. 객체들은 각자의 고유한 속성과 동작을 갖고 있다.
  • 소프트웨어 관점 : 서로 연관있는 변수(속성, property)들을 묶어놓은 데이터 덩어리
  • 상태(필드)와 행동(메소드)로 구성된다.
  • vs 인스턴스 : 클래스에 의해 생성된 실체로, 클래스의 정의를 바탕으로 실제 메모리 상에 할당된 객체를 의미한다.

객체 지향 프로그래밍의 4가지 특징

1. 다형성 (polymorphism)

  • 어떤 객체의 속성이나 기능이 상황에 따라 여러 가지 형태를 가질 수 있는 성질
    • 즉, 어떤 객체의 속성이나 기능이 그 맥락에 따라 다른 역할을 수행할 수 있는 객체 지향의 특성
  • 한 타입의 참조변수를 통해 여러 타입의 객체를 참조할 수 있도록 만든 것
    • 즉, 상위 클래스 타입의 참조변수로 하위 클래스의 객체를 참조할 수 있도록 하는 것
  • 동일한 인터페이스를 쓰면서도 객체의 타입에 따라 여러 다른 기능을 제공한다.

 

BEFORE

  • 문제점 : Driver 클래스와 다른 두 개의 클래스가 서로 직접적인 의존 관계(의존성)를 가지고 있다.
    • 이러한 상황을 '객체들 간의 결합도가 높다'고 표현한다.
class Driver {
    fun drive(car: Car) {
        car.moveForward()
        car.moveBackward()
    }
    
    fun drive(motorBike: MotorBike) {
        motorBike.moveForward()
        motorBike.moveBackward()
    }
}

fun main() {
    val car = Car()
    val motorBike = MotorBike()
    val driver = Driver()
    
    driver.drive(car)
    driver.drive(motorBike)
}

 

AFTER

  • 해결책 : 역할과 구현을 구분하여 객체들 간의 직접적인 결합을 피하고, 느슨한 관계 설정을 통해 보다 유연하고 변경이 용이한 프로그램을 설계해야 한다.
interface Vehicle {
    fun moveForward()
    fun moveBackward()
}

class Car : Vehicle {
    override fun moveForward() {
        println("자동차가 앞으로 전진한다")
    }

    override fun moveBackward() {
        println("자동차가 뒤로 후진한다")
    }
}

class MotorBike : Vehicle {
    override fun moveForward() {
        println("오토바이가 앞으로 전진하다")
    }

    override fun moveBackward() {
        println("오토바이가 뒤로 후진한다")
    }
}

class Driver {
    fun drive(vehicle: Vehicle) {
        vehicle.moveForward()
        vehicle.moveBackward()
    }
}

fun main() {
    val car: Vehicle = Car() // 높은 결합도
    val motorBike: Vehicle = MotorBike() // 높은 결합도
    val driver = Driver()
    
    // 자동차가 앞으로 전진한다
    // 자동차가 뒤로 후진한다
    driver.drive(car) 

    // 오토바이가 앞으로 전진하다  
    // 오토바이가 뒤로 후진한다
    driver.drive(motorBike) 
}

 

  • 결과 : 각 클래스의 내부 변경이나 다른 객체의 교체에 신경 쓰지 않고, 인터페이스에만 의존하여 수정이 필요할 때마다 코드 변경을 최소화할 수 있다.
    • 그러나 여전히 실행 클래스의 코드에서 객체를 생성할 때, Vehicle 객체가 Car 과 MotorBike 객체에 직접적으로 의존하고 있어서, 해당 객체를 다른 객체로 변경할 시 코드의 변경이 불가피하다.
      • 이 문제를 해결하기 위해 등장한 것이 바로 의존관계 주입(dependency injection) 이다.

 

결합도

 

[CS] 응집도(Cohesion)와 결합도(Coupling)

모듈(Module)크기와 상관없이 클래스나 패키지, 라이브러리 등 프로그램을 구성하는 임의의 요소프로그램을 구성하는 시스템을 기능 단위로 독립적인 부분으로 분리한 것단순히 규모가 큰 것을

dev-baik.tistory.com

 

2. 캡슐화 (Encapsulation)

  • 클래스 안에 서로 연관 있는 속성과 기능들을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것
    • 서로 관련 있는 데이터와 이를 처리할 수 있는 기능들을 한 곳에 모아 관리하는 것
  • 데이터 보호(data protection) : 외부로부터 클래스에 정의된 속성과 기능들을 보호
  • 데이터 은닉(data hiding) : 내부의 동작을 감추고 외부에는 필요한 부분만 노출
  • 외부로부터 클래스에 정의된 속성과 기능들을 보호하고, 필요한 부분만 외부로 노출될 수 있도록 하여 각 객체 고유의 독립성과 책임 영역을 안전하게 지키고자 하는 목적

 

해당 클래스나 멤버들을 외부에서 접근하지 못하도록 접근을 제한하는 방법

  • 1. 접근제어자(가시성 변경자(visibility modifier))
    • 코드 기반에 있는 선언에 대한 클래스 외부 접근을 제어한다.
    • 어떤 클래스의 구현에 대한 접근을 제한함으로써 그 클래스에 의존하는 외부 코드를 깨지 않고도 클래스 내부 구현을 변경할 수 있다.
      • 클래스 내부의 세부 구현을 숨겨서 외부 코드가 클래스의 내부에 의존하지 않도록 하는 것
    • public(공개) : 멤버를 어디서나 볼 수 있다.
    • internal(모듈 내부) : 멤버를 동일 컴파일 모듈 내에서만 볼 수 있다.
    • protected(보호) : 멤버를 해당 클래스와 그 하위 클래스 안에서만 볼 수 있다.
    • private(비공개) : 멤버를 해당 클래스 내부에서만 볼 수 있다.
변경자 클래스 멤버 최상위 선언
public(기본 가시성) 모든 곳에서 볼 수 있다. 모든 곳에서 볼 수 있다.
internal 같은 모듈 안에서만 볼 수 있다. 같은 모듈 안에서만 볼 수 있다.
protected 하위 클래스 안에서만 볼 수 있다. (최상위 선언에 적용할 수 없음)
private 하위 클래스 안에서만 볼 수 있다. 같은 파일 안에서만 볼 수 있다.
package package1

open class SuperClass {
    private val a = 1
    protected val b = 2
    internal val c = 3
    public val d = 4

    open fun printEach() {
        println(a)
        println(b)
        println(c)
        println(d)
    }
}

fun main() {
    val superClass = SuperClass()

    // private 멤버에 접근 불가능(에러)
    println(superClass.a)
    // protected 멤버에 접근 불가능(에러)
    println(superClass.b)
    println(superClass.c) // 3
    println(superClass.d) // 4
}
package package2

import package1.SuperClass

class SubClass : SuperClass() {
    override fun printEach() {
        // private 멤버에 접근 불가능(에러)
        println(a)
        println(b)
        println(c)
        println(d)
    }
}

fun main() {
    val parent = SuperClass()

    // private 멤버에 접근 불가능(에러)
    println(parent.a)
    // protected 멤버에 접근 불가능(에러)
    println(parent.b)
    println(parent.c) // 3
    println(parent.d) // 4
}
  • 2. getter/setter 메서드

 

BEFORE

  • 문제점 : Drvier 클래스가 Car 클래스의 세부적인 내부 로직을 알고 있다. 즉, 객체 간의 결합도가 높다.
class Car(private var name: String, private var color: String) {
    fun startEngine() {
        println("시동을 건다")
    }

    fun moveForward() {
        println("자동차가 앞으로 전진한다")
    }

    fun openWindow() {
        println("모든 창문을 연다")
    }
}

class Driver(private val name: String, private val car: Car) {
    fun drive() {
        car.startEngine()
        car.moveForward()
        car.openWindow()
    }
}

fun main() {
    val car = Car("테슬라 모델X", "레드")
    val driver = Driver("김코딩", car)

    // 시동을 건다
    // 자동차가 앞으로 전진한다
    // 모든 창문을 연다
    driver.drive()
}

 

AFTER

  • 해결책 : Car 클래스와 관련된 기능들은 온전히 Car 클래스에서만 관리되도록 하고, 불필요한 내부 동작의 노출을 최소화한다.
class Car(private var name: String, private var color: String) {
    private fun startEngine() {
        println("시동을 건다")
    }

    private fun moveForward() {
        println("자동차가 앞으로 전진한다")
    }

    private fun openWindow() {
        println("모든 창문을 연다")
    }

    fun operate() {
        startEngine()
        moveForward()
        openWindow()
    }
}

class Driver(private val name: String, private val car: Car) {
    fun getName() {
        return name
    }

    fun drive() {
        car.operate()
    }
}

fun main() {
    val car = Car("테슬라 모델X", "레드")
    val driver = Driver("김코딩", car)

    // 시동을 건다
    // 자동차가 앞으로 전진한다
    // 모든 창문을 연다
    driver.drive()
}

 

3. 추상화 (Abstration)

  • 사전적 의미 : 사물이나 표상을 어떤 성질, 공통성, 본질에 착안하여 그것을 추출하여 파악하는 것
    • 불필요한 세부 사항들은 제거하고 가장 본질적이고 공통적인 부분만을 추출하여 표현하는 것
    • 예를 들어, 서울의 지하철 노선도는 서울의 지리를 추상화시켜서 보여준다.
  • 소프트웨어 관점 : 객체의 공통적인 속성과 기능을 추출하여 정의하는 것
  • 추상 클래스와 인터페이스를 통해 어떤 객체가 수행해야 하는 핵심적인 역할만을 규정해 두고, 실제적인 구현은 해당 인터페이스를 구현하는 각각의 객체들에서 하도록 프로그램을 설계한다.
// 자동차와 오토바이의 공통적인 기능 추출하여 이동 수단 인터페이스에 정의
interface Vehicle {
    abstract fun start() // abstract 키워드 생략 가능
    fun moveForward()
    fun moveBackward()
}

class Car : Vehicle {
    override fun start() {
        println("자동차가 시동을 건다")
    }

    override fun moveForward() {
        println("자동차가 앞으로 전진한다")
    }

    override fun moveBackward() {
        println("자동차가 뒤로 후진한다")
    }
}

class MotorBike : Vehicle {
    override fun start() {
        println("오토바이가 시동을 건다")
    }

    override fun moveForward() {
        println("오토바이가 앞으로 전진한다")
    }

    override fun moveBackward() {
        println("오토바이가 뒤로 후진한다")
    }
}

fun main() {
    val car = Car()
    car.start() // 자동차가 시동을 건다
    car.moveForward() // 자동차가 앞으로 전진한다
    car.moveBackward() // 자동차가 뒤로 후진한다

    val motorBike = MotorBike()
    motorBike.start() // 오토바이가 시동을 건다
    motorBike.moveForward() // 오토바이가 앞으로 전진한다
    motorBike.moveBackward() // 오토바이가 뒤로 후진한다
}

 

abstract class vs interface

 

[Kotlin] 추상 클래스(abstract class), 인터페이스(interface)

추상 클래스(abstract class)클래스 : 구체적으로 데이터를 담아 인스턴스화하여 직접 다루는 클래스구체적이지 않은 추상적인 데이터를 담고 있는 클래스하위 클래스들의 공통점을 모아 추상화하

dev-baik.tistory.com

 

4. 상속 (Inheritance)

  • 하나의 자식 클래스가 상위에 있는 부모 클래스의 속성과 동작을 물려받는 개념
  • 기존 클래스를 확장하거나 변경하지 않고도 새 클래스를 쉽게 정의할 수 있어 코드 재사용성이 높아지고, 클래스 간 계층 관계를 구조적으로 형성할 수 있다.

 

BEFORE

class Car {
    var model = ""
    var color = ""
    var wheels = 0

    // Car 클래스 고유의 속성
    var isConvertible = false

    fun moveForward() {
        println("앞으로 전진한다")
    }

    fun moveBackward() {
        println("뒤로 후진한다")
    }

    // Car 클래스 공유의 기능
    fun openWindow() {
        println("모든 창문을 연다")
    }
}
class MotorBike {
    var model = ""
    var color = ""
    var wheels = 0

    // MotorBike 클래스 고유의 속성
    var isRaceable = false

    fun moveForward() {
        println("앞으로 전진한다")
    }

    fun moveBackward() {
        println("뒤로 후진한다")
    }

    // MotorBike 클래스 공유의 기능
    fun stunt() {
        println("오토바이로 묘기를 부린다")
    }
}

 

AFTER

open class Vehicle() {
    var model = ""
    var color = ""
    var wheels = 0

    open fun moveForward() {
        println("전진한다")
    }

    fun moveBackward() {
        println("후진한다")
    }
}

class Car() : Vehicle() {
    var isConvertible = false

    fun openWindow() {
        println("모든 창문을 연다")
    }
}

class MotorBike : Vehicle() {
    var isRaceable = false

    // 메소드 오버라이딩을 사용하여 상위 클래스의 기능을 재정의
    override fun moveForward() {
        println("오토바이가 앞으로 전진한다")
    }

    fun stunt() {
        println("오토바이가 묘기를 부린다")
    }
}

fun main() {
    val car = Car()
    val motorBike = MotorBike()

    car.model = "테슬라"
    car.color = "빨강색"

    println(car.color) // 빨강색
    println(car.model) // 테슬라

    car.moveForward() // 전진한다
    motorBike.moveForward() // 오토바이가 앞으로 전진한다

    car.openWindow() // 모든 창문을 연다
    motorBike.stunt() // 오토바이가 묘기를 부린다
}

 

오버로딩

  • 같은 이름의 메소드를 매개변수의 타입이나 개수를 다르게 하여 여러 번 정의하는 것
  • 같은 이름의 메소드가 다양한 입력에 대응할 수 있다.

 

오버라이딩

  • 상속받은 메소드의 내용을 자식 클래스에서 변경하는 것
  • 부모 클래스의 메소드를 자식 클래스에 맞게 재정의할 수 있다.

 

객체 지향 프로그래밍의 4가지 특징ㅣ추상화, 상속, 다형성, 캡슐화 -

객체 지향 프로그래밍은 객체의 유기적인 협력과 결합으로 파악하고자 하는 컴퓨터 프로그래밍의 패러다임을 의미합니다. 객체 지향 프로그래밍의 기본적인 개념과 그 설계를 바르게 하기 위

www.codestates.com

 

[INFCON Tech Series #3] 한눈에 보는 객체지향 프로그래밍 & 함수형 프로그래밍 - 인프런 | 스토리

객체? 순수함수? 개발자의 선택은! #OOP #FP #개발패러다임 #INFCON #인프콘 [사진] 개발자들이 함께 모여 서로의 경험과 인사이트를 나누는 축제, 인프콘! 8월 15일, 드디어 많은 분들이 기다려 주

www.inflearn.com

 

Kotlin In Action 4장

클래스와 인터페이스. 뻔하지 않은 생성자와 프로퍼티. 데이터 클래스. 클래스 위임. object 키워드 사용. 코틀린 인터페이스 안에는 추상 메서드뿐 아니라 구현이 있는 메소드도 정의할 수 있다.

velog.io

 

상속, 오버로딩, 오버라이딩의 개념과 차이점

상속, 오버로딩, 오버라이딩의 개념과 차이점에 대한 설명입니다.

f-lab.kr