본문 바로가기
Language/코틀린

[Kotlin] Fast campus 강의 안드로이드 앱 개발 코틀린 / 클래스(class), 객체지향(OOP), 다형성 (polymorphism)

by javapp 자바앱 2021. 10. 18.
728x90

안드로이드 앱 개발 코틀린편. 객체지향 에서는

 

클래스의 기본

다형성

Abstract 추상 , Interface

DataClass

Object

에 대해서 다룰 것이다.

 

 

 

 

객체지향프로그래밍에서 interface와 abstract 를 이용한

다형성 프로그래밍이 중요하다.

 

 

 

다형성 구성, Abstract Coupling

2021.10.02 - [소프트웨어공학/디자인 패턴] - [디자인 패턴] 설계 원칙

 

[디자인 패턴] 설계 원칙

 설계 원칙 기본 원칙들 Encapsulation, Abstraction Inheritance, Polymorphism Association/Aggregation/Composition 객체 : 상태와 행동으로 나타낸다. 객체지향 현실 세계의 개념(concepts or things)을..

javapp.tistory.com

 

 

 

디자인 패턴을 알고 간다면 더 좋은 효율적인 코드를 작성할 수 있을 것이다.

 

 


 

 

Class

 

 

간단한 코틀린 클래스

class Test1
{
    var name : String = "Test1"
    var age : Int = 10

    // 세컨더리 생성자 (Secondary constructor)
    constructor(){
        println(this.toString())
    }
    
    constructor(name:String, age:Int)
    {
        this.name = name
        this.age = age
    }

    fun showInfo() = println("${name} : ${age}")
}

Test1 : 10

 

용어

클래스의 변수 : 프로퍼티 (Property)

클래스의 함수 : 메소드 (Method)

 

 

java와 비교했을 때 생성자 부분이 많이 달라진다.

public class Test1 
{
    String name = "Test1";
    int age = 10;

    public Test1() {
        System.out.println(this.toString());
    }

    public Test1(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    void showinfo(){
        System.out.println(name + " : "+ age);
    }
}

 

 

생성자를 클래스명() 으로 처리하는 클래스
클래스에서 다른 생성자를 정의하려면
contructor 뒤에 : this(정의한 값)을 덛붙인다.

class Test2(name:String)	// 프라이머리 생성자
{
    var name : String = "test2"
    var age : Int = 20

    init{ // 기본 생성자
        this.name = name
    }

    constructor(name : String ,age : Int): this(name)	// 세컨더리 생성자, Test2(name)
    {
        this.name = name
        this.age = age
    }

    fun showInfo() = println("${name} : ${age}")
}

프라이머리 생성자  : class Test2(name : String)

기본 생성자          : init{ .. }

세컨더리 생성자    : constructor(name : String , age : int) : this(name)

 

 

 

 

상속을 위한 클래스 정의

open class ParentClass (msg : String)
{
    var message = msg

    fun sayHello() = println(message)
}

class ChildClass (msg : String) : ParentClass(msg) // -> 부모 생성자 호출
{}

 

java 버전

class SuperClass
{
    String message;

    SuperClass(String msg){
        message = msg;
    }

    void sayHello(){
        System.out.println(message);
    }
}

public class ChildClass extends SuperClass
{
    ChildClass(String msg) {
        super(msg);
    }
}

 

 


 

 

다형성

 

 

 

 

코틀린 에서의 오버로딩과 오버라이딩을 다룰 것이다.

  • 상위 클래스에서 메소드와 필드를 open으로 정의
  • 상속받은 클래스에서 override로 정의한다.
  • 오버로딩은 자바와 같다. 같은 이름의 다른 파라미터를 받는 메소드를 정의
  • 코틀린은 연산자 오버로딩을 제공한다.

 

open class BaseClass 
{
    open var name = "base"
    open fun f1() = println(this.toString())

    private fun onlyMyFunc() = println("BaseClass 내부에서만 사용")

    constructor()
    {
        onlyMyFunc()
    }
}

상속가능한 클래스를 구별하기 위해 open을 쓰는 듯

필드도 open으로 정의

 

 

class ChildClass : BaseClass()
{
	// 오버라이드 , 재정의
    override var name="child"
    
    override fun f1() = println(this.toString()+" 를 재정의함")

    // 오버로딩
    fun f2() = println("f2")
    fun f2(s : String)  = println("f2 : $s")
}

코틀린에서의 상속

class ChildClass : BaseClass()

 

오버라이드 할때 override 로 정의

 

 

    var obj1 = BaseClass()
    obj1.f1()

    var obj2 = ChildClass()
    obj2.f1()
    obj2.f2()
    obj2.f2("문자형 파라미터")

BaseClass 내부에서만 사용
org.jetbrains.kotlin.idea.scratch.generated.ScratchFileRunnerGenerated$ScratchFileRunnerGenerated$BaseClass@5479e3f
BaseClass 내부에서만 사용
org.jetbrains.kotlin.idea.scratch.generated.ScratchFileRunnerGenerated$ScratchFileRunnerGenerated$ChildClass@66133adc 를 재정의함
f2
f2 : 문자형 파라미터

 

 

 


 

 

 

연산자 오버로딩

연산자 오버로딩은 C++에서 operator++ 과 같이 정의하였다.

java에서는 연산자 오버로딩을 사용할 수 없다.

 

class Student(s : String)
{
    var name : String = s
    var score : Int = 0

    // 연산자 오버로딩
    operator fun plus(student : Student) : Int{
        return student.score + this.score
    }

    operator fun inc() : Student{
        this.score++
        return this
    }

    fun printMe() = println("${name}의 점수는 $score")
}

연산자 오버로딩을 통해 객체, 객체간 연산 가능

student++

student + student2

   // 연산자 오버로딩 테스트
    var student = Student("일모씨")
    (0..99).forEach{student++} // 총 10번
    student.printMe()

    var student2 = Student("이모씨")
    student2.score = 50

    println("두 학생의 점수는 ${student + student2}")

<결과>

일모씨의 점수는 100
두 학생의 점수는 150

 

 

 


 

interface , abstract, static 구현

  • interface : interface 이름 {} 으로 정의, 오버라이드하기 위해 open이나 기타 지시자 정의 X
  • abstract class : 구현 상속할 메소드도 abstract로 정의 , 상속에서는 override로 정의
  • static : 사용하려면 클래스 내에 companion object {} 를 만들고, 그 안에 멤버필드나 메소드 정의

 

 

 

interface

interface ITest{
    fun testFunc()
}

class InterImp1 : ITest{
    override fun testFunc() = println("InterImp1 testFunc()")

}

class InterImp2 : ITest{
    override fun testFunc() = println("InterImp2 testFunc()")

}

 

fun main()
{
    var whatYouWant = 1
    var inter : ITest =
        if(whatYouWant == 1) InterImp1()
        else InterImp2()

    // DIP
    // 구현 클래스를 알 필요 없다.
    interfaceTest(inter)
}

fun interfaceTest(pInter: ITest){
    pInter.testFunc()
}

InterImp1 testFunc()

 

 


 

 

추상 클래스 + static (companion object)

abstract class

abstract class TestAbstract
{
    fun testFunc() = println("abstract testFunc")
    abstract fun abstractFunc()
}

class TestAbstractImp : TestAbstract()
{
    override fun abstractFunc() = println("TestAbstractImp abstractFunc")

    // static
    //companion object {} 안에서 구현해야 static 가능
    companion object
    {
        var staticVar = "companion staticVar"
        fun staticFunc() = println("companion staticFunc")
    }
}
fun main()
{
    // 추상화
    var obj = TestAbstractImp()
    obj.testFunc() // 부모클래스 메소드 
    obj.abstractFunc() // 재정의된 메소드

    println()
    // static
    println(TestAbstractImp.staticVar)
    TestAbstractImp.staticFunc()
}

 

abstract testFunc
TestAbstractImp abstractFunc

companion staticVar
companion staticFunc

 

 

 

또 다른 예)

더보기
// 본래는 각 클래스 마다 파일 생성, 편의를 위해 한번에 생성함
// 1. 슈퍼클래스에서 상위 개념 정의
// Interface도 구현 가능
abstract class Animal {
    abstract fun eat()

    open fun showTool() : String {throw UnsupportedOperationException()}
}

// 2. 각 서브클래스에서 재정의/ 구현
class Human() : Animal()
{
    lateinit var tool : String

    constructor(tool: String) : this() {
        this.tool = tool
    }

    override fun eat() {
        println("밥을 먹음")
    }

    override fun showTool() : String{
        return tool
    }
}

class Cow : Animal()
{
    override fun eat() {
        println("풀을 먹음")
    }
}


fun main()
{
    // 3. 슈퍼 클래스 타입으로 참조변수 선언
    val animalList = mutableListOf<Animal>()
    animalList.add(Human("숟가락"));animalList.add(Cow())
    println(animalList.get(0).showTool())

    for (animal in animalList) animal.eat()

}


main()

<결과>

숟가락
밥을 먹음
풀을 먹음

 

 

 

자식 클래스에서 추상클래스 상속 받기 and 인터페이스 구현하는 방법

-- >    class TestAbstractImp : TestAbstract(), ITest

 

 

 

여기까지 클래스(class), 객체지향(OOP),  다형성 (polymorphism)을 코틀린으로 실습해보았습니다.

 

디자인 패턴을 배우면서 다형성이 매우 중요다고 할 수 있습니다.

 

클래스의 변경( 추가, 변경 , 삭제) 이 자주 일어난다면 interface, abstract class 를 활용하여 다형성을 이용한 프로그래밍을 하는 것을 추천합니다.

 

추상클래스(abstract class) : 공유 연산이 많을 때(공유되는 코드가 많을 때 추상클래스) / 
인터페이스(interface) (What to do) : 호출가능한 메소드 집합(interface) 을 Contract
concreate 클래스(How to do)

interface > 추상(abstract) > 구상(concrete)

Polymorphic Composition) 
Composition of Abstraction(interface) (not Concretion) / 구현X -> 변경영향 적음

 

 

감사합니다.

 

 

fast campus - 올인원 패키지 : 안드로이드앱 개발

 

 

댓글