Flutter 캠프 10일차

은성·2025년 12월 5일

Flutter 본캠프

목록 보기
10/57
post-thumbnail

오늘의 내용

메서드 , 생성자 , 제너릭 클래스 , 상속

아침 스터디

  • for문 익숙해지기
  • print를 for문 안에 쓸경우 1부터 5050까지의 계산이 전부 출력됨
    print를 for문 밖에 쓸 경우 최종 결과값만 출력됨

메서드

  • 클래스 , 객체의 동작을 정의하는 함수
    속성을 변경하거나 객체를 가지고 특정 작업을 수행해요
  • 속성 과 메서드 는 클래스 안에 있는 요소라서 클래스 의 멤버 (Member) 라고 부르기도 해요 !

메서드와 클래스에 차이점

  • 메서드는 클래스에 의존하고, 함수는 클래스에 의존하지 않는다는 차이가 있어요.
void function() {
  print('저는 함수입니다 !');
}

class Class {
	void method() {
		print('저는 메서드입니다 !');
	}
}

void main() {
  function(); // 저는 함수입니다 !
  
  Class object = Class();
  object.method(); // 저는 메서드입니다 !
}

method() 는 클래스 안에 있어서 클래스 의존적이지만,
function() 은 클래스 안에 있지 않아서 클래스 와 독립된 친구랍니다 🙂

인스턴스 메서드

  • 객체에 속해 있는 메서드
class Person {
  String name = '강미래';
	int age = 25;

  void introduce() {
    print('안녕 ? 나는 $age살 $name !');
  }
}

introduce() 가 인스턴스 메서드 에 해당해요.

특징

this 를 통해 접근할 수 있음

class Person {
  String name = '강미래';
  int age = 25;
  
  void printName() {
    print(name);
  }
  
  void printNameAndAge() {
    this.printName();
    print(age);
  }
}

물론, this 없이 printName(); 도 가능하겠죠 ?

클래스의 모든 곳에서 접근할 수 있음

class Person {
  String name = '강미래';
  int age = 25;
  
  void printName() {
    print(name);
  }
  
  void printNameAgain() {
    printName();
  }
}

정적 메서드

  • 클래스 메서드라고 부름
    객체에 종속되지 않고, 클래스 자체에 속하는 메서드
class Circle {
	static double pi = 3.14159;
	
  static void printPi() {
	  print('원주율은 $pi !');
  }
}

printPi() 의 맨 앞에 static 이 붙은 것 보이시나요 ?
static 이 붙은 클래스는 정적 클래스 랍니다 !

정적 메서드 특징

  • 클래스 이름을 통해 호출함
    객체를 생성하지 않고도 메서드를 호출 할 수 있음
class Circle {
	static double pi = 3.14159;
	double radius = 0;
	
  static void printPi() {
	  print(pi);
  }
  
  void printRadius() {
    print(radius);
  }
}

void main() {
	Circle.printPi(); // 3.14159
	Circle.printRadius(); // 오류 발생
}

printRadius() 는 인스턴스 메서드 이기 때문에
클래스 이름으로 접근할 수 없지만,
printPi() 는 정적 메서드 이기 때문에
클래스 이름으로 접근할 수 있어요.
정적 메서드 는 클래스 꺼니까
클래스 이름으로 접근하는게 당연 .. 🙂

객체를 통해 호출 할 수 없음

class Circle {
	static double pi = 3.14159;
	double radius = 0;
	
  static void printPi() {
	  print(pi);
  }
  
  void printRadius() {
    print(radius);
  }
}

void main() {
	Circle circle = Circle();
	circle.radius = 5;
	circle.printRadius(); // 5
	circle.printPi(); // 오류 발생
}

printPi() 는 정적 메서드 여서 객체 로 접근할 수 없지만,
printRadius() 는 인스턴스 메서드 여서 객체 로 접근 가능 !
정적 메서드 는 객체 에 종속된 친구가 아니기 때문에
객체 로 접근할 수 없는게 당연 .. 🙂

this를 통해 호출할 수 없음

class Circle {
	static double pi = 3.14159;
	double radius = 0;
	
  static void printPi() {
	  print(pi);
  }
  
  void printPiAgain() {
	  this.printPi(); // 오류 발생
  }
}

this 는 객체 를 가리키는 키워드인데,
정적 메서드 는 객체에 종속되는 친구가 아니기 때문에
당연히 정적 메서드 에 this 를 못 쓰지 않을까요 ? 🤔

코드 블록에서 인스턴스 변수를 사용할 수 없음

class Circle {
	double pi = 3.14159;
	double radius = 0;
	
  static void printPi() {
	  print(pi); // 오류 발생
  }
}


인스턴트 메서드의 코드블록에서 정적 변수는 사용할 수 있음

class Circle {
	static double pi = 3.14159;
	double radius = 0;
  
  void printArea() {
    print(pi * radius * radius);
  }
}

정적 변수 인 pi 를 인스턴스 메서드 에 사용해 주었네요 !

객체마다 개별적으로 동작하지 않고, 모두 동일하게 동작한다.


생성자

  • 객체를 생성하고, 초기화하기 위해 사용하는 특수한 메서드

특징

  • 클래스와 이름이 같음
  • 반환값이 없기 때문에 void 타입
  • 클래스를 통해 객체가 생성될 때 자동으로 호출

생성할 수 있는 객체의 수에는 제한이 없음

class Person {
  String name;
	int age;

  Person(this.name, this.age);
}

void main() {
  Person paul = Person('Paul', 25);
  Person mark = Person('Mark', 30);
}

paul 과 mark 모두 Person 클래스로 생성한 객체 랍니다 !
매개변수 생성자 로 생성해준 객체 라는 것도 잊지 말고 체크 👀

class Person {
  String name;
	int age;

  Person(this.name, this.age);
}

void main() {
  Person paul = Person('Paul', 25);
  Person mark = Person('Mark', 30);
}

paul 과 mark 모두 Person 클래스로 생성한 객체 랍니다 !
매개변수 생성자 로 생성해준 객체 라는 것도 잊지 말고 체크 👀

생성한 객체들은 서로 같이 않은 독립된 개체임

class Person {
  String name;
	int age;

  Person(this.name, this.age);
}

void main() {
	Person paul1 = Person('Paul', 25);
	Person paul2 = Person('Paul', 25);
	print(paul1 == paul2); // false
}

심지어는 같은 값이 들어갔어도 다른 객체로 인식해요 😱

기본 생성자

  • 매개변수를 갖지 않는 생성자

클래스 이름;

class Car {
  Car();
}

Car() 이 Car 클래스의 기본 생성자 랍니다 !

특징

  • 자동으로 정의되기 때문에 클래스에 따로 명시하지 않아도 된다.
class Car {
  
}

void main() {
	Car car = Car();
}

기본 생성자 는 생략해 주어도 되고,

class Car {
  Car();
}

void main() {
	Car car = Car();
}

직접 명시해 주어도 돼요 🙂

class Car {
  
}

void main() {
	Car car = new Car();
}

new 를 붙여도 된답니다 ! (하지만 귀찮으니까 저는 안 붙일래요 ..)

인스턴스 변수들이 모두 초기화되어 있는 상태여야 함

class Car {
	String name = '';
	List<String> models = [];
  
  Car();
}

name 과 models 에 각각 초기값이 있기 때문에 Good 👍🏼

class Car {
	String name; // Error: Field 'name' should be initialized because its type 'String' doesn't allow null.
	List<String> models; // Error: Field 'models' should be initialized because its type 'List<String>' doesn't allow null.
  
  Car();
}

name 과 models 에 초기값이 없는 상태로
기본 생성자를 정의하면 이렇게 오류가 나요 😅

매개변수 생성자

  • 매개변수를 갖는 생성자
  • 매개변수를 통해 외부에서 인스턴스 변수 들의 초기값을 설정해요.

클래스 이름;

class Car {
	String name;
	List<String> models;
  
  Car(this.name, this.models);
}

Car(this.name, this.models) 를 통해 받아온
this.name 과 this.models 가
Car 의 변수인 name 과 models 에 대입돼요.

[클래스 이름]([타입][매개변수 이름]) : this.변수;

class Car {
  String name;
  List<String> models;

  Car(String name, List<String> models)
      : this.name = name,
        this.models = models;
}

Car(String name, List<String> models) 를 통해 받아온
name 과 models 가
Car 의 변수인 name 과 models 에 각각 대입돼요.

[클래스 이름]([타입][매개변수 이름]) { this.변수; }

class Car {
  String name = '';
  List<String> models = [];

  Car(String name, List<String> models) {
    this.name = name;
    this.models = models;
  }
}

Car(String name, List<String> models) 를 통해 받아온
name 과 models 가 각각
Car 의 변수인 name 과 models 에 대입돼요.

특징

  • 객체 생성할 때 매개변수 를 넣지 않으면 오류가 나요 🚨
class Car {
	String name;
	List<String> models;
  
  Car(this.name, this.models);
}

void main() {
	Car car = Car(); // Error: Too few positional arguments: 2 required, 0 given.
}

생성자 에 2개의 매개변수 를 넣어줘야 한다는 오류 메시지가 떠요 !

네임드 생성자

  • 클래스 메서드 와 같은 형식으로 호출하는 생성자

[클래스 이름].[메서드 이름]([타입][매개변수 이름]) : this.변수;

class Car {
  String name;
  List<String> models;

  Car.fromList(List values)
      : this.name = values[0],
        this.models = values[1];
}

Car.fromList(List values) 에 있는
values 의 첫번째 요소는 Car 의 인스턴스 변수인 name 에,
values 의 두번째 요소는 Car 의 인스턴스 변수인 models 에
대입돼요.

class Car {
  String name;
  List<String> models;

  Car.fromList(List values)
      : this.name = values[0],
        this.models = values[1];

  void speakName() {
    print('저희는 $name 입니다 !');
  }

  void speakModels() {
    print('$models 모델을 가지고 있습니다 !');
  }
}

void main() {
  Car car = Car.fromList([
    'BMW',
    ['320i', '340i', 'M3']
  ]);
  car.speakName(); // 저희는 BMW 입니다 !
  car.speakModels(); // [320i, 340i, M3] 모델을 가지고 있습니다 !
}

‘BMW’ 는 name 에,
['320i', '340i', 'M3'] 은 models 에 대입되겠죠 ?

특징

  • 클래스 에 있는 변수 와 타입 이 맞지 않는 값을 넣으면 오류가 나요 🚨
class Car {
  String name;
  List<String> models;

  Car.fromList(List values)
      : this.name = values[0],
        this.models = values[1];
}

void main() {
  Car car = Car.fromList([
    'BMW',
    [1, 2, 3]
  ]); // TypeError: Instance of 'JSArray<int>': type 'List<int>' is not a subtype of type 'List<String>'
}

name 은 String 타입이기 때문에
values 의 첫번쨰 요소에 String 타입의 값을,
models 는 List<String> 타입이기 때문에
values 의 두번째 요소에 List<String> 타입의 값을
넣어줘야 해요.

사용하는 이유

  • 하나의 클래스로 여러 객체를 생성할 수 있기 때문에
    공통된 속성과 동작을 갖는 코드를 여러 번 작성하지 않아도 돼서
    재사용성 높은 코드를 만들 수 있음

제네릭 클래스

[클래스 이름]<타입 파라미터>


class Box<T> {
  T value;

  Box(this.value);

  T getValue() {
    return value;
  }
}

타입 파라미터는 보통 영어 대문자 한글자 (ex. E, T) 로 표현하고,
실제 코드를 실행할 때 타입 파라미터에 실제 데이터 타입을 넣으면 된다고
했던 것 기억하시나요 ? 👀

사용하는 이유

  • 제네릭 함수 와 마찬가지로
    특정 타입에 의존하지 않고, 여러 타입에 대해 동일한 코드를 적용할 수 있어서
    재사용성 높은 코드를 짤 수 있음
class IntBox {
	int value;
	
	IntBox(this.value);
	
	int getValue() {
		return value;
	}
}

class StringBox {
	String value;
	
	StringBox(this.value);
	
	String getValue() {
		return value;
	}
}

void main() {
  var intBox = IntBox(10);
  print(intBox.getValue()); // 10

  var stringBox = StringBox('Hello');
  print(stringBox.getValue()); // Hello
}


비슷한 형태인 클래스 2개를 제너릭으로 만들어 사용하면 , 효율적인 코드를 만들 수 있음


class Box<T> {
  T value;

  Box(this.value);

  T getValue() {
    return value;
  }
}

void main() {
  var intBox = Box<int>(10);
  print(intBox.getValue()); // 10

  var stringBox = Box<String>('Hello');
  print(stringBox.getValue()); // Hello
}

상속

  • 기존 클래스의 기능을 확장하여 새로운 클래스를 만드는것
  • 하나의 클래스가 다른 클래스의 속성과 메서드를 물려 받는것
    물려주는 클래스는 부모 클래스 , 물려받는 클래스는 자식 클래스

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

class Person {
  void eat() {
    print('냠냠 !');
  }
}

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

부모 클래스 를 확장한다는 의미에서 extends 라는 키워드를 사용해요.
Person 은 부모 클래스, Student 는 자식 클래스 겠죠 ?

특징

부모 클래스는 자식 클래스 에게 자신의 모든 속성과 메서드를 상속해요

class Person {
	String name = '';
	
  void eat() {
    print('냠냠 !');
  }
}

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

void main() {	
  Student student = Student();
  student.name = '강미래';
  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 = '강미래';

  void eat() {
    print('냠냠 !');
  }
}

class Student extends Person {
  void eatAndIntroduce() {
    super.eat();
    print('맛있게 먹는 ${super.name} ㅋㅋ');
  }
}

void main() {
  Student student = Student();
  student.eatAndIntroduce();
}

/*
냠냠 !
맛있게 먹는 강미래 ㅋㅋ
*/

this 는 그 객체 를 가리킨다고 했던 것 기억하시나요 ?
this 랑 비슷하게 super 는 부모 클래스 를 가리킨답니다 !
super.eat(); 을 하면 Person 에 있는 eat() 이 호출되고,
super.name 을 하면 Person 에 있는 인스턴스 변수 인 name 이 들어가요 🙂

재정의

  • 자식 클래스 가 부모 클래스 로부터 상속 받은 속성 과 메서드 를
    그대로 사용하지 않고, 덮어 씌우는 것을 의미해요.
  • 부모 클래스 에 정의되어 있는 속성 이나 메서드 가 마음에 안 들어서
    새로 정의하고 싶을 때 사용해요 🤣

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

class Person {
  String name = '강미래';

  void eat() {
    print('냠냠 !');
  }
}

class Student extends Person {
  
  String name = '여러분';

  void study() {
    print('열공하는 $name !');
  }
}

void main() {
  Student student = Student();
  student.study(); // 열공하는 여러분 !
}

Student 의 name 은 Person 의 name 을 재정의 한 속성 이랍니다 !
Student 의 name 은 ‘강미래’ 대신 ‘여러분’ 으로 재정의 되겠죠 ?

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

class Person {
  void eat() {
    print('냠냠 !');
  }
}

class Student extends Person {
  
  void eat() {
    print('쩝쩝 !');
  }
}

void main() {
  Student student = Student();
  student.eat(); // 쩝쩝 !
}

Person 의 eat() 을 Student 에서 재정의 해서
냠냠 ! 이 아니라 쩝쩝 ! 이 출력되는군요 🙂

기능 확장

class Person {
  void eat() {
    print('냠냠 !');
  }
}

class Student extends Person {
  
  void eat() {
    super.eat();
    print('쩝쩝 !');
  }
}

void main() {
  Student student = Student();
  student.eat();
}

/*
냠냠 !
쩝쩝 !
*/

super 를 통해 기존에 정의되어 있던 메서드 를 호출한 후에 재정의 하면 돼요 🙂

사용하는 이유

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

상속 시키 않고 싶을 때

final

  • 하나의 클래스로 부터 자식 클래스를 만들수 없도록 하는것
  • 상속 받을 수 없도록 만드는 키워드

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'.
}

상속 당하지 않게 하고 싶은 클래스 의 이름 옆에 final 을 붙이면
그 클래스 를 상속 했을 때 오류가 난답니다 🚨

객체 지향 프로그래밍

  • OOP
    객체들을 사용하여 프로그램을 구성하는 방식

사용하는 이유

  • 클래스 를 통해 틀을 정의해 두고, 여러 객체 를 만들기 때문에 효율적임
  • 클래스 를 상속 받아서 기능을 확장할 수 있기 때문에 코드를 재사용할 수 있음
  • 클래스 를 통해 만든 객체 들이 각각 독립적으로 동작하기 때문에
    특정 객체 를 수정해도 부작용이 생길 일이 적어요

예시 코드

class BMW {
	String name = 'BMW';
	List<String> models = ['320i', '340i', 'M3'];
	
	void introduceName() {
		print('안녕하세요, 저희는 $name 입니다 !');
	}
  
  void introduceModels() {
    print('저희는 $models 이렇게 ${models.length}가지 모델을 가지고 있습니다 !');
  }
}

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

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

이렇게 매번 클래스를 생성해 줄 수도 있지만 중복 코드가 생겨서 효율적이지 않겠죠 😅


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가지 모델을 가지고 있습니다 !
}

우리는 효율을 중요하게 생각하는 개발자니까
객체 지향 프로그래밍 을 통해서 효율적인 코드를 만들어 보자구요 💪🏼

0개의 댓글