Last active
August 12, 2022 04:56
-
-
Save XavierChanth/660c9cfdff08a4ce908a7be53ddd62da to your computer and use it in GitHub Desktop.
A nicer pattern for Dart beans.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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