배운것들을 정리합니다.
三昧境

Flutter/tip

[플러터 Flutter] 객체지향(Object Oriented Programing,OOP)의 이해

ujo_orr 2024. 7. 5. 01:27

객체지향 프로그래밍

Object Oriented Programing의 앞글자만 따서 OOP라고 불립니다. 이는 프로그래밍 패러다임의 하나로, 데이터를 객체로 다루며, 객체의 상호작용을 통해 프로그램을 설계하는 방식입니다.

쉽게 생각했을 때 아주 잘 정리된 코드 또는 구조방식이라고 볼 수 있습니다.
정리가 잘 되어있는 만큼 문제가 생긴 구간을 찾아서 유지보수하거나 사용하기 쉬울 것입니다.

 


 

객체 (Object)

데이터를 구조화하고 관련 기능을 함께 묶어 하나의 단위로 관리하는 방법입니다.
클래스를 기반으로 메모리에 생성된 구체적인 실체이며, 속성과 데이터를 처리하는 메서드를 포함합니다.

객체는 상태와 행동을 가지며, 이를 속성(Properties)메서드(Methods)라고 하며, 클래스의 인스턴스입니다.

 

객체의 구성 요소

  • 속성 (properties) : 객체가 가지는 데이터나 상태를 의미합니다. 속성은 보통 클래스에서 변수로 정의되며, 객체가 생성될 때 이 변수들이 초기화됩니다.

  • 메서드 (Methods) : 객체가 할 수 있는 행동이나 기능을 정의합니다. 메서드는 보통 클래스에서 함수로 정의되며, 객체의 상태를 변경하거나 특정 작업을 수행할 수 있습니다.

*Properties와 Attributes는 "속성"이라는 뜻으로 같지만 플러터에서는 Properties로 자주 쓰입니다.
Attributes는 HTML이나 XML에서 사용되며 플러터에서는 직접적으로 사용되진 않습니다.

 

게임을 예시로 들었을 때

객체 = 플레이어
이 플레이어들은 저 마다 다른 데이터 (아이템, 레벨)를 가지고 있습니다.

Player {
  String name = "hunter";
  String item = "gun"; 
  int level = 50;
}

 

하지만 이 객체들이 여러 개 일 경우에는

PlayerOne {
  String name = "hunter";
  String item = "gun";
  int level = 50;
}

PlayerTwo {
  String name = "archer";
  String item = "bow";
  int level = 40;
}

PlayerThree {
  String name = "wizard";
  String item = "staff";
  int level = 30;
}

이러한 형태처럼 상당히 많은 수의 객체들이 발생할 것입니다.

여기서 이 객체들은 "name", "item", "level" 처럼 다 동일한 속성(properties)을 가지고 있습니다.
다른 점이라고는 "hunter", "archer", "wizard"처럼 데이터(data)만 다를 뿐입니다.

허나 이는 플레이어가 하나하나 늘어날수록 코드를 일일이 복사해야 한다거나 일일이 쓰면서 발생하는
속성들을 하나씩 빼먹는다거나, 오탈자로 인해 버그가 발생한다거나 하는 이슈가 발생할 수도 있습니다.
그리고 나중에 "xp"라는 속성을 추가하려 하면 일일이 하나하나의 객체에 들어가서 수정해 줘야 한다는 불편함도 있습니다.

이것을 해결하기 위한 개념이 바로 class 입니다.

 


 

클래스 (Class)

객체를 정의하는 틀로, 객체의 속성(properties)행위(Method)를 정의합니다.

 

1번 (속성)

Dart에서 클래스는 class 키워드로 시작하며 class명은 파스칼케이스로 이루어집니다.
{}중괄호 안에 속성(properties)및 매서드(Method)를 담을 수 있으며, 위 클래스는 클래스 내부에서 속성별로 값을 담고 있습니다.

 


 

생성자 (Constructor)

생성자는 객체가 생성될 때 호출할 수 있으며, 객체의 초기 상태를 설정하는 역할을 합니다.
생성자는 객체와 같은 이름을 사용합니다.

2번

1번과 다르게 이 클래스는 'name'과 'item'은 속성에 대한 값을 가지고 있지 않습니다.
이 경우에는 객체(Object) 내 생성자(Constructor) 내부에서 this 키워드를 사용하여 인스턴스 변수와 매개변수를 구분할 수 있습니다.

 

[플러터 Flutter] 매개변수(Parameter), 인스턴스변수(InstanceVariable), 전역변수(GlobalVariable), 지역변수(Lo

매개변수 (Parameter) // ()소괄호 안이 비어있음. player함수는 parameter가 없음.void player() { String name = "name";}// ()소괄호 내부가 parameter.void player(String name) { print(name);}매개변수는 함수나 생성자에서 사

ujo-orr.tistory.com

(인스턴스변수와 매개변수는 블로그글 참고 하시면 됩니다.)

this 키워드는 현재 객체 자신을 참조하는 데 사용됩니다. Dart에서 생성자 내에서 this 키워드를 사용하여 인스턴스 변수와 매개변수를 구분할 수 있습니다.

this.name, this.item을 사용하여 인스턴스 변수 name과 item을 참조합니다.


객체지향의 4가지 특성

 

상속 (Inheritance)

새로운 클래스(자식클래스)가 기존클래스(부모클래스)의 속성과 메서드를 물려받는 기능입니다.
상속을 통해 코드의 재사용성을 높이고, 클래스 간의 계층 구조를 형성하여 코드를 구조화할 수 있습니다.

// 부모 클래스
class Player {
  String name;
  int level;
  
  Player(this.name, this.level);
  
  speak() {
    print('my name is $name');
  }
}

// 자식 클래스
class Hunter extends Player {
  Hunter(String name,int level) : super(name,level);  // 부모 클래스의 생성자 호출
  
  roud() {
    print('$name!, i\'m steel level$level!');
  }
}

void main() {
  var hunter = Hunter('Tony',1);
  hunter.speak();  // 상속받은 메서드 호출
  hunter.roud();   // 자식 클래스의 메서드 호출
}

// 출력
// my name is Tony
// Tony!, i'm steel level1!

부모클래스의 생성자는 this 키워드를 사용한다면
자식클래스의 생성자는 super 키워드를 사용합니다.


캡슐화 (Encapsulation)

데이터와 그 데이터를 처리하는 메서드를 하나의 단위로 묶는 것을 말합니다.
즉, 객체의 속성과 메서드를 하나로 캡슐화하여 외부에서 직접 접근할 수 없도록 보호하고, 객체의 내부 구현을 숨기는 것입니다.
이는 객체의 데이터 유효성을 보장하고, 객체 간의 결합도를 낮춰 유지보수성을 높이는 데 도움을 줍니다.

주로 접근 제어자(Private)를 사용하여 구현합니다.

 

[플러터 Flutter] Private, getter/setter가 무엇인가? 어떻게 사용하는가?

프라이빗 (Private) Dart에서는 _name처럼 변수나 메서드의 이름 앞에 _언더스코어를 붙이면 이 변수나 메서드를 프라이빗 취급합니다.프라이빗은 (같은 파일 내) 클래스 내부에서만 접근 가능하며

ujo-orr.tistory.com

(private 함수는 블로그글을 참고하시면 됩니다.)

class Inventory {
  String _userID; // 캡슐화된 데이터 필드
  double _balance = 0; // 캡슐화된 데이터 필드
  
  Inventory(this._userID);
  
  deposit(double amount) {
    if (amount > 0) {
      _balance += amount;
    }
  }
  
  double getBalance() {
    return _balance;
  }
}

void main() {
  var account = Inventory('Tony');
  account.deposit(1000);
  print('Balance: ${account.getBalance()}');
}

다형성 (Polymorphism)

하나의 인터페이스나 추상 클래스를 사용하여 다양한 클래스의 객체를 참조할 수 있는 능력을 말합니다.
다형성은 서브 클래스가 슈퍼 클래스의 메서드를 오버라이드(재정의)하거나 동일한 인터페이스를 구현함으로써 구현됩니다.
이는 코드의 유연성과 확장성을 높이며, 같은 코드를 사용하여 다양한 객체를 처리할 수 있도록 합니다.

주로 메서드 오버라이드(@override)를 통해 구현됩니다.

// 추상 클래스
abstract class Player {
  int level;
  void attack(); // 추상 메서드
  
  Player(this.level);
}

// 플레이어 클래스 1
class Mage extends Player {
  Mage(int level) : super(level);  // 부모 클래스의 생성자 호출

  @override
  void attack() {
    print('magic');
  }
}

// 플레이어 클래스 2
class Hunter extends Player {
  Hunter(int level) : super(level);  // 부모 클래스의 생성자 호출

  @override
  void attack() {
    print('shoot');
  }
}

void main() {
  List<Player> players = [
    Mage(1),
    Hunter(50),
  ];
  
  for (var player in players) {
    player.attack();  // 다형성을 통해 각 객체의 attack 메서드 호출
  }
}

추상화 (Abstraction)

복잡한 시스템을 단순화하여 중요한 부분(속성, 메서드)만 표현하고 불필요한 세부사항을 숨기는 것을 의미합니다.
주로 추상 클래스와 인터페이스를 통해 구현됩니다.
abstract 키워드를 사용하여 구현합니다.

abstract class Player {
  void attack();  // 추상 메서드
}

class Mage extends Player {
  @override
  void attack() {
    print('magic');
  }
}

class Hunter extends Player {
  @override
  void attack() {
    print('shoot');
  }
}

void main() {
  var mage = Mage();
  var hunter = Hunter();
  
  mage.attack();
  hunter.attack();
}

 


 

번외

 

mixin / with / on

 

mixin과 with 키워드는 객체지향의 4가지 특성(상속, 캡슐화, 다형성, 추상화) 중에서 상속  다형성과 관련이 있습니다.
다중 상속의 일부 기능 즉, 여러 클래스에서 재사용할 수 있는 기능(모듈화)을 제공합니다.
믹스인은 여러 클래스의 메서드와 속성을 하나의 클래스에 혼합할 수 있게 합니다.

mixinwith, on 키워드를 사용합니다.

on 키워드는 믹스인을 정의할 때 특정 슈퍼 클래스 또는 인터페이스를 요구함으로써 믹스인의 사용을 제한하고, 안전하게 메서드나 속성을 사용할 수 있도록 합니다.

mixin LevelUp {
  sound() {
    print("level UP");
  }
}

class Mage with LevelUp {}
class Hunter with LevelUp {}

void main() {
  var mage = Mage();
  var hunter = Hunter();
  mage.sound();
  hunter.sound();
}

 

extends와 mixin, with, on 활용

mixin Say on AnotherSay {
  say(String message) {
    print('chat: $message');
  }
}

class AnotherSay {
  doSomething() {
    print('Doing something');
  }
}

class Player extends AnotherSay with Say {
  behavior() {
    doSomething();
    say('HI');
  }
}

var player = Player();

main() {
  player.behavior();
}

 


 

static

 

static키워드는 클래스 인스턴스를 생성하지 않아도 클래스 자체를 통해 접근할 있는 변수를 만들 있습니다.

 

[플러터 Flutter] 매개변수(Parameter), 인스턴스변수(InstanceVariable), 전역변수(GlobalVariable), 지역변수(Lo

매개변수 (Parameter) // ()소괄호 안이 비어있음. player함수는 parameter가 없음.void player() { String name = "name";}// ()소괄호 내부가 parameter.void player(String name) { print(name);}매개변수는 함수나 생성자에서 사

ujo-orr.tistory.com

 

class MyStatic {
  static int number = 5; // static 변수
  
  static void printNumber() { // static 메서드
    print(number);
  }
}

class MyClass {
  int number = 10; // 인스턴스 변수
  
  void printNumber() { // 인스턴스 메서드
    print(number);
  }
}

void main() {
  // Static 멤버는 클래스 이름을 통해 직접 접근할 수 있습니다.
  print(MyStatic.number); // 5 출력
  MyStatic.printNumber(); // 5 출력
  
  // 인스턴스 멤버는 클래스 인스턴스를 통해 접근해야 합니다.
  MyClass myClass = MyClass();
  print(myClass.number); // 10 출력
  myClass.printNumber(); // 10 출력
}

static을 사용하지 않은 클래스 인스턴스들은 인스턴스를 생성해야 사용할 수 있지만

static 인스턴스를 생성하지 않고도 바로 사용이 가능합니다.

MyClass의 오류
일반 클래스는 클래스의 인스턴스를 생성해줘야 사용할 수 있는모습

클래스 인스턴스 멤버에 static을 붙이면 그 멤버는 클래스의 인스턴스가 아니라 클래스 자체에 속합니다.

, 클래스의 모든 인스턴스가 공유하는 변수 또는 메서드가 됩니다.

이러한 방식처럼 다른 클래스에서도 static 멤버를 사용할 수 있습니다.

static의 장점

  • 코드 간결성 향상
  • 재사용성 증대
  • 메모리 효율 개선

static의 주의점

  • static 키워드는 클래스 내부에서만 사용 가능하며, 다른 클래스에서 직접 변수 이름을 사용하여 접근할 수는 없습니다.
    . 연산자를 통해 간접 접근이 가능합니다.
  • static 변수는 인스턴스와 독립적으로 존재하기 때문에, 인스턴스 변수와 이름이 중복될 경우 오류가 발생할 수 있습니다.
  • static 함수는 클래스 인스턴스를 참조할 수 없습니다.

 

또한 이렇게 되면 전역 변수와 비슷할 수 있겠다 생각하실 수 있는데

Static

  • 범위
    • 클래스 내부에서만 유효하며, 클래스의 모든 인스턴스에서 공유됩니다.
  • 생명주기
    • 프로그램이 실행되는 동안 클래스가 로드될 때 생성되고 프로그램이 종료될 때 해체됩니다.
  • 캡슐화
    • 클리스의 일부로 취급되므로 접근 제어자(private)가 적용될 수 있습니다.

전역 변수

  • 범위
    • 프로그램의 모든 부분에서 접근 가능합니다.
  • 생명주기
    • 프로그램이 시작될 때 생성되고 프로그램이 종료될 때 해체됩니다.
  • 캡슐화
    • 캡슐화가 적용되지 않으며, 모든 코드에서 접근할 수 있기 때문에 관리와 디버깅이 어려울 수 있습니다.

 

따라서 static 변수는 클래스와 관련된 데이터를 공유할 때 유용하며, 전역 변수는 프로그램 전체에서 공유되는 데이터를 저장할 때 유용합니다.

전역 변수의 남용은 프로그램의 복잡성과 버그 발생 가능성을 높일 있으므로, 전역 변수 대신 static 변수를 사용하는 것이 나은 경우가 많습니다.

 


 

클래스를 활용하는 것은 굉장히 중요합니다.
Dart는 객체지향 언어로서 대부분의 코드가 클래스로 구성돼 있다고 볼 수 있습니다.

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(

심지어 앱개발을 할 때 맨날 보는 이 코드마저 class로 구성된 것을 볼 수 있습니다.