본문 바로가기

춘기IT/춘기개발

객체 지향 프로그래밍과 Dart (4) - 객체 지향과 상속

Today I Learned (250708) 4-3.

상속(Inheritance)

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

- 하나의 클래스가 다른 클래스의 속성과 메서드를 물려받는 것

  ㄴ 물려주는 클래스는 부모 클래스 (Parent Class, Superclass),

      물려받는 클래스는 자식 클래스 (Child Class, Subclass) 라고도 함

 

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이 들어감

 

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

재정의
- 자식 클래스가 부모 클래스로부터 상속 받은 속상과 메서드를 그대로 사용하지 않고 덮어 씌우는 것

재정의 사용 언제?
- 부모 클래스에 정의되어 있는 속성이나 메서드가 마음에 안들어서 새로 재정의하고 싶을 때 사용

@override [변수이름] = [값];
class Person {
  String name = '화춘기';

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

class Student extends Person {
  @override
  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 {
  @override
  void eat() {
    print('쩝쩝 !');
  }
}

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


/* Person의 eat()을 Student에서 재정의해서
냠냠 ! 이 아니라 쩝쩝 ! 이 출력​


재정의 기능 확장

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

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

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

/*
냠냠 !
쩝쩝 !

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

 

상속 사용하는 이유!

- 공통적인 속성과 메서드를 부모 클래스에 정의하고,

공통되지 않는 요소들만 따로 자식 클래스에 정의해서 중복된 코드를 줄이고, 코드의 재사용성을 높일 수 있음


Q. 상속 당하지 않게 클래스를 하고 싶을 때! 

→ final 사용

 

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 을 붙이면
그 클래스를 상속 했을 때 오류가 남

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

- OOP 라고도 함

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

 

객체 지향 프로그래밍 사용 이유!

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

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

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

 

객체 지향 프로그래밍 방식으로 작성한 예시 코드

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