[25.06.25 TIL] 프로그래밍과 개인 과제

김영민·2025년 6월 25일

🎯 오늘은 1강 남은 객체 지향 프로그램을 마저 듣고, 개인 과제를 시작했다.
우선, 기반 작업을 진행했고, 오류가 생기면 여기저기 고쳐도 보고, 바꾸기도 하면서 하나씩 해결했다.


💬 객체 지향 프로그래밍

🔥 객체 지향과 상속

✏️ 상속(Inheritance)

✅ 기존 클래스의 기능을 확장하여 새로운 클래스를 만드는 것

✅ 하나의 클래스가 다른 클래스의 속성메서드 를 물려받는 것으로, 물려주는 클래스는 부모 클래스(Parent Class, Superclass), 물려받는 클래스는 자식 클래스(Child Class, Subclass)라고 부른다.


📋 class [자식 클래스 이름] extends [부모 클래스 이름] { … }

class Person {
  void eat() {
    print('냠냠 !');
  }
}
//
class Student extends Person {
  void study() {
    print('열공 !');
  }
}

🔍 부모 클래스는 자식 클래스에게 자신의 모든 속성과 메서드를 상속한다.

class Person {
	String name = '';
//	
  void eat() {
    print('냠냠 !');
  }
}
//
class Student extends Person {
  void study() {
    print('열공 !');
  }
}
//
void main() {	
  Student student = Student();
  student.name = 'Mini';
  student.eat(); // 냠냠 !
  student.study(); // 열공 !
}

Student 에는 name 이랑 eat() 이 정의되어 있지 않아도 사용할 수 있다.


🔍 부모 클래스는 자식 클래스에 있는 속성, 메서드를 사용할 수 없다.

class Person {
  void eat() {
    print('냠냠 !');
  }
}
//
class Student extends Person {
  void study() {
    print('열공 !');
  }
}
//
void main() {	
	Person person = Person();
	person.study(); // Error: The method 'study' isn't defined for the class 'Person'.
}

🔍 super를 통해 자식 클래스가 부모 클래스의 속성과 메서드를 사용할 수 있다.

class Person {
  String name = 'Mini';
//
  void eat() {
    print('냠냠 !');
  }
}
//
class Student extends Person {
  void eatAndIntroduce() {
    super.eat();
    print('맛있게 먹는 ${super.name}');
  }
}
//
void main() {
  Student student = Student();
  student.eatAndIntroduce();
}
/*
냠냠 !
맛있게 먹는 Mini
*/

super 는 부모 클래스를 가리키며, super.eat(); 을 하면 Person 에 있는 eat() 이 호출되고, super.name 을 하면 Person 에 있는 인스턴스 변수인 name 이 들어간다.


🔍 자식 클래스 는 상속 받은 속성 과 메서드 를 재정의 하거나 기능을 확장할 수 있다


재정의(Overriding)

✔ 자식 클래스가 부모 클래스로부터 상속 받은 속성과 메서드를 그대로 사용하지 않고, 덮어 씌우는 것을 의미한다.

✔ 부모 클래스에 정의되어 있는 속성이나 메서드가 마음에 안 들어서 새로 정의하고 싶을 때 사용한다.


📋 @override [변수 이름] = [값];

class Person {
  String name = 'Mini';
//
  void eat() {
    print('냠냠 !');
  }
}
//
class Student extends Person {
  
  String name = '여러분';
//
  void study() {
    print('열공하는 $name !');
  }
}
//
void main() {
  Student student = Student();
  student.study(); // 열공하는 여러분 !
}

Student 의 namePersonname 을 재정의한 속성이다.


📋 @override [반환 타입] [함수 이름]() { … }

class Person {
  void eat() {
    print('냠냠 !');
  }
}
//
class Student extends Person {
  
  void eat() {
    print('쩝쩝 !');
  }
}
//
void main() {
  Student student = Student();
  student.eat(); // 쩝쩝 !
}

Personeat()Student 에서 재정의했다.


class Person {
  void eat() {
    print('냠냠 !');
  }
}
//
class Student extends Person {
  
  void eat() {
    super.eat();
    print('쩝쩝 !');
  }
}
//
void main() {
  Student student = Student();
  student.eat();
}
/*
냠냠 !
쩝쩝 !
*/

super 를 통해 기존에 정의되어 있던 메서드를 호출한 후에 재정의하면 된다.


💡 공통적인 속성과 메서드를 부모 클래스에 정의하고, 공통되지 않는 요소들만 따로 자식 클래스에 정의해서 중복된 코드를 줄이고, 코드의 재사용성을 높일 수 있다.


상속 허용

📋 final class [클래스 이름] { … }

final class Person {
  void eat() {
    print('냠냠 !');
  }
}
//
class Student extends Person {
	// Error: The type 'Student' must be 'base', 'final' or 'sealed' because the supertype 'Person' is 'final'.
}

👉 하나의 클래스로부터 자식 클래스를 만들 수 없도록 하는 것으로, 상속 받을 수 없도록 만든다.


🔥 객체 지향 프로그래밍(Object-Oriented Programming)

✅ 객체 (Object) 들을 사용하여 프로그램을 구성하는 방식

📕 클래스를 통해 틀을 정의해두고, 여러 객체를 만들기 때문에 효율적이다.

📒 클래스를 상속 받아서 기능을 확장할 수 있기 때문에 코드를 재사용할 수 있다.

📗 클래스를 통해 만든 객체들이 각각 독립적으로 동작하기 때문에 특정 객체를 수정해도 부작용이 생길 일이 적다.


💻 예제

class Car {
	String name;
	List<String> models;
//  
  Car(this.name, this.models);
//	
	void introduceName() {
		print('안녕하세요, 저희는 $name 입니다 !');
	}
//  
  void introduceModels() {
    print('저희는 $models 이렇게 ${models.length}가지 모델을 가지고 있습니다 !');
  }
}
//
void main() {
  Car bmw = Car('BMW', ['320i', '340i', 'M3']);
  print(bmw.name); // BMW
  print(bmw.models); // [320i, 340i, M3]
  bmw.introduceName(); // 안녕하세요, 저희는 BMW 입니다 !
  bmw.introduceModels(); // 저희는 [320i, 340i, M3] 이렇게 3가지 모델을 가지고 있습니다 !
//  
  Car benz = Car('Benz', ['A-Class', 'C-Class', 'E-Class', 'S-Class']);
  print(benz.name); // Benz
  print(benz.models); // [A-Class, C-Class, E-Class, S-Class]
  benz.introduceName(); // 안녕하세요, 저희는 Benz 입니다 !
  benz.introduceModels(); // 저희는 [A-Class, C-Class, E-Class, S-Class] 이렇게 4가지 모델을 가지고 있습니다 !
}

🥽 개인 과제

📋 콘솔 쇼핑몰

💾 시나리오

아래의 기능이 들어있는 콘솔 프로그램 만들기

  • 판매하는 상품 목록을 볼 수 있는 기능
  • 구매자가 구매하고 싶은 상품들을 장바구니에 담을 수 있는 기능
  • 구매자가 장바구니에 담은 상품들의 총 가격을 볼 수 있는 기능

✅ 필수 요소 및 정의

쇼핑몰을 정의하기 위한 ShoppingMall 클래스

📁 속성
✔ 판매하는 상품 목록 (List<Product>)
✔ 장바구니에 담은 상품들의 총 가격 (int)

📁 메서드
✔ 상품 목록을 출력하는 메서드 showProducts()
✔ 상품을 장바구니에 담는 메서드 addToCart()
✔ 장바구니에 담은 상품의 총 가격을 출력하는 메서드 showTotal()


상품을 정의하기 위한 Product 클래스

📁 속성
✔ 상품 이름 String
✔ 상품 1개당 가격 int


✅ 필수 기능

1. 판매하는 상품 목록을 볼 수 있는 기능

[ 설명 ]
1 을 입력했을 때 판매하고 있는 상품 목록을 출력합니다.

[ 조건 ]
✔ 출력 형태 : [상품명] / [상품 1개당 가격]원

셔츠 / 45000원
원피스 / 30000원
반팔티 / 35000원
반바지 / 38000원
양말 / 5000

[ 힌트 ]
✔ 반복문을 통해 상품의 정보를 하나씩 출력합니다.


2. 상품을 장바구니에 담을 수 있는 기능

[ 설명 ]
2 를 입력했을 때 장바구니에 담을 상품 이름 String 과 상품 개수 int 를 입력 받습니다.

[ 조건 ]
✔ 입력한 상품의 이름이 상품 목록에 있지 않거나 상품의 개수가 0 이하의 값이면 장바구니에 담기지 않습니다.
✔ 상품 목록에 없는 상품의 이름을 입력한 경우 입력값이 올바르지 않아요 ! 를 출력합니다.
✔ 상품의 개수를 숫자 형태로 입력하지 않은 경우 입력값이 올바르지 않아요 ! 를 출력합니다.
✔ 입력한 상품의 개수가 0 이하의 수인 경우 0개보다 많은 개수의 상품만 담을 수 있어요 ! 를 출력합니다.
✔ 입력한 상품의 이름과 상품의 개수가 올바른 값이면 장바구니에 담깁니다.
✔ 이 경우 장바구니에 상품이 담겼어요 ! 를 출력합니다.

[ 힌트 ]
✔ 조건문과 try-catch 문을 통해 입력값에 대한 처리를 할 수 있습니다.
contains() 또는 고차 함수를 통해 입력한 상품의 이름이 상품 목록에 있는지 판별할 수 있습니다.
int.parse() 를 통해 입력한 상품의 개수를 int 타입으로 변환할 수 있습니다.
✔ 장바구니에 담은 상품들의 총 가격을 담기 위한 인스턴스 변수를 ShoppingMall 클래스에 정의한 후 그 인스턴스 변수의 값에 더해줍니다.


3. 장바구니에 담은 상품들의 총 가격을 볼 수 있는 기능

[ 설명 ]
✔ 3 을 입력했을 때 구매자가 장바구니에 담은 상품들의 총 가격 (int) 을 계산하여 출력합니다.

[ 조건 ]
✔ 출력 형태 : 장바구니에 [가격]원 어치를 담으셨네요 !

장바구니에 35000원 어치를 담으셨네요 !

4. 쇼핑몰 프로그램을 종료할 수 있는 기능

[ 설명 ]
4 를 입력했을 때 쇼핑몰 프로그램이 종료됩니다.

[ 조건 ]
이용해 주셔서 감사합니다 ~ 안녕히 가세요 ! 출력 후 프로그램을 종료합니다.

[ 힌트 ]
while 문을 사용하고, 프로그램을 종료하기 위한 bool 타입의 변수로 while 문을 제어합니다.


❗ 주의!

  • 터미널이나 명령 프롬프트 등의 명령줄 인터페이스를 사용하여 구현합니다.
  • 입력 받는 기능은 dart:io 라이브러리의 stdin.readLineSync() 를 사용합니다.
    • stdin.readLineSync() 는 입력한 값을 String? 타입의 값으로 반환하는 메서드입니다.
  • 쇼핑몰에는 5개 이상의 상품이 있어야 하며, 생성자를 통해 생성합니다.
  • 4 를 입력하기 전까지는 쇼핑몰의 3가지 기능을 계속 사용할 수 있어야 합니다.
  • 1, 2, 3, 4 외의 값을 입력했을 때 지원하지 않는 기능입니다 ! 다시 시도해 주세요 .. 를 출력합니다.
  • 1, 2, 3, 4 를 입력 받을 때 각 숫자가 어떤 기능을 하는지 출력합니다.

💻 실행 예시


💻 초기 세팅

💾 프로젝트 생성

dart create -t console-full 프로젝트 이름
dart create -t console-full shopping_mall

💭 우선 shopping_mall 이란 이름으로 콘솔 프로그램을 만들었다.


shopping_mall/
 ┣ bin/
 ┃ ┗ shopping_mall.dart  ← 메인 코드(여기에 작성)
 ┣ pubspec.yaml
 ┗ ...

💭 여기에 해당 코드를 작성해보겠다.


dart run bin/shopping_mall.dart

💭 이렇게 run을 돌리면 실행되어 값이 나온다고 한다.


폴더역할
bin/실행 파일 (main 함수)
lib/기능, 로직, 클래스, 함수 모음
test/기능이 잘 작동하는지 확인하는 테스트 코드

💭 이런 식이라서 콘솔을 사용할 땐 bin에서 작업하는 것 같은데... 복잡해지면 lib에 작성하고 bin에 가져오는 식? 으로 작업한다는데 아직 감이 안잡힌다.


💾 Git, Fork 연동

과제 저장소

💬 예전에 한번 어떻게 사용하는지 궁금해서 써본 것 외에 본격적으로 쓰는 것은 이번이 처음인데, 기억이 잘 안나서 좀 헤멨다.

사실 많이.. 사용법이 기억이 안나서 GPT한테 물어봤는데 아까 콘솔에 대해서 물어봤다가 자꾸 콘솔 용어나.. 명령 프롬프트를 사용한 방식?을 자꾸 알려줘서...

나는 포크 프로그램을 사용하는데, 이게 Git 사용하는데 편리하다고 했던 것 같다.


✅ Git 저장소 만들기(README.md 체크) > 파일 업로드 > Fork Clone > 프로젝트 Clone 완료

💬 대충 이런식이다. README를 생성하지 않으니, 파일 업로드가 막히고, Fork에서 클론할 때 폴더를 잘못 선택해서 원본 프로젝트 안에 폴더가 생기기도 하고 문제가 좀 있었다.


📁 solo_proj_shopping_mall

💬 위와 같은 이름의 저장소와 클론 파일이 생겨 이제 코딩 작업을 시작할 수 있게 되었다.


💾 세팅

dart pub get
flutter pub add get

✅ 시작하면 오류가 뜨는데 라이브러리를 설치하면 해결된다. 강의에서는 아래 코드를 터미널에 입력했다.


import 'dart:io';

✅ 콘솔에서 출력값을 받기 위해 해당 라이브러리가 필요하다.
💬 콘솔에서 출력하기 위한 stdout.write(), 값을 받아오는 stdin.readLineSync()를 사용한다.


👩‍💻 기반 작업

✅ 쇼핑몰 메뉴, 상품 목록 출력 구현
✅ 프로그램 입력 및 종료 기능 구현


📁 lib/shopping_mall

✅ bin 폴더 파일에서 코드가 길어질 것 같아서 lib에 쓰고, 해당 파일을 import하여 사용하기로 했다.

class ShoppingMall {
  List<Product> products = [];
  int totalPrice = 0;

  ShoppingMall() {
    products.add(Product("셔츠", 45000));
    products.add(Product("원피스", 30000));
    products.add(Product("반팔티", 35000));
    products.add(Product("반바지", 38000));
    products.add(Product("양말", 5000));
  }

  void showProducts() {
    for (var p in products) {
      print("${p.name} / ${p.price}원");
    }
  } // 상품 목록

  void addToCart() {
    // for (var i = 0; i < products.length; i++) {
    //   var pd = products[i];
    //   products[i].name , products[i].price
    // }
  }

  void showTotal() {
    //
  }
} // 쇼핑몰 클래스

class Product {
  String name;
  int price;

  Product(this.name, this.price);
} // 상품 클래스

💬 우선 과제 필수 정의를 위해 ShoppingMall 클래스를 만들고, 속성에 판매 상품 목록 List<Product> products 와 총 가격을 넣을 totalPrice 을 만들었다.

밑에는 ShoppingMall 생성자를 만들어 products 에 상품명과 가격을 추가했다.

그리고 메서드 틀을 만들었고, 우선 상품 목록을 출력하는 메서드 showProducts() 를 구현했다.

상품 정의를 위한 Product 클래스도 만들어 생상자 String 타입 name, int 타입 price 속성을 만들었다.


이때 print("${p.name} / ${p.price}"); 이 부분이 출력이 안되어서 문제가 발생했었는데, 상품들을 main() 에서 추가해서 해당 클래스에서 활용할 수가 없었다.

그래서 내부에 생성자로 추가해주고, 혹은 bin 폴더에 상품을 추가하는 방법도 있었는데, 이게 코드가 보기에 깔끔하고, bin 폴더의 dart파일이 길어지니 쇼핑몰 클래스에 넣어줬다.

print 만으로는 출력하면 그냥 Product 그 함수의 주소?가 나오기 때문에, 변수 p에 리스트 products 를 반복문을 통해 모든 상품을 출력할 수 있도록 showProducts() 메서드를 구현했다.


강의에서는 클래스를 만들어주고 main에서 생성자에 값을 넣어 만드는 식으로 했다면, 이번에는 잘못 생각한게 실행할 main 파일이 해당 코드를 구현하는 이 파일이 아닌점,

class ShoppingMall {
  List<Product> products = [];
  int totalPrice = 0;
  }

이런식으로 하면 ShoppingMall 에는 productname, price 라는게 없기 때문에 사용할 수 없다는 것이다.


📁 bin/shopping_mall

import 'dart:io';
import 'package:shopping_mall/shopping_mall.dart';

void main(List<String> arguments) {
  stdout.write(
      "[1] 상품 목록 보기 / [2] 장바구니에 담기 / [3] 장바구니에 담긴 상품의 총 가격 보기 / [4] 프로그램 종료"); // run 출력값

  var shoppingMall = ShoppingMall();

  while (true) {
    String? numberInput = stdin.readLineSync();
    if (numberInput == "1") {
      shoppingMall.showProducts();
    } else if (numberInput == "2") {
      print("상품 이름과 상품 개수를 입력해주세요.");
    } else if (numberInput == "3") {
      print("장바구니에 $ShoppingMall.totalPrice원 어치를 담으셨네요 !");
    } else if (numberInput == "4") {
      print("이용해 주셔서 감사합니다 ~ 안녕히 가세요 !");
      break;
    } else {
      print("지원하지 않는 기능입니다 ! 다시 시도해 주세요 ..");
    } // 입력값 대응
  }

💬 여기서 run 을 돌릴거기 때문에 출력 및 입력 기능을 구현했다. lib 폴더에 있는 파일을 사용하기 위해 import 를 했다.

우선 해당 기능을 위해 import 'dart:io'; 가 import 되어 있어야 사용 가능 하기 때문에 추가해주었다. stdout.write 를 사용하면 출력이 가능하기 때문에 쇼핑몰 메뉴 출력을 해당 코드를 통해 구현했다.

그리고 쇼핑몰 클래스를 사용하기 위해, 변수에 ShoppingMall(); 을 넣어주어서 해당 상품목록들을 사용할 수 있게 했다.

if 문을 활용해서 입력값마다 대응하는 값을 출력하도록 만들었고, 기능 종료를 위해서 while 반복문을 통해서 4를 누르지 않는 한 계속 프로그램이 작동하도록 했다.

이를 위해 값을 true로 해서 계속 반복하게 하고, 4가 들어왔을 때 break; 하여 종료되도록 구현했다.


이때 ShoppingMall(); 이걸 가져오지 않은채로 그냥 ShoppingMall.name이나 ShoppingMall.products 같이 코드를 짜서 아무것도 되지 않고 자꾸 오류가 발생했다. import를 했어도 값을 사용하려면 불러와서 해당 변수를 사용해야 활용할 수 있는 것 같다.


입력값을 받기 위해 stdin.readLineSync(); 이걸 사용했어야 했는데 String 타입이라서 오류가 생겨서 아무것도 되지 않았다. 그래서 else 를 만들어 그 외의 입력값이 들어오는 것을 확인했고, 해당 입력값이 숫자라서 생기는 문제를 인식했다.

int number = int.parse(numberInput!); 그래서 이렇게 int로 형변환을 했는데, 이제 문자를 입력하면 이게 오류가 발생하는 것이다. 오직 숫자만 입력될 것이라고 생각했는데 그게 아니었던 것이다.

그래서 해당 코드를 삭제하고, String 타입으로 받되 숫자를 "1" 이렇게 문자로 받아버리는 것이다. 그러면 문자에도 대처가 가능하고, 그 외 숫자에도 대처가 가능하게 되는 것이다. 놀라운 깨달음!


💬 그렇게 출력과 입력값을 받았을 때 메세지가 잘 작동하도록 구현을 해서 1번까지의 기능을 하도록 만들었고, 이제 나머지 기능을 작동하도록 구현하면 될 것 같다.


🌼 Git Commit

기반 작업
- 쇼핑몰 메뉴, 상품 목록 출력 구현
- 프로그램 입력 및 종료 기능 구현


🌱 오늘은 이론이나 문법쪽 강의까지 듣고 개인 과제를 시작했다.
실제 프로젝트.. 라고 하기엔 뭐하지만 실제 작업을 진행해보니 시작부터 상당히 고난이 있었고 어려웠다.

그래도 오류가 발생했을 때, 좀 더 생각해보고 이것저것 고쳐가면서 해결하면 뿌듯하기도 하고 재밌었다.

물론 내일이 되면 생각이 달라질 수도 있지만... 아무튼 하나 만드는데 시간이 상당히 걸리고, 고민도 많이 되고, 해결도 오래걸리고... 아직 손에 안익숙해서도 있는 것 같다.


🚀 내일은 과제를 마무리하고 제출할 생각이다. 금요일 오전에 추가 작업해서 마감 전에 제출하는 것은 무리가 있어보여서, 내일 마무리하고 제출해야될 것 같다.

오늘 기반 작업하는데 상당히 시간이 걸리고, 기능 하나하나 구현하는데 힘들었는데 가능할지 모르겠다... 최대한 열심히 해봐야지!

profile
💻 [25.05.26~] Flutter 공부중⏳

0개의 댓글