ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Android] 간단한 계산기 (추상화)
    안드로이드 2024. 3. 7. 16:56

    ✏️ TIL(Today I Learned)

    AddOperation(더하기), SubtractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기) 연산 클래스들을 AbstractOperation라는 클래스명으로 만들어 사용하여 추상화했다. Calculator() 클래스를 만들어서, 다른 파일에 정의된 AbstractOperation 클래스를 활용하여 주어진 연산을 수행하게했다. Calculator 클래스가 AbstractOperation 클래스의 인스턴스를 받아들여서 해당 연산을 수행하는 방식으로 동작한다. 

    abstract class AbstractOperation {
        abstract fun operate(operand1: Double, operand2: Double): Double
    }
    
    class AddOperation : AbstractOperation() {
        override fun operate(operand1: Double, operand2: Double): Double {
            return operand1 + operand2
        }
    }
    
    class SubtractOperation : AbstractOperation() {
        override fun operate(operand1: Double, operand2: Double): Double {
            return operand1 - operand2
        }
    }
    
    class MultiplyOperation : AbstractOperation() {
        override fun operate(operand1: Double, operand2: Double): Double {
            return operand1 * operand2
        }
    }
    
    class DivideOperation : AbstractOperation() {
        override fun operate(operand1: Double, operand2: Double): Double {
            if (operand2 != 0.0) {
                return operand1 / operand2
            } else {
                throw ArithmeticException("Error: 0으로 나눌 수 없습니다.")
            }
        }
    }

     

    class Calculator {
        fun calculate(operation: AbstractOperation, operand1: Double, operand2: Double): Double {
            return operation.operate(operand1, operand2)
        }
    }

     

    Calculator 클래스와 AbstractOperation 클래스 사이에 느슨한 결합도가 있다. Calculator 클래스가 AbstractOperation 클래스를 직접적으로 참조하지만, 상속이나 구체적인 구현에 의존하지 않고, 대신에 추상화된 인터페이스를 통해 연산을 처리하기 때문이다. 새로운 연산을 추가하거나 기존 연산을 변경할 때 Calculator 클래스를 수정하지 않고도 가능하다. 이는 개별 클래스의 변경이 다른 클래스에 미치는 영향을 최소화하여 시스템의 유지보수와 확장성을 증가시키는 것을 의미한다.

     

    따라서 Calculator 클래스와 AbstractOperation 클래스 간의 결합도는 낮으며, OOP(Object-Oriented Programming, 객체 지향 프로그래밍) 설계 원칙 중 하나인 의존성 역전 원칙(Dependency Inversion Principle)을 따르고 있다.

     

    📝 공부한 Kotlin 요약 정리

    [ 추상화 (Abstraction) ]

    추상화(abstraction): 복잡한 자료, 모듈, 시스템 등으로부터 핵심적인 개념 또는 기능을 간추려 내는 것을 말한다즉, 클래스들의 공통적인 특성(변수, 메소드)등을 묶어 표현하는 상위 클래스를 만드는 것이란 뜻이다.

    추상화의 몇 가지 특징:

    • 공통된 특성 식별: 추상화는 다양한 객체들 간의 공통된 특성을 식별한다. 이러한 공통점은 하나의 추상적인 개념으로 모델링될 수 있다.
    • 세부 사항의 은닉: 추상화는 객체의 중요한 특징만을 강조하고, 구체적인 구현 세부 사항은 숨긴다. 이렇게 함으로써 시스템의 복잡성을 줄이고 코드의 가독성을 향상시킨다.
    • 코드 재사용성: 추상화를 통해 공통된 특징을 논리적으로 그룹화할 수 있으므로, 코드의 재사용성이 증가한다. 추상화된 개념은 다른 부분에서도 사용될 수 있으며, 이를 통해 코드 중복을 방지하고 생산성을 향상시킨다.
    • 유연성: 추상화를 사용하면 시스템이 변경되거나 확장될 때 더 유연하게 대처할 수 있다. 추상화된 인터페이스나 클래스를 통해 시스템의 구성 요소를 교체하거나 확장할 수 있다.
    • 다형성 지원: 추상화는 다형성을 지원한다. 즉, 동일한 인터페이스를 구현하는 여러 클래스를 가지고 있을 때, 이들을 동일한 방식으로 다룰 수 있다. 이는 유연하고 확장 가능한 소프트웨어를 만드는 데 중요하다.

    코틀린의 추상화

    • 인터페이스(Interfaces): 인터페이스는 추상화를 위한 주요 도구 중 하나이다. 인터페이스는 추상 메소드의 집합으로, 구현 클래스에게 메소드를 제공하는 틀을 제공한다. 코틀린에서는 interface 키워드를 사용하여 인터페이스를 정의할 수 있다.
      // Animal 인터페이스 선언
      interface Animal {
          fun makeSound() // 정의
      }
      
      // Dog 클래스 선언과 Animal 인터페이스 구현
      class Dog : Animal {
          // Animal 인터페이스의 makeSound 메서드를 오버라이드
          override fun makeSound() {
              println("Woof!")
          }
      }
      
      // Cat 클래스 선언과 Animal 인터페이스 구현
      class Cat : Animal {
          // Animal 인터페이스의 makeSound 메서드를 오버라이드
          override fun makeSound() {
              println("Meow!")
          }
      }
      
      fun main() {
          val dog = Dog()
          val cat = Cat() 
          dog.makeSound() // 출력: Woof! 
          cat.makeSound() // 출력: Meow! 
      }​
    • abstract 키워드
      • 추상 클래스: 추상 클래스는 하나 이상의 추상 메소드를 포함하는 클래스이다.
        추상 클래스는 일반적인 클래스와는 달리 객체를 직접 생성할 수 없다. 추상 클래스는 직접 인스턴스화할 수 없지만, 상속하여 하위 클래스에서 구체적인 메소드 구현을 제공할 수 있다. 코틀린에서는 
        abstract 키워드를 사용하여 추상 클래스를 정의할 수 있다.
      • 추상 메소드: 메소드의 선언만 있고 구현이 없는 메소드이다.
        추상 메소드는 하위 클래스에서 반드시 구현되어야 한다. 추상 메소드는 추상 클래스나 인터페이스 안에 정의된다. 추상 메소드를 선언하기 위해 메서드 앞에 abstract 키워드를 사용한다. 이때, 해당 클래스는 반드시 추상 클래스로 선언되어야만 한다.
        // 추상 클래스 Animal을 정의
        abstract class Animal {
            // 추상 메소드 makeSound()를 선언 
        	// 이 메서드는 하위 클래스에서 반드시 구현해야 함
            abstract fun makeSound()
        }
        
        // Animal을 상속받는 Dog 클래스를 정의
        class Dog : Animal() {
            // 추상 메소드 makeSound()를 오버라이드하여 강아지가 짖는 소리를 출력
            override fun makeSound() {
                println("Woof!")
            }
        }
        
        // Animal을 상속받는 Cat 클래스를 정의
        class Cat : Animal() {
            // 추상 메소드 makeSound()를 오버라이드하여 고양이가 울어대는 소리를 출력
            override fun makeSound() {
                println("Meow!")
            }
        }
        
        fun main() {
            val dog = Dog()
            val cat = Cat()
        
            // 각 객체의 makeSound() 메소드 호출해 소리를 출력
            dog.makeSound() // 출력: Woof!
            cat.makeSound() // 출력: Meow!
        }

     

    🔎 전체 코드

    화면 구성은 간단하게 했고, 따로 num1과 num2입력을 따로 만들지는 않고, 하나로 입력 받아서 결과: 옆 텍스트뷰에 결과가 출력되도록 만들었다.

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <TextView
            android:id="@+id/titleTv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="40dp"
            android:text="계산기"
            android:textColor="@color/purple_200"
            android:textSize="40sp"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.16" />
    
        <TextView
            android:id="@+id/etTv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="80dp"
            android:layout_marginStart="10dp"
            android:text="입력: "
            android:textSize="20sp"
            app:layout_constraintEnd_toStartOf="@+id/inputEt"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/titleTv" />
    
        <EditText
            android:id="@+id/inputEt"
            android:layout_width="150dp"
            android:layout_height="40dp"
            android:ems="10"
            android:gravity="center"
            android:inputType="number"
            app:layout_constraintBottom_toBottomOf="@+id/etTv"
            app:layout_constraintStart_toEndOf="@+id/etTv"
            app:layout_constraintTop_toTopOf="@+id/etTv" />
    
        <TextView
            android:id="@+id/resultTv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="결과: "
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="@+id/inputEt"
            app:layout_constraintStart_toEndOf="@+id/inputEt"
            app:layout_constraintTop_toTopOf="@+id/inputEt" />
    
        <TextView
            android:id="@+id/resultNumTv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="0.0"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="@+id/resultTv"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/resultTv"
            app:layout_constraintTop_toTopOf="@+id/resultTv" />
    
        <Button
            android:id="@+id/additionBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="30dp"
            android:text="+"
            app:layout_constraintBottom_toBottomOf="@+id/subtractionBtn"
            app:layout_constraintEnd_toStartOf="@+id/subtractionBtn"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintHorizontal_chainStyle="spread_inside"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/etTv" />
    
        <Button
            android:id="@+id/subtractionBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="-"
            app:layout_constraintEnd_toStartOf="@+id/multiplicationBtn"
            app:layout_constraintStart_toEndOf="@+id/additionBtn"
            app:layout_constraintTop_toTopOf="@+id/additionBtn" />
    
        <Button
            android:id="@+id/multiplicationBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="*"
            app:layout_constraintEnd_toStartOf="@+id/divisionBtn"
            app:layout_constraintStart_toEndOf="@+id/subtractionBtn"
            app:layout_constraintTop_toTopOf="@+id/subtractionBtn" />
    
        <Button
            android:id="@+id/divisionBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="10dp"
            android:text="/"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/multiplicationBtn"
            app:layout_constraintTop_toTopOf="@+id/multiplicationBtn" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>

     

    MainActivity를 작성하면서 사칙연산을 계산은 Calculator객체를 만들어서 했다. 

    package com.example.mycalculator
    
    import android.os.Bundle
    import android.widget.Button
    import android.widget.EditText
    import android.widget.TextView
    import androidx.appcompat.app.AppCompatActivity
    
    class MainActivity : AppCompatActivity() {
        private var inputEditText: EditText? = null
        private var additionButton: Button? = null
        private var subtractionButton: Button? = null
        private var multiplicationButton: Button? = null
        private var divisionButton: Button? = null
        private var resultTextView: TextView? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            // 뷰 찾기
            inputEditText = findViewById(R.id.inputEt)
            additionButton = findViewById(R.id.additionBtn)
            subtractionButton = findViewById(R.id.subtractionBtn)
            multiplicationButton = findViewById(R.id.multiplicationBtn)
            divisionButton = findViewById(R.id.divisionBtn)
            resultTextView = findViewById(R.id.resultNumTv)
            resultTextView?.text = "0.0"
    
            // 사칙연산 버튼 클릭 리스너 설정
            additionButton?.setOnClickListener { performCalculation('+') }
            subtractionButton?.setOnClickListener { performCalculation('-') }
            multiplicationButton?.setOnClickListener { performCalculation('*') }
            divisionButton?.setOnClickListener { performCalculation('/') }
        }
    
        // 계산하는 메서드
        private fun performCalculation(operator: Char) {
            val num1 = resultTextView?.text.toString().toDoubleOrNull() ?: return
            val num2 = inputEditText?.text.toString().toDoubleOrNull() ?: return
    
            val result = when (operator) {
                '+' -> Calculator.calculate(num1, num2, AddOperation())
                '-' -> Calculator.calculate(num1, num2, SubtractOperation())
                '*' -> Calculator.calculate(num1, num2, MultiplyOperation())
                '/' -> {
                    if (num2 == 0.0) {
                        resultTextView?.text = "오류! 0으로 나눌 수 없습니다."
                        return
                    }
                    Calculator.calculate(num1, num2, DivideOperation())
                }
                else -> {
                    resultTextView?.text = "잘못된 연산자입니다."
                    return
                }
            }
            resultTextView?.text = result.toString()
        }
    }

     

     

Designed by Tistory.