GRIDY

이번주는 팀빌딩부터 일본어 시험 준비까지 바쁜 한주였던것 같다.

 

팀은 같은 팀으로 서로가 장점이 다른 좋은 팀이 만들어진것 같다.

 

과제는 DOS 게임을 만들라고 했는데 난이도가 꽤 쉽다고(원래 개발자였던 내기준, 실제로는 좀 어려운 과제였다고 생각한다) 생각해서

좀 다른 방식으로 구현해보려고 했다.

근데 시간을 못맞출거 같아서 그냥 포기하고 AI로 만든 결과물로 제출했다 ㅠㅠ..

이번주는 귀찮아서 TIL을 작성 안했으니까 여기다가 이번주에 과제를 하면서 알게 된걸 정리해보려고 한다.

이번 과제는 Dart로 턴제 게임 만들기인데,

이미지와 같은 채팅 기반 턴제게임을 만드는 과제였다.

나는 그래서 옛날에 C언어로 Dos 게임을 만들어봤던 기억이 생각나서 비슷하게 한번 만들어볼까?? 그런 생각을 했다.

우선 DOS에서 글자에 색을 넣기 위해서는 DOS 색상문자인가?

// ANSI 컬러 코드 정의
const String reset = '\x1B[0m'; // 모든 속성 초기화
const String red = '\x1B[31m';  // 빨간색 전경색
const String green = '\x1B[32m'; // 초록색 전경색
const String yellow = '\x1B[33m'; // 노란색 전경색
const String blue = '\x1B[34m';  // 파란색 전경색

위와 같이 넣어야 한다고 해서 이건 좀 아니다 싶어서 라이브러리를 찾아보았다.

그래서 chalkdart, dart_console 라이브러리를 깔았다.

chalkdart는 문자열을 확장해서 색상을 입힐수 있다.

print('=== 🎮 ${'이름 설정'.green} 🎮 ===');

chalkdart와 dart_console을 사용해 방향키로 선택지를 선택하는 형식으로 만들어보았다.

주말에 일본어 시험이 없었다면 기한을 맞출 수 있을거 같았는데, 일본어 시험 준비하면서 하다보니까 일단 요구사항을 다 맞추지 못해서, 그냥 AI로 만든 코드로 제출했다.

이걸 만들면서 알게된 점은, 우선 Singleton 패턴이다.

이 코드를 짜면서 프론트엔드처럼 전역 상태관리를 어떻게 할 수 있을까? 하면서 찾아보다가 발견하였다.

[AI피셜]


싱글톤(Singleton) 패턴은 생성(Creational) 디자인 패턴 중 하나로, 특정 클래스의 인스턴스가 오직 하나만 존재하도록 보장하고, 해당 인스턴스에 대한 전역적인 접근 지점을 제공하는 패턴입니다.

쉽게 말해, 어떤 객체가 시스템 전체에서 단 하나만 존재해야 할 때 사용하는 방법입니다.

싱글톤 패턴의 주요 특징

  1. 단 하나의 인스턴스만 존재: 클래스에서 생성되는 객체가 무조건 한 개여야 합니다.
  2. 전역 접근 가능: 어디서든 해당 단일 인스턴스에 접근할 수 있는 방법을 제공합니다.
  3. 자기 통제: 인스턴스 생성을 스스로 통제하며, 외부에서 임의로 인스턴스를 생성하는 것을 막습니다.

왜 싱글톤 패턴을 사용할까요? (장점)

  • 메모리 절약 및 효율성 증가: 인스턴스를 한 번만 생성하므로, 동일한 객체가 여러 번 생성되어 불필요하게 메모리를 낭비하는 것을 막습니다. 특히 데이터베이스 연결 풀, 스레드 풀, 설정 관리자 등과 같이 시스템 전체에서 공유되어야 하는 자원 관리에 유용합니다.
  • 자원 관리 용이: 유일한 인스턴스를 통해 공유 자원에 대한 일관된 접근을 보장하고, 동시성 문제를 관리하기 쉬워집니다.
  • 데이터 공유 용이: 전역으로 접근 가능한 인스턴스이기 때문에, 여러 클래스나 모듈 간에 데이터를 쉽게 공유할 수 있습니다.

싱글톤 패턴은 언제 사용될까요? (사용 사례)

  • 설정 파일 관리자 (Configuration Manager): 애플리케이션의 설정 정보를 읽고 관리하는 객체는 하나만 존재해야 합니다.
  • 로그 기록기 (Logger): 모든 로그 메시지를 하나의 파일이나 스트림에 기록할 때, 로거 객체는 하나만 있는 것이 효율적입니다.
  • 데이터베이스 연결 풀 (Database Connection Pool): 여러 클라이언트가 데이터베이스에 접근할 때, 미리 생성된 연결을 재사용하여 효율성을 높입니다.
  • 스레드 풀 (Thread Pool): 미리 생성된 스레드를 관리하고 재사용하는 객체.

어쨋든 처음 알게된 디자인 패턴을 활용하여 전역 상태를 관리할 수 있도록 구현하였다.

import 'package:game/model/character.dart';
import 'package:game/model/monster.dart';

class GameState {
  // 싱글톤 인스턴스
  static final GameState _instance = GameState._internal();

  // 팩토리 생성자
  factory GameState() {
    return _instance;
  }

  // private 생성자
  GameState._internal();

  Character character = Character(
    name: '',
    gold: 0,
    level: 1,
    exp: 0,
    maxExp: 100,
    hp: 100,
    maxHp: 100,
    atk: 10,
    def: 0,
  );

  int stage = 1;
  int maxStage = 3;
  int deadMonsterCount = 0;

  List<Monster> monsterList = [];

  Monster? nextMonster;

  // 게임 설정
  bool soundEnabled = true;
  double soundVolume = 1.0;

  // 상태 초기화
  void reset() {
    character = Character(
      name: '',
      gold: 0,
      level: 1,
      exp: 0,
      maxExp: 100,
      hp: 100,
      maxHp: 100,
      atk: 10,
      def: 0,
    );

    stage = 1;
    deadMonsterCount = 0;
    monsterList = [];
    nextMonster = null;
  }

  // 경험치 획득
  void gainExp(int amount) {
    character.exp += amount;
    while (character.exp >= character.maxExp) {
      levelUp();
      character.exp = 0;
    }
  }

  // 레벨업
  void levelUp() {
    character.level++;
    character.exp -= character.maxExp;
    character.maxExp = (character.maxExp * 1.2).round();
    character.maxHp += 10;
    character.hp = character.maxHp;
    character.atk += 2;
    character.def += 1;
  }

  // 골드 획득/사용
  void addGold(int amount) {
    character.gold += amount;
  }

  bool useGold(int amount) {
    if (character.gold >= amount) {
      character.gold -= amount;
      return true;
    }
    return false;
  }

  // 체력 회복/감소
  void heal(int amount) {
    character.hp = (character.hp + amount).clamp(0, character.maxHp);
  }

  void takeDamage(int amount) {
    character.hp = (character.hp - amount).clamp(0, character.maxHp);
  }

  // 현재 상태 출력용 메서드
  Map<String, dynamic> getStatus() {
    return {
      'name': character.name,
      'level': character.level,
      'exp': '$character.exp/$character.maxExp',
      'health': '$character.hp/$character.maxHp',
      'attack': character.atk,
      'defense': character.def,
      'gold': character.gold,
    };
  }
}

다만 Singleton 패턴은 안티패턴이라고 평가받기도 한다고 하고, 나도 실제 개발에서 사용해본적은 없어서 그냥 지식 하나 쌓았다고 생각하고 넘어갈 것 같다.

[AI피셜]


싱글톤 패턴의 단점 (안티 패턴 논란)

싱글톤 패턴은 편리하고 유용하지만, 다음과 같은 단점으로 인해 "안티 패턴"으로 간주되기도 합니다.

  • 강한 결합 (Tight Coupling): 싱글톤 인스턴스는 전역적으로 접근 가능하므로, 많은 모듈이 싱글톤에 직접 의존하게 됩니다. 이는 모듈 간의 결합도를 높여 코드 변경 시 파급 효과가 커질 수 있습니다.
  • 테스트의 어려움: 싱글톤은 전역 상태를 가지므로, 단위 테스트 시 각 테스트가 독립적으로 실행되는 것을 방해할 수 있습니다. 이전에 실행된 테스트의 상태가 다음 테스트에 영향을 줄 수 있습니다.
  • 단일 책임 원칙 (SRP) 위배: 싱글톤 클래스는 자신의 주요 책임 외에 "단일 인스턴스임을 보장하는 책임"까지 가지게 됩니다. 이는 단일 책임 원칙(Single Responsibility Principle)을 위반할 수 있습니다.
  • 확장성 저해: 싱글톤은 단일 인스턴스를 강제하기 때문에, 나중에 여러 인스턴스가 필요한 상황이 생기면 코드 수정이 어려울 수 있습니다.
  • 동시성 문제 (Concurrency Issues): 멀티스레드 환경에서 여러 스레드가 동시에 싱글톤 인스턴스에 접근하려고 할 때, 인스턴스가 두 개 이상 생성되거나 데이터 불일치 문제가 발생할 수 있습니다. (위의 Dart 예시는 late final로 인해 스레드 안전합니다. 하지만 다른 언어에서는 추가적인 동기화 처리가 필요할 수 있습니다.)

결론

싱글톤 패턴은 특정 자원이 시스템 전체에서 하나만 존재해야 할 때 유용하게 사용될 수 있습니다. 하지만 그 편리함 뒤에는 강한 결합, 테스트의 어려움 등의 단점도 존재하므로, 사용하기 전에 해당 패턴이 정말 필요한지 신중하게 고려해야 합니다. 요즘에는 의존성 주입(Dependency Injection)과 같은 다른 패턴이나 프레임워크를 사용하여 싱글톤의 장점을 유지하면서 단점을 완화하는 접근 방식을 더 선호하기도 합니다.

조금만 더 만들었으면 기한을 맞출 수 있었을거 같은데, 다음주부터는 Flutter 과정에 들어가니까, 그냥 즐겼다고 생각하고 넘어가야겠다.


 

 

나도 백엔드 자체는 실무경험이 많지 않아서 좀 더 여러가지에 대해 공부해야 겠다고 느꼇다.

끗-

스파르타 코딩클럽 앱 창업 부트캠프를 시작하고 2주차...

새로운 조로 바뀌었다.

지난주는 인사이트 클럽(인사이트 자료를 보고 감상평? 말하는 시간) 같은 시간이 조금 의미없게 느껴졌는데 (아무래도 지난주는 마이크가 안되시는 분들도 많았어서...??),
이번주는 마이크도 다들 되시고 여기 분들은 다들 자신의 생각을 많이 얘기해주셔서 나름 의미있는 시간이었다고 생각이 들었다.

그리고 새로운 강의로 Dart 기초 강의가 생겼다.

원래도 개발자 출신이어서 이번주 공부도 크게 어렵지는 않았지만,
React를 하면서 객체지향을 많이 생각 안하게 되었었는데, 이번에 강의를 들으면서 기억이 조금씩 되살아나는 듯한 느낌이 들었다.

어느정도 아는 내용이 대부분이었지만, 아예 새로운 언어이기 때문에 강의는 조금씩 스킵하면서 내가 모르던 내용이 있었는지 체크하는 위주로 봤던 것 같다.

TIL은 매일 쓰고는 있는데 솔직히 귀찮다....

Dart 문법 정리 (반복문) - TIL (20250623)
Dart 문법 정리 (열거형) - TIL (20250624)
Dart 문법 정리 (컬렉션) - TIL (20250625)
Dart 문법 정리 (함수형 프로그래밍) - TIL (20250626)
Dart 문법 정리 / 객체지향프로그래밍 (클래스) - TIL (20250627)

기존에 했던 언어들이랑 달랐던 부분이라고 하면 네임드 생성자라고 다른 방식으로 초기화를 할 수 있는게 조금 신기했다.
그리고 Dart에서 사용하는 함수도 좀 알게되었다.

개인적인 체감상으로는 전체적인 구조가 Java와 JavaScript의 장점을 합한게 아닐까 하는 느낌이 드는 언어인 것 같다.

클래스

객체지향 프로그래밍(Object-Oriented Programming)의 핵심 개념이자 기본 단위

객체(Object)
클래스에서 정의한 구조를 기반으로 생성된 실제 데이터

클래스는 여러 속성(Attribute)과 메서드(Method)를 하나로 묶은 개념
속성(Attribute)

  • 클래스 안에서 작업을 수행할 때 사용 하는 데이터
  • 메서드(Method)*
  • 객체의 동작을 정의하는 함수
  • 속성을 변경하거나 객체를 가지고 특정 작업을 수행

함수와 메서드의 차이
메서드는 클래스에 의존하고 함수는 클래스에 의존하지 않는다는 차이점이 있다

메서드의 종류

  • 인스턴스 메서드 (Instance Method)
    • 객체에 속해 있는 메서드
    • this를 통해 접근 가능
    • 클래스의 모든 곳에서 접근 가능
  • 정적 메서드 (Static Method)
    • 클래스 메서드라고 부름
    • 클래스 이름을 통해 호출
    • 객체를 통해 호출 불가능
    • this를 통해 호출 불가능
    • 코드 블록에서 인스턴스 변수 사용 불가
  • 생성자
    • 기본 생성자
      • 매개변수를 갖지 않는 생성자
    • 매개변수 생성자
      • 매개변수를 갖는 생성자
      • 매개변수를 통해 외부에서 인스턴스 변수들의 초기 값을 설정
    • 네임드 생성자
      • 클래스 메서드와 같은 형식으로 호출하는 생성자

함수형 프로그래밍이란?

함수의 연속으로 프로그램을 구성하는 방식

  • 함수의 연속?
    • 메서드 체이닝 이라고도 불림
  • 가변적인 데이터의 사용을 최소화하여 프로그램을 구성하는 방식

 

함수형 프로그래밍의 특징

  • 순수 함수 (Pure Functions) 지향:
    • 동일한 입력에는 항상 동일한 출력을 반환합니다. (예측 가능성)
    • 함수 외부의 어떤 상태도 변경하지 않고, 외부 상태에 의존하지 않습니다. (부수 효과(Side Effect) 없음)
    • 이러한 특성 덕분에 테스트가 용이하고 병렬 처리, 동시성 문제 해결에 유리합니다.
  • 불변성 (Immutability):
    • 데이터를 변경하지 않고, 변경이 필요한 경우 원본 데이터의 복사본을 만들어 변경합니다.
    • 이는 프로그램의 상태 변화를 예측하기 쉽게 만들고, 특히 멀티스레드 환경에서 데이터 불일치 문제를 방지하는 데 도움을 줍니다.
  • 1급 객체로서의 함수 (First-Class Functions):
    • 함수를 변수에 할당하거나, 다른 함수의 인자로 전달하거나, 함수의 반환 값으로 사용할 수 있습니다.
    • 이는 고차 함수(Higher-Order Functions)를 구현하는 기반이 됩니다.
  • 부수 효과(Side Effect) 최소화:
    • 함수 외부의 상태를 변경하거나, 외부 상태에 영향을 미치는 작업을 최소화합니다.
    • 예를 들어, 전역 변수 변경, 파일 I/O, 콘솔 출력 등은 부수 효과로 간주될 수 있습니다.
  • 선언적 프로그래밍:
    • 프로그램이 "무엇"을 해야 하는지에 초점을 맞춥니다.
    • "어떻게" 해야 하는지에 대한 구체적인 절차보다는 함수의 조합을 통해 문제를 해결합니다.
  • 고차 함수 (Higher-Order Function)
    • 하나 이상의 함수를 인자(argument)로 받거나, 함수를 결과(return value)로 반환하여 사용

함수형 프로그래밍에 많이 사용하는 함수

  • 형변환 함수
    • toString(): 현재 객체를 문자열(String)으로 변환합니다.
    • int.parse(''): 주어진 문자열을 정수(int)로 변환합니다. (예: '123'123으로)
    • double.parse(''): 주어진 문자열을 실수(double)로 변환합니다. (예: '3.14'3.14로)
    • toList(): 반복 가능한(Iterable) 객체를 List로 변환합니다. List는 순서가 있고 중복을 허용합니다.
    • toSet(): 반복 가능한(Iterable) 객체를 Set으로 변환합니다. Set은 순서가 없으며 중복을 허용하지 않습니다.
    • toMap(): 반복 가능한(Iterable) 객체를 Map으로 변환합니다. 일반적으로 MapEntry 객체들의 Iterable에 사용됩니다.
  • 고차 함수(Higher-order Function)
    • map(): 각 요소를 새로운 형태로 변환하여 새 Iterable을 반환합니다.
    • where(): 주어진 조건을 만족하는 요소들만 포함하는 Iterable을 반환합니다.
    • firstWhere(): 주어진 조건을 만족하는 첫 번째 요소를 반환합니다. 일치하는 요소가 없을 경우를 대비하여 선택적으로 orElse 함수를 제공할 수 있습니다.
    • lastWhere(): 주어진 조건을 만족하는 마지막 요소를 반환합니다. 일치하는 요소가 없을 경우를 대비하여 선택적으로 orElse 함수를 제공할 수 있습니다.
    • reduce(): 첫 번째 요소부터 시작하여 함수를 적용하여 Iterable의 모든 요소를 단일 값으로 결합합니다.
    • fold(): 초기 값부터 시작하여 함수를 적용하여 Iterable의 모든 요소를 단일 값으로 결합합니다.
    • any(): Iterable최소한 하나의 요소라도 주어진 조건을 만족하는지 확인하여 true 또는 false를 반환합니다.
    • every(): Iterable모든 요소가 주어진 조건을 만족하는지 확인하여 true 또는 false를 반환합니다.
    • takeWhile(): 주어진 조건을 만족하는 동안의 요소들을 포함하는 Iterable을 반환합니다.
    • skipWhile(): 주어진 조건을 만족하는 동안의 요소들을 건너뛰고 나머지 요소들을 포함하는 Iterable을 반환합니다.

컬렉션

여러개의 값을 그룹으로 묶어서 효율적으로 관리 가능함

종류에는 List, Set, Map이 있음

List

  • 순서가 있는 값들이 묶인 형태
  • 배열이라고도 부른다
List<타입> 변수명 = 요소;

tip: 변수에 runtimeType으로 현재 변수의 타입을 알 수 있다
numbers.runtimeType // List<int>

Set

  • 중복을 허용하지 않는 값들이 묶인 형태
Set<int> numbers = {1,2,3,4,5}

Map

  • Key와 Value가 묶인 하나의 쌍으로 이루어진 형태
Map<String, String> people = {'Alice': 'Teacher', 'Bob': 'Student'}

키는 중복될 수 없지만 값은 중복될 수 없다.

열거형

  • 여러개의 상수 값을 묶은 형태
enum Color { red, green, blue }

enum Animal {
    cat,
    dog,
    tiger,
    elephant
}

Switch와 묶어서 사용 가능

enum Color { red, green, blue }

var myFavoriteColor = Color.green;
var result = "내가 제일 좋아하는 색은 ";

switch (myFavoriteColor) {
    case Color.red:
        result += "빨간색";
    case Color.green:
        result += "초록색";
    case Color.blue;
        result += "파란색";
}

print(result); // 내가 제일 좋아하는 색은 초록색

values, name을 통해 안의 값들을 확인 할 수 있다

values

enum Color { red, green, blue }

var colors = Color.values;
print(colors); // [Color.red, Color.green, Color.blue]
print(colors[1]); // Color.green

name

enum Color { red, green, blue }

print(Color.red.name); // red

tip: enum은 순서 개념이 있고 중복 데이터를 넣을 수 없다

+ Recent posts