post-custom-banner

Equatable 1탄

equatable | Dart Package

Equatable 사용해보기 2편

이번 글에서는 Flutter의 인기있는 라이브러리 중 하나인 Equatable 라이브러리에 대해서 작성하려고 한다. 보통 Bloc을 사용해보신 분이면 많이 익숙할 라이브러리일 것이다.

저도 Bloc을 자주 사용하지만 Equatable의 기능을 담고 있는 다른 방식의 Bloc 사용 법으로 개발을 해오다 보니 Equatable을 잘 사용하지는 않았었다.

최근에 velog글에 사용될 예제를 만들면서 Bloc을 좀 더 심플하게 생성하게 되어 Equatable을 자주 사용하게 되었다. 하지만 정확히 왜 써야하고 꼭 필수로 사용을 해야하는지에 대해서 알아보기 위해 글을 작성하게 되었다.

Flutter

Flutter에서 class Equal 기능을 사용하는 법과 Equatable 라이브러리를 사용 했을 때 차이점과 사용법에 대해서 알아보도록 하자.

class 비교

Equatable을 사용하는 이유에 대해서 알아보자.

Equatable은 위에서 설명했듯이 클래스를 비교하기 위해서 사용되고 있다.
왜 클래스를 비교하는데 Equatable Library가 필요한지에 대해서 명확히 짚어보기 위해 아래 예제를 준비했다.

자동차의 정보를 담고 있는 객체를 생성하였다. 해당 클래스는 자동차의 이름, 종류, 가격정보를 가지는 객체이다.

class Car {
  final String title;
  final String type;
  final int price;
  const Car(this.title, this.type, this.price);
}

Car 객체를 car1, car2로 파라미터를 똑같이 만들어 준 클래스 두 개를 각각 생성하였다.

 Car _car1 = Car("G80", "Sedan", 6000);
 Car _car2 = Car("G80", "Sedan", 6000);

아래와 같이 객체를 비교한 불리언 값을 출력해보자. 여기서 이해할 수 없는 출려 값이 나오게 된다.

car1과 car2의 title, type, price가 모두 동일한데, car1, car2의 객체를 서로 비교하면 false 값이 나온다. 왜 그럴까 ?

print(_car1.title == _car2.title);
print(_car1.type == _car2.type);
print(_car1.price == _car2.price);
print(_car1 == _car2);
// true
// true
// true
// false

사실 CS지식이 있으신 분들에게는 당연한 결과일 것이다. 하지만 이해하지 못하시는 분들이 있거나 정확히 이유를 모르고 사용하시던 분들도 있기에 간단하게 설명하자면 모델을 사용하기 위해서 객체를 생성하게 되는데, 이렇게 객체를 생성하는 과정을 인스턴스라고 한다.
인스턴스를 하면 컴퓨터 메모리의 한 영역을 차지하게 되는데, car1가 car2의 메모리 참조 값이 다르기 때문에 이러한 결과가 나오는 것이다.

자 그렇다면 객체가 생성될 때마다 객체간 비교가 불가능한 것인데, 이를 어떻게 해야할지 궁금할 것이다.

그전에 Dart 언어는 OOP 즉, 객체지향 프로그래밍이다. 여기서 객체는 Object이다. 그러기에 모든 클래스는 기본적으로 Object를 상속받은 상태라고 보면된다.

Flutter에서 Dart언어로 생성되는 모든 객체는 아래와 같은 형태를 가지게 되는데, 여기서 Object 상속 문법은 제외시켜서 간편하게 객체를 생성할 수 있도록 해준것이다.

class Car extends Object{
  final String title;
  final String type;
  final int price;
  const Car(this.title, this.type, this.price);
}

Object와 class equal간에 무슨 차이가 있길래 Object에 대한 이야기를 하였는지는 아래의 예제를 보면서 확인하자.

Dart의 모든 class는 아래와 같이 Object를 상속받고 있기 때문에 operator, hashCode를 override해서 재정의 할 수도 있다.

 
  bool operator ==(Object other) {
    return other is Car &&
        title == other.title &&
        type == other.type &&
        price == other.price;
  }

  
  int get hashCode {
    return title.hashCode + type.hashCode + price;
  }

이제 같은 객체라고 출력하는 것을 볼 수 있다.

print(_car1.title == _car2.title);
print(_car1.type == _car2.type);
print(_car1.price == _car2.price);
print(_car1 == _car2);
// true
// true
// true
// true

이번에는 파라미터의 값을 변경해보자. 당연히 false라고 출력된다.

Car _car1 = Car("G80", "Sedan", 6000);
Car _car2 = Car("G80", "Sedan", 8000);
print(_car1.title == _car2.title);
print(_car1.type == _car2.type);
print(_car1.price == _car2.price);
print(_car1 == _car2);
// true
// true
// false
// false

만약에 Car 클래스의 title이 같으면 같은 객체라고 만들어 주고 싶을 때는 어떻게 해야하는지 아래 코드를 살펴보자.

아까 작성했던 부분에서 title에 해당하는 값만 비교하게 하면 된다.

class Car extends Object {
	...
  
  bool operator ==(Object other) {
    return other is Car && title == other.title;
  }

  
  int get hashCode {
    return title.hashCode;
  }
}
print(_car1.title == _car2.title);
print(_car1.type == _car2.type);
print(_car1.price == _car2.price);
print(_car1 == _car2);
// true
// true
// false
// ture

지금까지 Dart언어의 class 비교에 대해서 무슨 문제가 있고, 어떻게 코드를 작성할 수 있을지에 대해 간단하게 알아보았다. 하지만 객체의 변수 값이 100개가 있다고 가정했을 때 저 코드를 일일히 넣어주는 것은 정말 노가다 작업이 될 것이다.

Equatable Library

Equal기능의 필요성에 대해서도 알아보았고, 어떻게 사용하는지도 알아보았으니 이번에는 좀 더 쉽게 Equal 기능을 사용하게 해주는 Equatable 라이브러리에 대해서 알아보도록 하자.

Library를 dependencie에 추가를 해주자.

pubspec.yaml

dependencies:
	equatable: ^2.0.5

Equatable 라이브러를 상속 받아 CarEquatable이라는 객체를 생성해 주었다.
Equatable을 상속받게 되면 아래 props를 재정의 할 수 있게 해주는데, 해당 부분은 우선 빈 배열로 두고 사용해 보자.

class CarEquatable extends Equatable {
  final String title;
  final String type;
  final int price;
  const CarEquatable(this.title, this.type, this.price);

  
  List<Object?> get props => [];
}

위에서 살펴본 예제와 똑같이 같은 내용의 객체를 각각 생성해 주었다. 자 이제 로그를 출력해보면 같은 class라는 출력 값이 나오게 된다. Equatable 라이브러리만 상속받아도 아래의 결과를 얻을 수 있다는 것을 확인했다.

CarEquatable _carEquatable1 = CarEquatable("G70", "SportSedan", 5000);
CarEquatable _carEquatable2 = CarEquatable("G70", "SportSedan", 5000);
print(_carEquatable1.title == _carEquatable2.title);
print(_carEquatable1.type == _carEquatable2.type);
print(_carEquatable1.price == _carEquatable2.price);
print(_carEquatable1 == _carEquatable2);
// true
// true
// true
// true

자 이번엔 title을 다르게 객체를 생성하고 출력해보면 당연히 같은 객체가 아니라고 출력이 될 것 같은데, 출력 값은 예상과는 다르게 동일한 객체라고 출력된다. 왜 이런 결과가 나올까요 ?

CarEquatable _carEquatable1 = CarEquatable("G70 Shooting Brake", "SportSedan", 5000);
CarEquatable _carEquatable2 = CarEquatable("G70", "SportSedan", 5000);
print(_carEquatable1.title == _carEquatable2.title);
print(_carEquatable1.type == _carEquatable2.type);
print(_carEquatable1.price == _carEquatable2.price);
print(_carEquatable1 == _carEquatable2);
// false
// true
// true
// true

그렇다면 이번에는 price도 다르게 생성하여 다시 출력을 해보자. 이번에도 동일 객체라고 출력이 된다.

CarEquatable _carEquatable1 = CarEquatable("G70 Shooting Brake", "SportSedan", 5000);
CarEquatable _carEquatable2 = CarEquatable("G70", "SportSedan", 5500);
print(_carEquatable1.title == _carEquatable2.title);
print(_carEquatable1.type == _carEquatable2.type);
print(_carEquatable1.price == _carEquatable2.price);
print(_carEquatable1 == _carEquatable2);
// false
// true
// false
// true

이번에는 모든 파라미터를 다르게 하여 출력을 해봐도 역시나 동일 객체라고 출력 값이 나오는 것을 볼 수 있는데, Equatable 라이브러리는 기본적으로 객체가 같다면 파라미터의 값이 다르더라도 동일 객체로 리턴하게 되어있다.

CarEquatable _carEquatable1 = CarEquatable("G70 Shooting Brake", "SportSedan", 5000);
CarEquatable _carEquatable2 = CarEquatable("G80", "Sedan", 7500);
print(_carEquatable1.title == _carEquatable2.title);
print(_carEquatable1.type == _carEquatable2.type);
print(_carEquatable1.price == _carEquatable2.price);
print(_carEquatable1 == _carEquatable2);
// false
// false
// false
// true

그렇다면 파라미터가 값이 전부 동일하지 않을 때에 다른 객체라고 인식하게 해주려면 어떻게 해야하는지 아래 예제를 통해 확인해보자.

props getter 부분에 파라미터 값을 넣어주니 이제 다른 객체라고 출력이 되는 것을 확인했다.

class CarEquatable extends Equatable {
  ...
  
  
  List<Object?> get props => [title, type, price];
}
CarEquatable _carEquatable1 = CarEquatable("G70 Shooting Brake", "SportSedan", 5000);
CarEquatable _carEquatable2 = CarEquatable("G80", "Sedan", 7500);
print(_carEquatable1.title == _carEquatable2.title);
print(_carEquatable1.type == _carEquatable2.type);
print(_carEquatable1.price == _carEquatable2.price);
print(_carEquatable1 == _carEquatable2);
// false
// false
// false
// false

props에 비교 대상이 되는 파라미터 값을 추가해주면 해당 파라미터 값이 기준이 되는 객체 비교 대상이 된다.

price를 equal의 기준으로 넣어주면 다른 객체로 인식할 것이다.

class CarEquatable extends Equatable {
  ...
  
  List<Object?> get props => [price];
}
CarEquatable _carEquatable1 = CarEquatable("G70", "SportSedan", 5000);
CarEquatable _carEquatable2 = CarEquatable("G70", "SportSedan", 5500);
print(_carEquatable1.title == _carEquatable2.title);
print(_carEquatable1.type == _carEquatable2.type);
print(_carEquatable1.price == _carEquatable2.price);
print(_carEquatable1 == _carEquatable2);
// true
// true
// false
// false

type을 비교대상에 추가하고 출력해봐도 당연히 price가 다르기에 동일 객체로 보지 않는 것을 확인할 수 있다.

class CarEquatable extends Equatable {
  ...
  
  List<Object?> get props => [type, price];
}
CarEquatable _carEquatable1 = CarEquatable("G70", "SportSedan", 5000);
CarEquatable _carEquatable2 = CarEquatable("G70", "SportSedan", 5500);
print(_carEquatable1.title == _carEquatable2.title);
print(_carEquatable1.type == _carEquatable2.type);
print(_carEquatable1.price == _carEquatable2.price);
print(_carEquatable1 == _carEquatable2);
// true
// true
// false
// false

마무리

이렇게 Class Equal 방법과 Equatable 라이브러리를 통한 객체 비교 및 사용법에 대해서 알아보았다.
Equal 기능은 상태 관리(State Management)에서도 중요하게 사용되고 있는데, 상태 이벤트를 발생 했을 때 상태의 변화가 있을 때도 없을 때도 있다. 상태 변화가 있을 때는 UI 변경을 하기위해 빌더를 호출해야 겠지만 상태 변화도 없는데 빌더를 호출하게 되면 불필요한 UI 상태 변화가 일어나게 될 것이다. 이럴 때 Equatable 라이브러리를 활용할 수 있다.

Equatable 라이브러리는 Bloc Pattern을 사용할 때를 제외하고는 잘 사용하지 않았는데, Bloc을 freezed code generator를 결합하여 사용할 때에는 Equatable 라이브러리를 따로 사용하지 않아도 되서 잘 사용을 하는 라이브러리는 아니었다.

Equatable을 한 번 사용하기 시작하면 정말 편한 라이브러리 중 하나가 된다. 앞으로는 폭 넓게 사용할 라이브러리가 될 것 같다는 생각이 든다.

원래 이번 글에서 모든 내용을 다루려고 하였는데, 글이 너무 길어져서 다음 글에서도 이어서 위에 잠깐 설명한 Equatable 라이브러리를 사용해야 하는 진짜 이유에 대해서 다루려고 한다.

profile
Flutter Developer
post-custom-banner

0개의 댓글