객체지향 프로그래밍

Soomin Kim·2023년 1월 30일

Android

목록 보기
2/14

OOP

OOP: 객체 지향 프로그래밍
1. 변수, 타입
2. 흐름 제어
3. 함수
4. 컬렉션
5. 클래스와 객체(상속 포함)
클래스: 데이터 타입 정의, 데이터 멤버와 메서드를 한곳에 저장, 가독성 좋은 코드

클래스

클래스: 차
properties: 최고속도, 바퀴, 사이즈, 색상 .. -> 변수
skills: drive, break .. -> 메서드

클래스: 속성과 기술을 정의해주는 청사진
클래스를 만든다 = 객체의 청사진을 만든다
객체: 생산된 차 한대

class Person constructor(_firstName: String, _lastName: String) { // or class Person constructor    (_firstName: String, _lastName: String) 
    // Member Variables (Properties) of the class
    var firstName: String
    var lastName: String
 
    // Initializer Block
    init {
        this.firstName = _firstName
        this.lastName = _lastName
 
        println("Initialized a new Person object with firstName = $firstName and lastName = $lastName")
    }
}
 
// create an object like so:
// val denis = Person("Denis", "Panjuta")
 
// Alternatively:
class Person (_firstName: String, _lastName: String) {
    // Member Variables (Properties) of the class
    var firstName: String = _firstName
    var lastName: String = _lastName
 
    // Initializer Block
    init {
        println("Initialized a new Person object with firstName = $firstName and lastName = $lastName")
    }
}
 
// Alternatively:
class Person(var firstName: String, var lastName: String) {
    // Initializer Block
    init {
        println("Initialized a new Person object with firstName = $firstName and lastName = $lastName")
    }
}
 
// Or even:
// whereby John and Doe will be default values 
class Person(var firstName: String = "John", var lastName: String= "Doe") {
    // Initializer Block
    init {
        println("Initialized a new Person object with firstName = $firstName and lastName = $lastName")
    }
}
 
// Create an object:
/*
    val john = Person()
    val johnPeterson = Person(lastname: "Peterson")
 
*/
 
class Person(var firstName: String, var lastName: String) {
    var age: Int? = null
    var hobby: String = "Watch Netflix"
    var myFirstName = firstName
 
    // Secondary Constructor
    constructor(firstName: String, lastName: String, age: Int): this(firstName, lastName)  {
        this.age = if(age > 0) age else throw IllegalArgumentException("Age must be greater than zero")
    }
 
    fun stateHobby(){
        println("$firstname \'s Hobby is: $hobby'" )
    }
}
 
// You can use primary or secondary Constructor to create an object
 
// Calls the primary constructor (Age will be null in this case)
val person1 = Person("Denis", "Panjuta")
 
// Calls the secondary constructor
val person2 = Person("Elon", "Musk", 48) 
 
 
 
// Having multiple overloads:
 
class Person(var firstName: String, var lastName: String) {
    var age: Int? = null
    var eyeColor: String? = null
    
    // Secondary Constructor
    constructor(firstName: String, lastName: String, age: Int): this(firstName, lastName)  {
        this.age = if(age > 0) age else throw IllegalArgumentException("Age must be greater than zero")
    }
 
    // Secondary Constructor
    constructor(firstName: String, lastName: String, age: Int, eyeColor: String): 
            this(firstName, lastName, age)  {
        this.eyeColor = eyeColor
    }
}
 
 
// SETTERS AND GETTERS
 
// User class with a Primary constructor that accepts
// three parameters
class Car(_brand: String, _model: String, _maxSpeed: Int) {
    // Properties of User class
    val brand: String = _brand         // Immutable (Read only)
    var model: String = _model  // Mutable
    var maxSpeed: Int = _maxSpeed       // Mutable
}
 
// Kotlin internally generates a default getter and setter for mutable properties, and a getter (only) for read-only properties.
 
It calls these getters and setters internally whenever 
// you access or modify a property using the dot(.) notation.
This is how it would look like internally
class Car(_brand: String, _model: String, _maxSpeed: Int) {
    val brand: String = _brand
        get() = field
    
    var model: String = _model
        get() = field
        set(value) {
            field = value
        }
    
    var maxSpeed: Int = _maxSpeed
        get() = field
        set(value) {
            field = value
        }
}
 
// value
// We use value as the name of the setter parameter. This is the default convention in Kotlin but you’re free to use any other name if you want.
// The value parameter contains the value that a property is assigned to. For example, when you write user.name = "Elon Musk", 
// the value parameter contains the assigned value "Elon Musk".
 
// 2. Backing Field (field)
// Backing field helps you refer to the property 
// inside the getter and setter methods. 
// This is required because if you use the property 
// directly inside the getter or setter then you’ll
// run into a recursive call which will generate 
// a StackOverflowError.
 
 
class Car() {
 
    lateinit var owner : String
 
    val myBrand: String = "BMW"
    // Custom getter
    get() {     
        return field.toLowerCase()
    }   
    
    
    // default setter and getter
    var myModel: String = "M5"
        private set
 
    var myMaxSpeed: Int = maxSpeed
        get() = field
        // Custom Setter
        set(value) {
            field = if(value > 0) value else throw IllegalArgumentException("_maxSpeed must be greater than zero")
    }
 
    init{
        this.myModel = "M3"
        this.owner = "Frank"
    }
}
fun main() {
    // soomin은 Person type의 변수
    var soomin = Person("Soomin", "Kim", 25)
    // 기본값으로 생성
    var john = Person()
    var johnPeterson = Person(lastName="Lee")
	
    // MobilePhone 객체 생성
    MobilePhone("Android", "Samsung", "Galaxy S20 Ultra")
    MobilePhone()
    MobilePhone("ios", "Apple", "iPhone 13")
    
    soomin.hobby = "drink"
    soomin.age = 26
    println("I'm ${soomin.age} years old")
    soomin.stateHobby()
    john.hobby = "play video games"
    john.stateHobby()

    var myCar = Car()
    println("brand is : ${myCar.myBrand}")
    myCar.maxSpeed = 230
    println("Maxspeed is ${myCar.maxSpeed}")


}

//constructor: 객체 생성 시, 값을 추가하게 해줌.
//변수를 만든다고도 볼 수 있음.
class Person constructor(_firstName: String = "John", _lastName: String = "Doe"){
    // Properties & Methods
    // 코드 실행하면 자동으로 실행되는 코드
    // Member Variables - Properties
    var age: Int? = null
    var hobby: String? = "watch Netflix"
    var firstName : String? = null

    init {
        this.firstName = _firstName
        println("Person created" + "firstName=$firstName and lastName=$lastName")
    }
    // Member secondary Constructor
    constructor(firstName: String, lastName: String, age: Int): this(firstName,lastName){
        // constructor에서 넘어온 매개변수 age가, 이 클래스의 변수 age에 할당된
        this.age = age
        println("Person created" + "firstName=$firstName and lastName=$lastName and age=$age")
    }
    // Member functions - Methods. 클래스 안의 함수
    fun stateHobby(){
        println("$firstName's hobby is $hobby")
    }
}

class MobilePhone constructor(osName: String = "Android", brand: String = "Samsung", model: String = "Galaxy S"){
    init{
        println("osName is $osName, brand is $brand, model is $model")
    }
}

// 함수 안에서 변수가 선언됐으면, 함수 스코프 안에서만 변수 사용 가능.
fun myFunction(a: Int) { // a는 매개변수
    // var a는 변수
    var a = a
    println("a is $a") // 여기서 print되는 a는 변수 a
}

class Car() {
    // 나중에 초기화.
    lateinit var owner: String
    val myBrand: String = "BMW"
        // Custom getter
    get(){
        return field.toLowerCase()
    }
    var maxSpeed: Int = 250
        //아래 코드들 자동 실행
    get() = field
    set(value){
        field= if(value>0) value else throw IllegalArgumentException("Max speed cannot be less than 0")
    }
    var myModel: String = "M5"
        // private: Only available in class
    private set // 이 클래스 안에서만 set을 할수있다.
    init {
        this.owner = "Frank"
    }

    //필드는 게터와 세터 메서드 안에서 프로퍼티 참조를 도와주며 뒷받침하는 필드
    // 필수임. 만약 게터나 세터 안에 프로퍼티를 입력하면 반복되는 호출이 되어 스택 오버플로우 현상이 나타날 것.
    // 그래서 위의 형태로 getter, setter 사용해야 되는 것.
    // value는 필드에 할당되는 값.
}

객체

상속

package eu.tutorials.kotlinbasics

// 프로퍼티나 메서드 같은 특성을 다른 클래스에서 상속받거나 줄이게 해줌
// 다른 클래스 속성 상속받는 클래스: Sub class or Child class or Derived class
// 다른 클래스에 속성을 상속하는 클래스: Super class Parent Class or Base Class

//open class여야 상속할 수 있음.
// 모든 클래스는 자동으로 최종값. 즉 자동으로 상속할 수 없음.
// Super Class, Parent Class, Base Class
open class Vehicle{
    //properties
    //methods
}
//
// sealed class 로 하면 상속을 막을 수 있음.
// Sub Class, Child class or Derived class of Vehicle.
// Super Class, Parent Class, Base Class of ElectricCar
open class Car2(override val maxSpeed: Double, val name: String, val brand: String): Drivable{
    open var range: Double = 0.0
    fun extendRange(amount: Double) {
        if (amount > 0) {
            range += amount
        }
    }
    // interface에서의 DRIVe
    override fun drive(): String{
        return "driving the interface drive"
    }
    open fun drive(distance: Double){
        println("Drove for $distance km")
    }
}
// Sub Class, Child class or Derived class of Car.
class ElectricCar(maxSpeed: Double, name: String, brand: String, batteryLife: Double): Car2(maxSpeed, name, brand){
    var chargerType = "Type1"
    // override range, drive functionality
    override var range = batteryLife * 6

    override fun drive(distance: Double){
        println("Drove for $distance KM on electricity")
    }

    override fun drive(): String{
        return "Drove for $range KM on electricity"
    }

    override fun brake(){
        // super: 슈퍼 클래스의 brake 함수를 호출. (자동차 클래스에서는 brake 기능을 구현안했으니까)
        super.brake()
        println("brake inside of electronic car")
    }

}
// Any class inherits from the Any Class -> 기본적인 함수들 이용할 수 있는 이
fun main(){
    var audiA3 = Car2(200.0, "A3", "Audi")
    var teslaS = ElectricCar(240.0,"S-Model", "Tesla", 85.0)
    teslaS.chargerType = "Type2"

    teslaS.drive(200.0)
    // override drive를 호출할 것.(매개변수 없으니까)
    teslaS.drive()

    // Car, ElctricCar에는 구현 안되어있지만, 인터페이스에서 상속 받으니까 사용 가능
   teslaS.brake()
    audiA3.brake()
    // Polymorphism(다형성): 비슷한 특성을 가진 객체들이 공통된 방법으로 여겨지는
    // 전기차가 자동차의 서브 클래스이고, 자동차가 인터페이스가 되어 가능
    audiA3.drive(200.0)
    //ElectricCar는 drive 메소드가 없는데도, 상속받아 쓸 수 있음.
    teslaS.extendRange(200.0)



}

//인터페이스는 클래스 기능을 확장하게 해줌.
interface Drivable{
    val maxSpeed: Double
    //함수 바디는 없고 함수 헤드만 있는 것.
    fun drive(): String
    fun brake() {
        println("The drivable is braking")
    }
    //인터페이스는 모든 프로퍼티와 함수를 구현할 수 있지만, 그럴 필요는 없음.
    //함수의 프로퍼티가 default implementation이 있으면 그 인터페이스를 이용하는 클래스가 오버라이딩 할수도 있음.

}

인터페이스

인터페이스: 기본적으로 클래스가 서명할 수도 있는 계약서
서명하면, 클래스는 인터페이스의 프로퍼티와 함수 구현을 제공할 의무를 가짐.

인터페이스는 다른 인터페이스에서 상속받을 수 있음.
즉 다른 인터페이스에서 상속 받는 고유 인터페이스를 만들 수 있음.

왜 인터페이스 사용?
나중에 구현하고 싶은 특정 함수와 클래스 프로퍼티가 있는데 바로 구현하고 싶지 않은 경우. 준비할 수 있음. 구체적인 함수 바디를 아직 안만들고 싶을 때.

추상 클래스와 인터페이스

package eu.tutorials.kotlinbasics

// Astract class는 예를 들 수 없음.(객체를 만들 수 없음)
// 하지만 Sub Class는 Abstract Class에서 상속할 수 있음.
// 추상 클래스의 멤버(프로퍼티, 메소드)는 직접 abstract 키워드를 쓰지 않는 이상, Non-abstract임.
abstract class Mammal(private val name: String, private val origin: String, private val weight: Double) { // Concrete(Non Abstract) Properties
    //Abstract property (Must be overridden by Subclasses)
    abstract var maxSpeed: Double

    // Abstract Methods (Must be implemented by Subclasses)
    // 즉 어떤 클래스가 Mammal을 상속받으면, 이 메서드를 구현해야 되는 것.
    abstract fun run()
    abstract fun breath()

    //Concrete (Non Abstract)Method
    fun displayDetails(){
        println("Name: $name, Origin: $origin, Weight: $weight, " + "Max Spped: $maxSpeed")

    }

}
//Mammal의 sub class
class Human(name: String, origin: String, weight: Double, override var maxSpeed: Double): Mammal(name, origin, weight){
    override fun run(){
        println("Runs on two legs")
    }

    override fun breath(){
        println("Breath through mouth or nose")
    }
}

fun main(){
    // 상속 받은 클래스에서는 예시를 만들 수 있음.
    val human = Human("Denis", "Russia", 70.0, 28.0)
    human.run()
    human.breath()
    // 아래는 불가능 (추상 클래스의 예시를 만들 수 없으니까)
    // val mammal = Mammal("Denis", "Russia", 70.0, 28.0)
}

인터페이스와 차이
인터페이스는 state를 저장할 수 없고, 여러 인터페이스를 구현할 수 있지만 클래스는 한개만 구현 가능. 생성자, 필드도 X

추상 클래스는 인터페이스가 추가할 수 있는건 다 할 수 있고, 추가적으로 필드와 생성자도 추가할 수 있음. state가 잘 저장될 수 있음.

Scope

데이터 클래스

상속

타입 캐스팅

package eu.tutorials.kotlinbasics

// Astract class는 예를 들 수 없음.(객체를 만들 수 없음)
// 하지만 Sub Class는 Abstract Class에서 상속할 수 있음.
// 추상 클래스의 멤버(프로퍼티, 메소드)는 직접 abstract 키워드를 쓰지 않는 이상, Non-abstract임.
abstract class Mammal(private val name: String, private val origin: String, private val weight: Double) { // Concrete(Non Abstract) Properties
    //Abstract property (Must be overridden by Subclasses)
    abstract var maxSpeed: Double

    // Abstract Methods (Must be implemented by Subclasses)
    // 즉 어떤 클래스가 Mammal을 상속받으면, 이 메서드를 구현해야 되는 것.
    abstract fun run()
    abstract fun breath()

    //Concrete (Non Abstract)Method
    fun displayDetails(){
        println("Name: $name, Origin: $origin, Weight: $weight, " + "Max Spped: $maxSpeed")

    }

}
//Mammal의 sub class
class Human(name: String, origin: String, weight: Double, override var maxSpeed: Double): Mammal(name, origin, weight){
    override fun run(){
        println("Runs on two legs")
    }

    override fun breath(){
        println("Breath through mouth or nose")
    }
}

fun main(){
    // 상속 받은 클래스에서는 예시를 만들 수 있음.
    val human = Human("Denis", "Russia", 70.0, 28.0)
    human.run()
    human.breath()
    // 아래는 불가능 (추상 클래스의 예시를 만들 수 없으니까)
    // val mammal = Mammal("Denis", "Russia", 70.0, 28.0)
}
profile
개발자지망생

0개의 댓글