2

My question is about how to properly use Factory constructors when dealing with relatively complext immutable objects. Suppose I want to return an instance of a class with some of the properties changed.

Example

@immutable
class SomeObject {
  final int id;
  final String name;
  final int upvote;
  final int downvote;
  final int favorite;

  SomeObject({this.id, this.name, this.upvote, this.downvote, this.favorite});

  factory SomeObject.upvoted() {
    return SomeObject(
      upvote: this.upvote + 1 // apparently can't use this keyword here, wrong syntax
    );
  }
  SomeObject.upvoted(SomeObject ref) {
    id = ref.id;
    // cant change an immutable type
  }

  SomeObject upvoted() {
    return SomeObject(
      upvote: upvote + 1,
      //... other properties are null, bad idea?
    );
  }

  SomeObject upvotedWithDefaultConstructorUsingReference(SomeObject ref) {
    // works but seems like an overkill, especially considering more number of properties
    return SomeObject(
      id: ref.id,
      name: ref.name,
      upvote: upvote + 1,
      downvote: ref.downvote,
      favorite: ref.downvote
    );
  }
}

The SomeObject.upvoted() would be an instance of the same class but its upvoted property is +1 more than the referenced one. And there will be more like as downvoted(), withNameChanged() or copyWith().

The first 2 are constructors and others are just methods that return an instance of SomeObject class. What should be the approach here? How can I use factory constructors while the class is immutable? Also I'm not sure about the difference of these 4 examples.

I have already read the answers for this question but it doesn't seem to answer mine.

Firat
  • 381
  • 1
  • 5
  • 17

1 Answers1

1

Looks like you want a copyWith type pattern:

class SomeObject {
  final int id;
  final String name;
  final int upVote;
  final int downVote;
  final int favorite;

  SomeObject({this.id, this.name, this.upVote, this.downVote, this.favorite});

  SomeObject copyWith({
    int id,
    String name,
    int upVote,
    int downVote,
    int favorite,
  }) {
    return SomeObject(
      id: id ?? this.id,
      name: name ?? this.name,
      upVote: upVote ?? this.upVote,
      downVote: downVote ?? this.downVote,
      favorite: favorite ?? this.favorite,
    );
  }
}

You could adapt this in whichever way you like: upVoted copies with upVote incremented, leaving the rest the same, or allowing them to be changed.

  SomeObject upVoted() {
    return SomeObject(
      id: id, // no need for 'this' here
      name: name,
      upVote: upVote + 1,
      downVote: downVote,
      favorite: favorite,
    );
  }

Combining the two you could come up with endless variations:

  SomeObject upVoted() => copyWith(upVote: upVote + 1);
  SomeObject downVoted() => copyWith(downVote: downVote + 1);
  SomeObject upVoteRetracted() => copyWith(upVote: upVote - 1);

... which then makes you start to wonder why this class is immutable. It seems like it would make more sense to have it hold and mutate its state, rather than making multiple copies with different values.

Richard Heap
  • 48,344
  • 9
  • 130
  • 112
  • Yes, the copyWith() type of approach is extremely common for Flutter widgets as well. Thanks! – Firat Apr 17 '20 at 10:31