Dart에서 객체 지향 프로그래밍: 클래스와 객체 심화 가이드

2024. 6. 21. 15:50Flutter/Dart Language

반응형

객체 지향 프로그래밍 (OOP)은 복잡한 문제를 작은, 관리하기 쉬운 객체로 분해하여 해결하는 프로그래밍 패러다임입니다. Dart는 객체 지향 언어로서, 클래스, 객체, 상속, 다형성 등 다양한 OOP 개념을 지원합니다.

이 블로그 게시물에서는 Dart에서 클래스와 객체를 사용하는 방법에 대해 심층적으로 살펴보겠습니다. 이미 기본적인 내용을 알고 있다고 가정하고, 더욱 심도 있는 이해를 위한 내용을 다루겠습니다.

1. 클래스 정의

클래스는 객체의 설계도를 정의하는 역할을 합니다.

클래스에는 객체의 속성 (멤버 변수)과 행위 (메서드)를 정의합니다.

class User {
  // 속성 (멤버 변수)
  String name;
  int age;

  // 생성자
  User(this.name, this.age);

  // 메서드
  void printInfo() {
    print("이름: $name, 나이: $age");
  }
}
 

위 코드에서는 User라는 클래스를 정의했습니다.

  • name과 age는 User 객체의 속성입니다.
  • User 생성자는 객체를 만들 때 사용됩니다.
  • printInfo 메서드는 객체의 정보를 출력하는 역할을 합니다.

2. 생성자

생성자는 객체를 만들 때 사용되는 함수입니다. 객체의 초기 속성값을 설정하는 역할을 합니다.

class User {
  // 속성 (멤버 변수)
  String name;
  int age;

  // 기본 생성자
  User() {
    name = "";
    age = 0;
  }

  // 이름과 나이를 매개변수로 받는 생성자
  User.withInfo(this.name, this.age);

  // 메서드
  void printInfo() {
    print("이름: $name, 나이: $age");
  }
}
 

위 코드에서는 두 개의 생성자를 정의했습니다.

  • User()는 기본 생성자로, 객체의 속성값을 초기화합니다.
  • User.withInfo(name, age)는 이름과 나이를 매개변수로 받는 생성자입니다.

3. 메서드

메서드는 객체의 행위를 정의하는 함수입니다. 객체의 속성에 접근하고 변경하거나, 다른 객체와 상호 작용하는 데 사용됩니다.

class User {
  // 속성 (멤버 변수)
  String name;
  int age;

  // 생성자
  User(this.name, this.age);

  // 메서드
  void printInfo() {
    print("이름: $name, 나이: $age");
  }

  bool isAdult() {
    return age >= 18;
  }
}
 

위 코드에서는 printInfo와 isAdult 두 개의 메서드를 정의했습니다.

  • printInfo 메서드는 객체의 정보를 출력합니다.
  • isAdult 메서드는 객체의 나이가 18세 이상인지 확인합니다.

4. getter와 setter

getter와 setter는 객체의 속성에 대한 접근 방식을 제어하는 데 사용됩니다.

 

getter: 객체의 속성값을 가져오는 메서드입니다.

class User {
  // 속성 (멤버 변수)
  String _name; // _로 시작하면 private 속성

  // getter
  String get name => _name;

  // 생성자
  User(this._name);

  // 메서드
  void printInfo() {
    print("이름: $name"); // getter 사용
  }
}
 

setter: 객체의 속성값을 설정하는 메서드입니다.

class User {
  // 속성 (멤버 변수)
  String _name; // _로 시작하면 private 속성

  // getter
  String get name => _name;

  // setter
  set name(String value) {
    if (value.length > 10) {
      throw ArgumentError("이름은 10자 이하로 입력해야 합니다.");
    }
    _name = value;
  }

  // 생성자
  User(this._name);

  // 메서드
  void printInfo() {
    print("이름: $name"); // getter 사용
  }
}
 

위 코드에서는 name 속성에 대한 getter와 setter 메서드를 정의했습니다.

  • getter 메서드는 _name 속성값을 반환합니다.
  • setter 메서드는 value라는 매개변수를 통해 새로운 속성값을 받습니다.
  • if 문을 사용하여 입력값의 유효성을 검사합니다. 입력값의 길이가 10자를 초과하면 ArgumentError 예외를 발생시킵니다.
  • 유효성 검사를 통과하면 _name 속성값을 새로운 값으로 설정합니다.

setter 메서드를 사용하면 다음과 같은 장점이 있습니다.

  • 데이터 유효성 검사: setter 메서드에서 입력값의 유효성을 검사하고, 유효하지 않은 값이 입력될 경우 예외를 발생시키거나 오류 메시지를 출력할 수 있습니다.
  • 비즈니스 로직 적용: setter 메서드에서 속성값을 변경하는 것과 관련된 비즈니스 로직을 적용할 수 있습니다.
  • 변경 이벤트 처리: setter 메서드에서 속성값이 변경될 때 발생하는 이벤트를 처리할 수 있습니다.

5. static 키워드

static 키워드는 클래스 자체와 관련된 속성이나 메서드를 정의하는 데 사용됩니다.

 

static 속성: 클래스의 모든 인스턴스에서 공유되는 속성입니다.

class User {
  // static 속성
  static int count = 0; // 생성된 User 객체의 개수

  // 속성 (멤버 변수)
  String name;
  int age;

  // 생성자
  User(this.name, this.age) {
    count++;
  }

  // 메서드
  void printInfo() {
    print("이름: $name, 나이: $age");
  }
}
 

위 코드에서는 count라는 static 속성을 정의했습니다.

  • count 속성은 모든 User 객체에서 공유됩니다.
  • 새로운 User 객체가 생성될 때마다 count 속성값이 1씩 증가합니다.

static 메서드: 클래스 자체를 호출하여 사용되는 메서드입니다.

class User {
  // static 메서드
  static User fromJson(Map<String, dynamic> json) {
    String name = json['name'];
    int age = json['age'];
    return User(name, age);
  }

  // 속성 (멤버 변수)
  String name;
  int age;

  // 생성자
  User(this.name, this.age);

  // 메서드
  void printInfo() {
    print("이름: $name, 나이: $age");
  }
}
 

위 코드에서는 fromJson이라는 static 메서드를 정의했습니다.

  • fromJson 메서드는 JSON 문자열을 파싱하여 User 객체를 생성합니다.
  • fromJson 메서드는 클래스 자체를 호출하여 사용됩니다.

static 키워드를 사용하면 다음과 같은 장점이 있습니다.

  • 메모리 효율 향상: static 속성과 메서드는 모든 인스턴스에서 공유되므로 메모리 사용량을 줄일 수 있습니다.
  • 코드 재사용성 향상: static 메서드는 클래스 자체와 관련된 기능을 제공하기 때문에 다른 코드에서 쉽게 재사용할 수 있습니다.
  • 코드 가독성 향상: static 속성과 메서드는 클래스 자체와 관련된 정보를 명확하게 보여주므로 코드 가독성을 향상시킬 수 있습니다.

6.  클래스와 객체 활용 예시

클래스와 객체는 Dart에서 프로그래밍을 구조화하고 코드를 재사용 가능하게 만드는 데 중요한 역할을 합니다.

다음은 다양한 상황에서 클래스와 객체를 활용하는 실제적인 예시입니다.

 

1. 은행 계좌 관리

class BankAccount {
  // 속성 (멤버 변수)
  String name; // 계좌 소유자 이름
  double balance; // 계좌 잔액

  // 생성자
  BankAccount(this.name, this.balance);

  // 메서드
  void deposit(double amount) {
    if (amount <= 0) {
      throw ArgumentError("입금 금액은 0보다 커야 합니다.");
    }
    balance += amount;
    print("$name 님, $amount 원 입금되었습니다. 현재 잔액: $balance 원");
  }

  void withdraw(double amount) {
    if (amount <= 0) {
      throw ArgumentError("출금 금액은 0보다 커야 합니다.");
    }
    if (amount > balance) {
      throw ArgumentError("출금 금액이 잔액보다 많습니다.");
    }
    balance -= amount;
    print("$name 님, $amount 원 출금되었습니다. 현재 잔액: $balance 원");
  }

  void printInfo() {
    print("계좌 소유자: $name");
    print("계좌 잔액: $balance 원");
  }
}

void main() {
  // BankAccount 객체 생성
  BankAccount myAccount = BankAccount("김철수", 100000.0);

  // 입금 및 출금
  myAccount.deposit(50000.0);
  myAccount.withdraw(20000.0);

  // 계좌 정보 출력
  myAccount.printInfo();
}
 

2. 제품 관리

class Product {
  // 속성 (멤버 변수)
  String name; // 제품 이름
  double price; // 제품 가격
  int stock; // 재고 수량

  // 생성자
  Product(this.name, this.price, this.stock);

  // 메서드
  void sell(int quantity) {
    if (quantity <= 0) {
      throw ArgumentError("판매 수량은 0보다 커야 합니다.");
    }
    if (quantity > stock) {
      throw ArgumentError("재고 부족입니다.");
    }
    stock -= quantity;
    print("$name 제품, $quantity 개 판매되었습니다. 현재 재고: $stock 개");
  }

  void printInfo() {
    print("제품 이름: $name");
    print("가격: $price 원");
    print("재고: $stock 개");
  }
}

void main() {
  // Product 객체 생성
  Product smartPhone = Product("스마트폰", 1000000.0, 100);
  Product laptop = Product("노트북", 1500000.0, 50);

  // 제품 판매
  smartPhone.sell(20);
  laptop.sell(10);

  // 제품 정보 출력
  smartPhone.printInfo();
  laptop.printInfo();
}

3. 게임 캐릭터 관리

class GameCharacter {
  // 속성 (멤버 변수)
  String name; // 캐릭터 이름
  int level; // 레벨
  int hp; // 체력
  int mp; // 마나
  int maxHp; // 최대 체력

  // 생성자
  GameCharacter(this.name, this.level, this.hp, this.mp, {this.maxHp = 100});

  // 메서드
  void attack(GameCharacter enemy) {
    if (enemy == null) {
      throw ArgumentError("공격 대상이 null입니다.");
    }
    enemy.hp -= 100;
    print("$name 이 $enemy 를 공격했습니다! $enemy 의 체력: ${enemy.hp} 남았습니다.");
  }

  void heal() {
    if (hp + 50 > maxHp) {
      hp = maxHp;
    } else {
      hp += 50;
    }
    print("$name 이 체력을 회복했습니다! 현재 체력: $hp");
  }

  void printInfo() {
    print("이름: $name");
    print("레벨: $level");
    print("체력: $hp/$maxHp");
    print("마나: $mp");
  }
}

void main() {
  // GameCharacter 객체 생성
  GameCharacter hero = GameCharacter("히어로", 10, 100, 50);
  GameCharacter monster = GameCharacter("몬스터", 5, 50, 0);

  // 캐릭터 정보 출력
  hero.printInfo();
  monster.printInfo();

  // 공격 및 치유
  hero.attack(monster);
  monster.attack(hero);
  hero.heal();

  // 캐릭터 정보 출력
  hero.printInfo();
  monster.printInfo();
}

4. 게시판 시스템 

class Post {
  // 속성 (멤버 변수)
  int id; // 게시글 ID
  String title; // 게시글 제목
  String content; // 게시글 내용
  String author; // 작성자
  DateTime createdAt; // 작성 시간

  // 생성자
  Post(this.id, this.title, this.content, this.author, this.createdAt);
}

class Board {
  // 속성 (멤버 변수)
  String name; // 게시판 이름
  List<Post> posts; // 게시글 목록

  // 생성자
  Board(this.name, this.posts);

  // 메서드
  void addPost(Post post) {
    posts.add(post);
    print("${post.title} 게시글이 $name 게시판에 등록되었습니다.");
  }

  void getPost(int id) {
    for (Post post in posts) {
      if (post.id == id) {
        print("게시글 제목: ${post.title}");
        print("게시글 내용: ${post.content}");
        print("작성자: ${post.author}");
        print("작성 시간: ${post.createdAt}");
        return;
      }
    }
    print("해당 ID의 게시글을 찾을 수 없습니다.");
  }

  void deletePost(int id) {
    for (int i = 0; i < posts.length; i++) {
      if (posts[i].id == id) {
        posts.removeAt(i);
        print("${id}번 게시글이 삭제되었습니다.");
        return;
      }
    }
    print("해당 ID의 게시글을 찾을 수 없습니다.");
  }

  void editPost(int id, String newTitle, String newContent) {
    for (int i = 0; i < posts.length; i++) {
      if (posts[i].id == id) {
        posts[i].title = newTitle;
        posts[i].content = newContent;
        print("${id}번 게시글이 수정되었습니다.");
        return;
      }
    }
    print("해당 ID의 게시글을 찾을 수 없습니다.");
  }

  void listPosts() {
    if (posts.isEmpty) {
      print("$name 게시판에는 아직 게시글이 없습니다.");
      return;
    }
    print("$name 게시판 목록:");
    for (Post post in posts) {
      print("  - [${post.id}] ${post.title}");
    }
  }
}

void main() {
  // 게시판 생성
  Board board = Board("공지사항", []);

  // 게시글 생성 및 추가
  Post post1 = Post(1, "공지사항 1", "게시판 이용 안내", "관리자", DateTime.now());
  Post post2 = Post(2, "공지사항 2", "중요 업데이트 공지", "개발팀", DateTime.now().subtract(Duration(days: 1)));

  board.addPost(post1);
  board.addPost(post2);

  // 게시글 목록 확인
  board.listPosts();

  // 게시글 조회
  board.getPost(1);
  board.getPost(3); // 존재하지 않는 게시글 ID

  // 게시글 수정
  board.editPost(1, "수정된 제목", "수정된 내용");
  board.editPost(3, "수정된 제목", "수정된 내용"); // 존재하지 않는 게시글 ID

  // 게시글 삭제
  board.deletePost(1);
  board.deletePost(3); // 존재하지 않는 게시글 ID
}
 

5. 쇼핑몰 시스템

class Product {
  // 속성 (멤버 변수)
  int id; // 제품 ID
  String name; // 제품 이름
  double price; // 가격
  int stock; // 재고 수량

  // 생성자
  Product(this.id, this.name, this.price, this.stock);
}

class Cart {
  // 속성 (멤버 변수)
  List<Product> items; // 장바구니에 담긴 제품 목록

  // 생성자
  Cart();

  // 메서드
  void addProduct(Product product) {
    items.add(product);
    print("${product.name} 제품이 장바구니에 추가되었습니다.");
  }

  void removeProduct(Product product) {
    items.remove(product);
    print("${product.name} 제품이 장바구니에서 제거되었습니다.");
  }

  void checkout() {
    double totalPrice = 0.0;
    for (Product product in items) {
      totalPrice += product.price;
    }
    print("총 결제 금액: $totalPrice 원");
    print("결제가 완료되었습니다. 감사합니다!");
    items.clear(); // 구매 후 장바구니 비우기
  }
}

void main() {
  // 제품 생성
  Product product1 = Product(1, "스마트폰", 1000000.0, 10);
  Product product2 = Product(2, "노트북", 1500000.0, 5);

  // 장바구니 생성
  Cart cart = Cart();

  // 장바구니에 제품 추가
  cart.addProduct(product1);
  cart.addProduct(product2);

  // 장바구니 확인
  cart.items.forEach((product) => print("${product.name} (가격: ${product.price}원)"));

  // 제품 제거
  cart.removeProduct(product1);

  // 결제
  cart.checkout();
}
 

6. 채팅 시스템

class User {
  // 속성 (멤버 변수)
  String name;

  // 생성자
  User(this.name);

  // 메서드
  void sendMessage(String message) {
    print("$name: $message");
  }
}

class ChatRoom {
  // 속성 (멤버 변수)
  List<User> users; // 채팅방 참여자 목록
  List<String> messages; // 채팅 메시지 목록

  // 생성자
  ChatRoom();

  // 메서드
  void join(User user) {
    users.add(user);
    print("$name 님이 채팅방에 입장했습니다.");
    messages.add("${user.name} 님이 입장했습니다.");
  }

  void leave(User user) {
    users.remove(user);
    print("$name 님이 채팅방을 나갔습니다.");
    messages.add("${user.name} 님이 나갔습니다.");
  }

  void sendMessage(User user, String message) {
    messages.add("[$name] $message");
    for (User user in users) {
      if (user != this.user) {
        user.sendMessage("[${this.user.name}] $message");
      }
    }
  }
}

void main() {
  // 사용자 생성
  User user1 = User("철수");
  User user2 = User("영희");
  User user3 = User("민수");

  // 채팅방 생성
  ChatRoom chatRoom = ChatRoom();

  // 채팅방 참여
  chatRoom.join(user1);
  chatRoom.join(user2);
  chatRoom.join(user3);

  // 메시지 전송
  user1.sendMessage("안녕하세요!");
  user2.sendMessage("반갑습니다!");
  user3.sendMessage("ㅎㅇㅎㅇ");

  // 채팅방 나가기
  chatRoom.leave(user3);
}
 

 

 

7. 객체 지향 프로그래밍의 장점과 단점

객체 지향 프로그래밍(OOP)은 객체를 기반으로 프로그램을 설계하는 프로그래밍 패러다임입니다. 객체는 데이터(속성)와 동작(메서드)를 가지고 있는 독립적인 단위입니다.

OOP는 다음과 같은 장점을 가지고 있습니다.

 

장점

  • 코드 재사용성: 상속과 다형성을 통해 코드를 재사용하여 개발 효율성을 높일 수 있습니다.
  • 유지보수 용이: 코드를 모듈화하여 관리하기 쉽고, 변경 시 영향 범위를 줄일 수 있습니다.
  • 명확한 코드 구조: 객체를 기반으로 설계하기 때문에 코드 구조가 명확하고 이해하기 쉽습니다.
  • 확장성: 새로운 기능을 추가하거나 변경하기 쉽습니다.

단점

  • 추상화 어려움: 객체 지향 설계는 추상화 능력이 필요하며, 처음에는 어려울 수 있습니다.
  • 런타임 오버헤드: 객체 생성 및 메서드 호출 시 런타임 오버헤드가 발생할 수 있습니다.
  • 과도한 객체 사용: 필요 이상으로 객체를 사용하면 오히려 코드를 복잡하게 만들 수 있습니다.

적절한 사용

 

객체 지향 프로그래밍은 모든 프로그램에 적합한 것은 아닙니다. 다음과 같은 경우 객체 지향 프로그래밍을 사용하는 것이 효과적입니다.

  • 대규모 프로젝트: 대규모 프로젝트는 코드를 모듈화하고 유지 관리하기 쉬워야 하기 때문에 객체 지향 프로그래밍이 적합합니다.
  • 복잡한 시스템: 복잡한 시스템은 객체를 사용하여 시스템을 작은 모듈로 분해하고 이해하기 쉽게 만들 수 있습니다.
  • 변경 가능성이 높은 프로젝트: 변경 가능성이 높은 프로젝트는 객체 지향 프로그래밍을 사용하여 변경에 유연하게 대처할 수 있습니다.

선택

 

객체 지향 프로그래밍을 사용할지 여부를 결정하기 전에 프로젝트의 특성을 고려해야 합니다. 프로젝트의 규모, 복잡성, 변경 가능성 등을 고려하여 적절한 프로그래밍 패러다임을 선택해야 합니다.

 

객체 지향 프로그래밍은 강력한 도구이지만, 모든 상황에 적합한 것은 아닙니다. 장점과 단점을 이해하고 적절하게 사용해야 합니다.

 

객체 지향 프로그래밍은 꾸준히 배우고 연습해야 숙련될 수 있는 기술입니다.

위에 소개된 자료들을 활용하고 꾸준히 노력한다면 여러분도 훌륭한 객체 지향 프로그래머가 될 수 있을 것입니다.

궁금한 점이 있으면 언제든지 물어보세요!

 

 

수발가족을 위한 일기장 “나비일기장”

 

https://play.google.com/store/apps/details?id=com.maccrey.navi_diary_release

 

구글플레이 앱 배포의 시작! 비공개테스트 20명의 테스터모집을 위한 앱 "테스터 쉐어"

 

https://play.google.com/store/apps/details?id=com.maccrey.tester_share_release

 

Tester Share [테스터쉐어] - Google Play 앱

Tester Share로 Google Play 앱 등록을 단순화하세요.

play.google.com

카카오톡 오픈 채팅방

https://open.kakao.com/o/gsS8Jbzg

 

반응형