이 글은 중고나라 기술 블로그의 글을 참고하여 작성하게 되었습니다.
우선 저 같은 경우에는 게시글의 목록을 보여주는 화면을 생성하는데, 각각의 페이지에서 다루는 게시글의 타입이 다르지만 보여주는 형태는 비슷했습니다. 하지만 타입이 다르다고 각각의 타입에 따라 위젯을 그리는 것은 비효율적이라고 생각하고 있었는데 중고나라 기술 블로그를 보고 도움이 되어서 정리해보려고 합니다.
Selaed Class
Dart 3.0 에 추가된 새로운 기능으로 Sealed Class 가 나왔습니다.
TypeScript의 union types를 대체할 수 있는거같은데요, 같은 성질을 지니는 유형을 생성하고 해당 유형의 멤버를 생성할 수 있습니다.
sealed class를 사용하면 아래의 이점을 얻을 수 있습니다.
- 무분별한 클래스 상속을 제한
- 안전하고 정확한 타입 안정성
설명은 여기까지하고 직접 코드를 보는게 빠르겠죠?
게시글 타입마다 다루는 모델을 예시로 들어보겠습니다.
Content의 유형은 A,B,C 세가지라고 가정하고 각 유형에 대한 클래스를 생성합니다.
class AContent {
final String id;
final String name;
final String content;
final String writer;
final String createdAt;
const AContent({
required this.id,
required this.name,
required this.content,
required this.writer,
required this.createdAt,
});
}
class BContent {
final String id;
final String name;
final String content;
final String writer;
final String createdAt;
const BContent({
required this.id,
required this.name,
required this.content,
required this.writer,
required this.createdAt,
});
}
class CContent {
final String id;
final String name;
final String content;
final String price;
final String writer;
final String createdAt;
const CContent({
required this.id,
required this.name,
required this.price,
required this.content,
required this.writer,
required this.createdAt,
});
}
ContentItem에서는 각 Content 클래스의 데이터를 보여줘야하는데, 위 클래스들을 사용하여 ItemWidget을 생성해 보겠습니다.
또한 CContent일 경우에만 price를 할당하므로, 해당 값만 Optinal 하게 처리하였습니다.
import 'package:flutter/material.dart';
class ContentItem extends StatelessWidget {
const ContentItem({
Key? key,
required this.id,
required this.name,
required this.content,
required this.writer,
required this.createdAt,
this.price
}) : super(key: key);
final String id;
final String name;
final String content;
final String writer;
final String createdAt;
final String? price;
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
Text(name),
Text(writer),
Text(createdAt),
],
),
Row(
children: [
Text(content),
if (price != null)
Text(price!),
],
)
],
);
}
}
하지만 이 경우에는 각 값에다 할당해야 하고, 구조가 바뀌면 다른 곳에도 모두 일일이 바꿔야 하는 번거로움이 있습니다.
또한 타입의 데이터 인지도 헷갈려서 분간이 어렵습니다.
따라서, 화면에 표시되어야 하는 UI 및 데이터의 형태 등이 달라질 경우 데이터를 효율적으로 관리할 방법이 필요합니다.
그래서 sealed Class를 통해 Content를 생성해보았습니다.
sealed class Content {
final String id;
final String name;
final String content;
final String writer;
final String createdAt;
const Content({
required this.id,
required this.name,
required this.content,
required this.writer,
required this.createdAt,
});
}
위 class를 impletement하는 Content를 생성해줍니다.
class AContent implements Content {
@override
final String id;
@override
final String name;
@override
final String content;
@override
final String writer;
@override
final String createdAt;
const AContent({
required this.id,
required this.name,
required this.content,
required this.writer,
required this.createdAt,
});
}
class BContent implements Content {
@override
final String id;
@override
final String name;
@override
final String content;
@override
final String writer;
@override
final String createdAt;
const BContent({
required this.id,
required this.name,
required this.content,
required this.writer,
required this.createdAt,
});
}
class CContent implements Content {
final String id;
@override
final String name;
@override
final String content;
@override
final String writer;
@override
final String createdAt;
final String price;
const CContent({
required this.id,
required this.name,
required this.content,
required this.writer,
required this.createdAt,
required this.price,
});
}
sealed class 사용의 장점
1. 명확한 타입 분리와 안전성
- 타입 안정성: sealed class를 사용하면 특정 클래스들이 상위 타입을 구현하도록 강제할 수 있습니다. 이를 통해 각 클래스가 명확하게 구분되고, 컴파일 시점에서 타입 검사가 이루어져 런타임 오류를 줄일 수 있습니다.
- 명확한 구조: 각 클래스(AContent, BContent, CContent)는 고유의 속성을 가지며, 이를 통해 해당 타입의 객체들이 어떤 속성을 가지는지 명확히 알 수 있습니다.
2. 유연한 JSON 파싱
- 유니온 키를 통한 파싱: freezed 패키지의 unionKey를 사용하면 JSON 데이터를 유연하게 파싱할 수 있습니다. JSON 데이터가 다양한 형태를 가질 때, 이를 각각의 클래스로 적절히 매핑할 수 있어 코드의 가독성과 유지보수성이 높아집니다.
- 중앙화된 데이터 처리: ContentDto 클래스를 통해 다양한 형태의 데이터를 중앙에서 관리할 수 있으며, 이는 코드의 일관성을 유지하는 데 큰 도움이 됩니다.
3. 객체지향적 설계 원칙 준수
- 단일 책임 원칙(SRP): 각 클래스는 자신만의 고유한 책임을 가지며, 다양한 요구사항을 충족시키기 위해 각자 필요한 속성과 메서드를 가질 수 있습니다. 예를 들어,CContent 클래스는 price를 처리하지만 AContent,BContent 클래스는 그렇지 않습니다.
- 개방-폐쇄 원칙(OCP): 새로운 타입의 콘텐츠를 추가하고 싶을 때, 기존 코드를 수정할 필요 없이 새로운 클래스를 추가하면 됩니다. 이는 코드의 확장성을 높여주며, 기존 코드에 영향을 미치지 않고 새로운 요구사항을 수용할 수 있게 합니다.
- 다형성: Content 타입을 통해 다양한 하위 클래스를 동일한 인터페이스로 다룰 수 있습니다. 이는 코드를 단순하고 유연하게 만들며, 새로운 차량 타입이 추가되더라도 기존 로직을 재사용할 수 있습니다.
4. 유지보수성과 확장성
- 유지보수 용이성: sealed class와 freezed를 사용하면 각 클래스의 변경사항이 명확히 구분되어 유지보수가 용이합니다. 각 클래스의 변경이 다른 클래스에 영향을 미치지 않으므로, 코드 수정 시 발생할 수 있는 오류를 줄일 수 있습니다.
- 확장성: 새로운 요구사항이 생길 때, 기존 구조를 크게 변경하지 않고도 새로운 기능을 추가할 수 있습니다. 이는 코드의 안정성을 유지하면서도 빠르게 변화하는 요구사항에 대응할 수 있게 합니다.
평소에 알기만 했지 실제로 작성해본적은 처음인데 많은 도움이 된 것 같습니다.
'Mobile > Dart' 카테고리의 다른 글
Dart에서 비동기 작업을 처리하는 방법 - 1 (Future) (0) | 2025.03.15 |
---|---|
[Dart] Equatable 뽀개기 (0) | 2024.10.07 |
[Dart] Factory 생성자 (0) | 2024.08.29 |
[Dart] 추상 클래스와 인터페이스의 차이점 (0) | 2024.08.23 |
[Dart] Stream 뽀개기 (0) | 2024.07.31 |