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

[flutter] 13 효율적인 데이터 관리 4, Class

by 복습쟁이 2024. 8. 31.
반응형

이번 포스팅에서 할 것

  • Class 특성의 이해
  • Constructor의 이해

 

 

 

 

Class의 개념

 

List<String> workoutName = [

싯업 친업 푸시업 스쿼트 버피

]

 

List<int> workoutGoal  = [

50 15 100 45 30

]

 

int workoutIndex = 3 일때, 위 표와 아래 표는 각각 스쿼트 / 45 에 대응한다. 하지만 엄연히 따지면 이 둘은 그저 별개의 리스트들일 뿐이다. 그리하여 개발자 본인이 아닌 타인이 이 표를 보았을 때, 즉각적으로 두 표의 상관관계를 이해하지 못할 수 있다. 그리하여 int goalIndex = 3 이라는 만들어버린다던가 하는 등 코드의 의도를 이해하지 못해 오차를 만들어낼 수 있다.

 

이를 방지하기 위해서는 [스쿼트의 목표 개수 45개] 관계를 문장화 되지 않은 암묵적 관계가 아닌 구조화된 확고한 관계를 만들어야 누가 보더라도 코드가 흔들리지 않는다. 이러한 확고한 구조를 만들 때 활용하는 것이 Class이다.

 

 

List<Workout> workouts  = [

name : 싯업
goal : 50
name : 친업
goal : 15
name : 푸시업
goal : 100
name : 스쿼트
goal : 45
name : 버피
goal : 30

]

이처럼 운동명과 목표를 뗄 수 없게 붙여놓는 방식이 Class 구조화이다. 자동차의 설계도는 클래스, 설계도를 통해 만들어 낸 실제 자동차를 객체라고 비유를 들어 설명을 많이 하기도 한다.

 

인터넷 사이트 회원가입 시 이름, Id, 비밀번호, 생년월일, 성별, 휴대전화 등등을 입력하는 폼을 Class, 그 폼에 정보들을 입력하여 회원가입을 마치고, 정보들이 모여 생긴 ID를 객체라고 생각하자.

 

즉 구조는 클래스, 쓸 수 있는 값은 객체이다.

 

keyword 클래스를 만들 때 시작
Class name UpperCamelCase로 만든다
Class body 중괄호로 만든다
properties 클래스 내에 선언하는 변수들. 멤버변수, 필드, 속성 등 여러 용어로 불린다.
Constructor 생성자. 클래스에 초기화값을 넣어주는 것. 클래스와 동일한 이름을 갖는다. 
클래스는 구조일 뿐이고, 실체인 객체로 만들기 위해서는 컨스트럭터가 필요함.
소괄호 안에 파라미터를 넣지 않고 기본 컨스트럭터를 생성할 수도 있음.
Method 클래스에 정의되는 함수. 멤버함수라고도 함.

 

실제 객체 생성을 예시로 들면 다음과 같다.

 

User hong=User('kdhong','1111','HongGilDong',14430101);

와 같이 컨스트럭터를 써 주고, 그 안에 컨스트럭터가 정의하는 파라미터 규칙에 따라 초기값 아규먼트를 넘겨주면 객체를 만든다.

위 예문은 id가 kdhong이고 pw는 1111이며 이름은 HongGilDong 이고 생년월일이14430410인 User라는 객체를 생성하여 hong이라는 변수에 저장하는 것이다. 그 hong이라는 변수는 타입이 User이다.

이 때, print(kdhong.pw); 를 하면 1111이 출력된다.

 

User hong2=User('kdhong','1111','HongGilDong',14430101);

이라는 객체를 생성했을 때, 안에 있는 값이 모두 동일하다 하더라도 hong과 hong2는 서로 다른 존재이다.

 

한편, 위 처럼 컨스트럭터를 선언했을 때, 아규먼트를 넘기지 않거나 다 넘기거나 모두 가능하다. 단, 컨스트럭터에 정해진 순서대로 넘어간다. 하나도 안 넘기면 디폴트값이 동작하게 된다.

 

함수와 동일하게 네임드 파라미터로 key.value 형태로 넘기는 것도 가능하다.  위 예시는 객체생성을 위해 다른 것들은 옵셔널이지만, pw만 필수적으로 넘겨줘야 한다.

 

 

네임드 컨스트럭터라고 부르는 이 것은 매우 독특하지만, 자주 사용되는 것이다. 네임드 컨스트럭터는 말 그대로 이름을 부여한 컨스트럭터이다. male이 그것이다. male이라는 네임드 컨스트럭터를 활용해 객체를 만들면 gender가 male로 고정되어 셋업된 객체가 반환되는 것이다.

 

 

 

 

 

 

 

Class 구조를 활용한 코드의 효율화

이제 안드로이드스튜디오로 돌아가서 workoutName과 workoutGoal을 class구조로 변환해보자.

통상 클래스 생성 시, 새로운 파일을 생성하여 하는게 협업하기 좋아 암묵적인 룰처럼 통용된다. lib폴더에 별도의 새 파일을 만들고, 위에서 본 대로 class를 선언 후, 클래스 명을 쓰고 프로퍼티와 컨스트럭터를 넣어주자. 이 때 변수명을 workoutName이나 workoutGoal이 아닌 name, goal로 사용한 것은 기존에 이미 변수명을 썼으므로 중복을 피하기 위해서이다.

 

import 'package:workout_tracker/workout.dart';

파일을 새로 만들어 클래스를 정의했으므로, main.dart에는 임포트를 해 줘야 오류가 나지 않는다.

 

 

 

workoutName의 '싯업'과 workoutGoal의 '50'이 하나의 셋트로 짝지어져 w1이라는 객체가 만들어 진 것이다. 이와 같은 방식으로 5개의 운동명과 목표갯수를 짝을 지어 객체를 만들었다. 

하지만, 이 많은 변수들을 항상 들고 다니며 활용하기 번거로우니, 동일한 타입의 변수들을 하나로 묶는 List를 활용했다. Workout이라는 타입의 변수만을 저장할 수 있는 List를 만들고, 이름을 workouts라고 명명하였다.

 

 

이제 앞서 만든 workoutName 및 workoutGoal 리스트와, w1~w5는 필요가 없어졌으므로 삭제하고, 이로 인해 발생하는 오류는 변수명을 workouts로 고쳐주면 된다.

우리가 기존에 리스트에서 썼던 운동명과 목표횟수 데이터는 객체 안에 name, goal이라는 프로퍼티에 저장되어 있다. 객체 안에 들어가 있는 프로퍼티에 접근하기 위해서는 닷.오퍼레이터를 사용한다.

위 사진의 영역은 '오늘의 운동'에 운동명과 운동횟수를 운동명의 갯수만큼 출력해 주는 부분이었다. workoutName의 length를 workouts의 length로 바꿔주고, workoutName 및 workoutGoal의 i번째 데이터를 가져오던 부분은 workouts의 .name 과 .goal의 데이터를 가져오도록 바꿔주는 것이다. 다른 부분들도 같은 논리로 처리해 주면 된다.

 

이렇게 클래스로 직관적으로 구조화하는 코딩을 객체지향이라고 한다. 

 

 

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: WorkoutTrackerPage(),
    );
  }
}

class WorkoutTrackerPage extends StatefulWidget {
  @override
  State<WorkoutTrackerPage> createState() => _WorkoutTrackerPageState();
}

class _WorkoutTrackerPageState extends State<WorkoutTrackerPage> {

  List<Workout> workouts = [
  Workout('싯업',50),
  Workout('친업', 15),
  Workout('스쿼트', 100),
  Workout('푸시업', 45),
  Workout('버피', 30)
  ];

  List<Row> workoutsToday = [];
  int workoutIndex = 0;
  List<Row> resultWorkouts = [];
  TextEditingController workoutController = TextEditingController();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    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}회'),
          ],
        ),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Workout Tracker Page'),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Card(
            color: Colors.grey.shade300,
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Column(
                children: [
                  Text(
                    '오늘의 운동',
                    style: TextStyle(fontSize: 19),
                  ),
                  ...workoutsToday
                ],
              ),
            ),
          ),

          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('${workouts[workoutIndex].name} 몇 회 진행하셨나요?',
                style: TextStyle(fontSize: 18,),
              ),
              SizedBox(
                width: 30,
                child: TextField(
                  controller: workoutController,
                  keyboardType: TextInputType.number,
                ),
              ),
              Text('회'
              ),
              SizedBox(width: 15,
              ),
              OutlinedButton(
                onPressed: (){

                  int userInputCount=int.parse(workoutController.text);
                  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,);
                    }

                  setState(() {
                    resultWorkouts.add(
                      Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          Text('${workoutIndex+1}. ${workouts[workoutIndex].name}'),
                          Text('${userInputCount}회/${workouts[workoutIndex].goal}회'),
                          icon
                        ],
                      ),
                    );
                  });
                  if(workoutIndex>=workouts.length-1){
                    workoutIndex=0;
                  }else{
                    workoutIndex++;
                  }
                  workoutController.clear();
                },
                child: Text('제출'),
              ),
            ],
          ),

          Expanded(
            child: Container(
              margin: EdgeInsets.symmetric(horizontal: 5),
              padding: EdgeInsets.all(10),
              decoration: BoxDecoration(
                color: Colors.grey.shade300,
                borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(10),
                  topRight: Radius.circular(10),
                ),
              ),
              child: Column(
                children: resultWorkouts
              ),
            ),
          ),
        ],
      ),
    );
  }
}

 

* 다음 포스팅에서 계속

 

 

 

 

 

728x90
반응형

댓글