Skip to content

Instantly share code, notes, and snippets.

@XavierChanth
Last active August 12, 2022 04:56
Show Gist options
  • Save XavierChanth/660c9cfdff08a4ce908a7be53ddd62da to your computer and use it in GitHub Desktop.
Save XavierChanth/660c9cfdff08a4ce908a7be53ddd62da to your computer and use it in GitHub Desktop.
A nicer pattern for Dart beans.
import 'dart:io';
// Enum as status Pattern (Approach 1)
enum MyStatusEnum { one, two, three }
class SmellyBean {
MyStatusEnum myStatus;
final String? onlyOnOne;
final String? onlyOnTwo;
final String? maybeOnTwo;
final String? oneOrThree;
SmellyBean._({required this.myStatus, this.onlyOnOne, this.onlyOnTwo, this.maybeOnTwo, this.oneOrThree});
factory SmellyBean.one({required String onlyOnOne, required String oneOrThree}) => SmellyBean._(
myStatus: MyStatusEnum.one,
onlyOnOne: onlyOnOne,
oneOrThree: oneOrThree,
);
factory SmellyBean.two({required String onlyOnTwo, String? maybeOnTwo}) => SmellyBean._(
myStatus: MyStatusEnum.two,
onlyOnTwo: onlyOnTwo,
maybeOnTwo: maybeOnTwo,
);
factory SmellyBean.three({required String oneOrThree}) => SmellyBean._(
myStatus: MyStatusEnum.three,
oneOrThree: oneOrThree,
);
}
// Inheritance pattern (Approach 2) ~ A cleaner way
// when we expect a certain set of values on certain statuses
abstract class TastyBean {}
abstract class TastyBeanOneOrThree implements TastyBean {
abstract final String oneOrThree;
}
class TastyBeanOne implements TastyBeanOneOrThree {
final String onlyOnOne;
@override
final String oneOrThree;
TastyBeanOne(this.onlyOnOne, this.oneOrThree);
}
class TastyBeanTwo implements TastyBean {
final String onlyOnTwo;
final String? maybeOnTwo;
TastyBeanTwo(this.onlyOnTwo, this.maybeOnTwo);
}
class TastyBeanThree implements TastyBeanOneOrThree {
@override
final String oneOrThree;
TastyBeanThree(this.oneOrThree);
}
/// Why to prefer approach 2 over approach 1 when possible:
/// I have to read the [SmellyBean] class in order to understand which fields aren't null
/// Where as the covariants of [TastyBean] naturally tell us which fields are available.
/// When not to use approach 2:
/// When a switch case is fundamentally more appropriate than if statements for handling branching
/// (I suspect there are very few examples where this is absolutely the case)
// EXAMPLE OF USAGE
SmellyBean getSmellyBeans() => SmellyBean.one(onlyOnOne: 'Hello,', oneOrThree: 'World!');
TastyBean getTastyBeans() => TastyBeanOne('Hello,', 'World!');
void main() {
SmellyBean smellyBeans = getSmellyBeans();
TastyBean tastyBeans = getTastyBeans();
// Simple flow
if (smellyBeans.myStatus == MyStatusEnum.one) {
stdout.writeln('${smellyBeans.onlyOnOne} ${smellyBeans.oneOrThree}');
// but why can I access
smellyBeans.onlyOnTwo;
// if status is one...
// and why is the type of
smellyBeans.onlyOnOne;
// a String? when we know it's not null
// it should be a String on the interface
}
if (tastyBeans is TastyBeanOne) {
stdout.writeln('${tastyBeans.onlyOnOne} ${tastyBeans.oneOrThree}');
// hey look,
// tastyBeans.onlyOnTwo;
// isn't a property here.
// That's a good sign!
}
// A more complex flow
// where we want to do the same thing for more than one status
// (In this case one and three)
// This is where a switch/case should make it easier to follow the branching.
// however Dart only supports switch fallthrough
// for empty cases, case one here is not supported in this commented example:
// switch (smellyBeans.myStatus) {
// case MyStatusEnum.one:
// stdout.write(smellyBeans.onlyOnOne);
// case MyStatusEnum.three:
// stdout.writeln(smellyBeans.oneOrThree);
// break;
// case MyStatusEnum.two:
// break;
// }
// Normally that is what a switch statement is useful for
// but it doesn't work without some hacky workaround
// like this technique:
switch (smellyBeans.myStatus) {
case MyStatusEnum.one:
stdout.write('${smellyBeans.onlyOnOne} ');
continue three;
three: // Wait, is that a label?
case MyStatusEnum.three:
stdout.writeln(smellyBeans.oneOrThree);
break;
case MyStatusEnum.two:
break;
}
// With that being said, approach 2 works for the complex example
// and it is relatively easy to follow
if (tastyBeans is TastyBeanOne) {
stdout.write('${smellyBeans.onlyOnOne} ');
}
if (tastyBeans is TastyBeanOneOrThree) {
stdout.writeln(smellyBeans.oneOrThree);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment