[FLUS 스터디 1주차] Dart 기초 - Class and Interface(클래스와 인터페이스)

sucream·2022년 9월 3일
0

flutter-study

목록 보기
6/17

드디어 클래스와 인터페이스에 대해서 얘기하게 되었다.
다트는 객체지향 언어다. 따라서 객체 위주의 프로그래밍을 하게 되는데, 이때 객체를 만들기 위해 필요한 것이 클래스와 인터페이스다. 클래스와 인터페이스는 다른 언어에도 많이 있으니 핵심만 확인해 보도록 하자.

1. Class

클래스는 객체의 상태와 행동을 정의하는 하나의 틀이다. 여기서 말하는 상태는 지금껏 사용한 변수들의 사용법과 다르지 않고, 행동은 함수와 다르지 않다. class로 선언 후 내부에 클래스에서 사용할 변수와 함수들을 작성한다.

class Animal {
	String name;
    
    void sayName() {
    	print('My name is ${this.name}');
    }
	
}

Constructors and this keyword

클래스를 새로운 객체로 만드는 행위를 인스턴스화한다고 한다. 클래스를 인스턴스로 만들기 위해 클래스에서 필요한 값들을 전달하는 행위가 이루어 지는데, 이를 클래스 내에 생성자가 담당한다.

생성자는 클래스와 이름이 같은 함수라고 생각하면 편하다. 생성자는 클래스를 인스턴스화 하기 위해 외부로부터 받아올 값들의 목록을 정의하고 최초에 필요한 작업들을 진행할 수 있다. 기존의 함수처럼 클래스 생성자도 [], {}를 사용하여 파라미터를 컨트롤할 수 있다.

클래스 내부의 함수(이하 메소드)에서 클래스의 변수(이하 멤버변수)에 접근하기 위해서는 this라는 키워드를 사용해야 하며, this.variable 형식으로 사용한다.

class Animal {
	String? name;  // 변수명이 입력되지 않을 수 있기 때문에 String?으로 지정해 줬다.
    
    // 클래스명과 같은 이름의 생성자이다.
    Animal(String name) {
    	this.name = name;
    }
    
    void sayName() {
    	print('My name is ${this.name}');
    }
}

void main() {
	var animal = Animal('사자');
    
    animal.sayName();
}

결과

My name is 사자

위 방식처럼 생성자를 지정할 수도 있지만, 다른 생성자 사용 방법도 있다. 한번 확인해 보도록 하자.

class Animal {
	String? name;  // 변수명이 입력되지 않을 수 있기 때문에 String?으로 지정해 줬다.
    
    // 클래스명과 같은 이름의 생성자이다.
    // this.name을 넣어서 바로 바인딩해주었다.
    Animal(this.name);
    
    void sayName() {
    	print('My name is ${this.name}');
    }
}

void main() {
	var animal = Animal('사자');
    
    animal.sayName();
}

결과

My name is 사자

다트는 변수명이 겹치지 않는 상황에선 this 사용하지 않는 것을 권장하는 것 같다.

2. Getters and Setters

다트는 클래스내 변수들에 대해 getter와 setter 기능을 제공한다.

class Rectangle {
	double left, top, width, height;

	Rectangle(this.left, this.top, this.width, this.height);

	// Define two calculated properties: right and bottom.
	double get right => left + width;
	set right(double value) => left = value - width;
	double get bottom => top + height;
 	set bottom(double value) => top = value - height;
}

void main() {
	var rect = Rectangle(3, 4, 20, 15);
    print('rect.left: ${rect.left}');
	print('rect.right: ${rect.right}');
}

결과

rect.left: 3
rect.right: 23

3. Class Inheritance and Mixin

객체지향에서 중요한 기능 중 하나가 상속이다. 부모 클래스를 상속해 자식 클래스를 만들어 활용할 수 있다.
다트는 죽음의 다이아몬드라 불리는 다중상속 문제를 위해 단일 상속만 지원한다. 인터페이스를 구현하는 방식으로 mixin 기능을 사용하는 자바와 달리 다트는 언어 차원에서 mixin을 지원한다.

Inheritance

상속에 사용된 상위 클래스를 부모/슈퍼클래스라고 부르며, 상속하는 클래스를 자식/서브클래스라고 부른다.
상속은 ChildClass extends ParentClass 형식으로 사용하며, 자식클래스는 생성자를 제외한 모든 속성과 메소드를 상속받는다.

상속시에는 자식클래스에서 부모클래스의 생성자를 호출하여 부모클래스에 필요한 값을 전달해 줘야 한다.

class Animal {
	String? name;  // 변수명이 입력되지 않을 수 있기 때문에 String?으로 지정해 줬다.
    
    // 클래스명과 같은 이름의 생성자이다.
    // this.name을 넣어서 바로 바인딩해주었다.
    Animal(this.name);
    
    void sayName() {
    	print('My name is ${this.name}');
    }
}

class Dog extends Animal {
	// 부모클래스에 name 변수를 선언하였기 때문에 초기화되면 자식클래스에서도 사용 가능하다.

	// 클래스명과 같은 이름의 생성자이다.
	Dog(String dogName) : super(dogName);  // 자식클래스에서 부모클래스의 생성자를 호출하여 name값을 전달한다.
	void bark() {
    	print('멍멍!');
    }
}

void main() {
	var dog = Dog('댕댕이');
    dog.sayName();  // Dog 클래스에 sayName 메소드가 없지만 부모클래스인 Animal에 있기 때문에 사용이 가능하다.
    dog.bark();  // 자식클래스 Dog에서 추가로 지정한 메소드도 정상적으로 이용이 가능하다.
}

결과

My name is 댕댕이
멍멍!

Method Overriding

자식클래스가 부모클래스에서 지정한 메소드를 변경하여 사용하고 싶을 때가 있다. 이때 메소드 오버라이딩을 사용한다. 자식클래스에서 부모클래스에 구현된 메소드와 동일한 이름의 메소드 위에 @override 어노테이션을 붙여 사용한다.

class Animal {
	String? name;  // 변수명이 입력되지 않을 수 있기 때문에 String?으로 지정해 줬다.
    
    // 클래스명과 같은 이름의 생성자이다.
    // this.name을 넣어서 바로 바인딩해주었다.
    Animal(this.name);
    
    void sayName() {
    	print('My name is ${this.name}');
    }
}

class Dog extends Animal {
	// 부모클래스에 name 변수를 선언하였기 때문에 초기화되면 자식클래스에서도 사용 가능하다.

	// 클래스명과 같은 이름의 생성자이다.
	Dog(String dogName) : super(dogName);  // 자식클래스에서 부모클래스의 생성자를 호출하여 name값을 전달한다.
	void bark() {
    	print('멍멍!');
    }
    
    // @override를 명시적으로 작성하여 해당 메소드라 오버라이드됨을 알림
    
    void sayName() {
    	print("저는 ${this.name}입니다 :)");
    }
}

void main() {
	var dog = Dog('댕댕이');
    dog.sayName();  // Dog 클래스에 sayName 메소드가 있어 해당 메소드가 실행된다.
    dog.bark();  // 자식클래스 Dog에서 추가로 지정한 메소드도 정상적으로 이용이 가능하다.
}

결과

저는 댕댕이입니다 :)
멍멍!

Mixin

클래스에 기능을 추가할 수 있는 방법으로, 다른 클래스의 부모클래스가 되지 않으면서 다른 클래스에서 사용할 수 있는 메서드를 포함하는 클래스를 의미한다. mixin에 사용되는 클래스는 생성자를 가지면 안되며, mixin클래스를 단독으로 인스턴스화하는 것을 막고싶을 때는 class 대신 mixin 키워드를 사용하면 된다. mixin을 사용하기 위해서는 with 구문을 사용한다.

class Animal {
	String? name;  // 변수명이 입력되지 않을 수 있기 때문에 String?으로 지정해 줬다.
    
    // 클래스명과 같은 이름의 생성자이다.
    // this.name을 넣어서 바로 바인딩해주었다.
    Animal(this.name);
    
    void sayName() {
    	print('My name is ${this.name}');
    }
}

// 짖는 기능을 가진 Bark Mixin
mixin Bark {
	void bark() {
    	print('멍멍!!');
    }
}

class Dog extends Animal with Bark {  // Bark를 사용한다.
	// 부모클래스에 name 변수를 선언하였기 때문에 초기화되면 자식클래스에서도 사용 가능하다.

	// 클래스명과 같은 이름의 생성자이다.
	Dog(String dogName) : super(dogName);  // 자식클래스에서 부모클래스의 생성자를 호출하여 name값을 전달한다.
    
    // @override를 명시적으로 작성하여 해당 메소드라 오버라이드됨을 알림
    
    void sayName() {
    	print("저는 ${this.name}입니다 :)");
    }
}

void main() {
	var dog = Dog('댕댕이');
    dog.sayName();  // Dog 클래스에 sayName 메소드가 있어 해당 메소드가 실행된다.
    dog.bark();  // 자식클래스 Dog에서 추가로 지정한 메소드도 정상적으로 이용이 가능하다.
}

결과

저는 댕댕이입니다 :)
멍멍!!

4. Implicit Interface

일반적으로 다른언어에서는 인터페이스를 정의하기 위해 interface 키워드를 사용하지만, 다트의 모든 클래스는 암묵적으로 인터페이스여서 별도의 키워드가 없다. 인터페이스를 사용하면 특정 기능의 구현을 강제할 수 있다. 인터페이스를 구현하기 위해서는 implements를 사용하며, 자바처럼 클래스는 하나 이상의 인터페이스를 구현할 수 있다.

Every class implicitly defines an interface containing all the instance members of the class and of any interfaces it implements. If you want to create a class A that supports class B’s API without inheriting B’s implementation, class A should implement the B interface.

// 전혀 차이가 없지만 인터페이스를 정의하였다!
class Animal {
	String? name;
    
    Animal(this.name);
    
    void sayName() {
    	print('My name is ${this.name}');
    }
}

class Cat implements Animal {
	// Animal 클래스에 정의되어있기 때문에 구현해야 에러가 나지 않는다.
	String? name;  
    
    // 상속이 아니기 때문에 super를 사용하지 않는다.
    Cat(this.name);
    
	// sayName 메소드를 구현하고 있다.
    // 상속과 달리 구현하지 않으면 에러가 발생한다.
	void sayName() {
    	print('야옹');
    }
}

void main() {
	var cat = Cat('야옹이');
    cat.sayName();
}

결과

야옹

5. Abstract Class

다트의 추상클래스가 오히려 다른 언어의 인터페이스와 비슷할지도 모르겠다. 추상클래스는 클래스의 일부를 구현하지 않을 수 있으며, 단독으로 인스턴스화 되지 못하도록 막을 수 있다. 클래스 앞에 abstract 키워드를 사용한다. 추상클래스 역시 구현되어야 하며 implements 키워드로 구현할 수 있다.

abstract class Animal {
	String? name;
    
    Animal(this.name);
    
    // sayName 메소드의 내용을 정의하지 않았다.
    void sayName();
}

class Cat implements Animal {
	// Animal 클래스에 정의되어있기 때문에 구현해야 에러가 나지 않는다.
	String? name;  
    
    // 상속이 아니기 때문에 super를 사용하지 않는다.
    Cat(this.name);
    
	// sayName 메소드를 구현하고 있다.
    // 상속과 달리 구현하지 않으면 에러가 발생한다.
	void sayName() {
    	print('야옹');
    }
}

void main() {
	var cat = Cat('야옹이');
    cat.sayName();
}

결과

야옹

Refference

profile
작은 오븐의 작은 빵

0개의 댓글