플러터에서의 Immutable Programming: copyWith 함수 마스터하기!

Written by 코드팩토리 JC

2월 11, 2024

서론

불변 프로그래밍: 현대 개발의 핵심

현대 소프트웨어 개발에서 불변 프로그래밍(Immutable Programming)의 중요성은 간과할 수 없는 요소입니다. 플러터(Flutter)에서도 마찬가지로 불변 프로그래밍 개념이 매우 중요하며, copyWith 함수는 이러한 불변성을 유지하는 데 핵심적인 역할을 합니다. 이 글에서는 플러터를 배우기 시작하는 개발자들에게 불변 프로그래밍의 중요성을 강조하고, copyWith 함수의 역할과 사용 방법에 대해 설명 해보겠습니다!

copyWith 함수란?

불변성을 위한 단순하지만 강력한 해결책

플러터에서 copyWith 함수는 불변 객체를 효율적으로 관리하는 강력한 방법을 제공합니다. 불변 객체는 한 번 생성된 후 상태가 변경되지 않는 객체를 의미하며, 개발자들에게 생산성과 예측 가능성을 높여줍니다. copyWith 함수를 사용하면 기존 객체의 상태를 유지하면서 필요한 부분만 변경하여 새 객체를 생성할 수 있습니다, 이는 플러터 개발을 할때 가독성 증진과 버그 감소에 중요한 역할을 합니다.

copyWith의 작동 원리 및 코드 예제

copyWith 함수는 Dart 언어에서 자체적으로 제공해주는 함수는 아닙니다. copyWith 함수는 클래스의 인스턴스에 대해 호출되며, 변경을 원하는 속성들을 매개변수로 받아 새로운 인스턴스를 생성합니다. 모든 속성이 복사되며, 지정된 속성만 새 값으로 갱신됩니다. 다음 예제를 확인해보고 copyWith 함수가 어떤식으로 사용되는지 확인 해보겠습니다.

class User {
  final String name;
  final int age;

  User({this.name, this.age});

  User copyWith({String name, int age}) {
    return User(
      name: name ?? this.name,
      age: age ?? this.age,
    );
  }
}

void main() {
  User user1 = User(name: 'Alice', age: 30);
  User user2 = user1.copyWith(age: 31);

  print(user1.name); // 출력: Alice
  print(user1.age); // 출력: 30
  print(user2.name); // 출력: Alice
  print(user2.age); // 출력: 31
}
Dart

이 코드에서 copyWith 메소드는 User 객체의 일부 속성만을 변경하여 새로운 User 객체를 생성합니다. 이렇게 함으로써, 원본 객체의 불변성은 유지되면서 필요한 속성의 값만 갱신할 수 있습니다. 거창한 설명해 비해 생각보다 크게 어렵지는 않죠? 그럼 이런 불변 프로그래밍의 장점이 도대체 뭔지 자세히 알아보도록 하겠습니다.

불변 프로그래밍의 중요성

불변성이란 무엇인가?

불변 프로그래밍, 즉 Immutable Programming은 객체가 생성된 후에는 그 상태를 변경할 수 없도록 하는 프로그래밍 패러다임입니다. 이러한 패러다임은 특히 현대의 복잡한 애플리케이션 개발에서 중요한 역할을 합니다. 불변 객체는 한 번 생성되면 그 상태가 고정되어, 예기치 않은 변경으로부터 보호받을 수 있습니다. 이는 다양한 버그를 방지하고, 코드의 예측 가능성을 높이는 데 큰 도움이 됩니다.

불변성이 가져오는 이점

  • 예측 가능성: 불변 객체는 생성 후 변경되지 않으므로, 코드의 동작을 예측하기가 더 쉬워집니다. 이는 특히 대규모 프로젝트나 팀 작업에서 중요한 장점입니다.
class ImmutableUser {
  final String name;
  final int age;

  ImmutableUser(this.name, this.age);
}

void main() {
  final user = ImmutableUser('Alice', 30);
  // user.name = 'Bob'; // 이 코드는 컴파일 에러를 발생시킴
  print(user.name); // 출력: Alice
}
Dart

이 예제에서, ImmutableUser 클래스는 한 번 생성되면 변경할 수 없습니다. 따라서 객체의 상태가 일관되게 유지되며, 코드의 동작을 예측하기 쉬워집니다.

  • 버그 감소: 불변 객체는 상태 변경으로 인한 부작용이 없으므로, 버그 발생 가능성이 크게 감소합니다.
class ImmutableSettings {
  final bool darkMode;
  final String language;

  ImmutableSettings(this.darkMode, this.language);

  ImmutableSettings copyWith({bool darkMode, String language}) {
    return ImmutableSettings(
      darkMode ?? this.darkMode,
      language ?? this.language,
    );
  }
}

void main() {
  final settings = ImmutableSettings(true, 'English');
  final updatedSettings = settings.copyWith(darkMode: false);
  // settings.darkMode = true; // 이 코드는 컴파일 에러를 발생시킴
  print(updatedSettings.darkMode); // 출력: false
}
Dart

이 코드에서, ImmutableSettings 객체는 copyWith 메서드를 통해서만 변경할 수 있습니다. 이 방식은 예상치 못한 상태 변경을 방지하고 버그를 줄입니다.

  • 멀티스레드 환경에서의 안정성: 불변 객체는 멀티스레드 환경에서 동시성 문제를 방지하는 데 도움이 됩니다. 객체의 상태가 변경되지 않기 때문에, 여러 스레드에서 동시에 접근해도 안전합니다.
class ImmutableCounter {
  final int count;

  ImmutableCounter(this.count);

  ImmutableCounter increment() {
    return ImmutableCounter(count + 1);
  }
}

void main() {
  final counter = ImmutableCounter(0);
  final incrementedCounter = counter.increment();
  print(incrementedCounter.count); // 출력: 1
}
Dart

이 코드에서 ImmutableCounter는 불변성을 유지합니다. 여러 스레드가 동시에 접근해도 count 값은 항상 일관성을 유지합니다.

  • 효율적인 메모리 관리: 플러터에서 불변 객체는 새로운 상태가 필요할 때만 객체를 생성합니다. 이는 메모리 관리를 최적화하고, 성능을 향상시키는 데 기여합니다.
class ImmutableConfiguration {
  final String environment;
  final String version;

  ImmutableConfiguration(this.environment, this.version);

  ImmutableConfiguration updateVersion(String newVersion) {
    return ImmutableConfiguration(environment, newVersion);
  }
}

void main() {
  final config = ImmutableConfiguration('production', '1.0.0');
  final updatedConfig = config.updateVersion('1.0.1');
  print(updatedConfig.version); // 출력: 1.0.1
}
Dart

이 예제에서 ImmutableConfiguration 객체는 새로운 버전으로 업데이트될 때만 새로운 객체가 생성됩니다. 이는 불필요한 객체 생성을 방지하고 메모리 사용을 최적화합니다.

copyWith 함수의 실제 사용 사례

MVC 패턴에서 모델 관리

플러터 개발에서 copyWith 함수는 특히 MVC(Model-View-Controller) 패턴을 사용하는 경우 모델 관리에 매우 효과적입니다. MVC 패턴에서 모델은 애플리케이션의 데이터와 비즈니스 로직을 나타냅니다. copyWith은 모델의 불변성을 유지하면서, 필요한 부분만 변경하여 새로운 상태를 생성할 수 있는 효율적인 방법을 제공합니다.

class TodoModel {
  final String title;
  final bool isCompleted;

  TodoModel({this.title, this.isCompleted = false});

  TodoModel copyWith({String title, bool isCompleted}) {
    return TodoModel(
      title: title ?? this.title,
      isCompleted: isCompleted ?? this.isCompleted,
    );
  }
}

void main() {
  TodoModel task = TodoModel(title: 'Flutter Study');
  TodoModel updatedTask = task.copyWith(isCompleted: true);

  print(updatedTask.title); // 출력: Flutter Study
  print(updatedTask.isCompleted); // 출력: true
}
Dart

이 예제에서 TodoModel 클래스는 할 일 목록의 항목을 나타냅니다. copyWith 메서드를 사용하여, 변경하고 싶은 프로퍼티만 변경하고 나머지 값은 유지 할 수 있습니다. 이 특정 예제에서는 할 일의 완료 상태가 변경될때마다 제목은 유지하며 완료 상태만을 변경할 수 있는걸 볼 수 있습니다. 에제는 프로퍼티가 두개밖에 없지만 프로퍼티가 10개, 20개 또는 더 많아질경우 copyWith 함수를 제작 해두는건 불변 프로그래밍을 할때 매우 유용합니다.

마무리

어떤가요? 이정도면 copyWith 함수가 얼마나 유용한지 느껴지나요? 여러분도 앞으로 copyWith 함수를 사용해서 손쉽게 불변 프로그래밍을 유지하고 효율적인 플러터 프로젝트를 진행 해보세요!

관련 포스트

ChatGPT가 이야기하는 2024년 개발자 로드맵

ChatGPT가 이야기하는 2024년 개발자 로드맵

서론 개발자의 여정을 시작하며 안녕하세요, 미래의 개발자 여러분! 오늘부터 시작하는 여러분의 개발 여정에 함께할 수 있어서 기쁩니다. 2023년은 기술이 매우 빠르게 변화하는 해였으며, 이러한 변화 속에서 개발자가 되기 위한 길은 더욱 다채롭고 흥미로워졌습니다. 이 로드맵은 초보자인 여러분이 개발의 세계에 첫발을 내딛는 데 필요한 기초부터 시작해, 점차 심화 단계로 나아가는 길을 안내해 드릴 것입니다. 백엔드 개발 이 글은 단순히 기술을 배우는 것 이상의 의미를 가집니다....

Flutter Freezed 플러그인! Entity Code Generation은 이거 하나로 끝!

Flutter Freezed 플러그인! Entity Code Generation은 이거 하나로 끝!

https://youtu.be/i5p6wXLAX7I 서론 Flutter 는 Code Generation 기능이 상당히 많이 활성화되어 있어요. 흔히들 많이 사용하는 json_serializable 라이브러리도 있고 retrofit 및 chopper 라이브러리도 있습니다. 오늘 알려드릴 freezed 또한 데이터 클래스에 편의 기능들을 제공해주는 code generation 라이브러리입니다. Freezed vs Json Serializable Code Generation 이라는...

Flutter Equatable 플러그인, 도대체 어디에 쓰는건가요?

Flutter Equatable 플러그인, 도대체 어디에 쓰는건가요?

https://youtu.be/9-FGJHTRRW0 서론 안녕하세요 코드팩토리입니다. 오늘은 Equatable 플러그인 사용법에대한 강의를 가져왔어요. 외부 플러그인들을 보거나 튜토리얼을 보면 클래스들이 Equatable 부모 클래스를 익스텐드하는걸 자주 볼 수 있는데 커뮤니티에서도 종종 질문이 들어와서 왜 Equatable 플러그인을 사용해야하는지 설명을 해보려고 합니다. Equatable 이란? Equatable 플러그인은 한 인스턴스와 다른 인스턴스가 같은 인스턴스인지...