Dartㅣ 클래스와 객체

휘Bin·2023년 6월 22일
0
post-thumbnail

클래스 선언과 생성

Dart에서 클래스는 다른 객체지향 언어와 비슷하게 class라는 예약어로 선언한다.
그리고 클래스에는 변수와 함수를 멤버로 선언할 수 있다.

선언한 class를 이용하려면 객체를 생성해야 한다. 그리고 생성한 객체 이름으로클래스에 선언한 변수와 함수를 이용하게 된다.

객체를 생성할 때는 new 연산자를 사용해도 되고 생략해도 된다. 즉, new 연산자 이용여부와 상관없이 클래스의 생성자를 호출하면 객체를 생성할 수 있게 된다.

Ex)

class Member(){
	String name = 'hwibin';
    int age = 20;
    
    void thanks(){
    	print('im $name, and im $age');
    }
}

Member member1 = new Member();

객체 멤버와 클래스 멤버

클래스에 선언한 변수나 함수를 '멤버'라고 한다. 그리고 이 멤버는 객체 멤버클래스 멤버로 구분된다.

객체 멤버는 생성된 객체를 이용해서 접근한다.

Ex)

Member member1 = Member();
member1.thanks();
member1.name = 'hwibin';
member1.age = 20;

위와 같이 객체 멤버는 객체를 생성하고 그 이름으로 접근을 하게 된다.

반면,

클래스 멤버는 static 예약어로 선언한 멤버이다. 그래서 '정적 멤버' 라고도 한다.

Ex)

class Me{
	static String name = 'hwibin';
    
    static thanks(){
    	print('thanks');
    }
}

static으로 선언한 클래스 멤버는 클래스 이름으로 접근할 수 있게 된다.
그리고 클래스 멤버는 객체로 접근할 수 없다.

Ex)

Me.name = 'hwibin2';

생선자 선언

'생성자'클래스에 선언되어 객체를 생성할 때 호출되는 함수이다. 모든 클래스는 생성자를 가지게 되며, 개발자가 만들지 않는다면 컴파일러가 자동으로 기본 생성자를 만들어준다.
다트에서 생성자가 없는 클래스는 존재할 수 없다.

Ex)

class Member(){
	member(){} => 생성자
}

다만, 다트에서는 명명된 생성자, 팩토리 생성자, 상수 생성자 등 다양한 형태로 정의가 된다. 또한, 생성자의 매개변수로 멤버를 초기화하는 초기화 목록도 제공한다.

멤버 초기화하기

생성자는 일반적으로 멤버를 초기화하는 용도로 사용한다. 즉, 객체를 생성할 때 매개변수로 전달 받은 데이터를 클래스에 선언된 멤버에 대입한다.

Ex)

class Member(){
	late String name;
    late int age;
    
    member(String name, int age){
    	this.name = name;
        this.age = age;
    }
}

위와 같이 매개변수로 멤버를 초기화하는 생성자는 this 예약어를 통해 간단하게 작성할 수 있다.

초기화 목록

생성자를 선언할 때 '초기화 목록'을 사용할 수도 있다. 초기화 목록은 생성자 선언부를 콜론(:)으로 구분하여 오른쪽에 작성하게 된다.
초기화 목록은 리스트에서 특정 항목을 선택하거나 함수 호출로 멤버를 초기화할 때 자주 사용된다.

Ex)

Member(List<int> students)
	: this.name = students[3],
      this.age = students[4]{}

초기화목록에서 특정 함수를 호출하고 그 반환값으로 클래스의 멤버를 초기화할 수도 있다. 다만, 생성자의 초기화 목록이 실행되는 시점이 객체 생성 이전이므로 호출할 수 있는 함수는 static이 추가된 클래스 멤버여야 한다.

Ex)

Mebmer(int num1, int num2)
	: this.age1 = calAge(num1),
      this.age2 = calAge(num2){}

명명된 생성자

다트 언어에서 클래스의 생성자를 다양하게 이용할 때 가장 중요하고 자주 사용되는 것이 '명명된 생성자' 이다.
명명된 생성자란, 이름이 있는 생성자라는 뜻으로, 한 클래스에 이름이 다른 생성자를 여러 개 선언하는 기법이다. 생성자를 여러 개 선언할 수 있는 오버로딩 기능을 제공하는 다른 언어도 있지만, 똑같은 이름으로 매개변수나 반환 타입만 다르게 선언한다는 점에서 다트와는 차이가 있다.

다트에서는 오버로딩을 지원하지 않는데, 예를 들어 자바에서의 오버로딩 기능의 경우이다.

Ex)

public class Member{
	Member(){}
    Member(String name){}
    Member(String name, int age){}
}

명명된 생성자는, 한 클래스에 이름을 다르게 해서 여러 생성자를 정의하는 기법이다. 즉, 다트에서는 클래스와 같은 이름 뿐 아니라, 다른 이름으로도 생성자를 만들 수 있다. 점(.)으로 연결해서 작성한다.

한 클래스에 클래스와 같은 이름의 생성자는 하나만 정의가 가능하지만, 명명된 생성자는 클래스와 다른 이름으로 여러 개 정의할 수 있다.
명명된 생성자로 객체를 만드는 코드는 아래와 같다.

Ex)

var member1 = School();
var member2 = School.korea();
var member3 = School.america();

※ 생성자 오버로딩 대신 명명된 생성자를 제공하는 이유?

오버로딩으로 선언한 생성자로 객체를 생성할 때는 매개변수 부분을 보고 어떤 생성자를 호출할지 결정한다. 하지만 생성자에 이름을 부여하면 생성자를 구분하기 더 쉬워진다.

this()로 다른 생성자 호출

한 클래스에서 생성자를 여러 개 선언하면 생성자에서 다른 생성자를 호출해야 하는 경우도 생긴다. 이렇게 되면 객체를 생성할 때 생성자가 중첩되어 호출된다.
이럴 때 this() 구문을 이용하면 되는데, this()는 생성자의 {} 안쪽 본문에 작성할 수 없다.

그래서 다른 생성자를 호출하는 this()는 생성자의 콜론(:) 오른쪽 초기화 목록에 작성해야 한다. 하지만 초기화 목록에 this() 호출문을 작성하면 생성자 본문을 작성할 수 없게 되며 본문 영역을 나타내는 {}를 작성하는 것만으로 오류가 발생할 수 있다.
따라서 명명된 생성자가 호출될 때 this()로 기본 생성자를 호출하는 올바른 코드는 아래와 같다.

class School{
	School(String name, int age){
    	pring('thanks');
    }
    School.student(String name2) : this(hwibin,20);
}

팩토리 생성자

'팩토리 생성자'factory 예약어로 선언한다. 객체를 생성할 때 호출 할 수 있지만, 생성자 호출만으로 객체가 생성되지 않는다. 팩토리 생성자에서 적절한 객체를 반환해줘야 한다. 즉, 팩토리 생성자는 클래스 외부에서는 생성자처럼 이용되지만 실제로는 클래스 타입의 객체를 반환하는 함수인 것이다.
아래처럼 객체를 반환하지 않으면 오류가 난다.

class School{
	factory School(){}
}

factory로 선언한 생성자는 반드시 객체를 반환해줘야 한다.
또한, 팩토리 생성자는 반환 타입을 명시할 수 없어 클래스 타입으로 고정된다. 따라서 클래스의 널 허용 여부에 따라 null 반환 여부도 결정된다.

따라서 팩토리 생성자가 선언된 클래스에는 객체를 생성하는 다른 생성자를 함께 선언하는 방법을 많이 사용한다.
주로 '캐시 알고리즘', '상속 관계에 따른 다형성 구현'에 유용한다.
아래는 캐시 알고리즘의 한 예이다.

class Image{
	late String url;
    static Map<String, Image> _cache = <String, Image>{};
    Image._instance(this.url);
    factory Image(String url){
    	if(_cache[url] == null){
        	var obj = Image._instance(url);
            _cache[url] = obj;
        }
        return _cache[url]!;
    }
}

상수 생성자 선언

'상수 생성자'const 예약어로 선언을 하며 본문을 가질 수 없다. 쉽게 이야기해서 {}를 가지지 못 한다는 것이다.

또한, 상수 생성자가 선언된 클래스의 모든 멤버 변수는 final로 선언해줘야 한다. 이 같은 특성으로 상수 생성자는 클래스의 모든 변수를 초깃값으로만 사용하도록 강제하는 수단으로 사용된다.

상수 생성자도 객체를 생성할 수 있으며 필요하다면 여러 개의 객체도 생성할 수 있다.

Ex)

class School{
	final int d1;
    const School(this.d1);
   
}

main(){
	var st1 = School(3);
    var st2 = School(9);
}

상수 객체 생성

객체를 생할 때도 const를 추가'상수 객체'로 만들 수 있다. 하지만 const로 객체를 생성하려면 생성자 또한 const로 선언해야 한다. 즉, const로 객체를 생성하려면 생성자에도 const를 추가해야 한다.

상수 생성자를 선언한 클래스라고 하더라도 일반 객체로 선언하면 서로 다른 객체가 생성된다. 즉, 같은 값을 가지더라도 서로 다른 객체인 것인다.

그러나 const를 붙여 상수 객체로 선언하면 생성자에 전달한 값이 똑같으면 객체를 다시 생성하지 않고 이전 값으로 생성한 객체를 그대로 사용한다. 즉, 상수 객체는 값이 똑같다면 객체를 재사용한다.

var d1 = const School(10);
var d2 = const School(10);

위를 보면, 객체 이름은 d1,d2로 다르지만, 실제 사용되는 값은 d1의 10인 것이다.

당연히 객체를 생성할 때 전달하는 초깃값이 다르면 서로 다른 객체가 생성된다. 즉, 상수 객체는 같은 값으로 생성한 객체를 재활용할 목적으로 사용한다.

하나는 const를 붙이고 하나는 안 붙여도 서로 다른 객체가 된다.

profile
One-step, one-step, steadily growing developer

0개의 댓글