현재 듣고 있는 강의에서 제네릭사용을 강조하는것 같아서 정리를 해보려고 한다. 사실 자바를 배울때도 제네릭을 사용해본적이 있고 다트를 처음 배울때도 제네릭을 사용해보았지만 제대로 정리해본적이 없어서 실제 코드에서 사용하려고 하면 어떻게 해야할지 몰랐던 적이 많았다. https://www.youtube.com/watch?v=NgcKGw6pIYg 영상을 보고 도움을 많이 받았다.
제네릭이란 ?
공식 문서에는 기본 배열 타입의 API 문서를 보면, List 타입이 List로 표기되어 있는 걸 볼 수 있다. <…> 표시는 List를 형식 타입 매개변수를 가지는 제네릭 (또는 매개변수화된) 타입으로 지정한다. 관례상 대부분의 타입 변수는 E, T, S, K, V 같은 single-letter 이름을 가진다.
보통 타입 세이프티 때문에 제네릭을 사용한다. 즉, class의 Type을 안전하게 사용하기 위한 목적이다. 또한 , 가독성이 좋아지고, 중복코드를 줄이며 유지보수를 보다 쉽게 만들어 준다.
타입안정성(Type Safety)을 코드를 통해 알아보자.
// 제네릭 사용 x
List strList = [];
strList.add('one');
strList.add('two');
strList.addAll(['three,four,five']);
strList.add(1);
// 제네릭 사용 o
List<String> strList = [];
strList.add('one');
strList.add('two');
strList.addAll(['three,four,five']);
//error : The argument type 'int' can't be assigned to the parameter type 'String'
strList.add(1);
리스트가 문자열 값만 가지게 하고 싶다면, 이렇게 제네릭을 사용하여 List로 리스트를 선언하면 됩니다. 그렇게 함으로써, 동료와 개발 툴이 문자열 이외의 값은 리스트에 추가될 수 없다는 것을 알수 있음으로써 , 가독성과 유지보수적인 부분이 매우 좋아진다.
제네릭이 어떤이유로 사용이 되는지 알아보았다. 그렇다면 제네릭을 사용한 코드랑 제네릭을 사용하지 않은 코드를 비교하면서 제네릭사용시 장점을 알아 보겠다.
import 'package:flutter_test/flutter_test.dart';
void main() {
test('Generic Test', () {
var chest = WorkOut(
name: '벤치프레스',
isIncludeArm: true,
description: '재밌다',
target: WorkOutTarget.Chest,
day: '월,목');
var back = WorkOut(
name: '데드리프트',
isIncludeArm: true,
description: '너무 재밌다.',
target: WorkOutTarget.Back,
day: '화,토');
var leg = WorkOut(
name: '스쿼트',
isIncludeArm: false,
description: '너무 힘듬',
target: WorkOutTarget.Leg,
day: '수');
chest.setWorkOutMore(Chest(op1: '가슴은 벤치프레스', op2: '인클라인 벤치프레스도 굳'));
back.setWorkOutMore(Back(op3: '데드리프트 재밌어', op4: '근데 너무 힘듬'));
leg.setWorkOutMore(Leg(op5: '스쿼트 진짜 너무 힘듬', op6: '근데 무게는 스쿼트만 느는중'));
print((chest.more as Chest).op1);
print((back.more as Back).op3);
print((leg.more as Leg).op6);
});
}
enum WorkOutTarget { Chest, Back, Leg }
abstract class CommonWorkOut {}
class Chest extends CommonWorkOut {
String op1;
String op2;
Chest({required this.op1, required this.op2});
}
class Back extends CommonWorkOut {
String op3;
String op4;
Back({required this.op3, required this.op4});
}
class Leg extends CommonWorkOut {
String op5;
String op6;
Leg({required this.op5, required this.op6});
}
class WorkOut {
String name;
bool isIncludeArm;
String description;
String day;
WorkOutTarget target;
WorkOut(
{required this.name,
required this.isIncludeArm,
required this.description,
required this.target,
required this.day});
CommonWorkOut? _more;
setWorkOutMore(CommonWorkOut commonWorkOut) {
_more = commonWorkOut;
}
dynamic get more => _more;
}
출력
가슴은 벤치프레스
데드리프트 재밌어
근데 무게는 스쿼트만 느는중
제네릭을 사용하지 않고 작성한 코드 입니다. 출력을 하기 위해서는 chest,back,leg class가 모두 CommonWorkOut을 상속 받고 있기 때문에 형변환을 해주어야 각각의 옵션 변수들을 출력할수 있게 됩니다.
제네릭을 사용할 경우
import 'package:flutter_test/flutter_test.dart';
void main() {
test('Generic Test', () {
var chest = WorkOut<Chest>(
name: '벤치프레스',
isIncludeArm: true,
description: '재밌다',
target: WorkOutTarget.Chest,
day: '월,목');
var back = WorkOut<Back>(
name: '데드리프트',
isIncludeArm: true,
description: '너무 재밌다.',
target: WorkOutTarget.Back,
day: '화,토');
var leg = WorkOut<Leg>(
name: '스쿼트',
isIncludeArm: false,
description: '너무 힘듬',
target: WorkOutTarget.Leg,
day: '수');
chest.setWorkOutMore(Chest(op1: '가슴은 벤치프레스', op2: '인클라인 벤치프레스도 굳'));
back.setWorkOutMore(Back(op3: '데드리프트 재밌어', op4: '근데 너무 힘듬'));
leg.setWorkOutMore(Leg(op5: '스쿼트 진짜 너무 힘듬', op6: '근데 무게는 스쿼트만 느는중'));
print(chest.more!.op1);
print(back.more!.op3);
print(leg.more!.op6);
});
}
enum WorkOutTarget { Chest, Back, Leg }
abstract class CommonWorkOut {}
class Chest extends CommonWorkOut {
String op1;
String op2;
Chest({required this.op1, required this.op2});
}
class Back extends CommonWorkOut {
String op3;
String op4;
Back({required this.op3, required this.op4});
}
class Leg extends CommonWorkOut {
String op5;
String op6;
Leg({required this.op5, required this.op6});
}
class WorkOut<T extends CommonWorkOut> {
String name;
bool isIncludeArm;
String description;
String day;
WorkOutTarget target;
WorkOut(
{required this.name,
required this.isIncludeArm,
required this.description,
required this.target,
required this.day});
T? _more;
setWorkOutMore(T commonWorkOut) {
_more = commonWorkOut;
}
T? get more => _more;
}
출력
가슴은 벤치프레스
데드리프트 재밌어
근데 무게는 스쿼트만 느는중
제네릭 사용시 WorkOut 클래스를 생성하게 되면 제네릭 타입를 받아줌으로써 chest,back,leg 생성시 제네릭 타입을 지정해 주기 때문에 출력을 하기 위해서 형변환을 해주지 않아도 됩니다.
참고
https://www.youtube.com/watch?v=NgcKGw6pIYg
https://dart-ko.dev/language/generics