-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extend existing class to use mixin? #2166
Comments
The "add API to existing class" sounds similar to Rust traits as well. There are several proposals for something like that (like #2122). |
Hi @lrhn and thanks for responding. I checked the following documentation: https://doc.rust-lang.org/book/ch10-02-traits.html. As mentioned in the documentation:
It seems what I want is quite similar to the following Rust sample code: pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
} Is it possible to do the same thing in current version of Dart? |
In this particular case where // Class that we cannot edit, in library L1.
class MyClassOne {
int count = 0;
}
// Extension in some library L2 that imports L1.
extension CustomMixinExt on MyClassOne {
double get value => count.toDouble();
set value(double v) => count = v.toInt();
void sayName() => print('This is MyStructOne');
int getNumber() => 1;
} This means that any expression of type It is a well-known issue that the methods are not available when You will not be able to invoke the added methods dynamically (extension methods are always resolved statically), the method implementations cannot be overridden by subclasses of You don't get to reuse a set of member declarations in a mixin like |
Here is a workaround: mixin CustomMixin {
double value = 0.0;
void sayName() {}
int getNumber() => 0;
}
/// This is a class defined by other people that I cannot modify
class MyClassOne {
final int count;
MyClassOne(this.count);
}
/// Combines the code in [CustomMixin] with [MyClassOne].
class MyClassTwo extends MyClassOne with CustomMixin {
MyClassTwo(int count) : super(count);
}
/// Easy conversion from [MyClassOne] to [MyClassTwo]
extension UseMixinOnClass on MyClassOne {
MyClassTwo get custom => MyClassTwo(count);
}
void main() {
final object = MyClassOne(0);
print(object.custom.getNumber()); // method from CustomMixin
} And in cases where you're the one creating instances of |
@Levi-Lesches Thanks for providing a possible solution. I also figure out another way to solve my problem using abstract class. Here is my sample code: /// There are two classes defined by other people that I cannot modify
class MyClassOne {
final int count;
MyClassOne({required this.count});
}
class MyClassTwo {
final int value;
MyClassTwo({required this.value});
}
/// This is something I want to use as a protocol
abstract class CustomProtocol {
static CustomProtocol? convertFromRawData(dynamic param) {
// convert to different subclass
if (param is MyClassOne) {
return ConvertedMyClassOne(rawData: param);
} else if (param is MyClassTwo) {
return ConvertedMyClassTwo(rawData: param);
}
print("param type ${param.runtimeType} is not supported, cannot be converted into concrete CustomProtocol");
return null;
}
// subclass must provide implementations of these protocol methods
void sayName() => throw UnimplementedError("CustomProtocol.sayName is not implemented");
int getNumber() => throw UnimplementedError("CustomProtocol.getNumber is not implemented");
}
class ConvertedMyClassOne extends CustomProtocol {
ConvertedMyClassOne({required this.rawData});
final MyClassOne rawData;
@override
void sayName() => print("I'm ConvertedMyClassOne");
@override
int getNumber() => rawData.count;
}
class ConvertedMyClassTwo extends CustomProtocol {
ConvertedMyClassTwo({required this.rawData});
final MyClassTwo rawData;
@override
void sayName() => print("I'm ConvertedMyClassTwo");
@override
int getNumber() => rawData.value;
}
void main() {
// Instead of using MyClassOne and MyClassTwo directly, use ConvertedMyClassOne and ConvertedMyClassTwo to treat them as CustomProtocol
// To create instance of class ConvertedMyClassOne or ConvertedMyClassTwo, use CustomProtocol.convertFromRawData
final myList = [
CustomProtocol.convertFromRawData(MyClassOne(count: 1)),
CustomProtocol.convertFromRawData(MyClassTwo(value: 2)),
];
for (final item in myList) {
item?.sayName();
print("getNumber == ${item?.getNumber()}");
}
} |
While that certainly works, I'd suggest going with my approach if possible since
However, I see that // ----- Classes you don't control -----
class MyClassOne {
final int count;
MyClassOne({required this.count});
}
class MyClassTwo {
final int value;
MyClassTwo({required this.value});
}
// ----- Classes you do control -----
abstract class CustomProtocol {
void sayName();
int get number;
}
class ConvertedOne extends CustomProtocol {
MyClassOne object;
ConvertedOne(this.object);
@override
int get number => object.count;
@override
void sayName() => print("I'm a ConvertedOne");
}
class ConvertedTwo extends CustomProtocol {
MyClassTwo object;
ConvertedTwo(this.object);
@override
int get number => object.value;
@override
void sayName() => print("I'm a ConvertedTwo");
}
// ----- Logic -----
void main() {
final one = MyClassOne(count: 1);
final two = MyClassTwo(value: 2);
final List<CustomProtocol> myList = [
ConvertedOne(one),
ConvertedTwo(two),
];
for (final item in myList) {
item.sayName();
print("item.number == ${item.number}");
}
} That would be helpful if you still need to access the original object's methods and fields. If you're only interested in a few fields, then you can make it even simpler by copying them into your own class and discarding the original: // ----- Classes you don't control -----
class ClassOne {
final int count;
ClassOne({required this.count});
}
class ClassTwo {
final int value;
ClassTwo({required this.value});
}
// ----- Classes you do control -----
class CustomProtocol {
final int number;
final String name;
CustomProtocol.fromOne(ClassOne obj) :
number = obj.count,
name = "ConvertedOne";
CustomProtocol.fromTwo(ClassTwo obj) :
number = obj.value,
name = "ConvertedTwo";
void sayName() => print("I'm a $name");
}
// ----- Logic -----
void main() {
final one = ClassOne(count: 1);
final two = ClassTwo(value: 2);
final myList = [
CustomProtocol.fromOne(one),
CustomProtocol.fromTwo(two),
];
for (final item in myList) {
item.sayName();
print("item.number == ${item.number}");
}
} |
Looking for this as well, for now I solve it by creating Union types. Here's an example Note: I know toString() can already do this, I just used String as an example cause it's easy lol @freezed
class RawString with _$RawString {
const factory RawString.string(String value) = StringRawString;
const factory RawString.integer(int value) = IntRawString;
const factory RawString.double(double value) = DoubleRawString;
const factory RawString.person(Person value) = PersonRawString;
const RawString._();
String toRawString() {
return map(
string: (value) => value.value,
integer: (value) => value.value.toString(),
double: (value) => value.value.toString(),
person: (value) => 'Person(name: ${value.value.name}, age: ${value.value.age})',
);
}
}
// Extensions for easy conversion
extension StringToRawString on String {
RawString toRawString() => RawString.string(this);
}
extension IntToRawString on int {
RawString toRawString() => RawString.integer(this);
}
extension DoubleToRawString on double {
RawString toRawString() => RawString.double(this);
}
extension PersonToRawString on Person {
RawString toRawString() => RawString.person(this);
}
// Example Person class
class Person {
final String name;
final int age;
Person(this.name, this.age);
}
// Display function
void display(RawString raw) {
print(raw.toRawString());
}
// Example usage:
void main() {
final person = Person('John', 30);
final number = 42;
final text = 'Hello';
final decimal = 3.14;
// Using extensions
display(text.toRawString()); // Outputs: Hello
display(number.toRawString()); // Outputs: 42
display(decimal.toRawString()); // Outputs: 3.14
display(person.toRawString()); // Outputs: Person(name: John, age: 30)
// Or using constructors directly
display(const RawString.string('Hello'));
display(const RawString.integer(42));
} |
Recently I'm using Dart to complete a small project, and I need to write an extension to an existing class. However, I'm not allowed to make this existing class to use a mixin with extension. The following code will not compile:
I'm also an iOS devloper using Swift. In Swift, I can write something like this:
Now
MyStructOne
acts as aCustomProtocol
.I think this feature is useful and provides more flexibility. Wondering whether this 'extension with mixin' syntax can be added as a feature of Dart?
Thanks!
The text was updated successfully, but these errors were encountered: