프로그래밍 패러다임
패러다임은 크게 두 가지로 나뉩니다
- Imperative Programming (명령형 프로그래밍)
- Declarative Programming (선언형 프로그래밍)
과거에는 명령형만 사용하였고 최근에 와서야 선언형이 쓰이게 되었다고 합니다
선언형은 주로 functional programming에서 많이 쓰이는데 대표적인 언어로는 Scala가 있습니다
logic programming은 주로 학자가 많이 사용한다고 합니다
선언형은 사실 어떤 사고를 거쳐서 작성해야 하는지 모르겠어서 요 정도로만 작성해 둘게요 (·•︠‿•︡ )
요 아래는 정확한 표현이 아니니 참고만 해주세요
Flutter는 데이터를 명령형으로 받아서 처리하고 UI를 선언형 방식으로 구현하는 걸 지향합니다
따라서 다트는 명령형 방식을 주로 택하게 되고 Flutter는 선언형 방식을 주로 택하게 됩니다
다트도 선언형으로 작성 가능하니 참고만 해주세요!
Structural Programming(구조적 프로그래밍)은 Procedural Programming(절차적 프로그래밍)의 하위 개념입니다
따라서 크게 구분해서 볼 필요는 없고 말 그대로 순서대로 코드가 실행되는 프로그래밍을 의미한다고 보시면 됩니다
그렇다면 Structural과 Object Oriented는 어떤 차이가 있을까요?
Structural은 데이터와 그 데이터를 처리하는 함수를 따로 두었지만 OOP는 그걸 합친 객체를 활용하여 프로그래밍합니다
그렇다면 객체란 무엇일까요?
OOP의 최소 단위로 OOP는 객체의 조합으로 구성된다고 합니다
객체는 data와 method로 나뉘는데요
data는 객체의 상태 즉 state를 나타내고 method는 data를 처리하는 행동의 성격을 갖습니다
객체의 data는 속성이라 부르며 이를 Dart에서는 property라고 부릅니다
다만 이를 attribute라고 부르기도 DB에선 field라고 부르기도 한답니다
핳 드디어 가장 중요한 부분입니다
OOP의 네 가지 특성을 살펴볼게요
- Encapsulation (캡슐화)
특정 경로로만 접근할 수 있도록 data와 method를 하나로 묶어 보호하는 것
private을 사용하여 캡슐화를 기술화할 수 있다
- Inheritance (상속)
1) 부모 클래스의 기능을 자식 클래스에 물려주는 것
2) 혈통을 연결시키는 것
extends로 상속시킬 수 있다
- Polymorphism (다형성)
@override로 method를 재정의하는 것
- Abstraction (추상화)
객체의 공통된 특징만을 노출하는 것
abstract로 추상화할 수 있으나 abstract만 가능한 것은 아니다
위 네 가지 중 먼저 상속을 중점적으로 다뤄보겠습니다
왜 상속이 필요한 걸까요?
답은 간단합니다 중복을 최대한 줄이기 위해서
만일 동일한 기능에 몇 가지만 추가하는 클래스가 여러 개 생긴다면 유지보수가 힘들겠죠
따라서 중복되는 기능은 따로 빼서 parent class로 만들고 추가되는 기능은 child class로 만들면 중복되는 기능을 수정할 때 딱 한 번만 고쳐주면 될 겁니다
상속은 아래와 같이 extends로 기술화시킬 수 있습니다
class Person {
String name;
int age;
Person(this.name, this.age);
String printProperties() {
return '제 이름은 $name이고 나이는 $age입니다.';
}
}
class Hyoeun extends Person{
String school;
Hyoeun(super.name, super.age, this.school);
void nameOfSchool() {
print('$school에 재학 중입니다.');
}
}
void main() {
var hyoeun = Hyoeun('이효은', 24, '동덕여자대학교');
print(hyoeun.printProperties());
hyoeun.nameOfSchool();
}
🤔 저렇게 따로 출력하지 않고 하나의 함수로 통일해서 모든 property를 출력하고 싶은데 방법이 없을까?
있습니다 @override를 사용하면 가능합니다
오버라이딩은 위에서 다룬 것처럼 method를 재정의하는 것입니다
위의 예제 코드로 설명하자면 Person class의 method인 printProperties를 자식 클래스인 Hyoeun class에서 재정의할 수 있다는 뜻입니다
class Person {
String name;
int age;
Person(this.name, this.age);
String printProperties() {
return '제 이름은 $name이고 나이는 $age입니다.';
}
}
class Hyoeun extends Person{
String school;
Hyoeun(super.name, super.age, this.school);
@override
String printProperties() {
return '제 이름은 $name이고 나이는 $age이며 학교는 $school입니다.');
}
}
void main() {
Hyoeun hyoeun = Hyoeun('이효은', 24, '동덕여자대학교');
print(hyoeun.printProperties());
}
죠금 뒤늦은 감이 있지만 아래의 constructor를 봐주세요
여기서 super와 this는 무얼 의미할까요?
맞습니다 super는 조부모 혹은 부모 클래스의 객체를 의미하며 this는 자식 클래스의 객체를 의미합니다
주의할 점은 super와 this 모두 클래스가 아닌 객체를 의미한다는 점 꼭 기억하기 (네 맞아요 제 자신한테 하는 말입니다🤭)
Hyoeun(super.name, super.age, this.school);
type의 경우 자식 클래스는 부모 클래스와 자식 클래스 타입 모두 인식되지만 부모 클래스는 오직 부모 클래스 타입만 인식될 뿐 자식 클래스 타입으론 인식되지 않습니다
말이 너무 어려워서 아래의 코드로 살펴보겠습니다
void main() {
Person someone = Person('아무개', 20);
Hyoeun hyoeun = Hyoeun('이효은', 24, '동덕여자대학교');
// Type Checking
print(someone is Person); // true
print(someone is Hyoeun); // false
print(hyoeun is Person); // true
print(hyoeun is Hyoeun); // true
}
부모 클래스 Person의 객체인 someone은 Person 타입으로만 인식되고 자식 클래스인 Hyoeun 타입으론 인식되지 않습니다
그러나 자식 클래스인 Hyoeun의 객체인 hyoeun은 부모 클래스인 Person과 자식 클래스인 Hyoeun 타입 전부 true로 인식된다는 사실을 파악할 수 있습니다
이제 추상화로 넘어가 abstract class를 살펴보겠습니다
추상화는 공통된 기능을 가진 무언가를 추상적으로 나타낸 것입니다
굳이 추상화를 해야 하는 건 공통된 기능으로 묶어 분류하면 관리가 더 편하기 때문입니다
🤔 추상화는 꼭 abstract 키워드를 사용해야 하는 건가?
그건 아닙니다! 하지만 abstract는 추상화를 위해 만들어진 것이기 때문에 주로 추상화엔 abstract을 많이 사용한다고 합니다
abstract class Animal {
bool isAlive = true;
int numberOfEyes() {
print(2);
}
void numberOfLegs();
}
🤨 오잉 isAlive와 numberOfEyes는 값이 있는데?
맞습니다 abstract class라도 concrete를 가질 수 있기 때문입니다
isAlive와 numberOfEyes는 확실하기 때문에 abstract class에서도 값을 줄 수 있는 겁니다
abstract class를 extends해서 concrete하게 만들 필요가 있는 건 오직 numberOfLegs 함수뿐이라고 보시면 됩니다
numberOfLegs처럼 구체적으로 정의되지 않은 method를 abstract method라 부릅니다
class Cat extends Animal {
@override
void numberOfLegs() {
print(4);
}
}
위와 같이 concrete class인 Cat을 만들 경우 Animal 클래스를 상속하여 abstract method를 구체화 및 구현합니다
상속으로 구체화 시 abstract method는 반드시 override하여 구현해 주어야 합니다
interface로 넘어가자면 Dart에는 interface 키워드가 따로 없기 때문에 implements로 상속하여 구현합니다
implements로 상속 시에는 extends와 달리 모든 property와 method 즉 모든 member를 재정의해 주어야 합니다
class Dog implements Animal {
@override
bool isAlive = true;
@override
int numberOfEyes() {
print(2);
}
@override
void numberOfLegs() {
print(4);
}
}
extends는 슈퍼클래스 하나만 상속 가능하고 다중 상속은 불가합니다
다만 implements로 상속할 경우에는 다중 상속이 가능합니다
class Kitty implements Animal, Cat // and so on
이제 거의 끝이 보이네욥 마지막으로 다룰 개념은 Mixin입니다
Mixin은 클래스가 아닌데 클래스처럼 쓰이고 상속이 불가하지만 상속처럼 작동되는 요상한 친구입니다
아 물론 혈통으로 연결되지는 않습니다
mixin은 부모 클래스가 각기 다른 자식 클래스들에 공통된 코드가 있을 때 중복을 제거하고자 사용됩니다
abstract class Person {
void talk();
}
abstract class Pet {
void sound();
}
mixin Eat {
void eat() {
print('냠냠');
}
}
class Someone extends Person with Eat {
@override
void talk() {
print('안녕하세요');
}
}
class Cat extends Pet with Eat {
@override
void sound() {
print('meow');
}
}
Someone class와 Cat class는 서로 슈퍼 클래스가 다르지만 eat 기능이 공통되므로 mixin으로 빼주면 모든 클래스에서 사용 가능합니다
Mixin은 여러 개를 상속처럼 사용할 수 있습니다
상속처럼입니다 상속은 아니에요!
class Cat extends Pet with Eat, Alive // and so on
mixin에서 on 키워드를 사용하면 해당 부모 클래스를 상속한 자식 클래스에서만 mixin을 사용할 수 있습니다
class Animal {
void move() {
print('움직인다.');
}
}
mixin Fly on Animal {
@override
void move() {
print('날아서 움직인다.');
}
}
class Bird extends Animal with Fly {
void wings() {
print('날개가 있다.');
}
}
void main() {
var sparrow = Bird();
sparrow.move(); // 날아서 움직인다
sparrow.wings(); // 날개가 있다
}
mixin class 키워드를 사용하면 with으로 상속할 시 mixin, extends로 상속할 시 class로 동작한다고 합니다
mixin class Emoji {
void cute() {
print('이모지는 죄다 귀엽다.');
}
}
class Face with Emoji {
// mixin으로 동작하여 cute 함수를 사용할 수 있다
}
class Flags extends Emoji {
@override // Emoji가 class로 동작하여 상속이 가능해진다
void cute() {
print('깃발마저도 귀엽다.');
}
}
부족하지만 OOP의 기본 개념을 정리하는 데 도움이 되셨기를 바라며 글을 마칩니다🫠
Share article