본문 바로가기
취미노트/코딩공부

[flutter] 15 객체지향 구조 2, Encapsulation

by 복습쟁이 2024. 9. 5.
반응형

이번에 알아볼 것

  • Encapsulation (캡슐화)
  • 지난 포스팅까지 진행한 Workout Tracker App의 객체지향화

 

 

 

 

복습

 

 

우리가 지난번 작업한 WorkoutManager Class의 구조는 다음과 같다.

Properties workouts 운동명과 목표횟수를 짝지어진 5개의 객체가 포함된 List 변수
workoutIndex 운동의 순서를 변경하는 int형 변수
resultworkouts 운동의 결과를 보여주는 변수
Methods getWorksoutsToday '오늘의 운동'을 생성한 뒤, return해주는 메소드.
workouts의 데이터를 기반으로 Row를 만들어 List로 반환
addResultWorkout 사용자가 입력한 카운트(운동횟수)를 받아
그에 해당하는 결과값(아이콘)을 resultWorkouts에 저장하는 메소드

 

 

캡슐화는 왜?

 

캡슐화의 의미는 데이터를 캡슐로 감싸서 숨김. 감춤. 이다. 대체 언제 왜 사용할까?

기존 개발자의 최초 기획 의도를 다른 개발자는 100% 알지 못한다. 그렇기 때문에 기존 개발자가 최적이라고 판단했던 부분을 다른 개발자가 손을 댈 수도 있다. 변수가 몇 개 이내라면 말로 충분히 소통할 수 있겠지만, 수 천개가 있다면 말이 달라진다. 구두로 전달은 어렵기 때문에 캡슐화로 변경하지 못하게 할 필요가 있는 것이다.

 

예를 들어 우리가 만지고 있는 프로그램에서, 새로운 개발자가 workouts에 Workout('수영', 12)를 넣었다고 가정해보자. 코드 상으로는 에러가 없는 수정이지만, 개발자인 나의 의도는 헬스장에서 할 수 있는 운동만을 넣고자 했으니 기획단계 오류가 되는 것이다. 이러한 오류는 찾아내기도 어렵다.

 

 

 

 Private Data

 

언더바가 붙은 함수나 변수는 프라이빗 멤버가 된다. 예를 들어 workouts를 _workouts로 주면, 같은 파일 내에서는 접근이 가능하지만 다른 파일에서는 접근이 불가하다. 파일 단위로 읽기 전용으로 만들어 접근 자체를 막거나, 손 대지 못하게 할 파일을 정해서 구두로 다른 개발자에게 소통하는 식으로 간단하게 분리가 가능하다.

* Dart는 파일 기준으로 Private와 Public이 동작하지만, 다른 언어는 통상 Class 기준으로 프라이빗이 동작한다.

 

 

 

 

 

 

workout tracker App에 적용해보기

 

import 'package:flutter/material.dart';
import 'package:workout_tracker/workout.dart';

class WorkoutManager {
  List<Workout> _workouts = [
    Workout('싯업', 50),
    Workout('친업', 15),
    Workout('스쿼트', 100),
    Workout('푸시업', 45),
    Workout('버피', 30),
  ];
  int workoutIndex = 0;
  List<Row> resultWorkouts = [];

  List<Row> getWorkoutsToday() {
    List<Row> workoutsToday = [];
    for (var i = 0; i < _workouts.length; i++) {
      workoutsToday.add(
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text('${i + 1}.${_workouts[i].name}'),
            Text('${_workouts[i].goal}회'),
          ],
        ),
      );
    }
    return workoutsToday;
  }

  void addResultWorkout(int userInputCount){

    Icon icon;
    if(userInputCount<_workouts[workoutIndex].goal){
      icon=Icon(Icons.sentiment_dissatisfied_rounded,color: Colors.red,);
    }else{
      icon=Icon(Icons.sentiment_satisfied_rounded,color: Colors.green,);
    }

    resultWorkouts.add(
      Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text('${workoutIndex+1}. ${_workouts[workoutIndex].name}'),
          Text('${userInputCount}회/${_workouts[workoutIndex].goal}회'),
          icon
        ],
      ),
    );

  }

}

workouts를 손대지 못하게 하기 위해, 언더바를 붙였다.

프라이빗 변수가 되긴 했지만, 변수명이 변경된 것이기 때문에 기존에 workouts를 호출하던 코드들은 이름을 수정해 줘야 한다.

 

main.dart에서는 _workouts에 접근할 수가 없다. 파일명을 수정해주더라도 여전히 오류가 뜬다. 오류를 해결하기 위해서는 필요한 데이터를 메쏘드를 통해 제공해 줘야 한다. 

 

String getWorkoutName(){
  return _workouts[workoutIndex].name;
}

먼저 workout_manager.dart 파일에 위와 같은 퍼블릭 메쏘드를 하나 추가해 준다. 

이름은 getWorkoutName이고, _workouts[workoutIndex].name을 반환해주는 기능을 한다.

같은 파일 내에 있으므로 이 메쏘드는 프라이빗 메쏘드에 접근할 수 있다.

 

Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    Text('${workoutManager.getWorkoutName()} 몇 회 진행하셨나요?',
      style: TextStyle(fontSize: 18,),
    ),

main.dart에서 방금 만든 퍼블릭 메쏘드를 호출하는 방식으로 필요한 부분을 수정해 준다. 이렇게 되면 workouts는 손 대지 못하지만, 운동명은 받아갈 수 있게 되는 것이다.

 

int getWorkoutLength(){
  return _workouts.length;
}
OutlinedButton(
  onPressed: (){

    int userInputCount=int.parse(workoutController.text);

    setState(() {
      workoutManager.addResultWorkout(userInputCount);
    });

    if(workoutManager.workoutIndex>=workoutManager.getWorkoutLength()-1){
      workoutManager.workoutIndex=0;
    }else{
      workoutManager.workoutIndex++;
    }
    workoutController.clear();
  },
  child: Text('제출'),
),

마찬가지로, workouts의 길이를 리턴해주는 메쏘드를 하나 만들어줘서, main.dart에 가서 호출하도록 수정하자. 이렇게 되면 workouts를 수정하지는 못하지만, 허락된 제한된 정보만을 호출해서 가져갈 수 있게 캡슐화가 된다.

 

절차를 간단히 요약하면 다음과 같다

1. 언더바 처리

2. 같은 파일 내에 기존에 호출하던 부분들을 언더바로 수정 

3. 퍼블릭메쏘드를 만들어서 기능 구현

4. main.dart에서 해당 메쏘드를 호출하여 기능 완성

 

 

다음은 운동순서를 캡슐화해 볼 차례다.

운동순서는 workoutIndex에서 관리했었다. 

절차는 처음은 동일하다. 언더바를 붙여주고, 같은 파일 내 호출부분을 모두 수정해준다.

 

 

메인.dart에서 워크아웃인덱스를 사용하던 부분을 분석해보자.

workoutManager.workoutIndex++ 는 증가(다음운동으로 진행)하는 기능을 담당했다.

 

void nextWorkout(){
  if(_workoutIndex<getWorkoutLength()-1){
  _workoutIndex++;}

}

이를 구현할 퍼블릿 메쏘드를 workout_manager.dart 파일에 구현해줘야 한다.

증가만 할 거니 리턴은 필요없는 void로 타입을 잡아서 만들어준다.

이 함수가 호출되면, _workoutIndex를 증가시켜주도록 한다.

이 때, 운동숫자보다 더 큰 수로 늘어나지 못하도록 제한하는 기능을 if구문을 참조하여 함께 써 줬다.

논리는 운동의 개수가 인덱스보다 작을 때만 인덱스를 증가시키도록 하는 것이다.

 

void reset(){
  _workoutIndex=0;
}

다음으로, 0으로 초기화하는 기능을 별도의 메쏘드로 구현하자.

main에서 if구문으로 구현했던 내용을 참고해서, 새로 메쏘드를 하나 만들자.

 

 

bool isFinish(){
  if(_workoutIndex>=getWorkoutLength()-1){
    return true;
  }else{
    return false;
  }
}

이번에는 위에서 만든 리셋 기능이 작동할 수 있는 조건 메쏘드를 하나 만들자.

isFinish라는 이름으로 참거짓을 리턴하는 bool 타입으로 선언했다.

운동개수만큼 왔는지, 아닌지를 판단해주는

설정된 운동을 끝까지 다 했으면 true, 아니면 false가 리턴되는 함수이다.

 

 

이제 이 코드를 실행하기 위해 메인함수로 돌아가서, isFinish를 호출하고, true이면 reset을, 아니면 nextWorkout을 실행하는 함수를 만들어 주면 된다.

 

이러한 리팩토링은 일견 비효율로 보일 수 있지만 사실 그렇지 않다. 비록 하나의 if문으로 끝나던 것을 세 개로 나눠서 함수를 만들었으므로 코드량은 많아질지라도, 아래의 코드는 어떤 개발자가 보더라도 흐름의 해석이 쉽다. 함수가 뭔가 시작되면 리셋, 아니면 넥스트인 것이다. 반면 위의 코드는 해석하려면 전체 코드를 모두 참조해 봐야 한다. workoutIndex가 무엇이고 workoutManager가 어떤 역할인지 등등을 다 알아야 해석이 가능한 것이다. 개발은 하루이틀에 끝나지 않고, 연 단위로 이루어지기 때문에 파악하기 쉬운 코드가 유지보수하기 쉬운 효율적인 코드이며 잘 짜여진 코드이다. 

 

 

 

이제, 메인에서 무언가를 직접 다루는 함수는 resultWorkout 하나만 남았다. 

 

List<Row> getResultWorkouts(){
  return _resultWorkouts; 
}

단순하게 이를 프라이빗 처리해 준 뒤, 호출할 수 있는 퍼블릭 메쏘드를 하나 만들어주면 된다.

 

child: Column(
  children: workoutManager.getResultWorkouts()
),

호출도 다음과 같이 변경해 준다.

 

 

앱 기능들이 무리없이 다 잘 작동함을 확인할 수 있다.

 

 

child: Column(
  children: workoutManager.resultWorkouts,
),

 

위처럼 단순 함수를 호출하는 방법은 정석적인 메쏘드 작성 외에도 getter(게터)를 이용한 방법도 있다.

게터를 사용하면 main.dart에서 프로퍼티를 호출하듯 함수를 호출할 수 있다는 점과, 마지막 줄처럼 축약하여 사용할 수 있다는 특이점이 있다.

 

 

 

이렇게 리팩토링함으로써 코드는 아래 도표와 같은 구조로 구별되었다. 외부 다른 개발자는 화면출력과 관련된 작업에만 개입할 수 있다. 운동 관리에 관련된 기능, 운동 데이터 구조는 외부 개발자는 가져다 쓰기만 하고, 메인 개발자만 설정할 수 있도록 구조적 안정성이 개선되었다. 이것이 인캡슐레이션(캡슐화)이라는 객체지향 구조이다.

main.dart
WorkoutTracker
workout_manager.dart
WorkoutManager
workout_manager.dart
Workouts
화면출력 운동관리 운동 데이터 구조
728x90
반응형

댓글